mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 703031 - [highlighter] Refactor the highlighter code. Create highlighter.jsm Patch A; r=rcampbell
This commit is contained in:
parent
6fb0c39e7f
commit
f7f39d5d20
@ -49,6 +49,7 @@ EXTRA_JS_MODULES = \
|
||||
domplate.jsm \
|
||||
InsideOutBox.jsm \
|
||||
TreePanel.jsm \
|
||||
highlighter.jsm \
|
||||
$(NULL)
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
|
678
browser/devtools/highlighter/highlighter.jsm
Normal file
678
browser/devtools/highlighter/highlighter.jsm
Normal file
@ -0,0 +1,678 @@
|
||||
//// Highlighter
|
||||
|
||||
/**
|
||||
* A highlighter mechanism.
|
||||
*
|
||||
* The highlighter is built dynamically once the Inspector is invoked:
|
||||
* <stack id="highlighter-container">
|
||||
* <vbox id="highlighter-veil-container">...</vbox>
|
||||
* <box id="highlighter-controls>...</vbox>
|
||||
* </stack>
|
||||
*
|
||||
* @param object aInspector
|
||||
* The InspectorUI instance.
|
||||
*/
|
||||
function Highlighter(aInspector)
|
||||
{
|
||||
this.IUI = aInspector;
|
||||
this._init();
|
||||
}
|
||||
|
||||
Highlighter.prototype = {
|
||||
_init: function Highlighter__init()
|
||||
{
|
||||
this.browser = this.IUI.browser;
|
||||
this.chromeDoc = this.IUI.chromeDoc;
|
||||
|
||||
let stack = this.browser.parentNode;
|
||||
this.win = this.browser.contentWindow;
|
||||
this._highlighting = false;
|
||||
|
||||
this.highlighterContainer = this.chromeDoc.createElement("stack");
|
||||
this.highlighterContainer.id = "highlighter-container";
|
||||
|
||||
this.veilContainer = this.chromeDoc.createElement("vbox");
|
||||
this.veilContainer.id = "highlighter-veil-container";
|
||||
|
||||
// The controlsBox will host the different interactive
|
||||
// elements of the highlighter (buttons, toolbars, ...).
|
||||
let controlsBox = this.chromeDoc.createElement("box");
|
||||
controlsBox.id = "highlighter-controls";
|
||||
this.highlighterContainer.appendChild(this.veilContainer);
|
||||
this.highlighterContainer.appendChild(controlsBox);
|
||||
|
||||
stack.appendChild(this.highlighterContainer);
|
||||
|
||||
// The veil will make the whole page darker except
|
||||
// for the region of the selected box.
|
||||
this.buildVeil(this.veilContainer);
|
||||
|
||||
this.buildInfobar(controlsBox);
|
||||
|
||||
if (!this.IUI.store.getValue(this.winID, "inspecting")) {
|
||||
this.veilContainer.setAttribute("locked", true);
|
||||
this.nodeInfo.container.setAttribute("locked", true);
|
||||
}
|
||||
|
||||
this.browser.addEventListener("resize", this, true);
|
||||
this.browser.addEventListener("scroll", this, true);
|
||||
|
||||
this.transitionDisabler = null;
|
||||
|
||||
this.computeZoomFactor();
|
||||
this.handleResize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the veil:
|
||||
*
|
||||
* <vbox id="highlighter-veil-container">
|
||||
* <box id="highlighter-veil-topbox" class="highlighter-veil"/>
|
||||
* <hbox id="highlighter-veil-middlebox">
|
||||
* <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
|
||||
* <box id="highlighter-veil-transparentbox"/>
|
||||
* <box id="highlighter-veil-rightbox" class="highlighter-veil"/>
|
||||
* </hbox>
|
||||
* <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
|
||||
* </vbox>
|
||||
*
|
||||
* @param nsIDOMElement aParent
|
||||
* The container of the veil boxes.
|
||||
*/
|
||||
buildVeil: function Highlighter_buildVeil(aParent)
|
||||
{
|
||||
// We will need to resize these boxes to surround a node.
|
||||
// See highlightRectangle().
|
||||
|
||||
this.veilTopBox = this.chromeDoc.createElement("box");
|
||||
this.veilTopBox.id = "highlighter-veil-topbox";
|
||||
this.veilTopBox.className = "highlighter-veil";
|
||||
|
||||
this.veilMiddleBox = this.chromeDoc.createElement("hbox");
|
||||
this.veilMiddleBox.id = "highlighter-veil-middlebox";
|
||||
|
||||
this.veilLeftBox = this.chromeDoc.createElement("box");
|
||||
this.veilLeftBox.id = "highlighter-veil-leftbox";
|
||||
this.veilLeftBox.className = "highlighter-veil";
|
||||
|
||||
this.veilTransparentBox = this.chromeDoc.createElement("box");
|
||||
this.veilTransparentBox.id = "highlighter-veil-transparentbox";
|
||||
|
||||
// We don't need any references to veilRightBox and veilBottomBox.
|
||||
// These boxes are automatically resized (flex=1)
|
||||
|
||||
let veilRightBox = this.chromeDoc.createElement("box");
|
||||
veilRightBox.id = "highlighter-veil-rightbox";
|
||||
veilRightBox.className = "highlighter-veil";
|
||||
|
||||
let veilBottomBox = this.chromeDoc.createElement("box");
|
||||
veilBottomBox.id = "highlighter-veil-bottombox";
|
||||
veilBottomBox.className = "highlighter-veil";
|
||||
|
||||
this.veilMiddleBox.appendChild(this.veilLeftBox);
|
||||
this.veilMiddleBox.appendChild(this.veilTransparentBox);
|
||||
this.veilMiddleBox.appendChild(veilRightBox);
|
||||
|
||||
aParent.appendChild(this.veilTopBox);
|
||||
aParent.appendChild(this.veilMiddleBox);
|
||||
aParent.appendChild(veilBottomBox);
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the node Infobar.
|
||||
*
|
||||
* <box id="highlighter-nodeinfobar-container">
|
||||
* <box id="Highlighter-nodeinfobar-arrow-top"/>
|
||||
* <vbox id="highlighter-nodeinfobar">
|
||||
* <label id="highlighter-nodeinfobar-tagname"/>
|
||||
* <label id="highlighter-nodeinfobar-id"/>
|
||||
* <vbox id="highlighter-nodeinfobar-classes"/>
|
||||
* </vbox>
|
||||
* <box id="Highlighter-nodeinfobar-arrow-bottom"/>
|
||||
* </box>
|
||||
*
|
||||
* @param nsIDOMElement aParent
|
||||
* The container of the infobar.
|
||||
*/
|
||||
buildInfobar: function Highlighter_buildInfobar(aParent)
|
||||
{
|
||||
let container = this.chromeDoc.createElement("box");
|
||||
container.id = "highlighter-nodeinfobar-container";
|
||||
container.setAttribute("position", "top");
|
||||
container.setAttribute("disabled", "true");
|
||||
|
||||
let nodeInfobar = this.chromeDoc.createElement("hbox");
|
||||
nodeInfobar.id = "highlighter-nodeinfobar";
|
||||
|
||||
let arrowBoxTop = this.chromeDoc.createElement("box");
|
||||
arrowBoxTop.className = "highlighter-nodeinfobar-arrow";
|
||||
arrowBoxTop.id = "highlighter-nodeinfobar-arrow-top";
|
||||
|
||||
let arrowBoxBottom = this.chromeDoc.createElement("box");
|
||||
arrowBoxBottom.className = "highlighter-nodeinfobar-arrow";
|
||||
arrowBoxBottom.id = "highlighter-nodeinfobar-arrow-bottom";
|
||||
|
||||
let tagNameLabel = this.chromeDoc.createElement("label");
|
||||
tagNameLabel.id = "highlighter-nodeinfobar-tagname";
|
||||
tagNameLabel.className = "plain";
|
||||
|
||||
let idLabel = this.chromeDoc.createElement("label");
|
||||
idLabel.id = "highlighter-nodeinfobar-id";
|
||||
idLabel.className = "plain";
|
||||
|
||||
let classesBox = this.chromeDoc.createElement("hbox");
|
||||
classesBox.id = "highlighter-nodeinfobar-classes";
|
||||
|
||||
nodeInfobar.appendChild(tagNameLabel);
|
||||
nodeInfobar.appendChild(idLabel);
|
||||
nodeInfobar.appendChild(classesBox);
|
||||
container.appendChild(arrowBoxTop);
|
||||
container.appendChild(nodeInfobar);
|
||||
container.appendChild(arrowBoxBottom);
|
||||
|
||||
aParent.appendChild(container);
|
||||
|
||||
let barHeight = container.getBoundingClientRect().height;
|
||||
|
||||
this.nodeInfo = {
|
||||
tagNameLabel: tagNameLabel,
|
||||
idLabel: idLabel,
|
||||
classesBox: classesBox,
|
||||
container: container,
|
||||
barHeight: barHeight,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes.
|
||||
*/
|
||||
destroy: function Highlighter_destroy()
|
||||
{
|
||||
this.IUI.win.clearTimeout(this.transitionDisabler);
|
||||
this.browser.removeEventListener("scroll", this, true);
|
||||
this.browser.removeEventListener("resize", this, true);
|
||||
this.boundCloseEventHandler = null;
|
||||
this._contentRect = null;
|
||||
this._highlightRect = null;
|
||||
this._highlighting = false;
|
||||
this.veilTopBox = null;
|
||||
this.veilLeftBox = null;
|
||||
this.veilMiddleBox = null;
|
||||
this.veilTransparentBox = null;
|
||||
this.veilContainer = null;
|
||||
this.node = null;
|
||||
this.nodeInfo = null;
|
||||
this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
|
||||
this.highlighterContainer = null;
|
||||
this.win = null
|
||||
this.browser = null;
|
||||
this.chromeDoc = null;
|
||||
this.IUI = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the highlighter highlighting? Public method for querying the state
|
||||
* of the highlighter.
|
||||
*/
|
||||
get isHighlighting() {
|
||||
return this._highlighting;
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight this.node, unhilighting first if necessary.
|
||||
*
|
||||
* @param boolean aScroll
|
||||
* Boolean determining whether to scroll or not.
|
||||
*/
|
||||
highlight: function Highlighter_highlight(aScroll)
|
||||
{
|
||||
let rect = null;
|
||||
|
||||
if (this.node && this.isNodeHighlightable(this.node)) {
|
||||
|
||||
if (aScroll) {
|
||||
this.node.scrollIntoView();
|
||||
}
|
||||
|
||||
let clientRect = this.node.getBoundingClientRect();
|
||||
|
||||
// Go up in the tree of frames to determine the correct rectangle.
|
||||
// clientRect is read-only, we need to be able to change properties.
|
||||
rect = {top: clientRect.top,
|
||||
left: clientRect.left,
|
||||
width: clientRect.width,
|
||||
height: clientRect.height};
|
||||
|
||||
let frameWin = this.node.ownerDocument.defaultView;
|
||||
|
||||
// We iterate through all the parent windows.
|
||||
while (true) {
|
||||
|
||||
// Does the selection overflow on the right of its window?
|
||||
let diffx = frameWin.innerWidth - (rect.left + rect.width);
|
||||
if (diffx < 0) {
|
||||
rect.width += diffx;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the bottom of its window?
|
||||
let diffy = frameWin.innerHeight - (rect.top + rect.height);
|
||||
if (diffy < 0) {
|
||||
rect.height += diffy;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the left of its window?
|
||||
if (rect.left < 0) {
|
||||
rect.width += rect.left;
|
||||
rect.left = 0;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the top of its window?
|
||||
if (rect.top < 0) {
|
||||
rect.height += rect.top;
|
||||
rect.top = 0;
|
||||
}
|
||||
|
||||
// Selection has been clipped to fit in its own window.
|
||||
|
||||
// Are we in the top-level window?
|
||||
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in an iframe.
|
||||
// We take into account the parent iframe position and its
|
||||
// offset (borders and padding).
|
||||
let frameRect = frameWin.frameElement.getBoundingClientRect();
|
||||
|
||||
let [offsetTop, offsetLeft] =
|
||||
this.IUI.getIframeContentOffset(frameWin.frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = frameWin.parent;
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightRectangle(rect);
|
||||
|
||||
this.moveInfobar();
|
||||
|
||||
if (this._highlighting) {
|
||||
Services.obs.notifyObservers(null,
|
||||
INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight the given node.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* a DOM element to be highlighted
|
||||
* @param object aParams
|
||||
* extra parameters object
|
||||
*/
|
||||
highlightNode: function Highlighter_highlightNode(aNode, aParams)
|
||||
{
|
||||
this.node = aNode;
|
||||
this.updateInfobar();
|
||||
this.highlight(aParams && aParams.scroll);
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight a rectangular region.
|
||||
*
|
||||
* @param object aRect
|
||||
* The rectangle region to highlight.
|
||||
* @returns boolean
|
||||
* True if the rectangle was highlighted, false otherwise.
|
||||
*/
|
||||
highlightRectangle: function Highlighter_highlightRectangle(aRect)
|
||||
{
|
||||
if (!aRect) {
|
||||
this.unhighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
let oldRect = this._contentRect;
|
||||
|
||||
if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
|
||||
aRect.width == oldRect.width && aRect.height == oldRect.height) {
|
||||
return this._highlighting; // same rectangle
|
||||
}
|
||||
|
||||
// adjust rect for zoom scaling
|
||||
let aRectScaled = {};
|
||||
for (let prop in aRect) {
|
||||
aRectScaled[prop] = aRect[prop] * this.zoom;
|
||||
}
|
||||
|
||||
if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
|
||||
aRectScaled.width > 0 && aRectScaled.height > 0) {
|
||||
|
||||
this.veilTransparentBox.style.visibility = "visible";
|
||||
|
||||
// The bottom div and the right div are flexibles (flex=1).
|
||||
// We don't need to resize them.
|
||||
this.veilTopBox.style.height = aRectScaled.top + "px";
|
||||
this.veilLeftBox.style.width = aRectScaled.left + "px";
|
||||
this.veilMiddleBox.style.height = aRectScaled.height + "px";
|
||||
this.veilTransparentBox.style.width = aRectScaled.width + "px";
|
||||
|
||||
this._highlighting = true;
|
||||
} else {
|
||||
this.unhighlight();
|
||||
}
|
||||
|
||||
this._contentRect = aRect; // save orig (non-scaled) rect
|
||||
this._highlightRect = aRectScaled; // and save the scaled rect.
|
||||
|
||||
return this._highlighting;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the highlighter surface.
|
||||
*/
|
||||
unhighlight: function Highlighter_unhighlight()
|
||||
{
|
||||
this._highlighting = false;
|
||||
this.veilMiddleBox.style.height = 0;
|
||||
this.veilTransparentBox.style.width = 0;
|
||||
this.veilTransparentBox.style.visibility = "hidden";
|
||||
Services.obs.notifyObservers(null,
|
||||
INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update node information (tagName#id.class)
|
||||
*/
|
||||
updateInfobar: function Highlighter_updateInfobar()
|
||||
{
|
||||
// Tag name
|
||||
this.nodeInfo.tagNameLabel.textContent = this.node.tagName;
|
||||
|
||||
// ID
|
||||
this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
|
||||
|
||||
// Classes
|
||||
let classes = this.nodeInfo.classesBox;
|
||||
while (classes.hasChildNodes()) {
|
||||
classes.removeChild(classes.firstChild);
|
||||
}
|
||||
|
||||
if (this.node.className) {
|
||||
let fragment = this.chromeDoc.createDocumentFragment();
|
||||
for (let i = 0; i < this.node.classList.length; i++) {
|
||||
let classLabel = this.chromeDoc.createElement("label");
|
||||
classLabel.className = "highlighter-nodeinfobar-class plain";
|
||||
classLabel.textContent = "." + this.node.classList[i];
|
||||
fragment.appendChild(classLabel);
|
||||
}
|
||||
classes.appendChild(fragment);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the Infobar to the right place in the highlighter.
|
||||
*/
|
||||
moveInfobar: function Highlighter_moveInfobar()
|
||||
{
|
||||
if (this._highlightRect) {
|
||||
let winHeight = this.win.innerHeight * this.zoom;
|
||||
let winWidth = this.win.innerWidth * this.zoom;
|
||||
|
||||
let rect = {top: this._highlightRect.top,
|
||||
left: this._highlightRect.left,
|
||||
width: this._highlightRect.width,
|
||||
height: this._highlightRect.height};
|
||||
|
||||
rect.top = Math.max(rect.top, 0);
|
||||
rect.left = Math.max(rect.left, 0);
|
||||
rect.width = Math.max(rect.width, 0);
|
||||
rect.height = Math.max(rect.height, 0);
|
||||
|
||||
rect.top = Math.min(rect.top, winHeight);
|
||||
rect.left = Math.min(rect.left, winWidth);
|
||||
|
||||
this.nodeInfo.container.removeAttribute("disabled");
|
||||
// Can the bar be above the node?
|
||||
if (rect.top < this.nodeInfo.barHeight) {
|
||||
// No. Can we move the toolbar under the node?
|
||||
if (rect.top + rect.height +
|
||||
this.nodeInfo.barHeight > winHeight) {
|
||||
// No. Let's move it inside.
|
||||
this.nodeInfo.container.style.top = rect.top + "px";
|
||||
this.nodeInfo.container.setAttribute("position", "overlap");
|
||||
} else {
|
||||
// Yes. Let's move it under the node.
|
||||
this.nodeInfo.container.style.top = rect.top + rect.height + "px";
|
||||
this.nodeInfo.container.setAttribute("position", "bottom");
|
||||
}
|
||||
} else {
|
||||
// Yes. Let's move it on top of the node.
|
||||
this.nodeInfo.container.style.top =
|
||||
rect.top - this.nodeInfo.barHeight + "px";
|
||||
this.nodeInfo.container.setAttribute("position", "top");
|
||||
}
|
||||
|
||||
let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
|
||||
let left = rect.left + rect.width / 2 - barWidth / 2;
|
||||
|
||||
// Make sure the whole infobar is visible
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
this.nodeInfo.container.setAttribute("hide-arrow", "true");
|
||||
} else {
|
||||
if (left + barWidth > winWidth) {
|
||||
left = winWidth - barWidth;
|
||||
this.nodeInfo.container.setAttribute("hide-arrow", "true");
|
||||
} else {
|
||||
this.nodeInfo.container.removeAttribute("hide-arrow");
|
||||
}
|
||||
}
|
||||
this.nodeInfo.container.style.left = left + "px";
|
||||
} else {
|
||||
this.nodeInfo.container.style.left = "0";
|
||||
this.nodeInfo.container.style.top = "0";
|
||||
this.nodeInfo.container.setAttribute("position", "top");
|
||||
this.nodeInfo.container.setAttribute("hide-arrow", "true");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the midpoint of a line from pointA to pointB.
|
||||
*
|
||||
* @param object aPointA
|
||||
* An object with x and y properties.
|
||||
* @param object aPointB
|
||||
* An object with x and y properties.
|
||||
* @returns object
|
||||
* An object with x and y properties.
|
||||
*/
|
||||
midPoint: function Highlighter_midPoint(aPointA, aPointB)
|
||||
{
|
||||
let pointC = { };
|
||||
pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
|
||||
pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
|
||||
return pointC;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the node under the highlighter rectangle. Useful for testing.
|
||||
* Calculation based on midpoint of diagonal from top left to bottom right
|
||||
* of panel.
|
||||
*
|
||||
* @returns nsIDOMNode|null
|
||||
* Returns the node under the current highlighter rectangle. Null is
|
||||
* returned if there is no node highlighted.
|
||||
*/
|
||||
get highlitNode()
|
||||
{
|
||||
// Not highlighting? Bail.
|
||||
if (!this._highlighting || !this._contentRect) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let a = {
|
||||
x: this._contentRect.left,
|
||||
y: this._contentRect.top
|
||||
};
|
||||
|
||||
let b = {
|
||||
x: a.x + this._contentRect.width,
|
||||
y: a.y + this._contentRect.height
|
||||
};
|
||||
|
||||
// Get midpoint of diagonal line.
|
||||
let midpoint = this.midPoint(a, b);
|
||||
|
||||
return this.IUI.elementFromPoint(this.win.document, midpoint.x,
|
||||
midpoint.y);
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the specified node highlightable?
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* the DOM element in question
|
||||
* @returns boolean
|
||||
* True if the node is highlightable or false otherwise.
|
||||
*/
|
||||
isNodeHighlightable: function Highlighter_isNodeHighlightable(aNode)
|
||||
{
|
||||
if (aNode.nodeType != aNode.ELEMENT_NODE) {
|
||||
return false;
|
||||
}
|
||||
let nodeName = aNode.nodeName.toLowerCase();
|
||||
return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Store page zoom factor.
|
||||
*/
|
||||
computeZoomFactor: function Highlighter_computeZoomFactor() {
|
||||
this.zoom =
|
||||
this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils)
|
||||
.screenPixelsPerCSSPixel;
|
||||
},
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
//// Event Handling
|
||||
|
||||
attachInspectListeners: function Highlighter_attachInspectListeners()
|
||||
{
|
||||
this.browser.addEventListener("mousemove", this, true);
|
||||
this.browser.addEventListener("click", this, true);
|
||||
this.browser.addEventListener("dblclick", this, true);
|
||||
this.browser.addEventListener("mousedown", this, true);
|
||||
this.browser.addEventListener("mouseup", this, true);
|
||||
},
|
||||
|
||||
detachInspectListeners: function Highlighter_detachInspectListeners()
|
||||
{
|
||||
this.browser.removeEventListener("mousemove", this, true);
|
||||
this.browser.removeEventListener("click", this, true);
|
||||
this.browser.removeEventListener("dblclick", this, true);
|
||||
this.browser.removeEventListener("mousedown", this, true);
|
||||
this.browser.removeEventListener("mouseup", this, true);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generic event handler.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
* The DOM event object.
|
||||
*/
|
||||
handleEvent: function Highlighter_handleEvent(aEvent)
|
||||
{
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
this.handleClick(aEvent);
|
||||
break;
|
||||
case "mousemove":
|
||||
this.handleMouseMove(aEvent);
|
||||
break;
|
||||
case "resize":
|
||||
this.computeZoomFactor();
|
||||
this.brieflyDisableTransitions();
|
||||
this.handleResize(aEvent);
|
||||
break;
|
||||
case "dblclick":
|
||||
case "mousedown":
|
||||
case "mouseup":
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
case "scroll":
|
||||
this.brieflyDisableTransitions();
|
||||
this.highlight();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable the CSS transitions for a short time to avoid laggy animations
|
||||
* during scrolling or resizing.
|
||||
*/
|
||||
brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
|
||||
{
|
||||
if (this.transitionDisabler) {
|
||||
this.IUI.win.clearTimeout(this.transitionDisabler);
|
||||
} else {
|
||||
this.veilContainer.setAttribute("disable-transitions", "true");
|
||||
this.nodeInfo.container.setAttribute("disable-transitions", "true");
|
||||
}
|
||||
this.transitionDisabler =
|
||||
this.IUI.win.setTimeout(function() {
|
||||
this.veilContainer.removeAttribute("disable-transitions");
|
||||
this.nodeInfo.container.removeAttribute("disable-transitions");
|
||||
this.transitionDisabler = null;
|
||||
}.bind(this), 500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle clicks.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
* The DOM event.
|
||||
*/
|
||||
handleClick: function Highlighter_handleClick(aEvent)
|
||||
{
|
||||
// Stop inspection when the user clicks on a node.
|
||||
if (aEvent.button == 0) {
|
||||
let win = aEvent.target.ownerDocument.defaultView;
|
||||
this.IUI.stopInspecting();
|
||||
win.focus();
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle mousemoves in panel when InspectorUI.inspecting is true.
|
||||
*
|
||||
* @param nsiDOMEvent aEvent
|
||||
* The MouseEvent triggering the method.
|
||||
*/
|
||||
handleMouseMove: function Highlighter_handleMouseMove(aEvent)
|
||||
{
|
||||
let element = this.IUI.elementFromPoint(aEvent.target.ownerDocument,
|
||||
aEvent.clientX, aEvent.clientY);
|
||||
if (element && element != this.node) {
|
||||
this.IUI.inspectNode(element);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle window resize events.
|
||||
*/
|
||||
handleResize: function Highlighter_handleResize()
|
||||
{
|
||||
this.highlight();
|
||||
},
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -98,683 +98,6 @@ const INSPECTOR_NOTIFICATIONS = {
|
||||
EDITOR_SAVED: "inspector-editor-saved",
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// Highlighter
|
||||
|
||||
/**
|
||||
* A highlighter mechanism.
|
||||
*
|
||||
* The highlighter is built dynamically once the Inspector is invoked:
|
||||
* <stack id="highlighter-container">
|
||||
* <vbox id="highlighter-veil-container">...</vbox>
|
||||
* <box id="highlighter-controls>...</vbox>
|
||||
* </stack>
|
||||
*
|
||||
* @param object aInspector
|
||||
* The InspectorUI instance.
|
||||
*/
|
||||
function Highlighter(aInspector)
|
||||
{
|
||||
this.IUI = aInspector;
|
||||
this._init();
|
||||
}
|
||||
|
||||
Highlighter.prototype = {
|
||||
_init: function Highlighter__init()
|
||||
{
|
||||
this.browser = this.IUI.browser;
|
||||
this.chromeDoc = this.IUI.chromeDoc;
|
||||
|
||||
let stack = this.browser.parentNode;
|
||||
this.win = this.browser.contentWindow;
|
||||
this._highlighting = false;
|
||||
|
||||
this.highlighterContainer = this.chromeDoc.createElement("stack");
|
||||
this.highlighterContainer.id = "highlighter-container";
|
||||
|
||||
this.veilContainer = this.chromeDoc.createElement("vbox");
|
||||
this.veilContainer.id = "highlighter-veil-container";
|
||||
|
||||
// The controlsBox will host the different interactive
|
||||
// elements of the highlighter (buttons, toolbars, ...).
|
||||
let controlsBox = this.chromeDoc.createElement("box");
|
||||
controlsBox.id = "highlighter-controls";
|
||||
this.highlighterContainer.appendChild(this.veilContainer);
|
||||
this.highlighterContainer.appendChild(controlsBox);
|
||||
|
||||
stack.appendChild(this.highlighterContainer);
|
||||
|
||||
// The veil will make the whole page darker except
|
||||
// for the region of the selected box.
|
||||
this.buildVeil(this.veilContainer);
|
||||
|
||||
this.buildInfobar(controlsBox);
|
||||
|
||||
if (!this.IUI.store.getValue(this.winID, "inspecting")) {
|
||||
this.veilContainer.setAttribute("locked", true);
|
||||
this.nodeInfo.container.setAttribute("locked", true);
|
||||
}
|
||||
|
||||
this.browser.addEventListener("resize", this, true);
|
||||
this.browser.addEventListener("scroll", this, true);
|
||||
|
||||
this.transitionDisabler = null;
|
||||
|
||||
this.computeZoomFactor();
|
||||
this.handleResize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the veil:
|
||||
*
|
||||
* <vbox id="highlighter-veil-container">
|
||||
* <box id="highlighter-veil-topbox" class="highlighter-veil"/>
|
||||
* <hbox id="highlighter-veil-middlebox">
|
||||
* <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
|
||||
* <box id="highlighter-veil-transparentbox"/>
|
||||
* <box id="highlighter-veil-rightbox" class="highlighter-veil"/>
|
||||
* </hbox>
|
||||
* <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
|
||||
* </vbox>
|
||||
*
|
||||
* @param nsIDOMElement aParent
|
||||
* The container of the veil boxes.
|
||||
*/
|
||||
buildVeil: function Highlighter_buildVeil(aParent)
|
||||
{
|
||||
// We will need to resize these boxes to surround a node.
|
||||
// See highlightRectangle().
|
||||
|
||||
this.veilTopBox = this.chromeDoc.createElement("box");
|
||||
this.veilTopBox.id = "highlighter-veil-topbox";
|
||||
this.veilTopBox.className = "highlighter-veil";
|
||||
|
||||
this.veilMiddleBox = this.chromeDoc.createElement("hbox");
|
||||
this.veilMiddleBox.id = "highlighter-veil-middlebox";
|
||||
|
||||
this.veilLeftBox = this.chromeDoc.createElement("box");
|
||||
this.veilLeftBox.id = "highlighter-veil-leftbox";
|
||||
this.veilLeftBox.className = "highlighter-veil";
|
||||
|
||||
this.veilTransparentBox = this.chromeDoc.createElement("box");
|
||||
this.veilTransparentBox.id = "highlighter-veil-transparentbox";
|
||||
|
||||
// We don't need any references to veilRightBox and veilBottomBox.
|
||||
// These boxes are automatically resized (flex=1)
|
||||
|
||||
let veilRightBox = this.chromeDoc.createElement("box");
|
||||
veilRightBox.id = "highlighter-veil-rightbox";
|
||||
veilRightBox.className = "highlighter-veil";
|
||||
|
||||
let veilBottomBox = this.chromeDoc.createElement("box");
|
||||
veilBottomBox.id = "highlighter-veil-bottombox";
|
||||
veilBottomBox.className = "highlighter-veil";
|
||||
|
||||
this.veilMiddleBox.appendChild(this.veilLeftBox);
|
||||
this.veilMiddleBox.appendChild(this.veilTransparentBox);
|
||||
this.veilMiddleBox.appendChild(veilRightBox);
|
||||
|
||||
aParent.appendChild(this.veilTopBox);
|
||||
aParent.appendChild(this.veilMiddleBox);
|
||||
aParent.appendChild(veilBottomBox);
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the node Infobar.
|
||||
*
|
||||
* <box id="highlighter-nodeinfobar-container">
|
||||
* <box id="Highlighter-nodeinfobar-arrow-top"/>
|
||||
* <vbox id="highlighter-nodeinfobar">
|
||||
* <label id="highlighter-nodeinfobar-tagname"/>
|
||||
* <label id="highlighter-nodeinfobar-id"/>
|
||||
* <vbox id="highlighter-nodeinfobar-classes"/>
|
||||
* </vbox>
|
||||
* <box id="Highlighter-nodeinfobar-arrow-bottom"/>
|
||||
* </box>
|
||||
*
|
||||
* @param nsIDOMElement aParent
|
||||
* The container of the infobar.
|
||||
*/
|
||||
buildInfobar: function Highlighter_buildInfobar(aParent)
|
||||
{
|
||||
let container = this.chromeDoc.createElement("box");
|
||||
container.id = "highlighter-nodeinfobar-container";
|
||||
container.setAttribute("position", "top");
|
||||
container.setAttribute("disabled", "true");
|
||||
|
||||
let nodeInfobar = this.chromeDoc.createElement("hbox");
|
||||
nodeInfobar.id = "highlighter-nodeinfobar";
|
||||
|
||||
let arrowBoxTop = this.chromeDoc.createElement("box");
|
||||
arrowBoxTop.className = "highlighter-nodeinfobar-arrow";
|
||||
arrowBoxTop.id = "highlighter-nodeinfobar-arrow-top";
|
||||
|
||||
let arrowBoxBottom = this.chromeDoc.createElement("box");
|
||||
arrowBoxBottom.className = "highlighter-nodeinfobar-arrow";
|
||||
arrowBoxBottom.id = "highlighter-nodeinfobar-arrow-bottom";
|
||||
|
||||
let tagNameLabel = this.chromeDoc.createElement("label");
|
||||
tagNameLabel.id = "highlighter-nodeinfobar-tagname";
|
||||
tagNameLabel.className = "plain";
|
||||
|
||||
let idLabel = this.chromeDoc.createElement("label");
|
||||
idLabel.id = "highlighter-nodeinfobar-id";
|
||||
idLabel.className = "plain";
|
||||
|
||||
let classesBox = this.chromeDoc.createElement("hbox");
|
||||
classesBox.id = "highlighter-nodeinfobar-classes";
|
||||
|
||||
nodeInfobar.appendChild(tagNameLabel);
|
||||
nodeInfobar.appendChild(idLabel);
|
||||
nodeInfobar.appendChild(classesBox);
|
||||
container.appendChild(arrowBoxTop);
|
||||
container.appendChild(nodeInfobar);
|
||||
container.appendChild(arrowBoxBottom);
|
||||
|
||||
aParent.appendChild(container);
|
||||
|
||||
let barHeight = container.getBoundingClientRect().height;
|
||||
|
||||
this.nodeInfo = {
|
||||
tagNameLabel: tagNameLabel,
|
||||
idLabel: idLabel,
|
||||
classesBox: classesBox,
|
||||
container: container,
|
||||
barHeight: barHeight,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes.
|
||||
*/
|
||||
destroy: function Highlighter_destroy()
|
||||
{
|
||||
this.IUI.win.clearTimeout(this.transitionDisabler);
|
||||
this.browser.removeEventListener("scroll", this, true);
|
||||
this.browser.removeEventListener("resize", this, true);
|
||||
this.boundCloseEventHandler = null;
|
||||
this._contentRect = null;
|
||||
this._highlightRect = null;
|
||||
this._highlighting = false;
|
||||
this.veilTopBox = null;
|
||||
this.veilLeftBox = null;
|
||||
this.veilMiddleBox = null;
|
||||
this.veilTransparentBox = null;
|
||||
this.veilContainer = null;
|
||||
this.node = null;
|
||||
this.nodeInfo = null;
|
||||
this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
|
||||
this.highlighterContainer = null;
|
||||
this.win = null
|
||||
this.browser = null;
|
||||
this.chromeDoc = null;
|
||||
this.IUI = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the highlighter highlighting? Public method for querying the state
|
||||
* of the highlighter.
|
||||
*/
|
||||
get isHighlighting() {
|
||||
return this._highlighting;
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight this.node, unhilighting first if necessary.
|
||||
*
|
||||
* @param boolean aScroll
|
||||
* Boolean determining whether to scroll or not.
|
||||
*/
|
||||
highlight: function Highlighter_highlight(aScroll)
|
||||
{
|
||||
let rect = null;
|
||||
|
||||
if (this.node && this.isNodeHighlightable(this.node)) {
|
||||
|
||||
if (aScroll) {
|
||||
this.node.scrollIntoView();
|
||||
}
|
||||
|
||||
let clientRect = this.node.getBoundingClientRect();
|
||||
|
||||
// Go up in the tree of frames to determine the correct rectangle.
|
||||
// clientRect is read-only, we need to be able to change properties.
|
||||
rect = {top: clientRect.top,
|
||||
left: clientRect.left,
|
||||
width: clientRect.width,
|
||||
height: clientRect.height};
|
||||
|
||||
let frameWin = this.node.ownerDocument.defaultView;
|
||||
|
||||
// We iterate through all the parent windows.
|
||||
while (true) {
|
||||
|
||||
// Does the selection overflow on the right of its window?
|
||||
let diffx = frameWin.innerWidth - (rect.left + rect.width);
|
||||
if (diffx < 0) {
|
||||
rect.width += diffx;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the bottom of its window?
|
||||
let diffy = frameWin.innerHeight - (rect.top + rect.height);
|
||||
if (diffy < 0) {
|
||||
rect.height += diffy;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the left of its window?
|
||||
if (rect.left < 0) {
|
||||
rect.width += rect.left;
|
||||
rect.left = 0;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the top of its window?
|
||||
if (rect.top < 0) {
|
||||
rect.height += rect.top;
|
||||
rect.top = 0;
|
||||
}
|
||||
|
||||
// Selection has been clipped to fit in its own window.
|
||||
|
||||
// Are we in the top-level window?
|
||||
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in an iframe.
|
||||
// We take into account the parent iframe position and its
|
||||
// offset (borders and padding).
|
||||
let frameRect = frameWin.frameElement.getBoundingClientRect();
|
||||
|
||||
let [offsetTop, offsetLeft] =
|
||||
this.IUI.getIframeContentOffset(frameWin.frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = frameWin.parent;
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightRectangle(rect);
|
||||
|
||||
this.moveInfobar();
|
||||
|
||||
if (this._highlighting) {
|
||||
Services.obs.notifyObservers(null,
|
||||
INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight the given node.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* a DOM element to be highlighted
|
||||
* @param object aParams
|
||||
* extra parameters object
|
||||
*/
|
||||
highlightNode: function Highlighter_highlightNode(aNode, aParams)
|
||||
{
|
||||
this.node = aNode;
|
||||
this.updateInfobar();
|
||||
this.highlight(aParams && aParams.scroll);
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight a rectangular region.
|
||||
*
|
||||
* @param object aRect
|
||||
* The rectangle region to highlight.
|
||||
* @returns boolean
|
||||
* True if the rectangle was highlighted, false otherwise.
|
||||
*/
|
||||
highlightRectangle: function Highlighter_highlightRectangle(aRect)
|
||||
{
|
||||
if (!aRect) {
|
||||
this.unhighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
let oldRect = this._contentRect;
|
||||
|
||||
if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
|
||||
aRect.width == oldRect.width && aRect.height == oldRect.height) {
|
||||
return this._highlighting; // same rectangle
|
||||
}
|
||||
|
||||
// adjust rect for zoom scaling
|
||||
let aRectScaled = {};
|
||||
for (let prop in aRect) {
|
||||
aRectScaled[prop] = aRect[prop] * this.zoom;
|
||||
}
|
||||
|
||||
if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
|
||||
aRectScaled.width > 0 && aRectScaled.height > 0) {
|
||||
|
||||
this.veilTransparentBox.style.visibility = "visible";
|
||||
|
||||
// The bottom div and the right div are flexibles (flex=1).
|
||||
// We don't need to resize them.
|
||||
this.veilTopBox.style.height = aRectScaled.top + "px";
|
||||
this.veilLeftBox.style.width = aRectScaled.left + "px";
|
||||
this.veilMiddleBox.style.height = aRectScaled.height + "px";
|
||||
this.veilTransparentBox.style.width = aRectScaled.width + "px";
|
||||
|
||||
this._highlighting = true;
|
||||
} else {
|
||||
this.unhighlight();
|
||||
}
|
||||
|
||||
this._contentRect = aRect; // save orig (non-scaled) rect
|
||||
this._highlightRect = aRectScaled; // and save the scaled rect.
|
||||
|
||||
return this._highlighting;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the highlighter surface.
|
||||
*/
|
||||
unhighlight: function Highlighter_unhighlight()
|
||||
{
|
||||
this._highlighting = false;
|
||||
this.veilMiddleBox.style.height = 0;
|
||||
this.veilTransparentBox.style.width = 0;
|
||||
this.veilTransparentBox.style.visibility = "hidden";
|
||||
Services.obs.notifyObservers(null,
|
||||
INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update node information (tagName#id.class)
|
||||
*/
|
||||
updateInfobar: function Highlighter_updateInfobar()
|
||||
{
|
||||
// Tag name
|
||||
this.nodeInfo.tagNameLabel.textContent = this.node.tagName;
|
||||
|
||||
// ID
|
||||
this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
|
||||
|
||||
// Classes
|
||||
let classes = this.nodeInfo.classesBox;
|
||||
while (classes.hasChildNodes()) {
|
||||
classes.removeChild(classes.firstChild);
|
||||
}
|
||||
|
||||
if (this.node.className) {
|
||||
let fragment = this.chromeDoc.createDocumentFragment();
|
||||
for (let i = 0; i < this.node.classList.length; i++) {
|
||||
let classLabel = this.chromeDoc.createElement("label");
|
||||
classLabel.className = "highlighter-nodeinfobar-class plain";
|
||||
classLabel.textContent = "." + this.node.classList[i];
|
||||
fragment.appendChild(classLabel);
|
||||
}
|
||||
classes.appendChild(fragment);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the Infobar to the right place in the highlighter.
|
||||
*/
|
||||
moveInfobar: function Highlighter_moveInfobar()
|
||||
{
|
||||
if (this._highlightRect) {
|
||||
let winHeight = this.win.innerHeight * this.zoom;
|
||||
let winWidth = this.win.innerWidth * this.zoom;
|
||||
|
||||
let rect = {top: this._highlightRect.top,
|
||||
left: this._highlightRect.left,
|
||||
width: this._highlightRect.width,
|
||||
height: this._highlightRect.height};
|
||||
|
||||
rect.top = Math.max(rect.top, 0);
|
||||
rect.left = Math.max(rect.left, 0);
|
||||
rect.width = Math.max(rect.width, 0);
|
||||
rect.height = Math.max(rect.height, 0);
|
||||
|
||||
rect.top = Math.min(rect.top, winHeight);
|
||||
rect.left = Math.min(rect.left, winWidth);
|
||||
|
||||
this.nodeInfo.container.removeAttribute("disabled");
|
||||
// Can the bar be above the node?
|
||||
if (rect.top < this.nodeInfo.barHeight) {
|
||||
// No. Can we move the toolbar under the node?
|
||||
if (rect.top + rect.height +
|
||||
this.nodeInfo.barHeight > winHeight) {
|
||||
// No. Let's move it inside.
|
||||
this.nodeInfo.container.style.top = rect.top + "px";
|
||||
this.nodeInfo.container.setAttribute("position", "overlap");
|
||||
} else {
|
||||
// Yes. Let's move it under the node.
|
||||
this.nodeInfo.container.style.top = rect.top + rect.height + "px";
|
||||
this.nodeInfo.container.setAttribute("position", "bottom");
|
||||
}
|
||||
} else {
|
||||
// Yes. Let's move it on top of the node.
|
||||
this.nodeInfo.container.style.top =
|
||||
rect.top - this.nodeInfo.barHeight + "px";
|
||||
this.nodeInfo.container.setAttribute("position", "top");
|
||||
}
|
||||
|
||||
let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
|
||||
let left = rect.left + rect.width / 2 - barWidth / 2;
|
||||
|
||||
// Make sure the whole infobar is visible
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
this.nodeInfo.container.setAttribute("hide-arrow", "true");
|
||||
} else {
|
||||
if (left + barWidth > winWidth) {
|
||||
left = winWidth - barWidth;
|
||||
this.nodeInfo.container.setAttribute("hide-arrow", "true");
|
||||
} else {
|
||||
this.nodeInfo.container.removeAttribute("hide-arrow");
|
||||
}
|
||||
}
|
||||
this.nodeInfo.container.style.left = left + "px";
|
||||
} else {
|
||||
this.nodeInfo.container.style.left = "0";
|
||||
this.nodeInfo.container.style.top = "0";
|
||||
this.nodeInfo.container.setAttribute("position", "top");
|
||||
this.nodeInfo.container.setAttribute("hide-arrow", "true");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the midpoint of a line from pointA to pointB.
|
||||
*
|
||||
* @param object aPointA
|
||||
* An object with x and y properties.
|
||||
* @param object aPointB
|
||||
* An object with x and y properties.
|
||||
* @returns object
|
||||
* An object with x and y properties.
|
||||
*/
|
||||
midPoint: function Highlighter_midPoint(aPointA, aPointB)
|
||||
{
|
||||
let pointC = { };
|
||||
pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
|
||||
pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
|
||||
return pointC;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the node under the highlighter rectangle. Useful for testing.
|
||||
* Calculation based on midpoint of diagonal from top left to bottom right
|
||||
* of panel.
|
||||
*
|
||||
* @returns nsIDOMNode|null
|
||||
* Returns the node under the current highlighter rectangle. Null is
|
||||
* returned if there is no node highlighted.
|
||||
*/
|
||||
get highlitNode()
|
||||
{
|
||||
// Not highlighting? Bail.
|
||||
if (!this._highlighting || !this._contentRect) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let a = {
|
||||
x: this._contentRect.left,
|
||||
y: this._contentRect.top
|
||||
};
|
||||
|
||||
let b = {
|
||||
x: a.x + this._contentRect.width,
|
||||
y: a.y + this._contentRect.height
|
||||
};
|
||||
|
||||
// Get midpoint of diagonal line.
|
||||
let midpoint = this.midPoint(a, b);
|
||||
|
||||
return this.IUI.elementFromPoint(this.win.document, midpoint.x,
|
||||
midpoint.y);
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the specified node highlightable?
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* the DOM element in question
|
||||
* @returns boolean
|
||||
* True if the node is highlightable or false otherwise.
|
||||
*/
|
||||
isNodeHighlightable: function Highlighter_isNodeHighlightable(aNode)
|
||||
{
|
||||
if (aNode.nodeType != aNode.ELEMENT_NODE) {
|
||||
return false;
|
||||
}
|
||||
let nodeName = aNode.nodeName.toLowerCase();
|
||||
return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Store page zoom factor.
|
||||
*/
|
||||
computeZoomFactor: function Highlighter_computeZoomFactor() {
|
||||
this.zoom =
|
||||
this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils)
|
||||
.screenPixelsPerCSSPixel;
|
||||
},
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
//// Event Handling
|
||||
|
||||
attachInspectListeners: function Highlighter_attachInspectListeners()
|
||||
{
|
||||
this.browser.addEventListener("mousemove", this, true);
|
||||
this.browser.addEventListener("click", this, true);
|
||||
this.browser.addEventListener("dblclick", this, true);
|
||||
this.browser.addEventListener("mousedown", this, true);
|
||||
this.browser.addEventListener("mouseup", this, true);
|
||||
},
|
||||
|
||||
detachInspectListeners: function Highlighter_detachInspectListeners()
|
||||
{
|
||||
this.browser.removeEventListener("mousemove", this, true);
|
||||
this.browser.removeEventListener("click", this, true);
|
||||
this.browser.removeEventListener("dblclick", this, true);
|
||||
this.browser.removeEventListener("mousedown", this, true);
|
||||
this.browser.removeEventListener("mouseup", this, true);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generic event handler.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
* The DOM event object.
|
||||
*/
|
||||
handleEvent: function Highlighter_handleEvent(aEvent)
|
||||
{
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
this.handleClick(aEvent);
|
||||
break;
|
||||
case "mousemove":
|
||||
this.handleMouseMove(aEvent);
|
||||
break;
|
||||
case "resize":
|
||||
this.computeZoomFactor();
|
||||
this.brieflyDisableTransitions();
|
||||
this.handleResize(aEvent);
|
||||
break;
|
||||
case "dblclick":
|
||||
case "mousedown":
|
||||
case "mouseup":
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
case "scroll":
|
||||
this.brieflyDisableTransitions();
|
||||
this.highlight();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable the CSS transitions for a short time to avoid laggy animations
|
||||
* during scrolling or resizing.
|
||||
*/
|
||||
brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
|
||||
{
|
||||
if (this.transitionDisabler) {
|
||||
this.IUI.win.clearTimeout(this.transitionDisabler);
|
||||
} else {
|
||||
this.veilContainer.setAttribute("disable-transitions", "true");
|
||||
this.nodeInfo.container.setAttribute("disable-transitions", "true");
|
||||
}
|
||||
this.transitionDisabler =
|
||||
this.IUI.win.setTimeout(function() {
|
||||
this.veilContainer.removeAttribute("disable-transitions");
|
||||
this.nodeInfo.container.removeAttribute("disable-transitions");
|
||||
this.transitionDisabler = null;
|
||||
}.bind(this), 500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle clicks.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
* The DOM event.
|
||||
*/
|
||||
handleClick: function Highlighter_handleClick(aEvent)
|
||||
{
|
||||
// Stop inspection when the user clicks on a node.
|
||||
if (aEvent.button == 0) {
|
||||
let win = aEvent.target.ownerDocument.defaultView;
|
||||
this.IUI.stopInspecting();
|
||||
win.focus();
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle mousemoves in panel when InspectorUI.inspecting is true.
|
||||
*
|
||||
* @param nsiDOMEvent aEvent
|
||||
* The MouseEvent triggering the method.
|
||||
*/
|
||||
handleMouseMove: function Highlighter_handleMouseMove(aEvent)
|
||||
{
|
||||
let element = this.IUI.elementFromPoint(aEvent.target.ownerDocument,
|
||||
aEvent.clientX, aEvent.clientY);
|
||||
if (element && element != this.node) {
|
||||
this.IUI.inspectNode(element);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle window resize events.
|
||||
*/
|
||||
handleResize: function Highlighter_handleResize()
|
||||
{
|
||||
this.highlight();
|
||||
},
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// InspectorUI
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user