mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 663778 - Box Model Highlighter r=jwalker
This commit is contained in:
parent
3c5af4257d
commit
4ba79c2f4a
@ -4,6 +4,7 @@
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
@namespace html url("http://www.w3.org/1999/xhtml");
|
||||
@namespace svg url("http://www.w3.org/2000/svg");
|
||||
|
||||
#main-window:not([chromehidden~="toolbar"]) {
|
||||
%ifdef XP_MACOSX
|
||||
|
@ -6,25 +6,15 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.highlighter-outline-container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highlighter-outline {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.highlighter-outline[hidden] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
.highlighter-outline:not([disable-transitions]) {
|
||||
transition-property: opacity, top, left, width, height;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: linear;
|
||||
/*
|
||||
* Box model highlighter
|
||||
*/
|
||||
svg|svg.box-model-root[hidden],
|
||||
svg|line.box-model-guide-top[hidden],
|
||||
svg|line.box-model-guide-right[hidden],
|
||||
svg|line.box-model-guide-left[hidden],
|
||||
svg|line.box-model-guide-bottom[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -45,13 +35,6 @@
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
.highlighter-nodeinfobar-positioner:not([disable-transitions]),
|
||||
.highlighter-nodeinfobar-positioner[disable-transitions][force-transitions] {
|
||||
transition-property: transform, opacity, top, left;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.highlighter-nodeinfobar-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
@ -160,20 +160,22 @@ Selection.prototype = {
|
||||
|
||||
setNodeFront: function(value, reason="unknown") {
|
||||
this.reason = reason;
|
||||
if (value !== this._nodeFront) {
|
||||
let rawValue = null;
|
||||
if (value && value.isLocal_toBeDeprecated()) {
|
||||
rawValue = value.rawNode();
|
||||
}
|
||||
this.emit("before-new-node", rawValue, reason);
|
||||
this.emit("before-new-node-front", value, reason);
|
||||
let previousNode = this._node;
|
||||
let previousFront = this._nodeFront;
|
||||
this._node = rawValue;
|
||||
this._nodeFront = value;
|
||||
this.emit("new-node", previousNode, this.reason);
|
||||
this.emit("new-node-front", value, this.reason);
|
||||
|
||||
// We used to return here if the node had not changed but we now need to
|
||||
// set the node even if it is already set otherwise it is not possible to
|
||||
// e.g. highlight the same node twice.
|
||||
let rawValue = null;
|
||||
if (value && value.isLocal_toBeDeprecated()) {
|
||||
rawValue = value.rawNode();
|
||||
}
|
||||
this.emit("before-new-node", rawValue, reason);
|
||||
this.emit("before-new-node-front", value, reason);
|
||||
let previousNode = this._node;
|
||||
let previousFront = this._nodeFront;
|
||||
this._node = rawValue;
|
||||
this._nodeFront = value;
|
||||
this.emit("new-node", previousNode, this.reason);
|
||||
this.emit("new-node-front", value, this.reason);
|
||||
},
|
||||
|
||||
get documentFront() {
|
||||
|
@ -73,6 +73,8 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this)
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.highlighterUtils = new ToolboxHighlighterUtils(this);
|
||||
this._highlighterReady = this._highlighterReady.bind(this);
|
||||
this._highlighterHidden = this._highlighterHidden.bind(this);
|
||||
|
||||
this._target.on("close", this.destroy);
|
||||
|
||||
@ -1097,8 +1099,14 @@ Toolbox.prototype = {
|
||||
this._inspector = InspectorFront(this._target.client, this._target.form);
|
||||
this._walker = yield this._inspector.getWalker();
|
||||
this._selection = new Selection(this._walker);
|
||||
|
||||
if (this.highlighterUtils.isRemoteHighlightable) {
|
||||
this._highlighter = yield this._inspector.getHighlighter();
|
||||
let autohide = !gDevTools.testing;
|
||||
|
||||
this.walker.on("highlighter-ready", this._highlighterReady);
|
||||
this.walker.on("highlighter-hide", this._highlighterHidden);
|
||||
|
||||
this._highlighter = yield this._inspector.getHighlighter(autohide);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
@ -1110,6 +1118,10 @@ Toolbox.prototype = {
|
||||
* Returns a promise that resolves when the fronts are destroyed
|
||||
*/
|
||||
destroyInspector: function() {
|
||||
if (this._destroying) {
|
||||
return this._destroying;
|
||||
}
|
||||
|
||||
if (!this._inspector) {
|
||||
return promise.resolve();
|
||||
}
|
||||
@ -1125,6 +1137,9 @@ Toolbox.prototype = {
|
||||
this._selection.destroy();
|
||||
}
|
||||
|
||||
this.walker.off("highlighter-ready", this._highlighterReady);
|
||||
this.walker.off("highlighter-hide", this._highlighterHidden);
|
||||
|
||||
this._inspector = null;
|
||||
this._highlighter = null;
|
||||
this._selection = null;
|
||||
@ -1135,7 +1150,9 @@ Toolbox.prototype = {
|
||||
// Releasing the walker (if it has been created)
|
||||
// This can fail, but in any case, we want to continue destroying the
|
||||
// inspector/highlighter/selection
|
||||
let walker = this._walker ? this._walker.release() : promise.resolve();
|
||||
let walker = (this._destroying = this._walker) ?
|
||||
this._walker.release() :
|
||||
promise.resolve();
|
||||
return walker.then(outstanding, outstanding);
|
||||
},
|
||||
|
||||
@ -1224,7 +1241,15 @@ Toolbox.prototype = {
|
||||
this._host = null;
|
||||
this._toolPanels.clear();
|
||||
}).then(null, console.error);
|
||||
}
|
||||
},
|
||||
|
||||
_highlighterReady: function() {
|
||||
this.emit("highlighter-ready");
|
||||
},
|
||||
|
||||
_highlighterHidden: function() {
|
||||
this.emit("highlighter-hide");
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1284,6 +1309,7 @@ ToolboxHighlighterUtils.prototype = {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let done = () => {
|
||||
this._isPicking = true;
|
||||
this.toolbox.emit("picker-started");
|
||||
this.toolbox.on("select", this.stopPicker);
|
||||
deferred.resolve();
|
||||
@ -1293,20 +1319,20 @@ ToolboxHighlighterUtils.prototype = {
|
||||
this.toolbox.initInspector(),
|
||||
this.toolbox.selectTool("inspector")
|
||||
]).then(() => {
|
||||
this._isPicking = true;
|
||||
this.toolbox._pickerButton.setAttribute("checked", "true");
|
||||
|
||||
if (this.isRemoteHighlightable) {
|
||||
this.toolbox.highlighter.pick().then(done);
|
||||
|
||||
this.toolbox.walker.on("picker-node-hovered", this._onPickerNodeHovered);
|
||||
this.toolbox.walker.on("picker-node-picked", this._onPickerNodePicked);
|
||||
|
||||
this.toolbox.highlighter.pick().then(done);
|
||||
} else {
|
||||
this.toolbox.walker.pick().then(node => {
|
||||
this.toolbox.selection.setNodeFront(node, "picker-node-picked");
|
||||
this.stopPicker();
|
||||
return this.toolbox.walker.pick().then(node => {
|
||||
this.toolbox.selection.setNodeFront(node, "picker-node-picked").then(() => {
|
||||
this.stopPicker();
|
||||
done();
|
||||
});
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -165,7 +165,7 @@ MarkupView.prototype = {
|
||||
},
|
||||
|
||||
_onMouseLeave: function() {
|
||||
this._hideBoxModel();
|
||||
this._hideBoxModel(true);
|
||||
if (this._hoveredNode) {
|
||||
this._containers.get(this._hoveredNode).hovered = false;
|
||||
}
|
||||
@ -176,8 +176,8 @@ MarkupView.prototype = {
|
||||
this._inspector.toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
|
||||
},
|
||||
|
||||
_hideBoxModel: function() {
|
||||
this._inspector.toolbox.highlighterUtils.unhighlight();
|
||||
_hideBoxModel: function(forceHide) {
|
||||
this._inspector.toolbox.highlighterUtils.unhighlight(forceHide);
|
||||
},
|
||||
|
||||
_briefBoxModelTimer: null,
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
@namespace html url("http://www.w3.org/1999/xhtml");
|
||||
@namespace svg url("http://www.w3.org/2000/svg");
|
||||
|
||||
%include ../shared/browser.inc
|
||||
%include linuxShared.inc
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
@namespace html url("http://www.w3.org/1999/xhtml");
|
||||
@namespace svg url("http://www.w3.org/2000/svg");
|
||||
|
||||
#urlbar:-moz-lwtheme:not([focused="true"]),
|
||||
.searchbar-textbox:-moz-lwtheme:not([focused="true"]) {
|
||||
|
@ -4,12 +4,40 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
%endif
|
||||
|
||||
/* Highlighter */
|
||||
/* Box model highlighter */
|
||||
svg|g.box-model-container {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.highlighter-outline {
|
||||
box-shadow: 0 0 0 1px black;
|
||||
outline: 1px dashed white;
|
||||
outline-offset: -1px;
|
||||
svg|polygon.box-model-content {
|
||||
fill: #80d4ff;
|
||||
}
|
||||
|
||||
svg|polygon.box-model-padding {
|
||||
fill: #66cc52;
|
||||
}
|
||||
|
||||
svg|polygon.box-model-border {
|
||||
fill: #ffe431;
|
||||
}
|
||||
|
||||
svg|polygon.box-model-margin {
|
||||
fill: #d89b28;
|
||||
}
|
||||
|
||||
svg|polygon.box-model-content,
|
||||
svg|polygon.box-model-padding,
|
||||
svg|polygon.box-model-border,
|
||||
svg|polygon.box-model-margin {
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
svg|line.box-model-guide-top,
|
||||
svg|line.box-model-guide-right,
|
||||
svg|line.box-model-guide-bottom,
|
||||
svg|line.box-model-guide-left {
|
||||
stroke: #08C;
|
||||
stroke-dasharray: 5 3;
|
||||
}
|
||||
|
||||
/* Highlighter - Node Infobar */
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
@namespace html url("http://www.w3.org/1999/xhtml");
|
||||
@namespace svg url("http://www.w3.org/2000/svg");
|
||||
|
||||
%include ../shared/browser.inc
|
||||
%include windowsShared.inc
|
||||
|
@ -24,77 +24,73 @@ this.LayoutHelpers = LayoutHelpers = function(aTopLevelWindow) {
|
||||
LayoutHelpers.prototype = {
|
||||
|
||||
/**
|
||||
* Compute the position and the dimensions for the visible portion
|
||||
* of a node, relativalely to the root window.
|
||||
* Get box quads adjusted for iframes and zoom level.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* a DOM element to be highlighted
|
||||
* @param {DOMNode} node
|
||||
* The node for which we are to get the box model region quads
|
||||
* @param {String} region
|
||||
* The box model region to return:
|
||||
* "content", "padding", "border" or "margin"
|
||||
*/
|
||||
getDirtyRect: function LH_getDirectyRect(aNode) {
|
||||
let frameWin = aNode.ownerDocument.defaultView;
|
||||
let clientRect = aNode.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};
|
||||
|
||||
// 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 (this.isTopLevelWindow(frameWin)) {
|
||||
break;
|
||||
}
|
||||
|
||||
let frameElement = this.getFrameElement(frameWin);
|
||||
if (!frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in an iframe.
|
||||
// We take into account the parent iframe position and its
|
||||
// offset (borders and padding).
|
||||
let frameRect = frameElement.getBoundingClientRect();
|
||||
|
||||
let [offsetTop, offsetLeft] =
|
||||
this.getIframeContentOffset(frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = this.getParentWindow(frameWin);
|
||||
getAdjustedQuads: function(node, region) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
return rect;
|
||||
let [quads] = node.getBoxQuads({
|
||||
box: region
|
||||
});
|
||||
|
||||
if (!quads) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [xOffset, yOffset] = this._getNodeOffsets(node);
|
||||
let scale = this.calculateScale(node);
|
||||
|
||||
return {
|
||||
p1: {
|
||||
w: quads.p1.w * scale,
|
||||
x: quads.p1.x * scale + xOffset,
|
||||
y: quads.p1.y * scale + yOffset,
|
||||
z: quads.p1.z * scale
|
||||
},
|
||||
p2: {
|
||||
w: quads.p2.w * scale,
|
||||
x: quads.p2.x * scale + xOffset,
|
||||
y: quads.p2.y * scale + yOffset,
|
||||
z: quads.p2.z * scale
|
||||
},
|
||||
p3: {
|
||||
w: quads.p3.w * scale,
|
||||
x: quads.p3.x * scale + xOffset,
|
||||
y: quads.p3.y * scale + yOffset,
|
||||
z: quads.p3.z * scale
|
||||
},
|
||||
p4: {
|
||||
w: quads.p4.w * scale,
|
||||
x: quads.p4.x * scale + xOffset,
|
||||
y: quads.p4.y * scale + yOffset,
|
||||
z: quads.p4.z * scale
|
||||
},
|
||||
bounds: {
|
||||
bottom: quads.bounds.bottom * scale + yOffset,
|
||||
height: quads.bounds.height * scale,
|
||||
left: quads.bounds.left * scale + xOffset,
|
||||
right: quads.bounds.right * scale + xOffset,
|
||||
top: quads.bounds.top * scale + yOffset,
|
||||
width: quads.bounds.width * scale,
|
||||
x: quads.bounds.x * scale + xOffset,
|
||||
y: quads.bounds.y * scale + yOffset
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
calculateScale: function(node) {
|
||||
let win = node.ownerDocument.defaultView;
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
return winUtils.fullZoom;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -112,7 +108,7 @@ LayoutHelpers.prototype = {
|
||||
|
||||
// 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 + aContentWindow.pageYOffset,
|
||||
let rect = {top: clientRect.top + aContentWindow.pageYOffset,
|
||||
left: clientRect.left + aContentWindow.pageXOffset,
|
||||
width: clientRect.width,
|
||||
height: clientRect.height};
|
||||
@ -178,26 +174,6 @@ LayoutHelpers.prototype = {
|
||||
return [borderTop + paddingTop, borderLeft + paddingLeft];
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply the page zoom factor.
|
||||
*/
|
||||
getZoomedRect: function LH_getZoomedRect(aWin, aRect) {
|
||||
// get page zoom factor, if any
|
||||
let zoom =
|
||||
aWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils)
|
||||
.fullZoom;
|
||||
|
||||
// adjust rect for zoom scaling
|
||||
let aRectScaled = {};
|
||||
for (let prop in aRect) {
|
||||
aRectScaled[prop] = aRect[prop] * zoom;
|
||||
}
|
||||
|
||||
return aRectScaled;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Find an element from the given coordinates. This method descends through
|
||||
* frames to find the element the user clicked inside frames.
|
||||
@ -243,8 +219,7 @@ LayoutHelpers.prototype = {
|
||||
* appear on the top of the viewport. It is true by default, and that is
|
||||
* usually what you want.
|
||||
*/
|
||||
scrollIntoViewIfNeeded:
|
||||
function LH_scrollIntoViewIfNeeded(elem, centered) {
|
||||
scrollIntoViewIfNeeded: function(elem, centered) {
|
||||
// We want to default to centering the element in the page,
|
||||
// so as to keep the context of the element.
|
||||
centered = centered === undefined? true: !!centered;
|
||||
@ -397,4 +372,138 @@ LayoutHelpers.prototype = {
|
||||
|
||||
return winUtils.containerElement;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the x and y offsets for a node taking iframes into account.
|
||||
*
|
||||
* @param {DOMNode} node
|
||||
* The node for which we are to get the offset
|
||||
*/
|
||||
_getNodeOffsets: function(node) {
|
||||
let xOffset = 0;
|
||||
let yOffset = 0;
|
||||
let frameWin = node.ownerDocument.defaultView;
|
||||
let scale = this.calculateScale(node);
|
||||
|
||||
while (true) {
|
||||
// Are we in the top-level window?
|
||||
if (this.isTopLevelWindow(frameWin)) {
|
||||
break;
|
||||
}
|
||||
|
||||
let frameElement = this.getFrameElement(frameWin);
|
||||
if (!frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in an iframe.
|
||||
// We take into account the parent iframe position and its
|
||||
// offset (borders and padding).
|
||||
let frameRect = frameElement.getBoundingClientRect();
|
||||
|
||||
let [offsetTop, offsetLeft] =
|
||||
this.getIframeContentOffset(frameElement);
|
||||
|
||||
xOffset += frameRect.left + offsetLeft;
|
||||
yOffset += frameRect.top + offsetTop;
|
||||
|
||||
frameWin = this.getParentWindow(frameWin);
|
||||
}
|
||||
|
||||
return [xOffset * scale, yOffset * scale];
|
||||
},
|
||||
|
||||
|
||||
|
||||
/********************************************************************
|
||||
* GetBoxQuads POLYFILL START TODO: Remove this when bug 917755 is fixed.
|
||||
********************************************************************/
|
||||
_getBoxQuadsFromRect: function(rect, node) {
|
||||
let scale = this.calculateScale(node);
|
||||
let [xOffset, yOffset] = this._getNodeOffsets(node);
|
||||
|
||||
let out = {
|
||||
p1: {
|
||||
x: rect.left * scale + xOffset,
|
||||
y: rect.top * scale + yOffset
|
||||
},
|
||||
p2: {
|
||||
x: (rect.left + rect.width) * scale + xOffset,
|
||||
y: rect.top * scale + yOffset
|
||||
},
|
||||
p3: {
|
||||
x: (rect.left + rect.width) * scale + xOffset,
|
||||
y: (rect.top + rect.height) * scale + yOffset
|
||||
},
|
||||
p4: {
|
||||
x: rect.left * scale + xOffset,
|
||||
y: (rect.top + rect.height) * scale + yOffset
|
||||
}
|
||||
};
|
||||
|
||||
out.bounds = {
|
||||
bottom: out.p4.y,
|
||||
height: out.p4.y - out.p1.y,
|
||||
left: out.p1.x,
|
||||
right: out.p2.x,
|
||||
top: out.p1.y,
|
||||
width: out.p2.x - out.p1.x,
|
||||
x: out.p1.x,
|
||||
y: out.p1.y
|
||||
};
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
_parseNb: function(distance) {
|
||||
let nb = parseFloat(distance, 10);
|
||||
return isNaN(nb) ? 0 : nb;
|
||||
},
|
||||
|
||||
getAdjustedQuadsPolyfill: function(node, region) {
|
||||
// Get the border-box rect
|
||||
// Note that this is relative to the node's viewport, so before we can use
|
||||
// it, will need to go back up the frames like getRect
|
||||
let borderRect = node.getBoundingClientRect();
|
||||
|
||||
// If the boxType is border, no need to go any further, we're done
|
||||
if (region === "border") {
|
||||
return this._getBoxQuadsFromRect(borderRect, node);
|
||||
}
|
||||
|
||||
// Else, need to get margin/padding/border distances
|
||||
let style = node.ownerDocument.defaultView.getComputedStyle(node);
|
||||
let camel = s => s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
let distances = {border:{}, padding:{}, margin: {}};
|
||||
|
||||
for (let side of ["top", "right", "bottom", "left"]) {
|
||||
distances.border[side] = this._parseNb(style["border" + camel(side) + "Width"]);
|
||||
distances.padding[side] = this._parseNb(style["padding" + camel(side)]);
|
||||
distances.margin[side] = this._parseNb(style["margin" + camel(side)]);
|
||||
}
|
||||
|
||||
// From the border-box rect, calculate the content-box, padding-box and
|
||||
// margin-box rects
|
||||
function offsetRect(rect, offsetType, dir=1) {
|
||||
return {
|
||||
top: rect.top + (dir * distances[offsetType].top),
|
||||
left: rect.left + (dir * distances[offsetType].left),
|
||||
width: rect.width - (dir * (distances[offsetType].left + distances[offsetType].right)),
|
||||
height: rect.height - (dir * (distances[offsetType].top + distances[offsetType].bottom))
|
||||
};
|
||||
}
|
||||
|
||||
if (region === "margin") {
|
||||
return this._getBoxQuadsFromRect(offsetRect(borderRect, "margin", -1), node);
|
||||
} else if (region === "padding") {
|
||||
return this._getBoxQuadsFromRect(offsetRect(borderRect, "border"), node);
|
||||
} else if (region === "content") {
|
||||
let paddingRect = offsetRect(borderRect, "border");
|
||||
return this._getBoxQuadsFromRect(offsetRect(paddingRect, "padding"), node);
|
||||
}
|
||||
},
|
||||
|
||||
/********************************************************************
|
||||
* GetBoxQuads POLYFILL END
|
||||
********************************************************************/
|
||||
};
|
||||
|
@ -9,6 +9,10 @@ const Services = require("Services");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const {Arg, Option, method} = protocol;
|
||||
const events = require("sdk/event/core");
|
||||
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const GUIDE_STROKE_WIDTH = 1;
|
||||
|
||||
// Make sure the domnode type is known here
|
||||
require("devtools/server/actors/inspector");
|
||||
|
||||
@ -21,7 +25,9 @@ const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted";
|
||||
let HELPER_SHEET = ".__fx-devtools-hide-shortcut__ { visibility: hidden !important } ";
|
||||
HELPER_SHEET += ":-moz-devtools-highlighted { outline: 2px dashed #F06!important; outline-offset: -2px!important } ";
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
const HIGHLIGHTER_PICKED_TIMER = 1000;
|
||||
const INFO_BAR_OFFSET = 5;
|
||||
|
||||
/**
|
||||
* The HighlighterActor is the server-side entry points for any tool that wishes
|
||||
@ -36,15 +42,23 @@ const HIGHLIGHTER_PICKED_TIMER = 1000;
|
||||
let HighlighterActor = protocol.ActorClass({
|
||||
typeName: "highlighter",
|
||||
|
||||
initialize: function(inspector) {
|
||||
initialize: function(inspector, autohide) {
|
||||
protocol.Actor.prototype.initialize.call(this, null);
|
||||
|
||||
this._autohide = autohide;
|
||||
this._inspector = inspector;
|
||||
this._walker = this._inspector.walker;
|
||||
this._tabActor = this._inspector.tabActor;
|
||||
|
||||
this._highlighterReady = this._highlighterReady.bind(this);
|
||||
this._highlighterHidden = this._highlighterHidden.bind(this);
|
||||
|
||||
if (this._supportsBoxModelHighlighter()) {
|
||||
this._boxModelHighlighter = new BoxModelHighlighter(this._tabActor);
|
||||
this._boxModelHighlighter =
|
||||
new BoxModelHighlighter(this._tabActor, this._inspector);
|
||||
|
||||
this._boxModelHighlighter.on("ready", this._highlighterReady);
|
||||
this._boxModelHighlighter.on("hide", this._highlighterHidden);
|
||||
} else {
|
||||
this._boxModelHighlighter = new SimpleOutlineHighlighter(this._tabActor);
|
||||
}
|
||||
@ -63,9 +77,12 @@ let HighlighterActor = protocol.ActorClass({
|
||||
destroy: function() {
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
if (this._boxModelHighlighter) {
|
||||
this._boxModelHighlighter.off("ready", this._highlighterReady);
|
||||
this._boxModelHighlighter.off("hide", this._highlighterHidden);
|
||||
this._boxModelHighlighter.destroy();
|
||||
this._boxModelHighlighter = null;
|
||||
}
|
||||
this._autohide = null;
|
||||
this._inspector = null;
|
||||
this._walker = null;
|
||||
this._tabActor = null;
|
||||
@ -79,8 +96,7 @@ let HighlighterActor = protocol.ActorClass({
|
||||
*
|
||||
* @param NodeActor The node to be highlighted
|
||||
* @param Options See the request part for existing options. Note that not
|
||||
* all options may be supported by all types of highlighters. The simple
|
||||
* outline highlighter for instance does not scrollIntoView
|
||||
* all options may be supported by all types of highlighters.
|
||||
*/
|
||||
showBoxModel: method(function(node, options={}) {
|
||||
if (node && this._isNodeValidForHighlighting(node.rawNode)) {
|
||||
@ -91,7 +107,8 @@ let HighlighterActor = protocol.ActorClass({
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
scrollIntoView: Option(1)
|
||||
scrollIntoView: Option(1),
|
||||
region: Option(1)
|
||||
}
|
||||
}),
|
||||
|
||||
@ -135,6 +152,7 @@ let HighlighterActor = protocol.ActorClass({
|
||||
*/
|
||||
_isPicking: false,
|
||||
_hoveredNode: null,
|
||||
|
||||
pick: method(function() {
|
||||
if (this._isPicking) {
|
||||
return null;
|
||||
@ -150,9 +168,11 @@ let HighlighterActor = protocol.ActorClass({
|
||||
this._preventContentEvent(event);
|
||||
this._stopPickerListeners();
|
||||
this._isPicking = false;
|
||||
this._tabActor.window.setTimeout(() => {
|
||||
this._boxModelHighlighter.hide();
|
||||
}, HIGHLIGHTER_PICKED_TIMER);
|
||||
if (this._autohide) {
|
||||
this._tabActor.window.setTimeout(() => {
|
||||
this._boxModelHighlighter.hide();
|
||||
}, HIGHLIGHTER_PICKED_TIMER);
|
||||
}
|
||||
events.emit(this._walker, "picker-node-picked", this._findAndAttachElement(event));
|
||||
};
|
||||
|
||||
@ -217,6 +237,14 @@ let HighlighterActor = protocol.ActorClass({
|
||||
target.removeEventListener("dblclick", this._preventContentEvent, true);
|
||||
},
|
||||
|
||||
_highlighterReady: function() {
|
||||
events.emit(this._inspector.walker, "highlighter-ready");
|
||||
},
|
||||
|
||||
_highlighterHidden: function() {
|
||||
events.emit(this._inspector.walker, "highlighter-hide");
|
||||
},
|
||||
|
||||
cancelPick: method(function() {
|
||||
if (this._isPicking) {
|
||||
this._boxModelHighlighter.hide();
|
||||
@ -247,26 +275,41 @@ let HighlighterFront = protocol.FrontClass(HighlighterActor, {});
|
||||
* h.destroy();
|
||||
*
|
||||
* Structure:
|
||||
* <stack class="highlighter-container">
|
||||
* <box class="highlighter-outline-container">
|
||||
* <box class="highlighter-outline" />
|
||||
* </box>
|
||||
* <box class="highlighter-nodeinfobar-container">
|
||||
* <box class="highlighter-nodeinfobar-positioner" position="top/bottom">
|
||||
* <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/>
|
||||
* <hbox class="highlighter-nodeinfobar">
|
||||
* <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox>
|
||||
* </hbox>
|
||||
* <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
|
||||
* </box>
|
||||
* </box>
|
||||
* </stack>
|
||||
* <stack class="highlighter-container">
|
||||
* <svg class="box-model-root" hidden="true">
|
||||
* <g class="box-model-container">
|
||||
* <polygon class="box-model-margin" points="317,122 747,36 747,181 317,267" />
|
||||
* <polygon class="box-model-border" points="317,128 747,42 747,161 317,247" />
|
||||
* <polygon class="box-model-padding" points="323,127 747,42 747,161 323,246" />
|
||||
* <polygon class="box-model-content" points="335,137 735,57 735,152 335,232" />
|
||||
* </g>
|
||||
* <line class="box-model-guide-top" x1="0" y1="592" x2="99999" y2="592" />
|
||||
* <line class="box-model-guide-right" x1="735" y1="0" x2="735" y2="99999" />
|
||||
* <line class="box-model-guide-bottom" x1="0" y1="612" x2="99999" y2="612" />
|
||||
* <line class="box-model-guide-left" x1="334" y1="0" x2="334" y2="99999" />
|
||||
* </svg>
|
||||
* <box class="highlighter-nodeinfobar-container">
|
||||
* <box class="highlighter-nodeinfobar-positioner" position="top" />
|
||||
* <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
|
||||
* <hbox class="highlighter-nodeinfobar">
|
||||
* <hbox class="highlighter-nodeinfobar-text" align="center" flex="1">
|
||||
* <span class="highlighter-nodeinfobar-tagname">Node name</span>
|
||||
* <span class="highlighter-nodeinfobar-id">Node id</span>
|
||||
* <span class="highlighter-nodeinfobar-classes">.someClass</span>
|
||||
* <span class="highlighter-nodeinfobar-pseudo-classes">:hover</span>
|
||||
* </hbox>
|
||||
* </hbox>
|
||||
* <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
|
||||
* </box>
|
||||
* </box>
|
||||
* </stack>
|
||||
*/
|
||||
function BoxModelHighlighter(tabActor) {
|
||||
function BoxModelHighlighter(tabActor, inspector) {
|
||||
this.browser = tabActor.browser;
|
||||
this.win = tabActor.window;
|
||||
this.chromeDoc = this.browser.ownerDocument;
|
||||
this.chromeWin = this.chromeDoc.defaultView;
|
||||
this._inspector = inspector;
|
||||
|
||||
this.layoutHelpers = new LayoutHelpers(this.win);
|
||||
this.chromeLayoutHelper = new LayoutHelpers(this.chromeWin);
|
||||
@ -274,32 +317,56 @@ function BoxModelHighlighter(tabActor) {
|
||||
this.transitionDisabler = null;
|
||||
this.pageEventsMuter = null;
|
||||
this._update = this._update.bind(this);
|
||||
this.handleEvent = this.handleEvent.bind(this);
|
||||
this.currentNode = null;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
this._initMarkup();
|
||||
}
|
||||
|
||||
BoxModelHighlighter.prototype = {
|
||||
get zoom() {
|
||||
return this.win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
|
||||
},
|
||||
|
||||
_initMarkup: function() {
|
||||
let stack = this.browser.parentNode;
|
||||
|
||||
this.highlighterContainer = this.chromeDoc.createElement("stack");
|
||||
this.highlighterContainer.className = "highlighter-container";
|
||||
this._highlighterContainer = this.chromeDoc.createElement("stack");
|
||||
this._highlighterContainer.className = "highlighter-container";
|
||||
|
||||
this.outline = this.chromeDoc.createElement("box");
|
||||
this.outline.className = "highlighter-outline";
|
||||
this._svgRoot = this._createSVGNode("root", "svg", this._highlighterContainer);
|
||||
|
||||
let outlineContainer = this.chromeDoc.createElement("box");
|
||||
outlineContainer.appendChild(this.outline);
|
||||
outlineContainer.className = "highlighter-outline-container";
|
||||
this.highlighterContainer.appendChild(outlineContainer);
|
||||
this._boxModelContainer = this._createSVGNode("container", "g", this._svgRoot);
|
||||
|
||||
this._boxModelNodes = {
|
||||
margin: this._createSVGNode("margin", "polygon", this._boxModelContainer),
|
||||
border: this._createSVGNode("border", "polygon", this._boxModelContainer),
|
||||
padding: this._createSVGNode("padding", "polygon", this._boxModelContainer),
|
||||
content: this._createSVGNode("content", "polygon", this._boxModelContainer)
|
||||
};
|
||||
|
||||
this._guideNodes = {
|
||||
top: this._createSVGNode("guide-top", "line", this._svgRoot),
|
||||
right: this._createSVGNode("guide-right", "line", this._svgRoot),
|
||||
bottom: this._createSVGNode("guide-bottom", "line", this._svgRoot),
|
||||
left: this._createSVGNode("guide-left", "line", this._svgRoot)
|
||||
};
|
||||
|
||||
this._guideNodes.top.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
|
||||
this._guideNodes.right.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
|
||||
this._guideNodes.bottom.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
|
||||
this._guideNodes.left.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
|
||||
|
||||
this._highlighterContainer.appendChild(this._svgRoot);
|
||||
|
||||
let infobarContainer = this.chromeDoc.createElement("box");
|
||||
infobarContainer.className = "highlighter-nodeinfobar-container";
|
||||
this.highlighterContainer.appendChild(infobarContainer);
|
||||
this._highlighterContainer.appendChild(infobarContainer);
|
||||
|
||||
// Insert the highlighter right after the browser
|
||||
stack.insertBefore(this.highlighterContainer, stack.childNodes[1]);
|
||||
stack.insertBefore(this._highlighterContainer, stack.childNodes[1]);
|
||||
|
||||
// Building the infobar
|
||||
let infobarPositioner = this.chromeDoc.createElement("box");
|
||||
@ -362,6 +429,15 @@ BoxModelHighlighter.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
_createSVGNode: function(classPostfix, nodeType, parent) {
|
||||
let node = this.chromeDoc.createElementNS(SVG_NS, nodeType);
|
||||
node.setAttribute("class", "box-model-" + classPostfix);
|
||||
|
||||
parent.appendChild(node);
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes. Remove listeners.
|
||||
*/
|
||||
@ -371,15 +447,13 @@ BoxModelHighlighter.prototype = {
|
||||
this.chromeWin.clearTimeout(this.transitionDisabler);
|
||||
this.chromeWin.clearTimeout(this.pageEventsMuter);
|
||||
|
||||
this._contentRect = null;
|
||||
this._highlightRect = null;
|
||||
this.outline = null;
|
||||
this.nodeInfo = null;
|
||||
|
||||
this.highlighterContainer.remove();
|
||||
this.highlighterContainer = null;
|
||||
this._highlighterContainer.remove();
|
||||
this._highlighterContainer = null;
|
||||
|
||||
this.win = null
|
||||
this.rect = null;
|
||||
this.win = null;
|
||||
this.browser = null;
|
||||
this.chromeDoc = null;
|
||||
this.chromeWin = null;
|
||||
@ -390,28 +464,29 @@ BoxModelHighlighter.prototype = {
|
||||
* Show the highlighter on a given node
|
||||
*
|
||||
* @param {DOMNode} node
|
||||
* @param {Object} options
|
||||
* Object used for passing options
|
||||
*/
|
||||
show: function(node, options={}) {
|
||||
if (!this.currentNode || node !== this.currentNode) {
|
||||
this.currentNode = node;
|
||||
this.currentNode = node;
|
||||
|
||||
this._showInfobar();
|
||||
this._computeZoomFactor();
|
||||
this._detachPageListeners();
|
||||
this._attachPageListeners();
|
||||
this._update();
|
||||
this._trackMutations();
|
||||
this._showInfobar();
|
||||
this._detachPageListeners();
|
||||
this._attachPageListeners();
|
||||
this._update();
|
||||
this._trackMutations();
|
||||
|
||||
if (options.scrollIntoView) {
|
||||
this.chromeLayoutHelper.scrollIntoViewIfNeeded(node);
|
||||
}
|
||||
if (options.scrollIntoView) {
|
||||
this.chromeLayoutHelper.scrollIntoViewIfNeeded(node);
|
||||
}
|
||||
},
|
||||
|
||||
_trackMutations: function() {
|
||||
if (this.currentNode) {
|
||||
let win = this.currentNode.ownerDocument.defaultView;
|
||||
this.currentNodeObserver = new win.MutationObserver(this._update);
|
||||
this.currentNodeObserver = new win.MutationObserver(() => {
|
||||
this._update();
|
||||
});
|
||||
this.currentNodeObserver.observe(this.currentNode, {attributes: true});
|
||||
}
|
||||
},
|
||||
@ -433,21 +508,20 @@ BoxModelHighlighter.prototype = {
|
||||
* Update the highlighter on the current highlighted node (the one that was
|
||||
* passed as an argument to show(node)).
|
||||
* Should be called whenever node size or attributes change
|
||||
* @param {Boolean} brieflyDisableTransitions
|
||||
* In case _update is called during scrolling or repaint, set this
|
||||
* to true to avoid transitions
|
||||
* @param {Object} options
|
||||
* Object used for passing options. Valid options are:
|
||||
* - box: "content", "padding", "border" or "margin." This specifies
|
||||
* the box that the guides should outline. Default is content.
|
||||
*/
|
||||
_update: function(brieflyDisableTransitions) {
|
||||
_update: function(options={}) {
|
||||
if (this.currentNode) {
|
||||
let rect = this.layoutHelpers.getDirtyRect(this.currentNode);
|
||||
|
||||
if (this._highlightRectangle(rect, brieflyDisableTransitions)) {
|
||||
this._moveInfobar();
|
||||
this._updateInfobar();
|
||||
if (this._highlightBoxModel(options)) {
|
||||
this._showInfobar();
|
||||
} else {
|
||||
// Nothing to highlight (0px rectangle like a <script> tag for instance)
|
||||
this.hide();
|
||||
}
|
||||
this.emit("ready");
|
||||
}
|
||||
},
|
||||
|
||||
@ -458,17 +532,17 @@ BoxModelHighlighter.prototype = {
|
||||
if (this.currentNode) {
|
||||
this._untrackMutations();
|
||||
this.currentNode = null;
|
||||
this._hideOutline();
|
||||
this._hideBoxModel();
|
||||
this._hideInfobar();
|
||||
this._detachPageListeners();
|
||||
}
|
||||
this.emit("hide");
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the infobar
|
||||
*/
|
||||
_hideInfobar: function() {
|
||||
this.nodeInfo.positioner.setAttribute("force-transitions", "true");
|
||||
this.nodeInfo.positioner.setAttribute("hidden", "true");
|
||||
},
|
||||
|
||||
@ -477,82 +551,155 @@ BoxModelHighlighter.prototype = {
|
||||
*/
|
||||
_showInfobar: function() {
|
||||
this.nodeInfo.positioner.removeAttribute("hidden");
|
||||
this._moveInfobar();
|
||||
this.nodeInfo.positioner.removeAttribute("force-transitions");
|
||||
this._updateInfobar();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the outline
|
||||
* Hide the box model
|
||||
*/
|
||||
_hideOutline: function() {
|
||||
this.outline.setAttribute("hidden", "true");
|
||||
_hideBoxModel: function() {
|
||||
this._svgRoot.setAttribute("hidden", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the outline
|
||||
* Show the box model
|
||||
*/
|
||||
_showOutline: function() {
|
||||
this.outline.removeAttribute("hidden");
|
||||
_showBoxModel: function() {
|
||||
this._svgRoot.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight a rectangular region.
|
||||
* Highlight the box model.
|
||||
*
|
||||
* @param {object} aRect
|
||||
* The rectangle region to highlight.
|
||||
* @param {boolean} brieflyDisableTransitions
|
||||
* Set to true to avoid transitions during the highlighting
|
||||
* @param {Object} options
|
||||
* Object used for passing options. Valid options are:
|
||||
* - region: "content", "padding", "border" or "margin." This specifies
|
||||
* the region that the guides should outline. Default is content.
|
||||
* @return {boolean}
|
||||
* True if the rectangle was highlighted, false otherwise.
|
||||
*/
|
||||
_highlightRectangle: function(aRect, brieflyDisableTransitions) {
|
||||
if (!aRect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let oldRect = this._contentRect;
|
||||
|
||||
if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
|
||||
aRect.width == oldRect.width && aRect.height == oldRect.height) {
|
||||
this._showOutline();
|
||||
return true; // same rectangle
|
||||
}
|
||||
|
||||
let aRectScaled = this.layoutHelpers.getZoomedRect(this.win, aRect);
|
||||
_highlightBoxModel: function(options) {
|
||||
let isShown = false;
|
||||
|
||||
if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
|
||||
aRectScaled.width > 0 && aRectScaled.height > 0) {
|
||||
options.region = options.region || "content";
|
||||
|
||||
// The bottom div and the right div are flexibles (flex=1).
|
||||
// We don't need to resize them.
|
||||
let top = "top:" + aRectScaled.top + "px;";
|
||||
let left = "left:" + aRectScaled.left + "px;";
|
||||
let width = "width:" + aRectScaled.width + "px;";
|
||||
let height = "height:" + aRectScaled.height + "px;";
|
||||
// TODO: Remove this polyfill
|
||||
this.rect =
|
||||
this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, "margin");
|
||||
|
||||
if (brieflyDisableTransitions) {
|
||||
this._brieflyDisableTransitions();
|
||||
if (!this.rect) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.rect.bounds.width > 0 && this.rect.bounds.height > 0) {
|
||||
for (let boxType in this._boxModelNodes) {
|
||||
// TODO: Remove this polyfill
|
||||
let {p1, p2, p3, p4} = boxType === "margin" ? this.rect :
|
||||
this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, boxType);
|
||||
|
||||
let boxNode = this._boxModelNodes[boxType];
|
||||
boxNode.setAttribute("points",
|
||||
p1.x + "," + p1.y + " " +
|
||||
p2.x + "," + p2.y + " " +
|
||||
p3.x + "," + p3.y + " " +
|
||||
p4.x + "," + p4.y);
|
||||
|
||||
if (boxType === options.region) {
|
||||
this._showGuides(p1, p2, p3, p4);
|
||||
}
|
||||
}
|
||||
|
||||
this.outline.setAttribute("style", top + left + width + height);
|
||||
|
||||
isShown = true;
|
||||
this._showOutline();
|
||||
this._showBoxModel();
|
||||
} else {
|
||||
// Only return false if the element really is invisible.
|
||||
// A height of 0 and a non-0 width corresponds to a visible element that
|
||||
// is below the fold for instance
|
||||
if (aRectScaled.width > 0 || aRectScaled.height > 0) {
|
||||
if (this.rect.width > 0 || this.rect.height > 0) {
|
||||
isShown = true;
|
||||
this._hideOutline();
|
||||
this._hideBoxModel();
|
||||
}
|
||||
}
|
||||
return isShown;
|
||||
},
|
||||
|
||||
/**
|
||||
* We only want to show guides for horizontal and vertical edges as this helps
|
||||
* to line them up. This method finds these edges and displays a guide there.
|
||||
*
|
||||
* @param {DOMPoint} p1
|
||||
* Point 1
|
||||
* @param {DOMPoint} p2
|
||||
* Point 2
|
||||
* @param {DOMPoint} p3 [description]
|
||||
* Point 3
|
||||
* @param {DOMPoint} p4 [description]
|
||||
* Point 4
|
||||
*/
|
||||
_showGuides: function(p1, p2, p3, p4) {
|
||||
let allX = [p1.x, p2.x, p3.x, p4.x].sort();
|
||||
let allY = [p1.y, p2.y, p3.y, p4.y].sort();
|
||||
let toShowX = [];
|
||||
let toShowY = [];
|
||||
|
||||
for (let arr of [allX, allY]) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let val = arr[i];
|
||||
|
||||
if (i !== arr.lastIndexOf(val)) {
|
||||
if (arr === allX) {
|
||||
toShowX.push(val);
|
||||
} else {
|
||||
toShowY.push(val);
|
||||
}
|
||||
arr.splice(arr.lastIndexOf(val), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._contentRect = aRect; // save orig (non-scaled) rect
|
||||
this._highlightRect = aRectScaled; // and save the scaled rect.
|
||||
// Move guide into place or hide it if no valid co-ordinate was found.
|
||||
this._updateGuide(this._guideNodes.top, toShowY[0]);
|
||||
this._updateGuide(this._guideNodes.right, toShowX[1]);
|
||||
this._updateGuide(this._guideNodes.bottom, toShowY[1]);
|
||||
this._updateGuide(this._guideNodes.left, toShowX[0]);
|
||||
},
|
||||
|
||||
return isShown;
|
||||
/**
|
||||
* Move a guide to the appropriate position and display it. If no point is
|
||||
* passed then the guide is hidden.
|
||||
*
|
||||
* @param {SVGLine} guide
|
||||
* The guide to update
|
||||
* @param {Integer} point
|
||||
* x or y co-ordinate. If this is undefined we hide the guide.
|
||||
*/
|
||||
_updateGuide: function(guide, point=-1) {
|
||||
if (point > 0) {
|
||||
let offset = GUIDE_STROKE_WIDTH / 2;
|
||||
|
||||
if (guide === this._guideNodes.top || guide === this._guideNodes.left) {
|
||||
point -= offset;
|
||||
} else {
|
||||
point += offset;
|
||||
}
|
||||
|
||||
if (guide === this._guideNodes.top || guide === this._guideNodes.bottom) {
|
||||
guide.setAttribute("x1", 0);
|
||||
guide.setAttribute("y1", point);
|
||||
guide.setAttribute("x2", "100%");
|
||||
guide.setAttribute("y2", point);
|
||||
} else {
|
||||
guide.setAttribute("x1", point);
|
||||
guide.setAttribute("y1", 0);
|
||||
guide.setAttribute("x2", point);
|
||||
guide.setAttribute("y2", "100%");
|
||||
}
|
||||
guide.removeAttribute("hidden");
|
||||
return true;
|
||||
} else {
|
||||
guide.setAttribute("hidden", "true");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -579,6 +726,8 @@ BoxModelHighlighter.prototype = {
|
||||
|
||||
let pseudoBox = this.nodeInfo.pseudoClassesBox;
|
||||
pseudoBox.textContent = pseudos.join("");
|
||||
|
||||
this._moveInfobar();
|
||||
}
|
||||
},
|
||||
|
||||
@ -586,46 +735,33 @@ BoxModelHighlighter.prototype = {
|
||||
* Move the Infobar to the right place in the highlighter.
|
||||
*/
|
||||
_moveInfobar: function() {
|
||||
if (this._highlightRect) {
|
||||
if (this.rect) {
|
||||
let bounds = this.rect.bounds;
|
||||
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.positioner.removeAttribute("disabled");
|
||||
// Can the bar be above the node?
|
||||
if (rect.top < this.nodeInfo.barHeight) {
|
||||
if (bounds.top < this.nodeInfo.barHeight) {
|
||||
// No. Can we move the toolbar under the node?
|
||||
if (rect.top + rect.height +
|
||||
this.nodeInfo.barHeight > winHeight) {
|
||||
if (bounds.bottom + this.nodeInfo.barHeight > winHeight) {
|
||||
// No. Let's move it inside.
|
||||
this.nodeInfo.positioner.style.top = rect.top + "px";
|
||||
this.nodeInfo.positioner.style.top = bounds.top + "px";
|
||||
this.nodeInfo.positioner.setAttribute("position", "overlap");
|
||||
} else {
|
||||
// Yes. Let's move it under the node.
|
||||
this.nodeInfo.positioner.style.top = rect.top + rect.height + "px";
|
||||
this.nodeInfo.positioner.style.top = bounds.bottom - INFO_BAR_OFFSET + "px";
|
||||
this.nodeInfo.positioner.setAttribute("position", "bottom");
|
||||
}
|
||||
} else {
|
||||
// Yes. Let's move it on top of the node.
|
||||
this.nodeInfo.positioner.style.top =
|
||||
rect.top - this.nodeInfo.barHeight + "px";
|
||||
bounds.top + INFO_BAR_OFFSET - this.nodeInfo.barHeight + "px";
|
||||
this.nodeInfo.positioner.setAttribute("position", "top");
|
||||
}
|
||||
|
||||
let barWidth = this.nodeInfo.positioner.getBoundingClientRect().width;
|
||||
let left = rect.left + rect.width / 2 - barWidth / 2;
|
||||
let left = bounds.right - bounds.width / 2 - barWidth / 2;
|
||||
|
||||
// Make sure the whole infobar is visible
|
||||
if (left < 0) {
|
||||
@ -648,26 +784,24 @@ BoxModelHighlighter.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Store page zoom factor.
|
||||
*/
|
||||
_computeZoomFactor: function() {
|
||||
this.zoom =
|
||||
this.win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.fullZoom;
|
||||
},
|
||||
|
||||
_attachPageListeners: function() {
|
||||
this.browser.addEventListener("resize", this, true);
|
||||
this.browser.addEventListener("scroll", this, true);
|
||||
this.browser.addEventListener("MozAfterPaint", this, true);
|
||||
if (this.currentNode) {
|
||||
let win = this.currentNode.ownerGlobal;
|
||||
|
||||
win.addEventListener("scroll", this, false);
|
||||
win.addEventListener("resize", this, false);
|
||||
win.addEventListener("MozAfterPaint", this, false);
|
||||
}
|
||||
},
|
||||
|
||||
_detachPageListeners: function() {
|
||||
this.browser.removeEventListener("resize", this, true);
|
||||
this.browser.removeEventListener("scroll", this, true);
|
||||
this.browser.removeEventListener("MozAfterPaint", this, true);
|
||||
if (this.currentNode) {
|
||||
let win = this.currentNode.ownerGlobal;
|
||||
|
||||
win.removeEventListener("scroll", this, false);
|
||||
win.removeEventListener("resize", this, false);
|
||||
win.removeEventListener("MozAfterPaint", this, false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -679,33 +813,12 @@ BoxModelHighlighter.prototype = {
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "resize":
|
||||
this._computeZoomFactor();
|
||||
break;
|
||||
case "MozAfterPaint":
|
||||
case "scroll":
|
||||
this._update(true);
|
||||
this._update();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable the CSS transitions for a short time to avoid laggy animations
|
||||
* during scrolling or resizing.
|
||||
*/
|
||||
_brieflyDisableTransitions: function() {
|
||||
if (this.transitionDisabler) {
|
||||
this.chromeWin.clearTimeout(this.transitionDisabler);
|
||||
} else {
|
||||
this.outline.setAttribute("disable-transitions", "true");
|
||||
this.nodeInfo.positioner.setAttribute("disable-transitions", "true");
|
||||
}
|
||||
this.transitionDisabler =
|
||||
this.chromeWin.setTimeout(() => {
|
||||
this.outline.removeAttribute("disable-transitions");
|
||||
this.nodeInfo.positioner.removeAttribute("disable-transitions");
|
||||
this.transitionDisabler = null;
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -848,6 +848,12 @@ var WalkerActor = protocol.ActorClass({
|
||||
"picker-node-hovered" : {
|
||||
type: "pickerNodeHovered",
|
||||
node: Arg(0, "disconnectedNode")
|
||||
},
|
||||
"highlighter-ready" : {
|
||||
type: "highlighter-ready"
|
||||
},
|
||||
"highlighter-hide" : {
|
||||
type: "highlighter-hide"
|
||||
}
|
||||
},
|
||||
|
||||
@ -2545,17 +2551,17 @@ var InspectorActor = protocol.ActorClass({
|
||||
}
|
||||
}),
|
||||
|
||||
getHighlighter: method(function () {
|
||||
getHighlighter: method(function (autohide) {
|
||||
if (this._highlighterPromise) {
|
||||
return this._highlighterPromise;
|
||||
}
|
||||
|
||||
this._highlighterPromise = this.getWalker().then(walker => {
|
||||
return HighlighterActor(this);
|
||||
return HighlighterActor(this, autohide);
|
||||
});
|
||||
return this._highlighterPromise;
|
||||
}, {
|
||||
request: {},
|
||||
request: { autohide: Arg(0, "boolean") },
|
||||
response: {
|
||||
highligter: RetVal("highlighter")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user