☠☠ backed out by 2cb88d039a8b ☠ ☠ | |
author | Gabriel Luong <gabriel.luong@gmail.com> |
Sat, 28 Jan 2017 12:50:26 -0500 | |
changeset 378211 | 34e6599b9e3c8510edf111fa3534b71a693e4403 |
parent 378210 | 841363ee81e68afff388cda666e6fa3fc824a04d |
child 378212 | 725a0bf59e2bb42fa616e7ccc2cab249314d2d3d |
push id | 7198 |
push user | jlorenzo@mozilla.com |
push date | Tue, 18 Apr 2017 12:07:49 +0000 |
treeherder | mozilla-beta@d57aa49c3948 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jdescottes |
bugs | 1333561 |
milestone | 54.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/inspector/inspector.xhtml +++ b/devtools/client/inspector/inspector.xhtml @@ -8,16 +8,17 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <link rel="stylesheet" href="chrome://devtools/skin/widgets.css"/> <link rel="stylesheet" href="chrome://devtools/skin/inspector.css"/> <link rel="stylesheet" href="chrome://devtools/skin/rules.css"/> <link rel="stylesheet" href="chrome://devtools/skin/computed.css"/> <link rel="stylesheet" href="chrome://devtools/skin/fonts.css"/> + <link rel="stylesheet" href="chrome://devtools/skin/boxmodel.css"/> <link rel="stylesheet" href="chrome://devtools/skin/deprecated-boxmodel.css"/> <link rel="stylesheet" href="chrome://devtools/skin/layout.css"/> <link rel="stylesheet" href="chrome://devtools/skin/animationinspector.css"/> <link rel="stylesheet" href="resource://devtools/client/shared/components/sidebar-toggle.css"/> <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabs.css"/> <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabbar.css"/> <link rel="stylesheet" href="resource://devtools/client/inspector/components/inspector-tab-panel.css"/> <link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/split-box.css"/>
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/actions/box-model.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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_LAYOUT, +} = require("./index"); + +module.exports = { + + /** + * Update the layout state with the new layout properties. + */ + updateLayout(layout) { + return { + type: UPDATE_LAYOUT, + layout, + }; + }, + +};
--- a/devtools/client/inspector/layout/actions/index.js +++ b/devtools/client/inspector/layout/actions/index.js @@ -9,15 +9,18 @@ const { createEnum } = require("devtools createEnum([ // Update the grid highlighted state. "UPDATE_GRID_HIGHLIGHTED", // Update the entire grids state with the new list of grids. "UPDATE_GRIDS", + // Update the layout state with the latest layout properties. + "UPDATE_LAYOUT", + // Update the grid highlighter's show grid line numbers state. "UPDATE_SHOW_GRID_LINE_NUMBERS", // Update the grid highlighter's show infinite lines state. "UPDATE_SHOW_INFINITE_LINES", ], module.exports);
--- a/devtools/client/inspector/layout/actions/moz.build +++ b/devtools/client/inspector/layout/actions/moz.build @@ -1,11 +1,12 @@ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # 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/. DevToolsModules( + 'box-model.js', 'grids.js', 'highlighter-settings.js', 'index.js', )
--- a/devtools/client/inspector/layout/components/App.js +++ b/devtools/client/inspector/layout/components/App.js @@ -3,49 +3,65 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { addons, createClass, createFactory, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react"); const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { LocalizationHelper } = require("devtools/shared/l10n"); + const Accordion = createFactory(require("./Accordion")); +const BoxModel = createFactory(require("./BoxModel")); const Grid = createFactory(require("./Grid")); const Types = require("../types"); const { getStr } = require("../utils/l10n"); +const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties"; +const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI); + const App = createClass({ displayName: "App", propTypes: { + boxModel: PropTypes.shape(Types.boxModel).isRequired, grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired, + onShowBoxModelEditor: PropTypes.func.isRequired, + onHideBoxModelHighlighter: PropTypes.func.isRequired, + onShowBoxModelHighlighter: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, }, mixins: [ addons.PureRenderMixin ], render() { return dom.div( { id: "layout-container", }, Accordion({ items: [ { + header: BOXMODEL_L10N.getStr("boxmodel.title"), + component: BoxModel, + componentProps: this.props, + opened: true, + }, + { header: getStr("layout.header"), component: Grid, componentProps: this.props, - opened: true - } + opened: true, + }, ] }) ); }, }); module.exports = connect(state => state)(App);
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/components/BoxModel.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 { addons, createClass, createFactory, DOM: dom, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const BoxModelInfo = createFactory(require("./BoxModelInfo")); +const BoxModelMain = createFactory(require("./BoxModelMain")); + +const Types = require("../types"); + +module.exports = createClass({ + + displayName: "BoxModel", + + propTypes: { + boxModel: PropTypes.shape(Types.boxModel).isRequired, + onHideBoxModelHighlighter: PropTypes.func.isRequired, + onShowBoxModelEditor: PropTypes.func.isRequired, + onShowBoxModelHighlighter: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + render() { + let { + boxModel, + onHideBoxModelHighlighter, + onShowBoxModelEditor, + onShowBoxModelHighlighter, + } = this.props; + + return dom.div( + { + className: "boxmodel-container", + }, + BoxModelMain({ + boxModel, + onHideBoxModelHighlighter, + onShowBoxModelEditor, + onShowBoxModelHighlighter, + }), + BoxModelInfo({ + boxModel, + }) + ); + }, + +});
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/components/BoxModelEditable.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 { addons, createClass, DOM: dom, PropTypes } = + require("devtools/client/shared/vendor/react"); +const { editableItem } = require("devtools/client/shared/inplace-editor"); + +const LONG_TEXT_ROTATE_LIMIT = 3; + +module.exports = createClass({ + + displayName: "BoxModelEditable", + + propTypes: { + box: PropTypes.string.isRequired, + direction: PropTypes.string.isRequired, + property: PropTypes.string.isRequired, + textContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + onShowBoxModelEditor: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + componentDidMount() { + let { property, onShowBoxModelEditor } = this.props; + + editableItem({ + element: this.refs.span, + }, (element, event) => { + onShowBoxModelEditor(element, event, property); + }); + }, + + render() { + let { + box, + direction, + property, + textContent, + } = this.props; + + let rotate = (direction == "left" || direction == "right") && + textContent.toString().length > LONG_TEXT_ROTATE_LIMIT; + + return dom.p( + { + className: `boxmodel-${box} boxmodel-${direction} + ${rotate ? "boxmodel-rotate" : ""}`, + }, + dom.span( + { + className: "boxmodel-editable", + "data-box": box, + title: property, + ref: "span", + }, + textContent + ) + ); + }, + +});
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/components/BoxModelInfo.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 { LocalizationHelper } = require("devtools/shared/l10n"); +const { addons, createClass, DOM: dom, PropTypes } = + require("devtools/client/shared/vendor/react"); + +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); + +module.exports = createClass({ + + displayName: "BoxModelInfo", + + propTypes: { + boxModel: PropTypes.shape(Types.boxModel).isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + render() { + let { boxModel } = this.props; + let { layout } = boxModel; + let { width, height, position } = layout; + + return dom.div( + { + className: "boxmodel-info", + }, + dom.span( + { + className: "boxmodel-element-size", + }, + SHARED_L10N.getFormatStr("dimensions", width, height) + ), + dom.section( + { + className: "boxmodel-position-group", + }, + dom.button({ + className: "layout-geometry-editor devtools-button", + title: BOXMODEL_L10N.getStr("boxmodel.geometryButton.tooltip"), + }), + dom.span( + { + className: "boxmodel-element-position", + }, + position + ) + ) + ); + }, + +});
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/components/BoxModelMain.js @@ -0,0 +1,261 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 { addons, createClass, createFactory, DOM: dom, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const { LocalizationHelper } = require("devtools/shared/l10n"); + +const BoxModelEditable = createFactory(require("./BoxModelEditable")); + +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); + +module.exports = createClass({ + + displayName: "BoxModelMain", + + propTypes: { + boxModel: PropTypes.shape(Types.boxModel).isRequired, + onHideBoxModelHighlighter: PropTypes.func.isRequired, + onShowBoxModelEditor: PropTypes.func.isRequired, + onShowBoxModelHighlighter: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + getBorderOrPaddingValue(property) { + let { layout } = this.props.boxModel; + return layout[property] ? parseFloat(layout[property]) : "-"; + }, + + getHeightOrWidthValue(property) { + let { layout } = this.props.boxModel; + + if (property == undefined) { + return "-"; + } + + property -= parseFloat(layout["border-left-width"]) + + parseFloat(layout["border-right-width"]) + + parseFloat(layout["padding-left"]) + + parseFloat(layout["padding-right"]); + property = parseFloat(property.toPrecision(6)); + + return property; + }, + + getMarginValue(property, direction) { + let { layout } = this.props.boxModel; + let autoMargins = layout.autoMargins || {}; + let value = "-"; + + if (direction in autoMargins) { + value = "auto"; + } else if (layout[property]) { + value = parseFloat(layout[property]); + } + + return value; + }, + + onHighlightMouseOver(event) { + let region = event.target.getAttribute("data-box"); + if (!region) { + this.props.onHideBoxModelHighlighter(); + } + + this.props.onShowBoxModelHighlighter({ + region, + showOnly: region, + onlyRegionArea: true, + }); + }, + + render() { + let { boxModel, onShowBoxModelEditor } = this.props; + let { layout } = boxModel; + let { width, height } = 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"); + let paddingRight = this.getBorderOrPaddingValue("padding-right"); + let paddingBottom = this.getBorderOrPaddingValue("padding-bottom"); + let paddingLeft = this.getBorderOrPaddingValue("padding-left"); + + let marginTop = this.getMarginValue("margin-top", "top"); + let marginRight = this.getMarginValue("margin-right", "right"); + let marginBottom = this.getMarginValue("margin-bottom", "bottom"); + let marginLeft = this.getMarginValue("margin-left", "left"); + + width = this.getHeightOrWidthValue(width); + height = this.getHeightOrWidthValue(height); + + return dom.div( + { + className: "boxmodel-main", + onMouseOver: this.onHighlightMouseOver, + onMouseOut: this.props.onHideBoxModelHighlighter, + }, + dom.span( + { + className: "boxmodel-legend", + "data-box": "margin", + title: BOXMODEL_L10N.getStr("boxmodel.margin"), + }, + BOXMODEL_L10N.getStr("boxmodel.margin") + ), + dom.div( + { + className: "boxmodel-margins", + "data-box": "margin", + title: BOXMODEL_L10N.getStr("boxmodel.margin"), + }, + dom.span( + { + className: "boxmodel-legend", + "data-box": "border", + title: BOXMODEL_L10N.getStr("boxmodel.border"), + }, + BOXMODEL_L10N.getStr("boxmodel.border") + ), + dom.div( + { + className: "boxmodel-borders", + "data-box": "border", + title: BOXMODEL_L10N.getStr("boxmodel.border"), + }, + dom.span( + { + className: "boxmodel-legend", + "data-box": "padding", + title: BOXMODEL_L10N.getStr("boxmodel.padding"), + }, + BOXMODEL_L10N.getStr("boxmodel.padding") + ), + dom.div( + { + className: "boxmodel-paddings", + "data-box": "padding", + title: BOXMODEL_L10N.getStr("boxmodel.padding"), + }, + dom.div({ + className: "boxmodel-content", + "data-box": "content", + title: BOXMODEL_L10N.getStr("boxmodel.content"), + }) + ) + ), + ), + BoxModelEditable({ + box: "margin", + direction: "top", + property: "margin-top", + textContent: marginTop, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "margin", + direction: "right", + property: "margin-right", + textContent: marginRight, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "margin", + direction: "bottom", + property: "margin-bottom", + textContent: marginBottom, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "margin", + direction: "left", + property: "margin-left", + textContent: marginLeft, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "top", + property: "border-top-width", + textContent: borderTop, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "right", + property: "border-right-width", + textContent: borderRight, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "bottom", + property: "border-bottom-width", + textContent: borderBottom, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "left", + property: "border-left-width", + textContent: borderLeft, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "top", + property: "padding-top", + textContent: paddingTop, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "right", + property: "padding-right", + textContent: paddingRight, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "bottom", + property: "padding-bottom", + textContent: paddingBottom, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "left", + property: "padding-left", + textContent: paddingLeft, + onShowBoxModelEditor, + }), + dom.p( + { + className: "boxmodel-size", + }, + dom.span( + { + "data-box": "content", + title: BOXMODEL_L10N.getStr("boxmodel.content"), + }, + SHARED_L10N.getFormatStr("dimensions", width, height) + ) + ) + ); + }, + +});
--- a/devtools/client/inspector/layout/components/moz.build +++ b/devtools/client/inspector/layout/components/moz.build @@ -3,12 +3,16 @@ # This Source Code Form is subject to the terms of the Mozilla Public # 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/. DevToolsModules( 'Accordion.css', 'Accordion.js', 'App.js', + 'BoxModel.js', + 'BoxModelEditable.js', + 'BoxModelInfo.js', + 'BoxModelMain.js', 'Grid.js', 'GridDisplaySettings.js', 'GridList.js', )
--- a/devtools/client/inspector/layout/layout.js +++ b/devtools/client/inspector/layout/layout.js @@ -1,54 +1,68 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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 Services = require("Services"); const { Task } = require("devtools/shared/task"); +const { getCssProperties } = require("devtools/shared/fronts/css-properties"); +const { ReflowFront } = require("devtools/shared/fronts/reflow"); + +const { InplaceEditor } = require("devtools/client/shared/inplace-editor"); const { createFactory, createElement } = require("devtools/client/shared/vendor/react"); const { Provider } = require("devtools/client/shared/vendor/react-redux"); const { + updateLayout, +} = require("./actions/box-model"); +const { updateGridHighlighted, updateGrids, } = require("./actions/grids"); const { updateShowGridLineNumbers, updateShowInfiniteLines, } = require("./actions/highlighter-settings"); const App = createFactory(require("./components/App")); const Store = require("./store"); +const EditingSession = require("./utils/editing-session"); + const { LocalizationHelper } = require("devtools/shared/l10n"); const INSPECTOR_L10N = new LocalizationHelper("devtools/client/locales/inspector.properties"); +const NUMERIC = /^-?[\d\.]+$/; const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers"; const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines"; function LayoutView(inspector, window) { this.document = window.document; this.highlighters = inspector.highlighters; this.inspector = inspector; this.store = null; this.walker = this.inspector.walker; + this.updateBoxModel = this.updateBoxModel.bind(this); + this.onGridLayoutChange = this.onGridLayoutChange.bind(this); this.onHighlighterChange = this.onHighlighterChange.bind(this); + this.onNewSelection = this.onNewSelection.bind(this); this.onSidebarSelect = this.onSidebarSelect.bind(this); + this.init(); + this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange); this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange); + this.inspector.selection.on("new-node-front", this.onNewSelection); this.inspector.sidebar.on("select", this.onSidebarSelect); - - this.init(); } LayoutView.prototype = { /** * Initializes the layout view by fetching the LayoutFront from the walker, creating * the redux store and adding the view into the inspector sidebar. */ @@ -58,16 +72,105 @@ LayoutView.prototype = { } this.layoutInspector = yield this.inspector.walker.getLayoutInspector(); let store = this.store = Store(); this.loadHighlighterSettings(); let app = App({ + /** + * Hides the box-model highlighter on the currently selected element. + */ + onHideBoxModelHighlighter: () => { + let toolbox = this.inspector.toolbox; + toolbox.highlighterUtils.unhighlight(); + }, + + /** + * Shows the inplace editor when a box model editable value is clicked on the + * box model panel. + * + * @param {DOMNode} element + * The element that was clicked. + * @param {Event} event + * The event object. + * @param {String} property + * The name of the property. + */ + onShowBoxModelEditor: (element, event, property) => { + let session = new EditingSession({ + inspector: this.inspector, + doc: this.document, + elementRules: this.elementRules, + }); + let initialValue = session.getProperty(property); + + let editor = new InplaceEditor({ + element: element, + initial: initialValue, + contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE, + property: { + name: property + }, + start: self => { + self.elt.parentNode.classList.add("boxmodel-editing"); + }, + change: value => { + if (NUMERIC.test(value)) { + value += "px"; + } + + let properties = [ + { name: property, value: value } + ]; + + if (property.substring(0, 7) == "border-") { + let bprop = property.substring(0, property.length - 5) + "style"; + let style = session.getProperty(bprop); + if (!style || style == "none" || style == "hidden") { + properties.push({ name: bprop, value: "solid" }); + } + } + + session.setProperties(properties).catch(e => console.error(e)); + }, + done: (value, commit) => { + editor.elt.parentNode.classList.remove("boxmodel-editing"); + if (!commit) { + session.revert().then(() => { + session.destroy(); + }, e => console.error(e)); + return; + } + + let node = this.inspector.selection.nodeFront; + this.inspector.pageStyle.getLayout(node, { + autoMargins: true, + }).then(layout => { + this.store.dispatch(updateLayout(layout)); + }, e => console.error(e)); + }, + contextMenu: this.inspector.onTextBoxContextMenu, + cssProperties: getCssProperties(this.inspector.toolbox) + }, event); + }, + + /** + * Shows the box-model highlighter on the currently selected element. + * + * @param {Object} options + * Options passed to the highlighter actor. + */ + onShowBoxModelHighlighter: (options = {}) => { + let toolbox = this.inspector.toolbox; + let nodeFront = this.inspector.selection.nodeFront; + + toolbox.highlighterUtils.highlightNodeFront(nodeFront, options); + }, /** * Handler for a change in the input checkboxes in the GridList component. * Toggles on/off the grid highlighter for the provided grid container element. * * @param {NodeFront} node * The NodeFront of the grid container element for which the grid * highlighter is toggled on/off for. @@ -115,17 +218,16 @@ LayoutView.prototype = { let { grids, highlighterSettings } = this.store.getState(); for (let grid of grids) { if (grid.highlighted) { this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings); } } }, - }); let provider = createElement(Provider, { store, id: "layoutview", title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"), key: "layoutview", }, app); @@ -142,19 +244,26 @@ LayoutView.prototype = { /** * Destruction function called when the inspector is destroyed. Removes event listeners * and cleans up references. */ destroy() { this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange); this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange); + this.inspector.selection.off("new-node-front", this.onNewSelection); this.inspector.sidebar.off("select", this.onSidebarSelect); this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange); + if (this.reflowFront) { + this.untrackReflows(); + this.reflowFront.destroy(); + this.reflowFront = null; + } + this.document = null; this.inspector = null; this.layoutInspector = null; this.store = null; this.walker = null; }, /** @@ -162,36 +271,112 @@ LayoutView.prototype = { */ isPanelVisible() { return this.inspector.toolbox.currentToolId === "inspector" && this.inspector.sidebar && this.inspector.sidebar.getCurrentTabID() === "layoutview"; }, /** + * Returns true if the layout panel is visible and the current node is valid to + * be displayed in the view. + */ + isPanelVisibleAndNodeValid() { + return this.isPanelVisible() && + this.inspector.selection.isConnected() && + this.inspector.selection.isElementNode(); + }, + + /** * Load the grid highligher display settings into the store from the stored preferences. */ loadHighlighterSettings() { let { dispatch } = this.store; let showGridLineNumbers = Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS); let showInfinteLines = Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF); dispatch(updateShowGridLineNumbers(showGridLineNumbers)); dispatch(updateShowInfiniteLines(showInfinteLines)); }, /** - * Refreshes the layout view by dispatching the new grid data. This is called when the + * Starts listening to reflows in the current tab. + */ + trackReflows() { + if (!this.reflowFront) { + let { target } = this.inspector; + if (target.form.reflowActor) { + this.reflowFront = ReflowFront(target.client, + target.form); + } else { + return; + } + } + + this.reflowFront.on("reflows", this.updateBoxModel); + this.reflowFront.start(); + }, + + /** + * Stops listening to reflows in the current tab. + */ + untrackReflows() { + if (!this.reflowFront) { + return; + } + + this.reflowFront.off("reflows", this.updateBoxModel); + this.reflowFront.stop(); + }, + + /** + * Updates the box model panel by dispatching the new layout data. + */ + updateBoxModel() { + let lastRequest = Task.spawn((function* () { + if (!(this.isPanelVisible() && + this.inspector.selection.isConnected() && + this.inspector.selection.isElementNode())) { + return null; + } + + let node = this.inspector.selection.nodeFront; + let layout = yield this.inspector.pageStyle.getLayout(node, { + autoMargins: true, + }); + let styleEntries = yield this.inspector.pageStyle.getApplied(node, {}); + this.elementRules = styleEntries.map(e => e.rule); + + // 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; + } + + this._lastRequest = null; + + this.inspector.emit("boxmodel-view-updated"); + return null; + }).bind(this)).catch(console.error); + + this._lastRequest = lastRequest; + }, + + /** + * Updates the grid panel by dispatching the new grid data. This is called when the * layout view becomes visible or the view needs to be updated with new grid data. * * @param {Array|null} gridFronts * Optional array of all GridFront in the current page. */ - refresh: Task.async(function* (gridFronts) { + updateGridPanel: Task.async(function* (gridFronts) { // Stop refreshing if the inspector or store is already destroyed. if (!this.inspector || !this.store) { return; } // Get all the GridFront from the server if no gridFronts were provided. if (!gridFronts) { gridFronts = yield this.layoutInspector.getAllGrids(this.walker.rootNode); @@ -216,17 +401,17 @@ LayoutView.prototype = { /** * Handler for "grid-layout-changed" events emitted from the LayoutActor. * * @param {Array} grids * Array of all GridFront in the current page. */ onGridLayoutChange(grids) { if (this.isPanelVisible()) { - this.refresh(grids); + this.updateGridPanel(grids); } }, /** * Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted * from the HighlightersOverlay. Updates the NodeFront's grid highlighted state. * * @param {Event} event @@ -236,26 +421,44 @@ LayoutView.prototype = { * is shown for. */ onHighlighterChange(event, nodeFront) { let highlighted = event === "grid-highlighter-shown"; this.store.dispatch(updateGridHighlighted(nodeFront, highlighted)); }, /** + * Selection 'new-node-front' event handler. + */ + onNewSelection: function () { + if (!this.isPanelVisibleAndNodeValid()) { + return; + } + + this.updateBoxModel(); + }, + + /** * Handler for the inspector sidebar select event. Starts listening for * "grid-layout-changed" if the layout panel is visible. Otherwise, stop * listening for grid layout changes. Finally, refresh the layout view if * it is visible. */ onSidebarSelect() { if (!this.isPanelVisible()) { this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange); + this.untrackReflows(); return; } + if (this.inspector.selection.isConnected() && + this.inspector.selection.isElementNode()) { + this.trackReflows(); + } + this.layoutInspector.on("grid-layout-changed", this.onGridLayoutChange); - this.refresh(); + this.updateBoxModel(); + this.updateGridPanel(); }, }; module.exports = LayoutView;
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/reducers/box-model.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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_LAYOUT, +} = require("../actions/index"); + +const INITIAL_BOX_MODEL = { + layout: {}, +}; + +let reducers = { + + [UPDATE_LAYOUT](boxModel, { layout }) { + return Object.assign({}, boxModel, { + layout, + }); + }, + +}; + +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/layout/reducers/index.js +++ b/devtools/client/inspector/layout/reducers/index.js @@ -1,8 +1,9 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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"; +exports.boxModel = require("./box-model"); exports.grids = require("./grids"); exports.highlighterSettings = require("./highlighter-settings");
--- a/devtools/client/inspector/layout/reducers/moz.build +++ b/devtools/client/inspector/layout/reducers/moz.build @@ -1,11 +1,12 @@ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # 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/. DevToolsModules( + 'box-model.js', 'grids.js', 'highlighter-settings.js', 'index.js', )
--- a/devtools/client/inspector/layout/types.js +++ b/devtools/client/inspector/layout/types.js @@ -2,16 +2,26 @@ * 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 { PropTypes } = require("devtools/client/shared/vendor/react"); /** + * The box model data for the current selected node. + */ +exports.boxModel = { + + // The layout information of the current selected node + layout: PropTypes.object, + +}; + +/** * A single grid container in the document. */ exports.grid = { // The id of the grid id: PropTypes.number, // The grid fragment object of the grid container
--- a/devtools/client/themes/boxmodel.css +++ b/devtools/client/themes/boxmodel.css @@ -1,130 +1,128 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/ */ -#boxmodel-wrapper { - border-bottom-style: solid; - border-bottom-width: 1px; - border-color: var(--theme-splitter-color); -} +/** + * This is the stylesheet of the Box Model view implemented in the layout panel. + */ -#boxmodel-container { +.boxmodel-container { /* The view will grow bigger as the window gets resized, until 400px */ max-width: 400px; margin: 0px auto; padding: 0; } /* Header */ -#boxmodel-header, -#boxmodel-info { +.boxmodel-header, +.boxmodel-info { display: flex; align-items: center; padding: 4px 17px; } -#layout-geometry-editor { +.layout-geometry-editor { visibility: hidden; } -#layout-geometry-editor::before { +.layout-geometry-editor::before { background: url(images/geometry-editor.svg) no-repeat center center / 16px 16px; } /* Main: contains the box-model regions */ -#boxmodel-main { +.boxmodel-main { position: relative; box-sizing: border-box; /* The regions are semi-transparent, so the white background is partly visible */ background-color: white; color: var(--theme-selection-color); /* Make sure there is some space between the window's edges and the regions */ - margin: 0 14px 4px 14px; + margin: 14px 14px 4px 14px; width: calc(100% - 2 * 14px); } .boxmodel-margin, .boxmodel-size { color: var(--theme-highlight-blue); } /* Regions are 3 nested elements with wide borders and outlines */ -#boxmodel-content { +.boxmodel-content { height: 18px; } -#boxmodel-margins, -#boxmodel-borders, -#boxmodel-padding { +.boxmodel-margins, +.boxmodel-borders, +.boxmodel-paddings { border-color: hsla(210,100%,85%,0.2); border-width: 18px; border-style: solid; outline: dotted 1px hsl(210,100%,85%); } -#boxmodel-margins { +.boxmodel-margins { /* This opacity applies to all of the regions, since they are nested */ opacity: .8; } /* Regions colors */ -#boxmodel-margins { +.boxmodel-margins { border-color: #edff64; } -#boxmodel-borders { +.boxmodel-borders { border-color: #444444; } -#boxmodel-padding { +.boxmodel-paddings { border-color: #6a5acd; } -#boxmodel-content { +.boxmodel-content { background-color: #87ceeb; } -.theme-firebug #boxmodel-main, -.theme-firebug #boxmodel-borders, -.theme-firebug #boxmodel-content { +.theme-firebug .boxmodel-main, +.theme-firebug .boxmodel-borders, +.theme-firebug .boxmodel-content { border-style: solid; } -.theme-firebug #boxmodel-main, -.theme-firebug #boxmodel-header { +.theme-firebug .boxmodel-main, +.theme-firebug .boxmodel-header { font-family: var(--proportional-font-family); } -.theme-firebug #boxmodel-main { +.theme-firebug .boxmodel-main { color: var(--theme-body-color); font-size: var(--theme-toolbar-font-size); } -.theme-firebug #boxmodel-header { +.theme-firebug .boxmodel-header { font-size: var(--theme-toolbar-font-size); } /* Editable region sizes are contained in absolutely positioned <p> */ -#boxmodel-main > p { +.boxmodel-main > p { position: absolute; pointer-events: none; margin: 0; text-align: center; } -#boxmodel-main > p > span, -#boxmodel-main > p > input { +.boxmodel-main > p > span, +.boxmodel-main > p > input { vertical-align: middle; pointer-events: auto; } /* Coordinates for the region sizes */ .boxmodel-top, .boxmodel-bottom { @@ -230,29 +228,24 @@ border: 1px dashed transparent; -moz-user-select: text; } .boxmodel-editable:hover { border-bottom-color: hsl(0, 0%, 50%); } -.styleinspector-propertyeditor { - border: 1px solid #ccc; - padding: 0; -} - /* Make sure the content size doesn't appear as editable like the other sizes */ .boxmodel-size > span { cursor: default; } /* Box Model Info: contains the position and size of the element */ -#boxmodel-element-size { +.boxmodel-element-size { flex: 1; } -#boxmodel-position-group { +.boxmodel-position-group { display: flex; align-items: center; }