Bug 1345119 - Part 3: Display offset parent of absolutely positioned node in box model. r?jdescottes
MozReview-Commit-ID: 102vRTuIhEh
--- a/devtools/client/inspector/boxmodel/actions/box-model.js
+++ b/devtools/client/inspector/boxmodel/actions/box-model.js
@@ -2,16 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
UPDATE_GEOMETRY_EDITOR_ENABLED,
UPDATE_LAYOUT,
+ UPDATE_OFFSET_PARENT,
} = require("./index");
module.exports = {
/**
* Update the geometry editor's enabled state.
*
* @param {Boolean} enabled
@@ -29,9 +30,19 @@ module.exports = {
*/
updateLayout(layout) {
return {
type: UPDATE_LAYOUT,
layout,
};
},
+ /**
+ * Update the offset parent state with the new DOM node.
+ */
+ updateOffsetParent(offsetParent) {
+ return {
+ type: UPDATE_OFFSET_PARENT,
+ offsetParent,
+ };
+ }
+
};
--- a/devtools/client/inspector/boxmodel/actions/index.js
+++ b/devtools/client/inspector/boxmodel/actions/index.js
@@ -9,9 +9,12 @@ const { createEnum } = require("devtools
createEnum([
// Update the geometry editor's enabled state.
"UPDATE_GEOMETRY_EDITOR_ENABLED",
// Update the layout state with the latest layout properties.
"UPDATE_LAYOUT",
+ // Update the offset parent state with the new DOM node.
+ "UPDATE_OFFSET_PARENT",
+
], module.exports);
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -8,16 +8,17 @@ const { Task } = require("devtools/share
const { getCssProperties } = require("devtools/shared/fronts/css-properties");
const { ReflowFront } = require("devtools/shared/fronts/reflow");
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
const {
updateGeometryEditorEnabled,
updateLayout,
+ updateOffsetParent,
} = require("./actions/box-model");
const EditingSession = require("./utils/editing-session");
const NUMERIC = /^-?[\d\.]+$/;
/**
* A singleton instance of the box model controllers.
@@ -162,16 +163,22 @@ BoxModel.prototype = {
// Update the layout properties with whether or not the element's position is
// editable with the geometry editor.
let isPositionEditable = yield this.inspector.pageStyle.isPositionEditable(node);
layout = Object.assign({}, layout, {
isPositionEditable,
});
+ if (yield this.inspector.target.actorHasMethod("domwalker", "getOffsetParent")) {
+ // Update the redux store with the latest offset parent DOM node
+ let offsetParent = yield this.inspector.walker.getOffsetParent(node);
+ this.store.dispatch(updateOffsetParent(offsetParent));
+ }
+
// Update the redux store with the latest layout properties and update the box
// model view.
this.store.dispatch(updateLayout(layout));
// If a subsequent request has been made, wait for that one instead.
if (this._lastRequest != lastRequest) {
return this._lastRequest;
}
--- a/devtools/client/inspector/boxmodel/components/BoxModel.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModel.js
@@ -18,40 +18,46 @@ module.exports = createClass({
displayName: "BoxModel",
propTypes: {
boxModel: PropTypes.shape(Types.boxModel).isRequired,
showBoxModelProperties: PropTypes.bool.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelEditor: PropTypes.func.isRequired,
onShowBoxModelHighlighter: PropTypes.func.isRequired,
+ onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
onToggleGeometryEditor: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
render() {
let {
boxModel,
showBoxModelProperties,
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
+ onShowBoxModelHighlighterForNode,
onToggleGeometryEditor,
+ setSelectedNode,
} = this.props;
return dom.div(
{
className: "boxmodel-container",
},
BoxModelMain({
boxModel,
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
+ onShowBoxModelHighlighterForNode,
+ setSelectedNode,
}),
BoxModelInfo({
boxModel,
onToggleGeometryEditor,
}),
showBoxModelProperties ?
BoxModelProperties({
boxModel,
--- a/devtools/client/inspector/boxmodel/components/BoxModelApp.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelApp.js
@@ -24,17 +24,19 @@ const BoxModelApp = createClass({
displayName: "BoxModelApp",
propTypes: {
boxModel: PropTypes.shape(Types.boxModel).isRequired,
showBoxModelProperties: PropTypes.bool.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelEditor: PropTypes.func.isRequired,
onShowBoxModelHighlighter: PropTypes.func.isRequired,
+ onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
onToggleGeometryEditor: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
render() {
return Accordion({
items: [
{
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js
@@ -5,16 +5,19 @@
"use strict";
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
const { LocalizationHelper } = require("devtools/shared/l10n");
const BoxModelEditable = createFactory(require("./BoxModelEditable"));
+// Reps
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const Rep = createFactory(REPS.Rep);
const Types = require("../types");
const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
const SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
@@ -23,16 +26,18 @@ module.exports = createClass({
displayName: "BoxModelMain",
propTypes: {
boxModel: PropTypes.shape(Types.boxModel).isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelEditor: PropTypes.func.isRequired,
onShowBoxModelHighlighter: PropTypes.func.isRequired,
+ onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
getBorderOrPaddingValue(property) {
let { layout } = this.props.boxModel;
return layout[property] ? parseFloat(layout[property]) : "-";
},
@@ -90,29 +95,79 @@ module.exports = createClass({
return "-";
}
return layout[property] ? parseFloat(layout[property]) : "-";
},
onHighlightMouseOver(event) {
let region = event.target.getAttribute("data-box");
if (!region) {
+ let el = event.target;
+ do {
+ el = el.parentNode;
+ if (el && el.getAttribute("data-box")) {
+ region = el.getAttribute("data-box");
+ break;
+ }
+ } while (el.parentNode);
this.props.onHideBoxModelHighlighter();
}
+ if (region === "offset-parent") {
+ this.props.onHideBoxModelHighlighter();
+ this.props.onShowBoxModelHighlighterForNode(this.props.boxModel.offsetParent);
+ return;
+ }
+
this.props.onShowBoxModelHighlighter({
region,
showOnly: region,
onlyRegionArea: true,
});
},
+ /**
+ * While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
+ * translate nodeFront to a grip-like object that can be used with an ElementNode rep.
+ *
+ * @params {NodeFront} nodeFront
+ * The NodeFront for which we want to create a grip-like object.
+ * @returns {Object} a grip-like object that can be used with Reps.
+ */
+ translateNodeFrontToGrip(nodeFront) {
+ let {
+ attributes
+ } = nodeFront;
+
+ // The main difference between NodeFront and grips is that attributes are treated as
+ // a map in grips and as an array in NodeFronts.
+ let attributesMap = {};
+ for (let { name, value } of attributes) {
+ attributesMap[name] = value;
+ }
+
+ return {
+ actor: nodeFront.actorID,
+ preview: {
+ attributes: attributesMap,
+ attributesLength: attributes.length,
+ // nodeName is already lowerCased in Node grips
+ nodeName: nodeFront.nodeName.toLowerCase(),
+ nodeType: nodeFront.nodeType,
+ }
+ };
+ },
+
render() {
- let { boxModel, onShowBoxModelEditor } = this.props;
- let { layout } = boxModel;
+ let {
+ boxModel,
+ onShowBoxModelEditor,
+ setSelectedNode,
+ } = this.props;
+ let { layout, offsetParent } = boxModel;
let { height, width, position } = layout;
let borderTop = this.getBorderOrPaddingValue("border-top-width");
let borderRight = this.getBorderOrPaddingValue("border-right-width");
let borderBottom = this.getBorderOrPaddingValue("border-bottom-width");
let borderLeft = this.getBorderOrPaddingValue("border-left-width");
let paddingTop = this.getBorderOrPaddingValue("padding-top");
@@ -135,16 +190,33 @@ module.exports = createClass({
width = this.getWidthValue(width);
return dom.div(
{
className: "boxmodel-main",
onMouseOver: this.onHighlightMouseOver,
onMouseOut: this.props.onHideBoxModelHighlighter,
},
+ offsetParent ?
+ dom.span(
+ {
+ className: "boxmodel-offset-parent",
+ "data-box": "offset-parent",
+ },
+ Rep(
+ {
+ defaultRep: offsetParent,
+ mode: MODE.TINY,
+ object: this.translateNodeFrontToGrip(offsetParent),
+ onInspectIconClick: () => setSelectedNode(offsetParent, "box-model"),
+ }
+ )
+ )
+ :
+ null,
displayPosition ?
dom.span(
{
className: "boxmodel-legend",
"data-box": "position",
title: BOXMODEL_L10N.getFormatStr("boxmodel.position", position),
},
BOXMODEL_L10N.getFormatStr("boxmodel.position", position)
--- a/devtools/client/inspector/boxmodel/reducers/box-model.js
+++ b/devtools/client/inspector/boxmodel/reducers/box-model.js
@@ -2,37 +2,45 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
UPDATE_GEOMETRY_EDITOR_ENABLED,
UPDATE_LAYOUT,
+ UPDATE_OFFSET_PARENT,
} = require("../actions/index");
const INITIAL_BOX_MODEL = {
geometryEditorEnabled: false,
layout: {},
+ offsetParent: null
};
let reducers = {
[UPDATE_GEOMETRY_EDITOR_ENABLED](boxModel, { enabled }) {
return Object.assign({}, boxModel, {
geometryEditorEnabled: enabled,
});
},
[UPDATE_LAYOUT](boxModel, { layout }) {
return Object.assign({}, boxModel, {
layout,
});
},
+ [UPDATE_OFFSET_PARENT](boxModel, { offsetParent }) {
+ return Object.assign({}, boxModel, {
+ offsetParent,
+ });
+ },
+
};
module.exports = function (boxModel = INITIAL_BOX_MODEL, action) {
let reducer = reducers[action.type];
if (!reducer) {
return boxModel;
}
return reducer(boxModel, action);
--- a/devtools/client/inspector/boxmodel/types.js
+++ b/devtools/client/inspector/boxmodel/types.js
@@ -12,9 +12,12 @@ const { PropTypes } = require("devtools/
exports.boxModel = {
// Whether or not the geometry editor is enabled
geometryEditorEnabled: PropTypes.boolean,
// The layout information of the current selected node
layout: PropTypes.object,
+ // The offset parent for the selected node
+ offsetParent: PropTypes.object,
+
};
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -613,31 +613,38 @@ CssComputedView.prototype = {
this.inspector.emit("computed-view-sourcelinks-updated");
},
/**
* Render the box model view.
*/
createBoxModelView: function () {
let {
+ setSelectedNode,
+ onShowBoxModelHighlighterForNode,
+ } = this.inspector.getCommonComponentProps();
+
+ let {
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
onToggleGeometryEditor,
} = this.inspector.boxmodel.getComponentProps();
let provider = createElement(
Provider,
{ store: this.store },
BoxModelApp({
showBoxModelProperties: false,
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
+ onShowBoxModelHighlighterForNode,
onToggleGeometryEditor,
+ setSelectedNode,
})
);
ReactDOM.render(provider, this.boxModelWrapper);
},
/**
* The CSS as displayed by the UI.
*/
--- a/devtools/client/themes/boxmodel.css
+++ b/devtools/client/themes/boxmodel.css
@@ -312,8 +312,17 @@
.boxmodel-properties-header {
display: flex;
padding: 2px 0;
}
.boxmodel-properties-wrapper {
padding: 0 9px;
}
+
+/* Box Model Main - Offset Parent */
+
+.boxmodel-offset-parent {
+ position: absolute;
+ top: -20px;
+ right: -10px;
+ color: var(--theme-highlight-purple);
+}