new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/actions/flexbox.js
@@ -0,0 +1,47 @@
+/* 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 {
+ CLEAR_FLEXBOX,
+ UPDATE_FLEXBOX,
+ UPDATE_FLEXBOX_HIGHLIGHTED,
+} = require("./index");
+
+module.exports = {
+
+ /**
+ * Clears the flexbox state by resetting it back to the initial flexbox state.
+ */
+ clearFlexbox() {
+ return {
+ type: CLEAR_FLEXBOX,
+ };
+ },
+
+ /**
+ * Updates the flexbox state with the newly selected flexbox.
+ */
+ updateFlexbox(flexbox) {
+ return {
+ type: UPDATE_FLEXBOX,
+ flexbox,
+ };
+ },
+
+ /**
+ * Updates the flexbox highlighted state.
+ *
+ * @param {Boolean} highlighted
+ * Whether or not the flexbox highlighter is highlighting the flexbox.
+ */
+ updateFlexboxHighlighted(highlighted) {
+ return {
+ type: UPDATE_FLEXBOX_HIGHLIGHTED,
+ highlighted,
+ };
+ },
+
+};
--- a/devtools/client/inspector/flexbox/actions/index.js
+++ b/devtools/client/inspector/flexbox/actions/index.js
@@ -3,12 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { createEnum } = require("devtools/client/shared/enum");
createEnum([
- // Update the entire flexboxes state with the new list of flexboxes.
- "UPDATE_FLEXBOXES",
+ // Clears the flexbox state by resetting it back to the initial flexbox state.
+ "CLEAR_FLEXBOX",
+
+ // Updates the flexbox state with the newly selected flexbox.
+ "UPDATE_FLEXBOX",
+
+ // Updates the flexbox highlighted state.
+ "UPDATE_FLEXBOX_HIGHLIGHTED",
], module.exports);
--- a/devtools/client/inspector/flexbox/actions/moz.build
+++ b/devtools/client/inspector/flexbox/actions/moz.build
@@ -1,9 +1,10 @@
# -*- 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(
+ 'flexbox.js',
'index.js',
)
--- a/devtools/client/inspector/flexbox/components/Flexbox.js
+++ b/devtools/client/inspector/flexbox/components/Flexbox.js
@@ -1,26 +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 {
- DOM: dom,
- PureComponent,
-} = require("devtools/client/shared/vendor/react");
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
+
+const FlexboxItem = createFactory(require("./FlexboxItem"));
+
+const Types = require("../types");
class Flexbox extends PureComponent {
static get propTypes() {
- return {};
+ return {
+ flexbox: PropTypes.shape(Types.flexbox).isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ onHideBoxModelHighlighter: PropTypes.func.isRequired,
+ onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+ onToggleFlexboxHighlighter: PropTypes.func.isRequired,
+ };
}
render() {
- return dom.div(
- {
- id: "layout-flexbox-container",
- }
- );
+ const {
+ flexbox,
+ setSelectedNode,
+ onHideBoxModelHighlighter,
+ onShowBoxModelHighlighterForNode,
+ onToggleFlexboxHighlighter,
+ } = this.props;
+
+ return flexbox.actorID ?
+ dom.div({ id: "layout-flexbox-container" },
+ dom.div({ className: "flexbox-content" },
+ dom.div({ className: "flexbox-container" },
+ dom.span({}, getStr("flexbox.overlayFlexbox")),
+ dom.ul(
+ {
+ id: "flexbox-list",
+ className: "devtools-monospace",
+ },
+ FlexboxItem({
+ key: flexbox.id,
+ flexbox,
+ setSelectedNode,
+ onHideBoxModelHighlighter,
+ onShowBoxModelHighlighterForNode,
+ onToggleFlexboxHighlighter,
+ })
+ )
+ )
+ )
+ )
+ :
+ dom.div({ className: "devtools-sidepanel-no-result" },
+ getStr("flexbox.noFlexboxeOnThisPage")
+ );
}
}
module.exports = Flexbox;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexboxItem.js
@@ -0,0 +1,99 @@
+/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
+
+// Reps
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const { Rep } = REPS;
+const ElementNode = REPS.ElementNode;
+
+const Types = require("../types");
+
+class FlexboxItem extends PureComponent {
+ static get propTypes() {
+ return {
+ flexbox: PropTypes.shape(Types.flexbox).isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ onHideBoxModelHighlighter: PropTypes.func.isRequired,
+ onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+ onToggleFlexboxHighlighter: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+ this.onFlexboxCheckboxClick = this.onFlexboxCheckboxClick.bind(this);
+ this.onFlexboxInspectIconClick = this.onFlexboxInspectIconClick.bind(this);
+ }
+
+ onFlexboxCheckboxClick(e) {
+ // If the click was on the svg icon to select the node in the inspector, bail out.
+ const originalTarget = e.nativeEvent && e.nativeEvent.explicitOriginalTarget;
+ if (originalTarget && originalTarget.namespaceURI === "http://www.w3.org/2000/svg") {
+ // We should be able to cancel the click event propagation after the following reps
+ // issue is implemented : https://github.com/devtools-html/reps/issues/95 .
+ e.preventDefault();
+ return;
+ }
+
+ const {
+ flexbox,
+ onToggleFlexboxHighlighter,
+ } = this.props;
+
+ onToggleFlexboxHighlighter(flexbox.nodeFront);
+ }
+
+ onFlexboxInspectIconClick(nodeFront) {
+ const { setSelectedNode } = this.props;
+ setSelectedNode(nodeFront, "layout-panel").catch(e => console.error(e));
+ nodeFront.scrollIntoView().catch(e => console.error(e));
+ }
+
+ render() {
+ const {
+ flexbox,
+ onHideBoxModelHighlighter,
+ onShowBoxModelHighlighterForNode,
+ } = this.props;
+ const {
+ actorID,
+ highlighted,
+ nodeFront,
+ } = flexbox;
+
+ return dom.li(
+ {},
+ dom.label(
+ {},
+ dom.input(
+ {
+ type: "checkbox",
+ value: actorID,
+ checked: highlighted,
+ onChange: this.onFlexboxCheckboxClick,
+ }
+ ),
+ Rep(
+ {
+ defaultRep: ElementNode,
+ mode: MODE.TINY,
+ object: translateNodeFrontToGrip(nodeFront),
+ onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
+ onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront),
+ onInspectIconClick: () => this.onFlexboxInspectIconClick(nodeFront),
+ }
+ )
+ )
+ );
+ }
+}
+
+module.exports = FlexboxItem;
--- a/devtools/client/inspector/flexbox/components/moz.build
+++ b/devtools/client/inspector/flexbox/components/moz.build
@@ -1,9 +1,10 @@
# -*- 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(
'Flexbox.js',
+ 'FlexboxItem.js',
)
--- a/devtools/client/inspector/flexbox/flexbox.js
+++ b/devtools/client/inspector/flexbox/flexbox.js
@@ -1,21 +1,251 @@
/* 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 { throttle } = require("devtools/client/inspector/shared/utils");
+
+const {
+ clearFlexbox,
+ updateFlexbox,
+ updateFlexboxHighlighted,
+} = require("./actions/flexbox");
+
class FlexboxInspector {
constructor(inspector, window) {
this.document = window.document;
+ this.highlighters = inspector.highlighters;
this.inspector = inspector;
this.store = inspector.store;
+ this.walker = inspector.walker;
+
+ this.onHighlighterChange = this.onHighlighterChange.bind(this);
+ this.onReflow = throttle(this.onReflow, 500, this);
+ this.onSidebarSelect = this.onSidebarSelect.bind(this);
+ this.onToggleFlexboxHighlighter = this.onToggleFlexboxHighlighter.bind(this);
+ this.onUpdatePanel = this.onUpdatePanel.bind(this);
+
+ this.init();
+ }
+
+ async init() {
+ if (!this.inspector) {
+ return;
+ }
+
+ this.hasGetCurrentFlexbox = await this.inspector.target.actorHasMethod("layout",
+ "getCurrentFlexbox");
+ this.layoutInspector = await this.walker.getLayoutInspector();
+
+ this.highlighters.on("flexbox-highlighter-hidden", this.onHighlighterChange);
+ this.highlighters.on("flexbox-highlighter-shown", this.onHighlighterChange);
+ this.inspector.sidebar.on("select", this.onSidebarSelect);
+
+ this.onSidebarSelect();
}
destroy() {
+ this.highlighters.off("flexbox-highlighter-hidden", this.onHighlighterChange);
+ this.highlighters.off("flexbox-highlighter-shown", this.onHighlighterChange);
+ this.inspector.sidebar.off("select", this.onSidebarSelect);
+
+ this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
+
this.document = null;
+ this.highlighters = null;
this.inspector = null;
+ this.hasGetCurrentFlexbox = null;
+ this.layoutInspector = null;
this.store = null;
+ this.walker = null;
+ }
+
+ getComponentProps() {
+ return {
+ onToggleFlexboxHighlighter: this.onToggleFlexboxHighlighter,
+ };
+ }
+
+ /**
+ * Returns true if the layout panel is visible, and false otherwise.
+ */
+ isPanelVisible() {
+ return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
+ this.inspector.toolbox.currentToolId === "inspector" &&
+ this.inspector.sidebar.getCurrentTabID() === "layoutview";
+ }
+
+ /**
+ * Handler for "flexbox-highlighter-shown" and "flexbox-highlighter-hidden" events
+ * emitted from the HighlightersOverlay. Updates the flex container highlighted state
+ * only if the provided NodeFront is the current selected flex container.
+ *
+ * @param {Event} event
+ * Event that was triggered.
+ * @param {NodeFront} nodeFront
+ * The NodeFront of the flex container element for which the flexbox
+ * highlighter is shown for.
+ */
+ onHighlighterChange(event, nodeFront) {
+ const { flexbox } = this.store.getState();
+ const highlighted = event === "flexbox-highlighter-shown";
+
+ if (flexbox.nodeFront === nodeFront && flexbox.highlighted !== highlighted) {
+ this.store.dispatch(updateFlexboxHighlighted(highlighted));
+ }
+ }
+
+ /**
+ * Handler for the "reflow" event fired by the inspector's reflow tracker. On reflows,
+ * updates the flexbox panel because the shape of the flexbox on the page may have
+ * changed.
+ *
+ * TODO: In the future, we will want to compare the flex item fragment data returned
+ * for rendering the flexbox outline.
+ */
+ async onReflow() {
+ if (!this.isPanelVisible()) {
+ return;
+ }
+
+ const { flexbox } = this.store.getState();
+
+ let flexboxFront;
+ try {
+ if (!this.hasGetCurrentFlexbox) {
+ return;
+ }
+
+ flexboxFront = await this.layoutInspector.getCurrentFlexbox(
+ this.inspector.selection.nodeFront);
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ return;
+ }
+
+ // Clear the flexbox panel if there is no flex container for the current node
+ // selection.
+ if (!flexboxFront) {
+ this.store.dispatch(clearFlexbox());
+ return;
+ }
+
+ // Do nothing because the same flex container is still selected.
+ if (flexbox.actorID == flexboxFront.actorID) {
+ return;
+ }
+
+ // Update the flexbox panel with the new flexbox front contents.
+ this.update(flexboxFront);
+ }
+
+ /**
+ * Handler for the inspector sidebar "select" event. Updates the flexbox panel if it
+ * is visible.
+ */
+ onSidebarSelect() {
+ if (!this.isPanelVisible()) {
+ this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
+ this.inspector.off("new-root", this.onUpdatePanel);
+ this.inspector.selection.off("new-node-front", this.onUpdatePanel);
+ return;
+ }
+
+ this.inspector.reflowTracker.trackReflows(this, this.onReflow);
+ this.inspector.on("new-root", this.onUpdatePanel);
+ this.inspector.selection.on("new-node-front", this.onUpdatePanel);
+
+ this.update();
+ }
+
+ /**
+ * Handler for a change in the input checkboxes in the FlexboxItem component.
+ * Toggles on/off the flexbox highlighter for the provided flex container element.
+ *
+ * @param {NodeFront} node
+ * The NodeFront of the flexb container element for which the flexbox
+ * highlighter is toggled on/off for.
+ */
+ onToggleFlexboxHighlighter(node) {
+ this.highlighters.toggleFlexboxHighlighter(node);
+ this.store.dispatch(updateFlexboxHighlighted(node !==
+ this.highlighters.flexboxHighlighterShow));
+ }
+
+ /**
+ * Handler for "new-root" event fired by the inspector and "new-node-front" event fired
+ * by the inspector selection. Updates the flexbox panel if it is visible.
+ */
+ onUpdatePanel() {
+ if (!this.isPanelVisible()) {
+ return;
+ }
+
+ this.update();
+ }
+
+ /**
+ * Updates the flexbox panel by dispatching the new flexbox data. This is called when
+ * the layout view becomes visible or a new node is selected and needs to be update
+ * with new flexbox data.
+ *
+ * @param {FlexboxFront|Null} flexboxFront
+ * THe FlexboxFront of the flex container for the current node selection.
+ */
+ async update(flexboxFront) {
+ // Stop refreshing if the inspector or store is already destroyed or no node is
+ // selected.
+ if (!this.inspector || !this.store || !this.inspector.selection.nodeFront) {
+ return;
+ }
+
+ // Fetch the current flexbox if no flexbox front was passed into this update.
+ if (!flexboxFront) {
+ try {
+ if (!this.hasGetCurrentFlexbox) {
+ return;
+ }
+
+ flexboxFront = await this.layoutInspector.getCurrentFlexbox(
+ this.inspector.selection.nodeFront);
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ return;
+ }
+ }
+
+ // Clear the flexbox panel if there is no flex container for the current node
+ // selection.
+ if (!flexboxFront) {
+ this.store.dispatch(clearFlexbox());
+ return;
+ }
+
+ let nodeFront = flexboxFront.containerNodeFront;
+
+ // If the FlexboxFront doesn't yet have access to the NodeFront for its container,
+ // then get it from the walker. This happens when the walker hasn't seen this
+ // particular DOM Node in the tree yet or when we are connected to an older server.
+ if (!nodeFront) {
+ try {
+ nodeFront = await this.walker.getNodeFromActor(flexboxFront.actorID,
+ ["containerEl"]);
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ return;
+ }
+ }
+
+ this.store.dispatch(updateFlexbox({
+ actorID: flexboxFront.actorID,
+ highlighted: nodeFront == this.highlighters.flexboxHighlighterShown,
+ nodeFront,
+ }));
}
}
module.exports = FlexboxInspector;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/reducers/flexbox.js
@@ -0,0 +1,46 @@
+/* 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 {
+ CLEAR_FLEXBOX,
+ UPDATE_FLEXBOX,
+ UPDATE_FLEXBOX_HIGHLIGHTED,
+} = require("../actions/index");
+
+const INITIAL_FLEXBOX = {
+ // The actor ID of the flex container.
+ actorID: null,
+ // Whether or not the flexbox highlighter is highlighting the flex container.
+ highlighted: false,
+ // The NodeFront of the flex container.
+ nodeFront: null,
+};
+
+let reducers = {
+
+ [CLEAR_FLEXBOX](flexbox, _) {
+ return INITIAL_FLEXBOX;
+ },
+
+ [UPDATE_FLEXBOX](_, { flexbox }) {
+ return flexbox;
+ },
+
+ [UPDATE_FLEXBOX_HIGHLIGHTED](flexbox, { highlighted }) {
+ return Object.assign({}, flexbox, {
+ highlighted,
+ });
+ },
+
+};
+
+module.exports = function (flexbox = INITIAL_FLEXBOX, action) {
+ let reducer = reducers[action.type];
+ if (!reducer) {
+ return flexbox;
+ }
+ return reducer(flexbox, action);
+};
deleted file mode 100644
--- a/devtools/client/inspector/flexbox/reducers/flexboxes.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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 INITIAL_FLEXBOXES = [];
-
-let reducers = {
-
-};
-
-module.exports = function (flexboxes = INITIAL_FLEXBOXES, action) {
- let reducer = reducers[action.type];
- if (!reducer) {
- return flexboxes;
- }
- return reducer(flexboxes, action);
-};
--- a/devtools/client/inspector/flexbox/reducers/index.js
+++ b/devtools/client/inspector/flexbox/reducers/index.js
@@ -1,7 +1,7 @@
/* 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.flexboxes = require("./flexboxes");
+exports.flexbox = require("./flexbox");
--- a/devtools/client/inspector/flexbox/reducers/moz.build
+++ b/devtools/client/inspector/flexbox/reducers/moz.build
@@ -1,10 +1,10 @@
# -*- 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(
- 'flexboxes.js',
+ 'flexbox.js',
'index.js',
)
--- a/devtools/client/inspector/flexbox/types.js
+++ b/devtools/client/inspector/flexbox/types.js
@@ -2,14 +2,19 @@
* 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-prop-types");
exports.flexbox = {
- // The id of the flexbox container.
- id: PropTypes.number,
+
+ // The actor ID of the flex container.
+ actorID: PropTypes.number,
- // The node front of the flexbox container.
+ // Whether or not the flexbox highlighter is highlighting the flex container.
+ highlighted: PropTypes.bool,
+
+ // The NodeFront of the flex container.
nodeFront: PropTypes.object,
+
};
--- a/devtools/client/inspector/grids/components/Grid.js
+++ b/devtools/client/inspector/grids/components/Grid.js
@@ -2,23 +2,23 @@
* 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 { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
const GridDisplaySettings = createFactory(require("./GridDisplaySettings"));
const GridList = createFactory(require("./GridList"));
const GridOutline = createFactory(require("./GridOutline"));
const Types = require("../types");
-const { getStr } = require("../utils/l10n");
class Grid extends PureComponent {
static get propTypes() {
return {
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
setSelectedNode: PropTypes.func.isRequired,
--- a/devtools/client/inspector/grids/components/GridDisplaySettings.js
+++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js
@@ -2,19 +2,19 @@
* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
const Types = require("../types");
-const { getStr } = require("../utils/l10n");
class GridDisplaySettings extends PureComponent {
static get propTypes() {
return {
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
onToggleShowGridAreas: PropTypes.func.isRequired,
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
onToggleShowInfiniteLines: PropTypes.func.isRequired,
--- a/devtools/client/inspector/grids/components/GridList.js
+++ b/devtools/client/inspector/grids/components/GridList.js
@@ -2,21 +2,21 @@
* 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 { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
const GridItem = createFactory(require("./GridItem"));
const Types = require("../types");
-const { getStr } = require("../utils/l10n");
class GridList extends PureComponent {
static get propTypes() {
return {
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
setSelectedNode: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -3,19 +3,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Services = require("Services");
const { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
const Types = require("../types");
-const { getStr } = require("../utils/l10n");
// The delay prior to executing the grid cell highlighting.
const GRID_HIGHLIGHTING_DEBOUNCE = 50;
// Prefs for the max number of rows/cols a grid container can have for
// the outline to display.
const GRID_OUTLINE_MAX_ROWS_PREF =
Services.prefs.getIntPref("devtools.gridinspector.gridOutlineMaxRows");
deleted file mode 100644
--- a/devtools/client/inspector/grids/utils/l10n.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* 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 L10N = new LocalizationHelper("devtools/client/locales/layout.properties");
-
-module.exports = {
- getStr: (...args) => L10N.getStr(...args),
- getFormatStr: (...args) => L10N.getFormatStr(...args),
- getFormatStrWithNumbers: (...args) => L10N.getFormatStrWithNumbers(...args),
- numberWithDecimals: (...args) => L10N.numberWithDecimals(...args),
-};
--- a/devtools/client/inspector/grids/utils/moz.build
+++ b/devtools/client/inspector/grids/utils/moz.build
@@ -1,10 +1,9 @@
# -*- 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(
- 'l10n.js',
'utils.js',
)
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -37,16 +37,22 @@ class LayoutView {
let {
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
onToggleGeometryEditor,
} = this.inspector.getPanel("boxmodel").getComponentProps();
+ this.flexboxInspector = new FlexboxInspector(this.inspector,
+ this.inspector.panelWin);
+ let {
+ onToggleFlexboxHighlighter,
+ } = this.flexboxInspector.getComponentProps();
+
this.gridInspector = new GridInspector(this.inspector, this.inspector.panelWin);
let {
getSwatchColorPickerTooltip,
onSetGridOverlayColor,
onShowGridAreaHighlight,
onShowGridCellHighlight,
onShowGridLineNamesHighlight,
onToggleGridHighlighter,
@@ -66,16 +72,17 @@ class LayoutView {
onHideBoxModelHighlighter,
onSetGridOverlayColor,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
onShowGridAreaHighlight,
onShowGridCellHighlight,
onShowGridLineNamesHighlight,
+ onToggleFlexboxHighlighter,
onToggleGeometryEditor,
onToggleGridHighlighter,
onToggleShowGridAreas,
onToggleShowGridLineNumbers,
onToggleShowInfiniteLines,
});
let provider = createElement(Provider, {
@@ -88,16 +95,17 @@ class LayoutView {
// Expose the provider to let inspector.js use it in setupSidebar.
this.provider = provider;
}
/**
* Destruction function called when the inspector is destroyed. Cleans up references.
*/
destroy() {
+ this.flexboxInspector.destroy();
this.gridInspector.destroy();
this.document = null;
this.inspector = null;
this.store = null;
}
}
--- a/devtools/client/inspector/layout/moz.build
+++ b/devtools/client/inspector/layout/moz.build
@@ -1,13 +1,14 @@
# -*- 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/.
DIRS += [
'components',
+ 'utils',
]
DevToolsModules(
'layout.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/utils/l10n.js
@@ -0,0 +1,15 @@
+/* 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 L10N = new LocalizationHelper("devtools/client/locales/layout.properties");
+
+module.exports = {
+ getStr: (...args) => L10N.getStr(...args),
+ getFormatStr: (...args) => L10N.getFormatStr(...args),
+ getFormatStrWithNumbers: (...args) => L10N.getFormatStrWithNumbers(...args),
+ numberWithDecimals: (...args) => L10N.numberWithDecimals(...args),
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/utils/moz.build
@@ -0,0 +1,9 @@
+# -*- 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(
+ 'l10n.js',
+)
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -7,13 +7,13 @@
// This file exposes the Redux reducers of the box model, grid and grid highlighter
// settings.
exports.animations = require("devtools/client/inspector/animation/reducers/animations");
exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
exports.changes = require("devtools/client/inspector/changes/reducers/changes");
exports.events = require("devtools/client/inspector/events/reducers/events");
exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
-exports.flexboxes = require("devtools/client/inspector/flexbox/reducers/flexboxes");
+exports.flexbox = require("devtools/client/inspector/flexbox/reducers/flexbox");
exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
exports.fonts = require("devtools/client/inspector/fonts/reducers/fonts");
exports.grids = require("devtools/client/inspector/grids/reducers/grids");
exports.highlighterSettings = require("devtools/client/inspector/grids/reducers/highlighter-settings");
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -3,16 +3,24 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# LOCALIZATION NOTE This file contains the Layout Inspector strings.
# The Layout Inspector is a panel accessible in the Inspector sidebar.
# LOCALIZATION NOTE (flexbox.header): The accordion header for the Flexbox pane.
flexbox.header=Flexbox
+# LOCALIZATION NOTE (flexbox.noFlexboxeOnThisPage): In the case where there are no CSS
+# flex containers to display.
+flexbox.noFlexboxeOnThisPage=Select a Flex container or item to continue.
+
+# LOCALIZATION NOTE (flexbox.overlayFlexbox): Header for the list of flex container
+# elements if only one item can be selected.
+flexbox.overlayFlexbox=Overlay Flexbox
+
# LOCALIZATION NOTE (layout.cannotShowGridOutline, layout.cannotSHowGridOutline.title):
# In the case where the grid outline cannot be effectively displayed.
layout.cannotShowGridOutline=Cannot show outline for this grid
layout.cannotShowGridOutline.title=The selected grid’s outline cannot effectively fit inside the layout panel for it to be usable.
# LOCALIZATION NOTE (layout.displayAreaNames): Label of the display area names setting
# option in the CSS Grid pane.
layout.displayAreaNames=Display area names
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -8,76 +8,84 @@
overflow: auto;
min-width: 200px;
}
/**
* Common styles for shared components
*/
+.flexbox-container,
.grid-container {
display: flex;
flex-direction: column;
flex: 1 auto;
align-items: center;
min-width: 140px;
}
+.grid-container:first-child {
+ margin-bottom: 10px;
+}
+
+.flexbox-container > span,
.grid-container > span {
font-weight: 600;
margin-bottom: 5px;
pointer-events: none;
}
+.flexbox-container > ul,
.grid-container > ul {
list-style: none;
margin: 0;
padding: 0;
}
+.flexbox-container li,
.grid-container li {
display: flex;
align-items: center;
padding: 4px 0;
}
+.flexbox-container input
.grid-container input {
margin: 0 5px;
}
+.flexbox-container label,
.grid-container label {
display: flex;
align-items: center;
}
/**
* Grid Container
*/
+#layout-flexbox-container,
#layout-grid-container {
display: flex;
flex-direction: column;
margin: 5px;
}
/**
* Grid Content
*/
+.flexbox-content,
.grid-content {
display: flex;
flex-wrap: wrap;
flex: 1;
margin: 5px 0;
}
-.grid-container:first-child {
- margin-bottom: 10px;
-}
-
/**
* Grid Outline
*/
.grid-outline-container {
margin: 5px 0;
}
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -1,14 +1,15 @@
/* 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 { Cu } = require("chrome");
const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
const { flexboxSpec, gridSpec, layoutSpec } = require("devtools/shared/specs/layout");
const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
const { getStringifiableFragments } =
require("devtools/server/actors/utils/css-grid-utils");
loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
@@ -25,17 +26,17 @@ loader.lazyRequireGetter(this, "CssLogic
* The |Grid| actor provides the grid fragment information to inspect the grid container.
*/
const FlexboxActor = ActorClassWithSpec(flexboxSpec, {
/**
* @param {LayoutActor} layoutActor
* The LayoutActor instance.
* @param {DOMNode} containerEl
- * The flexbox container element.
+ * The flex container element.
*/
initialize(layoutActor, containerEl) {
Actor.prototype.initialize.call(this, layoutActor.conn);
this.containerEl = containerEl;
this.walker = layoutActor.walker;
},
@@ -131,87 +132,83 @@ const LayoutActor = ActorClassWithSpec(l
destroy() {
Actor.prototype.destroy.call(this);
this.tabActor = null;
this.walker = null;
},
/**
- * Returns an array of FlexboxActor objects for all the flexbox containers found by
- * iterating below the given rootNode.
+ * Returns the flex container found by iterating on the given selected node. The current
+ * node can be a flex container or flex item. If it is a flex item, returns the parent
+ * flex container. Otherwise, return null if the current or parent node is not a flex
+ * container.
*
- * @param {Node|NodeActor} rootNode
- * The root node to start iterating at.
- * @return {Array} An array of FlexboxActor objects.
+ * @param {Node|NodeActor} node
+ * The node to start iterating at.
+ * @return {FlexboxActor|Null} The FlexboxActor of the flex container of the give node.
+ * Otherwise, returns null.
*/
- getFlexbox(rootNode) {
- let flexboxes = [];
-
- if (!rootNode) {
- return flexboxes;
+ getCurrentFlexbox(node) {
+ if (isNodeDead(node)) {
+ return null;
}
- let treeWalker = this.walker.getDocumentWalker(rootNode,
+ // Given node can either be a Node or a NodeActor.
+ if (node.rawNode) {
+ node = node.rawNode;
+ }
+
+ let treeWalker = this.walker.getDocumentWalker(node,
nodeFilterConstants.SHOW_ELEMENT);
+ let currentNode = treeWalker.currentNode;
+ let displayType = this.walker.getNode(currentNode).displayType;
- while (treeWalker.nextNode()) {
- let currentNode = treeWalker.currentNode;
- let computedStyle = CssLogic.getComputedStyle(currentNode);
+ if (!displayType) {
+ return null;
+ }
- if (!computedStyle) {
- continue;
+ // Check if the current node is a flex container.
+ if (displayType == "inline-flex" || displayType == "flex") {
+ return new FlexboxActor(this, treeWalker.currentNode);
+ }
+
+ // Otherwise, check if this is a flex item or the parent node is a flex container.
+ while ((currentNode = treeWalker.parentNode())) {
+ if (!currentNode) {
+ break;
}
- if (computedStyle.display == "inline-flex" || computedStyle.display == "flex") {
- let flexboxActor = new FlexboxActor(this, currentNode);
- flexboxes.push(flexboxActor);
+ displayType = this.walker.getNode(currentNode).displayType;
+
+ switch (displayType) {
+ case "inline-flex":
+ case "flex":
+ return new FlexboxActor(this, currentNode);
+ case "contents":
+ // Continue walking up the tree since the parent node is a content element.
+ continue;
}
+
+ break;
}
- return flexboxes;
- },
-
- /**
- * Returns an array of FlexboxActor objects for all existing flexbox containers found by
- * iterating below the given rootNode and optionally including nested frames.
- *
- * @param {NodeActor} rootNode
- * @param {Boolean} traverseFrames
- * Whether or not we should iterate through nested frames.
- * @return {Array} An array of FlexboxActor objects.
- */
- getAllFlexbox(rootNode, traverseFrames) {
- let flexboxes = [];
-
- if (!rootNode) {
- return flexboxes;
- }
-
- if (!traverseFrames) {
- return this.getFlexbox(rootNode.rawNode);
- }
-
- for (let {document} of this.tabActor.windows) {
- flexboxes = [...flexboxes, ...this.getFlexbox(document.documentElement)];
- }
-
- return flexboxes;
+ return null;
},
/**
* Returns an array of GridActor objects for all the grid elements contained in the
* given root node.
*
* @param {Node|NodeActor} node
* The root node for grid elements
* @return {Array} An array of GridActor objects.
*/
getGrids(node) {
- if (!node) {
+ if (isNodeDead(node)) {
return [];
}
// Root node can either be a Node or a NodeActor.
if (node.rawNode) {
node = node.rawNode;
}
@@ -227,11 +224,15 @@ const LayoutActor = ActorClassWithSpec(l
for (let frame of frames) {
gridActors = gridActors.concat(this.getGrids(frame.contentDocument));
}
return gridActors;
},
});
+function isNodeDead(node) {
+ return !node || !node.rawNode || Cu.isDeadWrapper(node.rawNode);
+}
+
exports.FlexboxActor = FlexboxActor;
exports.GridActor = GridActor;
exports.LayoutActor = LayoutActor;
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -107,17 +107,17 @@ const Types = exports.__TypesForTests =
front: "devtools/shared/fronts/highlighters",
},
{
types: ["domnodelist", "domwalker", "inspector"],
spec: "devtools/shared/specs/inspector",
front: "devtools/shared/fronts/inspector",
},
{
- types: ["grid", "layout"],
+ types: ["flexbox", "grid", "layout"],
spec: "devtools/shared/specs/layout",
front: "devtools/shared/fronts/layout",
},
{
types: ["memory"],
spec: "devtools/shared/specs/memory",
front: "devtools/shared/fronts/memory",
},
--- a/devtools/shared/specs/layout.js
+++ b/devtools/shared/specs/layout.js
@@ -17,23 +17,22 @@ const gridSpec = generateActorSpec({
methods: {},
});
const layoutSpec = generateActorSpec({
typeName: "layout",
methods: {
- getAllFlexbox: {
+ getCurrentFlexbox: {
request: {
- rootNode: Arg(0, "domnode"),
- traverseFrames: Arg(1, "nullable:boolean")
+ node: Arg(0, "domnode"),
},
response: {
- flexboxes: RetVal("array:flexbox")
+ flexbox: RetVal("nullable:flexbox")
}
},
getGrids: {
request: {
rootNode: Arg(0, "domnode")
},
response: {