1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/chrome/firefox",
  7     "firebug/chrome/reps",
  8     "firebug/lib/locale",
  9     "firebug/lib/events",
 10     "firebug/lib/wrapper",
 11     "firebug/lib/array",
 12     "firebug/lib/css",
 13     "firebug/lib/dom",
 14     "firebug/lib/xml",
 15     "firebug/chrome/window",
 16     "firebug/lib/system",
 17     "firebug/html/highlighterCache"
 18 ],
 19 function(Obj, Firebug, Firefox, FirebugReps, Locale, Events, Wrapper, Arr, Css, Dom, Xml,
 20     Win, System, HighlighterCache) {
 21 
 22 // ********************************************************************************************* //
 23 // Constants
 24 
 25 const inspectDelay = 200;
 26 const highlightCssUrl = "chrome://firebug/content/html/highlighter.css";
 27 const ident = HighlighterCache.ident;
 28 const Cu = Components.utils;
 29 
 30 // ********************************************************************************************* //
 31 // Globals
 32 
 33 var boxModelHighlighter = null;
 34 var frameHighlighter = null;
 35 
 36 // ********************************************************************************************* //
 37 
 38 /**
 39  * @module Implements Firebug Inspector logic.
 40  */
 41 Firebug.Inspector = Obj.extend(Firebug.Module,
 42 {
 43     dispatchName: "inspector",
 44     inspecting: false,
 45     inspectingPanel: null,
 46 
 47     /**
 48      * Main highlighter method. Can be used to highlight elements using the box model,
 49      * frame or image map highlighters. Can highlight single or multiple elements.
 50      *
 51      * Examples:
 52      * Firebug.Inspector.highlightObject([window.content.document.getElementById("gbar"),
 53      *     window.content.document.getElementById("logo")],
 54      *     window.content, "frame", null,
 55      *     ["#ff0000",{background:"#0000ff", border:"#ff0000"}])
 56      * or
 57      * Firebug.Inspector.highlightObject([window.content.document.getElementById("gbar"),
 58      *     window.content.document.getElementById("logo")], window.content, "boxModel", null,
 59      *     [{content: "#ff0000", padding: "#eeeeee", border: "#00ff00", margin: "#0000ff"},
 60      *         {content: "#00ff00", padding: "#eeeeee", border: "#00ff00", margin: "#0000ff"}])
 61      *
 62      * @param {Array} elementArr Elements to highlight
 63      * @param {Window} context Context of the elements to be highlighted
 64      * @param {String} [highlightType] Either "frame" or "boxModel". Default is configurable.
 65      * @param {String} [boxFrame] Displays the line guides for the box model layout view.
 66      *      Valid values are: "content", "padding", "border" or "margin"
 67      * @param {String | Array} [colorObj] Any valid html color e.g. red, #f00, #ff0000, etc.,
 68      *      a valid color object or any valid highlighter color array.
 69      */
 70     highlightObject: function(elementArr, context, highlightType, boxFrame, colorObj)
 71     {
 72         var i, elt, elementLen, oldContext, usingColorArray;
 73         var highlighter = highlightType ? getHighlighter(highlightType) : this.defaultHighlighter;
 74 
 75         if (!elementArr || !Arr.isArrayLike(elementArr))
 76         {
 77             // highlight a single element
 78             if (!elementArr || !Dom.isElement(elementArr) ||
 79                 (Wrapper.getContentView(elementArr) &&
 80                     !Xml.isVisible(Wrapper.getContentView(elementArr))))
 81             {
 82                 if (elementArr && Dom.isRange(elementArr))
 83                     elementArr = elementArr;
 84                 else if (elementArr && elementArr.nodeType == Node.TEXT_NODE)
 85                     elementArr = elementArr.parentNode;
 86                 else
 87                     elementArr = null;
 88             }
 89 
 90             if (elementArr && context && context.highlightTimeout)
 91             {
 92                 context.clearTimeout(context.highlightTimeout);
 93                 delete context.highlightTimeout;
 94             }
 95 
 96             oldContext = this.highlightedContext;
 97             if (oldContext && oldContext.window)
 98                 this.clearAllHighlights();
 99 
100             // Stop multi element highlighting
101             if (!elementArr)
102                 this.repaint.element = null;
103 
104             this.highlighter = highlighter;
105             this.highlightedContext = context;
106 
107             if (elementArr)
108             {
109                 if (elementArr.nodeName && !isVisibleElement(elementArr))
110                     highlighter.unhighlight(context);
111                 else if (context && context.window && context.window.document)
112                     highlighter.highlight(context, elementArr, boxFrame, colorObj, false);
113             }
114             else if (oldContext)
115             {
116                 oldContext.highlightTimeout = oldContext.setTimeout(function()
117                 {
118                     if (FBTrace.DBG_INSPECT)
119                         FBTrace.sysout("Removing inspector highlighter due to setTimeout loop");
120 
121                     if (!oldContext.highlightTimeout)
122                         return;
123 
124                     delete oldContext.highlightTimeout;
125 
126                     if (oldContext.window && oldContext.window.document)
127                     {
128                         highlighter.unhighlight(oldContext);
129                     }
130                 }, inspectDelay);
131             }
132         }
133         else
134         {
135             // Highlight multiple elements
136             if (context && context.highlightTimeout)
137             {
138                 context.clearTimeout(context.highlightTimeout);
139                 delete context.highlightTimeout;
140             }
141 
142             this.clearAllHighlights();
143             usingColorArray = Arr.isArray(colorObj);
144 
145             if (context && context.window && context.window.document)
146             {
147                 for (i=0, elementLen=elementArr.length; i<elementLen; i++)
148                 {
149                     elt = elementArr[i];
150 
151                     if (elt && elt instanceof HTMLElement)
152                     {
153                         if (elt.nodeType == Node.TEXT_NODE)
154                             elt = elt.parentNode;
155 
156                         var obj = usingColorArray ? colorObj[i] : colorObj;
157                         highlighter.highlight(context, elt, null, obj, true);
158                     }
159                 }
160             }
161 
162             storeHighlighterParams(null, context, elementArr, null, colorObj, highlightType, true);
163         }
164     },
165 
166     /**
167      * Clear all highlighted areas on a page.
168      */
169     clearAllHighlights: function()
170     {
171         HighlighterCache.clear();
172     },
173 
174     /**
175      * Toggle inspecting on / off
176      * @param {Window} [context] The window to begin inspecting in, necessary to toggle inspecting on.
177      */
178     toggleInspecting: function(context)
179     {
180         if (this.inspecting)
181             this.stopInspecting(true);
182         else
183             this.startInspecting(context);
184     },
185 
186     /**
187      * Check if the new panel has the inspectable property set. If so set it as the new inspectingPanel.
188      */
189     onPanelChanged: function()
190     {
191         if (this.inspecting)
192         {
193             var panelBar1 = Firebug.chrome.$("fbPanelBar1");
194             var panel = panelBar1.selectedPanel;
195 
196             if (panel && panel.inspectable)
197             {
198                 this.inspectNode(null);
199                 this.inspectingPanel = panel;
200             }
201         }
202     },
203 
204     /**
205      * Turn inspecting on.
206      * @param {Window} context The main browser window
207      */
208     startInspecting: function(context)
209     {
210         if (this.inspecting || !context || !context.loaded)
211             return;
212 
213         this.clearAllHighlights();
214 
215         this.inspecting = true;
216         this.inspectingContext = context;
217 
218         Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleInspecting", "checked", "true");
219         this.attachInspectListeners(context);
220 
221         var inspectingPanelName = this._resolveInspectingPanelName(context);
222         this.inspectingPanel = Firebug.chrome.switchToPanel(context, inspectingPanelName);
223 
224         if (Firebug.isDetached())
225             context.window.focus();
226         else if (Firebug.isMinimized())
227             Firebug.showBar(true);
228 
229         this.inspectingPanel.panelNode.focus();
230         this.inspectingPanel.startInspecting();
231 
232         Events.dispatch(this.fbListeners, "onStartInspecting", [context]);
233 
234         if (context.stopped)
235             Firebug.Debugger.thaw(context);
236 
237         var hoverNodes = context.window.document.querySelectorAll(":hover");
238 
239         if (hoverNodes.length != 0)
240             this.inspectNode(hoverNodes[hoverNodes.length-1]);
241     },
242 
243     /**
244      * Highlight a node using the frame highlighter. Can only be used after inspecting has already started.
245      * @param {Element} node The element to inspect
246      */
247     inspectNode: function(node)
248     {
249         if (node && node.nodeType != Node.ELEMENT_NODE)
250             node = node.parentNode;
251 
252         if (node && Firebug.shouldIgnore(node) && !node.fbProxyFor)
253             return;
254 
255         var context = this.inspectingContext;
256 
257         if (this.inspectTimeout)
258         {
259             context.clearTimeout(this.inspectTimeout);
260             delete this.inspectTimeout;
261         }
262 
263         if (node && node.fbProxyFor)
264             node = node.fbProxyFor;
265 
266         var inspectingPanel = this.inspectingPanel;
267 
268         // Some panels may want to only allow inspection of panel-supported objects
269         node = inspectingPanel ? inspectingPanel.getInspectNode(node) : node;
270 
271         var highlightColor = inspectingPanel ? inspectingPanel.inspectHighlightColor : "";
272         this.highlightObject(node, context, "frame", undefined, highlightColor);
273 
274         this.inspectingNode = node;
275 
276         if (node)
277         {
278             var _this = this;
279 
280             this.inspectTimeout = context.setTimeout(function()
281             {
282                 var selection = inspectingPanel ? inspectingPanel.inspectNode(node) : null;
283                 Events.dispatch(_this.fbListeners, "onInspectNode", [context, node]);
284                 if (selection)
285                     inspectingPanel.select(node);
286             }, inspectDelay);
287         }
288     },
289 
290     /**
291      * Stop inspecting and clear all highlights.
292      * @param {Boolean} canceled Indicates whether inspect was canceled (usually via the escape key)
293      * @param {Boolean} [waitForClick] Indicates whether the next click will still forward you
294      *      to the clicked element in the HTML panel.
295      */
296     stopInspecting: function(canceled, waitForClick)
297     {
298         if (!this.inspecting)
299             return;
300 
301         var context = this.inspectingContext;
302 
303         if (context.stopped)
304             Firebug.Debugger.freeze(context);
305 
306         if (this.inspectTimeout)
307         {
308             context.clearTimeout(this.inspectTimeout);
309             delete this.inspectTimeout;
310         }
311 
312         this.detachInspectListeners(context);
313         if (!waitForClick)
314             this.detachClickInspectListeners(context.window);
315 
316         Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleInspecting", "checked", "false");
317 
318         this.inspecting = false;
319 
320         if (this.inspectingPanel)
321         {
322             Firebug.chrome.unswitchToPanel(context, this.inspectingPanel.name, canceled);
323             this.inspectingPanel.stopInspecting(this.inspectingNode, canceled);
324         }
325         else
326         {
327             FBTrace.sysout("inspector.stopInspecting; ERROR? inspectingPanel is NULL");
328         }
329 
330         Events.dispatch(this.fbListeners, "onStopInspecting", [context, this.inspectingNode, canceled]);
331 
332         this.inspectNode(null);
333 
334         // Make sure there are no (indirect) references to the page document.
335         this.inspectingPanel = null;
336         this.inspectingContext = null;
337 
338         if (Firebug.isDetached())
339             window.focus();
340     },
341 
342     /**
343      * Get the name of the inspectable panel.
344      * @param {Window} context Context of the panel
345      */
346     _resolveInspectingPanelName: function(context)
347     {
348         var requestingPanel = context && context.getPanel(context.panelName);
349 
350         return (requestingPanel && requestingPanel.inspectable) ? requestingPanel.name : "html";
351     },
352 
353     /**
354      * Inspect from context menu.
355      * @param {Element} elt The element to inspect
356      */
357     inspectFromContextMenu: function(elt)
358     {
359         var panel;
360         var inspectingPanelName = "html";
361 
362         Firebug.toggleBar(true, inspectingPanelName);
363         Firebug.chrome.select(elt, inspectingPanelName);
364         panel = Firebug.chrome.selectPanel(inspectingPanelName);
365         panel.panelNode.focus();
366     },
367 
368     /**
369      * Navigate up and down through the DOM and highlight the result. This method is used by
370      * the key handlers for the up and down arrow keys.
371      *
372      * @param {String} dir Direction to navigate the Dom, either "up" or "down"
373      */
374     inspectNodeBy: function(dir)
375     {
376         var target;
377         var node = this.inspectingNode;
378 
379         if (dir == "up")
380         {
381             target = Firebug.chrome.getNextObject();
382         }
383         else if (dir == "down")
384         {
385             target = Firebug.chrome.getNextObject(true);
386             if (node && !target)
387             {
388                 target = node.contentDocument ?
389                     node.contentDocument.documentElement : Dom.getNextElement(node.firstChild);
390             }
391         }
392 
393         if (target && Dom.isElement(target))
394             this.inspectNode(target);
395         else
396             System.beep();
397     },
398 
399     /**
400      * Repaint the highlighter. Called from the window scroll and resize handlers.
401      */
402     repaint: function()
403     {
404         var rp = this.repaint;
405         var highlighter = rp.highlighter;
406         var context = rp.context;
407         var element = rp.element;
408         var boxFrame = rp.boxFrame;
409         var colorObj = rp.colorObj;
410         var highlightType = rp.highlightType;
411         var isMulti = rp.isMulti;
412 
413         if (!context || (!highlighter && !isMulti))
414             return;
415 
416         if (isMulti && element)
417         {
418             this.highlightObject(element, context, highlightType, null, colorObj);
419         }
420         else if (!isMulti)
421         {
422             var highlighterNode = HighlighterCache.get(highlighter.ident);
423 
424             if (highlighterNode && highlighter.ident === ident.boxModel)
425                 highlighterNode = highlighterNode.offset;
426 
427             if (highlighterNode && highlighterNode.parentNode)
428             {
429                 this.clearAllHighlights();
430                 highlighter.highlight(context, element, boxFrame, colorObj, isMulti);
431             }
432         }
433     },
434 
435     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
436 
437     /**
438      * Attach the scroll and resize handlers to elt's window. Called from every highlight call.
439      * @param {Element} elt Passed in order to reliably obtain context
440      */
441     attachRepaintInspectListeners: function(context, elt)
442     {
443         if (!elt || !elt.ownerDocument || !elt.ownerDocument.defaultView)
444             return;
445 
446         var win = elt.ownerDocument.defaultView;
447 
448         if (FBTrace.DBG_INSPECT)
449             FBTrace.sysout("inspector.attachRepaintInspectListeners to " + win.location.href, elt);
450 
451         // there is no way to check if the listeners have already been added and we should
452         // avoid adding properties to the users page.
453         // Adding them again will do no harm so lets just do that.
454 
455         // xxxHonza: I think that adding them twice could actually do harm,
456         // so make sure they are removed before.
457         context.removeEventListener(win.document, "resize", this.onInspectingResizeWindow, true);
458         context.removeEventListener(win.document, "scroll", this.onInspectingScroll, true);
459 
460         // Register again.
461         context.addEventListener(win.document, "resize", this.onInspectingResizeWindow, true);
462         context.addEventListener(win.document, "scroll", this.onInspectingScroll, true);
463     },
464 
465     /**
466      * Attach key and mouse events to windows recursively.
467      * @param {Window} context Context of the main browser window
468      */
469     attachInspectListeners: function(context)
470     {
471         var win = context.window;
472         if (!win || !win.document)
473             return;
474 
475         if (FBTrace.DBG_INSPECT)
476             FBTrace.sysout("inspector.attachInspectListeners to all subWindows of " + win.location);
477 
478         var chrome = Firebug.chrome;
479 
480         this.keyListeners =
481         [
482             chrome.keyCodeListen("RETURN", null, Obj.bindFixed(this.stopInspecting, this)),
483             chrome.keyCodeListen("ESCAPE", null, Obj.bindFixed(this.stopInspecting, this, true)),
484             chrome.keyCodeListen("UP", Events.isControl, Obj.bindFixed(this.inspectNodeBy, this,
485                 "up"), true),
486             chrome.keyCodeListen("DOWN", Events.isControl, Obj.bindFixed(this.inspectNodeBy, this,
487                 "down"), true),
488         ];
489 
490         Win.iterateWindows(win, Obj.bind(function(subWin)
491         {
492             if (FBTrace.DBG_INSPECT)
493                 FBTrace.sysout("inspector.attacheInspectListeners to " + subWin.location +
494                     " subWindow of " + win.location);
495 
496             Events.addEventListener(subWin.document, "mouseover", this.onInspectingMouseOver,
497                 true);
498             Events.addEventListener(subWin.document, "mousedown", this.onInspectingMouseDown,
499                 true);
500             Events.addEventListener(subWin.document, "mouseup", this.onInspectingMouseUp, true);
501             Events.addEventListener(subWin.document, "click", this.onInspectingClick, true);
502             Events.addEventListener(subWin.document, "keypress", this.onInspectingKeyPress, true);
503         }, this));
504     },
505 
506     /**
507      * Remove all event listeners except click listener from windows recursively.
508      * @param {Window} context Context of the main browser window
509      */
510     detachInspectListeners: function(context)
511     {
512         var i, keyListenersLen;
513         var win = context.window;
514 
515         if (!win || !win.document)
516             return;
517 
518         var chrome = Firebug.chrome;
519 
520         if (this.keyListeners)  // XXXjjb for some reason this is null sometimes.
521         {
522             keyListenersLen = this.keyListeners.length;
523             for (i = 0; i < keyListenersLen; ++i)
524                 chrome.keyIgnore(this.keyListeners[i]);
525             delete this.keyListeners;
526         }
527 
528         Win.iterateWindows(win, Obj.bind(function(subWin)
529         {
530             Events.removeEventListener(subWin.document, "mouseover", this.onInspectingMouseOver,
531                 true);
532             Events.removeEventListener(subWin.document, "mousedown", this.onInspectingMouseDown,
533                 true);
534             Events.removeEventListener(subWin.document, "mouseup", this.onInspectingMouseUp, true);
535             Events.removeEventListener(subWin.document, "keypress", this.onInspectingKeyPress,
536                 true);
537         }, this));
538     },
539 
540     /**
541      * Remove the click listener independently from detachInspectListeners because if we remove
542      * it after mousedown, we won't be able to cancel clicked links.
543      *
544      * @param {Window} context Context of the main browser window
545      */
546     detachClickInspectListeners: function(context)
547     {
548         Win.iterateWindows(context, Obj.bind(function(subWin)
549         {
550             Events.removeEventListener(subWin.document, "click", this.onInspectingClick, true);
551         }, this));
552     },
553 
554     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
555 
556     /**
557      * Repaint last highlight in the correct position on window resize.
558      * @param {Event} event Passed for tracing
559      */
560     onInspectingResizeWindow: function(event)
561     {
562         if (FBTrace.DBG_INSPECT)
563            FBTrace.sysout("onInspectingResizeWindow event", event);
564 
565         this.repaint();
566     },
567 
568     /**
569      * Repaint last highlight in the correct position on scroll.
570      * @param {Event} event Passed for tracing
571      */
572     onInspectingScroll: function(event)
573     {
574         if (FBTrace.DBG_INSPECT)
575            FBTrace.sysout("onInspectingScroll event", event);
576 
577         this.repaint();
578     },
579 
580     /**
581      * Call inspectNode(event.target) highlighting the element that was moused over.
582      * @param {Event} event Passed for tracing and to identify the target of inspection
583      */
584     onInspectingMouseOver: function(event)
585     {
586         if (FBTrace.DBG_INSPECT)
587            FBTrace.sysout("onInspectingMouseOver event", event);
588 
589         this.inspectNode(event.target);
590     },
591 
592     /**
593      * Trap mousedown events to prevent clicking a document from triggering a document's
594      * mousedown event when inspecting.
595      *
596      * @param {Event} event Used for tracing and canceling the event
597      */
598     onInspectingMouseDown: function(event)
599     {
600         if (FBTrace.DBG_INSPECT)
601         {
602             FBTrace.sysout("onInspectingMouseDown event", {originalTarget: event.originalTarget,
603                 tmpRealOriginalTarget:event.tmpRealOriginalTarget, event:event});
604         }
605 
606         // Allow to scroll the document while inspecting
607         if (event.originalTarget && event.originalTarget.tagName == "xul:thumb")
608             return;
609 
610         Events.cancelEvent(event);
611     },
612 
613     /**
614      * Trap mouseup events to prevent clicking a document from triggering a document's mouseup
615      * event when inspecting.
616      *
617      * @param {Event} event Used for tracing and canceling the event
618      */
619     onInspectingMouseUp: function(event)
620     {
621         if (FBTrace.DBG_INSPECT)
622         {
623             FBTrace.sysout("onInspectingMouseUp event", {originalTarget: event.originalTarget,
624                 tmpRealOriginalTarget:event.tmpRealOriginalTarget,event:event});
625         }
626 
627         // Allow to release scrollbar while inspecting
628         if (event.originalTarget && event.originalTarget.tagName == "xul:thumb")
629             return;
630 
631         this.stopInspecting(false, true);
632 
633         Events.cancelEvent(event);
634     },
635 
636     /**
637      * Trap click events to prevent clicking a document from triggering a document's click event
638      * when inspecting and removes the click inspect listener.
639      *
640      * @param {Event} event Used for tracing and canceling the event
641      */
642     onInspectingClick: function(event)
643     {
644         if (FBTrace.DBG_INSPECT)
645             FBTrace.sysout("onInspectingClick event", event);
646 
647         var win = event.currentTarget.defaultView;
648         if (win)
649         {
650             win = Win.getRootWindow(win);
651             this.detachClickInspectListeners(win);
652         }
653 
654         Events.cancelEvent(event);
655     },
656 
657     /**
658      * Trap keypress events to allow manipulation of the hovered elements
659      *
660      * @param {Event} event Used for canceling the event
661      */
662     onInspectingKeyPress: function(event)
663     {
664         if (event.keyCode == KeyEvent.DOM_VK_DELETE)
665         {
666             Events.dispatch(this.fbListeners, "onBeginFirebugChange", [this.inspectingNode, this]);
667             this.inspectingNode.parentNode.removeChild(this.inspectingNode);
668             Events.dispatch(this.fbListeners, "onEndFirebugChange", [this.inspectingNode, this]);
669             Events.cancelEvent(event);
670         }
671     },
672 
673     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
674     // extends Module
675 
676     /**
677      * Initialize the inspector
678      */
679     initialize: function()
680     {
681         Firebug.Module.initialize.apply(this, arguments);
682 
683         this.onInspectingResizeWindow = Obj.bind(this.onInspectingResizeWindow, this);
684         this.onInspectingScroll = Obj.bind(this.onInspectingScroll, this);
685         this.onInspectingMouseOver = Obj.bind(this.onInspectingMouseOver, this);
686         this.onInspectingMouseDown = Obj.bind(this.onInspectingMouseDown, this);
687         this.onInspectingMouseUp = Obj.bind(this.onInspectingMouseUp, this);
688         this.onInspectingClick = Obj.bind(this.onInspectingClick, this);
689         this.onInspectingKeyPress = Obj.bind(this.onInspectingKeyPress, this);
690         this.onPanelChanged = Obj.bind(this.onPanelChanged, this);
691 
692         this.updateOption("shadeBoxModel", Firebug.shadeBoxModel);
693         this.updateOption("showQuickInfoBox", Firebug.showQuickInfoBox);
694 
695         var panelBar1 = Firebug.chrome.$("fbPanelBar1");
696         Events.addEventListener(panelBar1, "selectPanel", this.onPanelChanged, false);
697 
698         if (FBTrace.DBG_INSPECT)
699             FBTrace.sysout("inspector.initialize;");
700     },
701 
702     shutdown: function()
703     {
704         Firebug.Module.shutdown.apply(this, arguments);
705 
706         var panelBar1 = Firebug.chrome.$("fbPanelBar1");
707         Events.removeEventListener(panelBar1, "selectPanel", this.onPanelChanged, false);
708     },
709 
710     /**
711      * Stop inspecting and delete timers.
712      * @param {Window} context Context of the main window
713      */
714     destroyContext: function(context)
715     {
716         if (context.highlightTimeout)
717         {
718             context.clearTimeout(context.highlightTimeout);
719             delete context.highlightTimeout;
720         }
721 
722         if (this.inspecting)
723             this.stopInspecting(true);
724     },
725 
726     /**
727      */
728     unwatchWindow: function(context, win)
729     {
730         try
731         {
732             this.hideQuickInfoBox();
733         }
734         catch (ex)
735         {
736             // Get unfortunate errors here sometimes, so let's just ignore them since the
737             // window is going away anyhow
738         }
739     },
740 
741     /**
742      * Called when a FF tab is created or activated (user changes FF tab). We stop inspecting
743      * in this situation.
744      *
745      * @param {xul:browser} [browser] Browser
746      * @param {Window} [context] The main browser window
747      */
748     showContext: function(browser, context)
749     {
750         if (this.inspecting)
751             this.stopInspecting(true);
752     },
753 
754     /**
755      * Called when a panel is shown.
756      * @param {xul:browser} [browser] Browser
757      * @param {Panel} [panel] Panel
758      */
759     showPanel: function(browser, panel)
760     {
761         // Don't disable the cmd_toggleInspecting command. The related shortcut <key> must
762         // be available even if Firebug is not activated for the site. See 4452
763         // The panel can be null (if disabled) so use the global context.
764         // var context = Firebug.currentContext;
765         // var disabled = (context && context.loaded) ? false : true;
766         // Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleInspecting", "disabled", disabled);
767     },
768 
769     /**
770      * Called after a context's page gets DOMContentLoaded. We enable inspection here.
771      * @param {Window} [context] Context of the main window
772      */
773     loadedContext: function(context)
774     {
775         // See the comment in showPanel.
776         // Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleInspecting", "disabled", "false");
777     },
778 
779     /**
780      * Update the shadeBoxModel or showQuickInfoBox options
781      * @param {String} name Either "shadeBoxModel" or "showQuickInfoBox"
782      * @param {Boolean} value Enable or Disable the option
783      */
784     updateOption: function(name, value)
785     {
786         if (name == "shadeBoxModel")
787         {
788             this.highlightObject(null);
789             this.defaultHighlighter = value ? getHighlighter("boxModel") : getHighlighter("frame");
790         }
791         else if (name == "showQuickInfoBox")
792         {
793             if (quickInfoBox.boxEnabled && !value)
794                 quickInfoBox.hide();
795 
796             quickInfoBox.boxEnabled = value;
797         }
798     },
799 
800     /**
801      * Gets stylesheet by Url.
802      * @param {Window} context the main browser window
803      * @param {String} url URL of the stylesheet
804      */
805     getObjectByURL: function(context, url)
806     {
807         var styleSheet = Css.getStyleSheetByHref(url, context);
808         if (styleSheet)
809             return styleSheet;
810     },
811 
812     /**
813      * Toggle the quick info box.
814      */
815     toggleQuickInfoBox: function()
816     {
817         var qiBox = Firebug.chrome.$("fbQuickInfoPanel");
818 
819         if (qiBox.state == "open")
820             quickInfoBox.hide();
821 
822         quickInfoBox.boxEnabled = !quickInfoBox.boxEnabled;
823 
824         Firebug.Options.set("showQuickInfoBox", quickInfoBox.boxEnabled);
825     },
826 
827     /**
828      * Hide the quick info box.
829      */
830     hideQuickInfoBox: function()
831     {
832         quickInfoBox.hide();
833 
834         this.inspectNode(null);
835     }
836 });
837 
838 // ********************************************************************************************* //
839 // Local Helpers
840 
841 function getHighlighter(type)
842 {
843     switch (type)
844     {
845         case "boxModel":
846             if (!boxModelHighlighter)
847                 boxModelHighlighter = new BoxModelHighlighter();
848 
849             return boxModelHighlighter;
850 
851         case "frame":
852             if (!frameHighlighter)
853                 frameHighlighter = new Firebug.Inspector.FrameHighlighter();
854 
855             return frameHighlighter;
856     }
857 }
858 
859 function pad(element, t, r, b, l)
860 {
861     var css = "padding:" + Math.abs(t) + "px " + Math.abs(r) + "px "
862          + Math.abs(b) + "px " + Math.abs(l) + "px !important;";
863 
864     if (element)
865         element.style.cssText = css;
866     else
867         return css;
868 }
869 
870 function moveImp(element, x, y)
871 {
872     var css = "left:" + x + "px !important;top:" + y + "px !important;";
873 
874     if (element)
875         element.style.cssText = css;
876     else
877         return css;
878 }
879 
880 function resizeImp(element, w, h)
881 {
882     var css = "width:" + w + "px !important;height:" + h + "px !important;";
883 
884     if (element)
885         element.style.cssText = css;
886     else
887         return css;
888 }
889 
890 // ********************************************************************************************* //
891 // Imagemap Highlighter
892 
893 function getImageMapHighlighter(context)
894 {
895     if (!context)
896         return;
897 
898     var canvas, ctx, mx, my;
899     var doc = context.window.document;
900 
901     var init = function(elt)
902     {
903         if (elt)
904             doc = elt.ownerDocument;
905 
906         canvas = doc.getElementById("firebugCanvas");
907 
908         if (!canvas)
909         {
910             canvas = doc.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
911             hideElementFromInspection(canvas);
912             canvas.id = "firebugCanvas";
913             canvas.className = "firebugResetStyles firebugBlockBackgroundColor firebugCanvas";
914             canvas.width = context.window.innerWidth;
915             canvas.height = context.window.innerHeight;
916 
917             Events.addEventListener(context.window, "scroll", function()
918             {
919                 context.imageMapHighlighter.show(false);
920             }, true);
921 
922             Events.addEventListener(doc, "mousemove", function(event)
923             {
924                 mx = event.clientX;
925                 my = event.clientY;
926             }, true);
927 
928             doc.body.appendChild(canvas);
929         }
930     };
931 
932     if (!context.imageMapHighlighter)
933     {
934         context.imageMapHighlighter =
935         {
936             ident: ident.imageMap,
937 
938             show: function(state)
939             {
940                 if (!canvas)
941                     init(null);
942 
943                 canvas.style.cssText = "display:"+(state ? "block" : "none")+" !important";
944             },
945 
946             getImages: function(mapName, multi)
947             {
948                 if (!mapName)
949                     return;
950 
951                 var xpe = new XPathEvaluator();
952                 var nsResolver = xpe.createNSResolver(doc.documentElement);
953 
954                 var elts = xpe.evaluate("//map[@name='" + mapName + "']", doc,
955                     nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
956 
957                 if (elts.snapshotLength === 0)
958                     return;
959 
960                 elts = xpe.evaluate("(//img | //input)[@usemap='#" + mapName + "']",
961                     doc.documentElement, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
962                 var eltsLen = elts.snapshotLength;
963 
964                 var images = [];
965                 for (var i = 0; i < eltsLen; ++i)
966                 {
967                     var elt = elts.snapshotItem(i);
968                     var rect = Dom.getLTRBWH(elt);
969 
970                     if (multi)
971                     {
972                         images.push(elt);
973                     }
974                     else if (rect.left <= mx && rect.right >= mx && rect.top <= my &&
975                         rect.bottom >= my)
976                     {
977                         images[0] = elt;
978                         break;
979                     }
980                 }
981 
982                 return images;
983             },
984 
985             highlight: function(eltArea, multi)
986             {
987                 if (!eltArea || !eltArea.coords)
988                     return;
989 
990                 var images = this.getImages(eltArea.parentNode.name, multi) || [];
991 
992                 init(eltArea);
993 
994                 var v = eltArea.coords.split(",");
995 
996                 if (!ctx)
997                     ctx = canvas.getContext("2d");
998 
999                 ctx.fillStyle = "rgba(135, 206, 235, 0.7)";
1000                 ctx.strokeStyle = "rgb(44, 167, 220)";
1001                 ctx.lineWidth = 2;
1002 
1003                 if (images.length == 0)
1004                     images[0] = eltArea;
1005 
1006                 for (var j = 0, imagesLen = images.length; j < imagesLen; ++j)
1007                 {
1008                     var rect = Dom.getLTRBWH(images[j], context);
1009 
1010                     ctx.beginPath();
1011 
1012                     if (!multi || (multi && j===0))
1013                         ctx.clearRect(0, 0, canvas.width, canvas.height);
1014 
1015                     var shape = eltArea.shape.toLowerCase();
1016 
1017                     if (shape === "rect")
1018                     {
1019                         ctx.rect(rect.left + parseInt(v[0], 10), rect.top + parseInt(v[1], 10), v[2] - v[0], v[3] - v[1]);
1020                     }
1021                     else if (shape === "circle")
1022                     {
1023                         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);
1024                     }
1025                     else
1026                     {
1027                         var vLen = v.length;
1028                         ctx.moveTo(rect.left + parseInt(v[0], 10), rect.top + parseInt(v[1], 10));
1029                         for (var i = 2; i < vLen; i += 2)
1030                             ctx.lineTo(rect.left + parseInt(v[i], 10), rect.top + parseInt(v[i + 1], 10));
1031                         ctx.lineTo(rect.left + parseInt(v[0], 10), rect.top + parseInt(v[1], 10));
1032                     }
1033 
1034                     ctx.fill();
1035                     ctx.stroke();
1036                     ctx.closePath();
1037                 }
1038 
1039                 this.show(true);
1040             },
1041 
1042             destroy: function()
1043             {
1044                 this.show(false);
1045                 canvas = null;
1046                 ctx = null;
1047             }
1048         }
1049     }
1050 
1051     return context.imageMapHighlighter;
1052 }
1053 
1054 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1055 
1056 var quickInfoBox =
1057 {
1058     boxEnabled: undefined,
1059     dragging: false,
1060     storedX: null,
1061     storedY: null,
1062 
1063     show: function(element)
1064     {
1065         if (!this.boxEnabled || !element)
1066             return;
1067 
1068         this.needsToHide = false;
1069 
1070         var domAttribs = ["nodeName", "id", "name", "offsetWidth", "offsetHeight"];
1071         var cssAttribs = ["position"];
1072         var compAttribs = [
1073             "width", "height", "zIndex", "position", "top", "right", "bottom", "left",
1074             "margin-top", "margin-right", "margin-bottom", "margin-left", "color",
1075             "backgroundColor", "fontFamily", "cssFloat", "display", "visibility"];
1076         var qiBox = Firebug.chrome.$("fbQuickInfoPanel");
1077 
1078         if (qiBox.state==="closed")
1079         {
1080             this.storedX = this.storedX || Firefox.getElementById("content").tabContainer.boxObject.screenX + 5;
1081             this.storedY = this.storedY || Firefox.getElementById("content").tabContainer.boxObject.screenY + 35;
1082 
1083             // Dynamically set noautohide to avoid mozilla bug 545265.
1084             if (!this.noautohideAdded)
1085             {
1086                 this.noautohideAdded = true;
1087                 qiBox.addEventListener("popupshowing", function runOnce()
1088                 {
1089                     qiBox.removeEventListener("popupshowing", runOnce, false);
1090                     qiBox.setAttribute("noautohide", true);
1091                 }, false);
1092             }
1093             qiBox.openPopupAtScreen(this.storedX, this.storedY, false);
1094         }
1095 
1096         qiBox.removeChild(qiBox.firstChild);
1097         var vbox = document.createElement("vbox");
1098         qiBox.appendChild(vbox);
1099 
1100         var needsTitle = this.addRows(element, vbox, domAttribs);
1101         var needsTitle2 = this.addRows(element.style, vbox, cssAttribs);
1102 
1103         var lab;
1104         if (needsTitle || needsTitle2)
1105         {
1106             lab = document.createElement("label");
1107             lab.setAttribute("class", "fbQuickInfoBoxTitle");
1108             lab.setAttribute("value", Locale.$STR("quickInfo"));
1109             vbox.insertBefore(lab, vbox.firstChild);
1110         }
1111 
1112         lab = document.createElement("label");
1113         lab.setAttribute("class", "fbQuickInfoBoxTitle");
1114         lab.setAttribute("value", Locale.$STR("computedStyle"));
1115         vbox.appendChild(lab);
1116 
1117         this.addRows(element, vbox, compAttribs, true);
1118     },
1119 
1120     hide: function()
1121     {
1122         // if mouse is over panel defer hiding to mouseout to not cause flickering
1123         var qiBox = Firebug.chrome.$("fbQuickInfoPanel");
1124         if (qiBox.state==="closed")
1125             return;
1126 
1127         if (qiBox.mozMatchesSelector(":hover"))
1128             return;
1129 
1130         this.storedX = qiBox.boxObject.screenX;
1131         this.storedY = qiBox.boxObject.screenY;
1132         qiBox.hidePopup();
1133     },
1134 
1135     addRows: function(domBase, vbox, attribs, computedStyle)
1136     {
1137         if (!domBase)
1138             return;
1139 
1140         var needsTitle = false;
1141         for (var i = 0; i < attribs.length; i++)
1142         {
1143             var value;
1144             if (computedStyle)
1145             {
1146                 var cs = getNonFrameBody(domBase).ownerDocument.defaultView.getComputedStyle(domBase, null);
1147                 value = cs.getPropertyValue(attribs[i]);
1148 
1149                 if (value && /rgb\(\d+,\s\d+,\s\d+\)/.test(value))
1150                     value = rgbToHex(value);
1151             }
1152             else
1153             {
1154                 value = domBase[attribs[i]];
1155             }
1156 
1157             if (value)
1158             {
1159                 needsTitle = true;
1160                 var hbox = document.createElement("hbox");
1161                 var lab = document.createElement("label");
1162                 lab.setAttribute("class", "fbQuickInfoName");
1163                 lab.setAttribute("value", attribs[i]);
1164                 hbox.appendChild(lab);
1165                 var desc = document.createElement("label");
1166                 desc.setAttribute("class", "fbQuickInfoValue");
1167                 desc.appendChild(document.createTextNode(": " + value));
1168                 hbox.appendChild(desc);
1169                 vbox.appendChild(hbox);
1170             }
1171         }
1172 
1173         return needsTitle;
1174     }
1175 };
1176 
1177 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1178 
1179 Firebug.Inspector.FrameHighlighter = function()
1180 {
1181 };
1182 
1183 Firebug.Inspector.FrameHighlighter.prototype =
1184 {
1185     ident: ident.frame,
1186 
1187     doNotHighlight: function(element)
1188     {
1189         return false; // (element instanceof XULElement);
1190     },
1191 
1192     highlight: function(context, element, extra, colorObj, isMulti)
1193     {
1194         if (this.doNotHighlight(element))
1195             return;
1196 
1197         // if a single color was passed in lets use it as the border color
1198         if (typeof colorObj === "string")
1199             colorObj = {background: "transparent", border: colorObj};
1200         else
1201             colorObj = colorObj || {background: "transparent", border: "highlight"};
1202 
1203         Firebug.Inspector.attachRepaintInspectListeners(context, element);
1204         storeHighlighterParams(this, context, element, null, colorObj, null, isMulti);
1205 
1206         var cs;
1207         var offset = Dom.getLTRBWH(element);
1208         var x = offset.left, y = offset.top;
1209         var w = offset.width, h = offset.height;
1210 
1211         if (FBTrace.DBG_INSPECT)
1212             FBTrace.sysout("FrameHighlighter HTML tag:" + element.tagName + " x:" + x +
1213                 " y:" + y + " w:" + w + " h:" + h);
1214 
1215         var wacked = isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h);
1216         if (wacked)
1217         {
1218             if (FBTrace.DBG_INSPECT)
1219                 FBTrace.sysout("FrameHighlighter.highlight has bad boxObject for " + element.tagName);
1220 
1221             return;
1222         }
1223 
1224         if (element.tagName !== "AREA")
1225         {
1226             if (FBTrace.DBG_INSPECT)
1227                 FBTrace.sysout("FrameHighlighter " + element.tagName);
1228             var body = getNonFrameBody(element);
1229             if (!body)
1230                 return this.unhighlight(context);
1231 
1232             this.ihl && this.ihl.show(false);
1233 
1234             quickInfoBox.show(element);
1235             var highlighter = this.getHighlighter(context, isMulti);
1236             var bgDiv = highlighter.firstChild;
1237             var css = moveImp(null, x, y) + resizeImp(null, w, h);
1238 
1239             if (Dom.isElement(element))
1240             {
1241                 cs = body.ownerDocument.defaultView.getComputedStyle(element, null);
1242 
1243                 if (cs.transform && cs.transform != "none")
1244                     css += "transform: " + cs.transform + " !important;" +
1245                            "transform-origin: " + cs.transformOrigin + " !important;";
1246                 if (cs.borderRadius)
1247                     css += "border-radius: " + cs.borderRadius + " !important;";
1248                 if (cs.borderTopLeftRadius)
1249                     css += "border-top-left-radius: " + cs.borderTopLeftRadius + " !important;";
1250                 if (cs.borderTopRightRadius)
1251                     css += "border-top-right-radius: " + cs.borderTopRightRadius + " !important;";
1252                 if (cs.borderBottomRightRadius)
1253                     css += "border-bottom-right-radius: " + cs.borderBottomRightRadius + " !important;";
1254                 if (cs.borderBottomLeftRadius)
1255                     css += "border-bottom-left-radius: " + cs.borderBottomLeftRadius + " !important;";
1256             }
1257             css += "box-shadow: 0 0 2px 2px "+
1258                 (colorObj && colorObj.border ? colorObj.border : "highlight")+"!important;";
1259 
1260             if (colorObj && colorObj.background)
1261             {
1262                 bgDiv.style.cssText = "width: 100%!important; height: 100%!important;" +
1263                     "background-color: "+colorObj.background+"!important; opacity: 0.6!important;";
1264             }
1265             else
1266             {
1267                 bgDiv.style.cssText = "background-color: transparent!important;";
1268             }
1269 
1270             highlighter.style.cssText = css;
1271 
1272             var needsAppend = !highlighter.parentNode || highlighter.ownerDocument != body.ownerDocument;
1273             if (needsAppend)
1274             {
1275                 if (FBTrace.DBG_INSPECT)
1276                     FBTrace.sysout("FrameHighlighter needsAppend: " + highlighter.ownerDocument.documentURI +
1277                         " !?= " + body.ownerDocument.documentURI, highlighter);
1278 
1279                 attachStyles(context, body.ownerDocument);
1280 
1281                 try
1282                 {
1283                     body.appendChild(highlighter);
1284                 }
1285                 catch(exc)
1286                 {
1287                     if (FBTrace.DBG_INSPECT)
1288                         FBTrace.sysout("inspector.FrameHighlighter.highlight body.appendChild FAILS for body " +
1289                             body + " " + exc, exc);
1290                 }
1291 
1292                 // otherwise the proxies take up screen space in browser.xul
1293                 if (element.ownerDocument && element.ownerDocument.contentType.indexOf("xul") === -1)
1294                     createProxiesForDisabledElements(body);
1295             }
1296         }
1297         else
1298         {
1299             this.ihl = getImageMapHighlighter(context);
1300             this.ihl.highlight(element, false);
1301         }
1302     },
1303 
1304     unhighlight: function(context)
1305     {
1306         if (FBTrace.DBG_INSPECT)
1307             FBTrace.sysout("FrameHighlighter unhighlight", context.window.location);
1308 
1309         var highlighter = this.getHighlighter(context);
1310         var body = highlighter.parentNode;
1311         if (body)
1312         {
1313             body.removeChild(highlighter);
1314             quickInfoBox.hide();
1315         }
1316 
1317         this.ihl && this.ihl.destroy();
1318         this.ihl = null;
1319     },
1320 
1321     getHighlighter: function(context, isMulti)
1322     {
1323         if (!isMulti)
1324         {
1325             var div = HighlighterCache.get(ident.frame);
1326             if (div)
1327                 return div;
1328         }
1329 
1330         var doc = context.window.document;
1331         div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
1332         var div2 = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
1333 
1334         hideElementFromInspection(div);
1335         hideElementFromInspection(div2);
1336 
1337         div.className = "firebugResetStyles firebugBlockBackgroundColor";
1338         div2.className = "firebugResetStyles";
1339         div.appendChild(div2);
1340         div.ident = ident.frame;
1341         HighlighterCache.add(div);
1342         return div;
1343     }
1344 };
1345 
1346 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1347 
1348 function BoxModelHighlighter()
1349 {
1350 }
1351 
1352 Firebug.Inspector.BoxModelHighlighter = BoxModelHighlighter;
1353 
1354 BoxModelHighlighter.prototype =
1355 {
1356     ident: ident.boxModel,
1357 
1358     highlight: function(context, element, boxFrame, colorObj, isMulti)
1359     {
1360         var line, contentCssText, paddingCssText, borderCssText, marginCssText,
1361             nodes = this.getNodes(context, isMulti),
1362             highlightFrame = boxFrame ? nodes[boxFrame] : null;
1363 
1364         // if a single color was passed in lets use it as the content box color
1365         if (typeof colorObj === "string")
1366             colorObj = {content: colorObj, padding: "SlateBlue", border: "#444444", margin: "#EDFF64"};
1367         else
1368             colorObj = colorObj || {content: "SkyBlue", padding: "SlateBlue", border: "#444444", margin: "#EDFF64"};
1369 
1370         Firebug.Inspector.attachRepaintInspectListeners(context, element);
1371         storeHighlighterParams(this, context, element, boxFrame, colorObj, null, isMulti);
1372 
1373         if (context.highlightFrame)
1374             Css.removeClass(context.highlightFrame, "firebugHighlightBox");
1375 
1376         if (element.tagName !== "AREA")
1377         {
1378             this.ihl && this.ihl.show(false);
1379 
1380             quickInfoBox.show(element);
1381             context.highlightFrame = highlightFrame;
1382 
1383             if (highlightFrame)
1384             {
1385                 Css.setClass(nodes.offset, "firebugHighlightGroup");
1386                 Css.setClass(highlightFrame, "firebugHighlightBox");
1387             }
1388             else
1389                 Css.removeClass(nodes.offset, "firebugHighlightGroup");
1390 
1391             var win = (element.ownerDocument ? element.ownerDocument.defaultView : null);
1392             if (!win)
1393                 return;
1394 
1395             var style = win.getComputedStyle(element, "");
1396             if (!style)
1397             {
1398                 if (FBTrace.DBG_INSPECT)
1399                     FBTrace.sysout("highlight: no style for element " + element, element);
1400                 return;
1401             }
1402 
1403             var styles = Css.readBoxStyles(style);
1404             var offset = Dom.getLTRBWH(element);
1405             var x = offset.left - Math.abs(styles.marginLeft);
1406             var y = offset.top - Math.abs(styles.marginTop);
1407             var w = offset.width - (styles.paddingLeft + styles.paddingRight + styles.borderLeft + styles.borderRight);
1408             var h = offset.height - (styles.paddingTop + styles.paddingBottom + styles.borderTop + styles.borderBottom);
1409 
1410             moveImp(nodes.offset, x, y);
1411             marginCssText = pad(null, styles.marginTop, styles.marginRight, styles.marginBottom, styles.marginLeft);
1412             borderCssText = pad(null, styles.borderTop, styles.borderRight, styles.borderBottom, styles.borderLeft);
1413             paddingCssText = pad(null, styles.paddingTop, styles.paddingRight, styles.paddingBottom, styles.paddingLeft);
1414             contentCssText = resizeImp(null, w, h);
1415 
1416             // element.tagName !== "BODY" for issue 2447. hopefully temporary, robc
1417             var showLines = Firebug.showRulers && boxFrame && element.tagName !== "BODY";
1418             if (showLines)
1419             {
1420                 var offsetParent = element.offsetParent;
1421 
1422                 if (offsetParent)
1423                     this.setNodesByOffsetParent(win, offsetParent, nodes);
1424 
1425                 var left = x;
1426                 var top = y;
1427                 var width = w-1;
1428                 var height = h-1;
1429 
1430                 if (boxFrame == "content")
1431                 {
1432                     left += Math.abs(styles.marginLeft) + Math.abs(styles.borderLeft)
1433                         + Math.abs(styles.paddingLeft);
1434                     top += Math.abs(styles.marginTop) + Math.abs(styles.borderTop)
1435                         + Math.abs(styles.paddingTop);
1436                 }
1437                 else if (boxFrame == "padding")
1438                 {
1439                     left += Math.abs(styles.marginLeft) + Math.abs(styles.borderLeft);
1440                     top += Math.abs(styles.marginTop) + Math.abs(styles.borderTop);
1441                     width += Math.abs(styles.paddingLeft) + Math.abs(styles.paddingRight);
1442                     height += Math.abs(styles.paddingTop) + Math.abs(styles.paddingBottom);
1443                 }
1444                 else if (boxFrame == "border")
1445                 {
1446                     left += Math.abs(styles.marginLeft);
1447                     top += Math.abs(styles.marginTop);
1448                     width += Math.abs(styles.paddingLeft) + Math.abs(styles.paddingRight)
1449                          + Math.abs(styles.borderLeft) + Math.abs(styles.borderRight);
1450                     height += Math.abs(styles.paddingTop) + Math.abs(styles.paddingBottom)
1451                         + Math.abs(styles.borderTop) + Math.abs(styles.borderBottom);
1452                 }
1453                 else if (boxFrame == "margin")
1454                 {
1455                     width += Math.abs(styles.paddingLeft) + Math.abs(styles.paddingRight)
1456                          + Math.abs(styles.borderLeft) + Math.abs(styles.borderRight)
1457                          + Math.abs(styles.marginLeft) + Math.abs(styles.marginRight);
1458                     height += Math.abs(styles.paddingTop) + Math.abs(styles.paddingBottom)
1459                         + Math.abs(styles.borderTop) + Math.abs(styles.borderBottom)
1460                         + Math.abs(styles.marginTop) + Math.abs(styles.marginBottom);
1461                 }
1462 
1463                 moveImp(nodes.lines.top, 0, top);
1464                 moveImp(nodes.lines.right, left + width, 0);
1465                 moveImp(nodes.lines.bottom, 0, top + height);
1466                 moveImp(nodes.lines.left, left, 0)
1467             }
1468 
1469             var body = getNonFrameBody(element);
1470             if (!body)
1471                 return this.unhighlight(context);
1472 
1473             if (colorObj.content)
1474                 nodes.content.style.cssText = contentCssText + " background-color: " + colorObj.content + " !important;";
1475             else
1476                 nodes.content.style.cssText = contentCssText + " background-color: #87CEEB !important;";
1477 
1478             if (colorObj.padding)
1479                 nodes.padding.style.cssText = paddingCssText + " background-color: " + colorObj.padding + " !important;";
1480             else
1481                 nodes.padding.style.cssText = paddingCssText + " background-color: #6A5ACD !important;";
1482 
1483             if (colorObj.border)
1484                 nodes.border.style.cssText = borderCssText + " background-color: " + colorObj.border + " !important;";
1485             else
1486                 nodes.border.style.cssText = borderCssText + " background-color: #444444 !important;";
1487 
1488             if (colorObj.margin)
1489                 nodes.margin.style.cssText = marginCssText + " background-color: " + colorObj.margin + " !important;";
1490             else
1491                 nodes.margin.style.cssText = marginCssText + " background-color: #EDFF64 !important;";
1492 
1493             var needsAppend = !nodes.offset.parentNode
1494                 || nodes.offset.parentNode.ownerDocument != body.ownerDocument;
1495 
1496             if (needsAppend)
1497             {
1498                 attachStyles(context, body.ownerDocument);
1499                 body.appendChild(nodes.offset);
1500             }
1501 
1502             if (showLines)
1503             {
1504                 if (!nodes.lines.top.parentNode)
1505                 {
1506                     if (nodes.parent)
1507                         body.appendChild(nodes.parent);
1508 
1509                     for (line in nodes.lines)
1510                         body.appendChild(nodes.lines[line]);
1511                 }
1512             }
1513             else if (nodes.lines.top.parentNode)
1514             {
1515                 if (nodes.parent)
1516                     body.removeChild(nodes.parent);
1517 
1518                 for (line in nodes.lines)
1519                     body.removeChild(nodes.lines[line]);
1520             }
1521         }
1522         else
1523         {
1524             this.ihl = getImageMapHighlighter(context);
1525             this.ihl.highlight(element, true);
1526         }
1527     },
1528 
1529     unhighlight: function(context)
1530     {
1531         HighlighterCache.clear();
1532         quickInfoBox.hide();
1533     },
1534 
1535     getNodes: function(context, isMulti)
1536     {
1537         if (context.window)
1538         {
1539             var doc = context.window.document;
1540 
1541             if (FBTrace.DBG_ERRORS && !doc)
1542                 FBTrace.sysout("inspector getNodes no document for window:" + window.location);
1543             if (FBTrace.DBG_INSPECT && doc)
1544                 FBTrace.sysout("inspect.getNodes doc: " + doc.location);
1545 
1546             if (!isMulti)
1547             {
1548                 var nodes = HighlighterCache.get(ident.boxModel);
1549                 if (nodes)
1550                     return nodes;
1551             }
1552 
1553             var Ruler = "firebugResetStyles firebugBlockBackgroundColor firebugRuler firebugRuler";
1554             var Box = "firebugResetStyles firebugBlockBackgroundColor firebugLayoutBox firebugLayoutBox";
1555             var CustomizableBox = "firebugResetStyles firebugLayoutBox";
1556             var Line = "firebugResetStyles firebugBlockBackgroundColor firebugLayoutLine firebugLayoutLine";
1557 
1558             function create(className, name)
1559             {
1560                 var div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
1561                 hideElementFromInspection(div);
1562 
1563                 if (className !== CustomizableBox)
1564                     div.className = className + name;
1565                 else
1566                     div.className = className;
1567 
1568                 return div;
1569             }
1570 
1571             nodes =
1572             {
1573                 parent: create(Box, "Parent"),
1574                 rulerH: create(Ruler, "H"),
1575                 rulerV: create(Ruler, "V"),
1576                 offset: create(Box, "Offset"),
1577                 margin: create(CustomizableBox, "Margin"),
1578                 border: create(CustomizableBox, "Border"),
1579                 padding: create(CustomizableBox, "Padding"),
1580                 content: create(CustomizableBox, "Content"),
1581                 lines: {
1582                     top: create(Line, "Top"),
1583                     right: create(Line, "Right"),
1584                     bottom: create(Line, "Bottom"),
1585                     left: create(Line, "Left")
1586                 }
1587             };
1588 
1589             nodes.parent.appendChild(nodes.rulerH);
1590             nodes.parent.appendChild(nodes.rulerV);
1591             nodes.offset.appendChild(nodes.margin);
1592             nodes.margin.appendChild(nodes.border);
1593             nodes.border.appendChild(nodes.padding);
1594             nodes.padding.appendChild(nodes.content);
1595         }
1596 
1597         nodes.ident = ident.boxModel;
1598         HighlighterCache.add(nodes);
1599         return nodes;
1600     },
1601 
1602     setNodesByOffsetParent: function(win, offsetParent, nodes)
1603     {
1604         var parentStyle = win.getComputedStyle(offsetParent, "");
1605         var parentOffset = Dom.getLTRBWH(offsetParent);
1606         var parentX = parentOffset.left + parseInt(parentStyle.borderLeftWidth, 10);
1607         var parentY = parentOffset.top + parseInt(parentStyle.borderTopWidth, 10);
1608         var parentW = offsetParent.offsetWidth-1;
1609         var parentH = offsetParent.offsetHeight-1;
1610 
1611         nodes.parent.style.cssText = moveImp(null, parentX, parentY) + resizeImp(null, parentW, parentH);
1612 
1613         if (parentX < 14)
1614             Css.setClass(nodes.parent, "overflowRulerX");
1615         else
1616             Css.removeClass(nodes.parent, "overflowRulerX");
1617 
1618         if (parentY < 14)
1619             Css.setClass(nodes.parent, "overflowRulerY");
1620         else
1621             Css.removeClass(nodes.parent, "overflowRulerY");
1622     }
1623 };
1624 
1625 // ********************************************************************************************* //
1626 
1627 function getNonFrameBody(elt)
1628 {
1629     if (Dom.isRange(elt))
1630     {
1631         elt = elt.commonAncestorContainer;
1632     }
1633     var body = Dom.getBody(elt.ownerDocument);
1634     return (body.localName && body.localName.toUpperCase() === "FRAMESET") ? null : body;
1635 }
1636 
1637 function attachStyles(context, doc)
1638 {
1639     if (!context.highlightStyleCache)
1640         context.highlightStyleCache = new WeakMap();
1641     var highlightStyleCache = context.highlightStyleCache;
1642 
1643     var style;
1644     if (highlightStyleCache.has(doc))
1645     {
1646         style = highlightStyleCache.get(doc);
1647     }
1648     else
1649     {
1650         style = Css.createStyleSheet(doc, highlightCssUrl);
1651         highlightStyleCache.set(doc, style);
1652     }
1653 
1654     // Cater for the possiblity that someone might have removed our stylesheet.
1655     if (!style.parentNode)
1656         Css.addStyleSheet(doc, style);
1657 }
1658 
1659 function createProxiesForDisabledElements(body)
1660 {
1661     var i, rect, div, node, cs, css,
1662         doc = body.ownerDocument,
1663         xpe = new XPathEvaluator(),
1664         nsResolver = xpe.createNSResolver(doc.documentElement);
1665 
1666     var result = xpe.evaluate("//*[@disabled]", doc.documentElement,
1667         nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1668 
1669     var l = result.snapshotLength;
1670     for (i=0; i<l; i++)
1671     {
1672         node = result.snapshotItem(i);
1673         cs = body.ownerDocument.defaultView.getComputedStyle(node, null);
1674         rect = node.getBoundingClientRect();
1675         div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
1676         hideElementFromInspection(div);
1677         div.className = "firebugResetStyles fbProxyElement";
1678 
1679         css = moveImp(null, rect.left, rect.top + body.scrollTop) + resizeImp(null, rect.width, rect.height);
1680         if (cs.transform && cs.transform != "none")
1681             css += "transform:" + cs.transform + " !important;" +
1682                    "transform-origin:" + cs.transformOrigin + " !important;";
1683         if (cs.borderRadius)
1684             css += "border-radius:" + cs.borderRadius + " !important;";
1685         if (cs.borderTopLeftRadius)
1686             css += "border-top-left-radius:" + cs.borderTopLeftRadius + " !important;";
1687         if (cs.borderTopRightRadius)
1688             css += "border-top-right-radius:" + cs.borderTopRightRadius + " !important;";
1689         if (cs.borderBottomRightRadius)
1690             css += "border-bottom-right-radius:" + cs.borderBottomRightRadius + " !important;";
1691         if (cs.borderBottomLeftRadius)
1692             css += "border-bottom-left-radius:" + cs.borderBottomLeftRadius + " !important;";
1693 
1694         div.style.cssText = css;
1695         div.fbProxyFor = node;
1696 
1697         body.appendChild(div);
1698         div.ident = ident.proxyElt;
1699         HighlighterCache.add(div);
1700     }
1701 }
1702 
1703 function rgbToHex(value)
1704 {
1705     return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, function(_, r, g, b)
1706     {
1707         return "#"+((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase();
1708     });
1709 }
1710 
1711 function isVisibleElement(elt)
1712 {
1713     var invisibleElements =
1714         {
1715             "head": true,
1716             "base": true,
1717             "basefont": true,
1718             "isindex": true,
1719             "link": true,
1720             "meta": true,
1721             "script": true,
1722             "style": true,
1723             "title": true
1724         };
1725 
1726     return !invisibleElements[elt.nodeName.toLowerCase()];
1727 }
1728 
1729 function hideElementFromInspection(elt)
1730 {
1731     if (!FBTrace.DBG_INSPECT)
1732         Firebug.setIgnored(elt);
1733 }
1734 
1735 // highlightType is only to be used for multihighlighters
1736 function storeHighlighterParams(highlighter, context, element, boxFrame, colorObj,
1737     highlightType, isMulti)
1738 {
1739     var fir = Firebug.Inspector.repaint;
1740 
1741     fir.highlighter = highlighter;
1742     fir.context = context;
1743     fir.element = element;
1744     fir.boxFrame = boxFrame;
1745     fir.colorObj = colorObj;
1746     fir.highlightType = highlightType;
1747     fir.isMulti = isMulti;
1748 
1749     Firebug.Inspector.highlightedContext = context;
1750 }
1751 
1752 // ********************************************************************************************* //
1753 // Registration
1754 
1755 Firebug.registerModule(Firebug.Inspector);
1756 
1757 return Firebug.Inspector;
1758 
1759 // ********************************************************************************************* //
1760 });
1761