summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/flexbox
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/inspector/flexbox
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/inspector/flexbox')
-rw-r--r--devtools/client/inspector/flexbox/actions/flexbox-highlighter.js39
-rw-r--r--devtools/client/inspector/flexbox/actions/flexbox.js59
-rw-r--r--devtools/client/inspector/flexbox/actions/index.js24
-rw-r--r--devtools/client/inspector/flexbox/actions/moz.build11
-rw-r--r--devtools/client/inspector/flexbox/components/FlexContainer.js125
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItem.js60
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemList.js62
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemSelector.js81
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js173
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js326
-rw-r--r--devtools/client/inspector/flexbox/components/Flexbox.js128
-rw-r--r--devtools/client/inspector/flexbox/components/Header.js142
-rw-r--r--devtools/client/inspector/flexbox/components/moz.build16
-rw-r--r--devtools/client/inspector/flexbox/flexbox.js569
-rw-r--r--devtools/client/inspector/flexbox/moz.build18
-rw-r--r--devtools/client/inspector/flexbox/reducers/flexbox.js90
-rw-r--r--devtools/client/inspector/flexbox/reducers/index.js7
-rw-r--r--devtools/client/inspector/flexbox/reducers/moz.build10
-rw-r--r--devtools/client/inspector/flexbox/test/Ahem.ttfbin0 -> 12480 bytes
-rw-r--r--devtools/client/inspector/flexbox/test/browser.toml91
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_accordion_state.js120
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item.js43
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_accordion_state.js107
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_updates_on_change.js54
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js51
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_container_properties.js59
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_empty_state.js24
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_grand_parent_flex.js55
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_ESC.js71
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_RETURN.js92
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_opened_telemetry.js37
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_list_01.js49
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_list_02.js35
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_list_updates_on_change.js47
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_exists.js30
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_has_correct_layout.js67
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_hidden_when_useless.js46
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_renders_basisfinal_points_correctly.js40
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_column.js53
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_different_writing_modes.js42
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_non_flex_item_is_not_shown.js30
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_pseudo_elements_are_listed.js28
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_flexibility_not_displayed_when_useless.js48
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_do_not_show_unspecified_min_dimension.js45
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_exists.js36
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_different_writing_modes.js75
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_pseudos.js40
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_text_nodes.js40
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_has_correct_sections.js86
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_matches_properties_with_!important.js41
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_updates_on_change.js50
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_sizing_wanted_to_grow_but_was_clamped.js44
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_listed.js28
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_not_inlined.js52
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_01.js55
-rw-r--r--devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_02.js102
-rw-r--r--devtools/client/inspector/flexbox/test/doc_flexbox_CSS_property_with_!important.html22
-rw-r--r--devtools/client/inspector/flexbox/test/doc_flexbox_pseudos.html27
-rw-r--r--devtools/client/inspector/flexbox/test/doc_flexbox_specific_cases.html121
-rw-r--r--devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html20
-rw-r--r--devtools/client/inspector/flexbox/test/doc_flexbox_unauthored_min_dimension.html29
-rw-r--r--devtools/client/inspector/flexbox/test/doc_flexbox_writing_modes.html40
-rw-r--r--devtools/client/inspector/flexbox/test/head.js81
-rw-r--r--devtools/client/inspector/flexbox/types.js145
64 files changed, 4438 insertions, 0 deletions
diff --git a/devtools/client/inspector/flexbox/actions/flexbox-highlighter.js b/devtools/client/inspector/flexbox/actions/flexbox-highlighter.js
new file mode 100644
index 0000000000..487a4c3b12
--- /dev/null
+++ b/devtools/client/inspector/flexbox/actions/flexbox-highlighter.js
@@ -0,0 +1,39 @@
+/* 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";
+
+/**
+ * This module exports thunks.
+ * Thunks are functions that can be dispatched to the Inspector Redux store.
+ *
+ * These functions receive one object with options that contains:
+ * - dispatch() => function to dispatch Redux actions to the store
+ * - getState() => function to get the current state of the entire Inspector Redux store
+ * - inspector => object instance of Inspector panel
+ *
+ * They provide a shortcut for React components to invoke the flexbox highlighter
+ * without having to know where the highlighter exists.
+ */
+
+module.exports = {
+ /**
+ * Toggle the flexbox highlighter for the given node front.
+ *
+ * @param {NodeFront} nodeFront
+ * Node for which the highlighter should be toggled.
+ * @param {String} reason
+ * Reason why the highlighter was toggled; used in telemetry.
+ */
+ toggleFlexboxHighlighter(nodeFront, reason) {
+ return async thunkOptions => {
+ const { inspector } = thunkOptions;
+ if (!inspector || inspector._destroyed) {
+ return;
+ }
+
+ await inspector.highlighters.toggleFlexboxHighlighter(nodeFront, reason);
+ };
+ },
+};
diff --git a/devtools/client/inspector/flexbox/actions/flexbox.js b/devtools/client/inspector/flexbox/actions/flexbox.js
new file mode 100644
index 0000000000..64c1e1f074
--- /dev/null
+++ b/devtools/client/inspector/flexbox/actions/flexbox.js
@@ -0,0 +1,59 @@
+/* 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_COLOR,
+ UPDATE_FLEXBOX_HIGHLIGHTED,
+} = require("resource://devtools/client/inspector/flexbox/actions/index.js");
+
+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 color used for the flexbox's highlighter.
+ *
+ * @param {String} color
+ * The color to use for this nodeFront's flexbox highlighter.
+ */
+ updateFlexboxColor(color) {
+ return {
+ type: UPDATE_FLEXBOX_COLOR,
+ color,
+ };
+ },
+
+ /**
+ * 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,
+ };
+ },
+};
diff --git a/devtools/client/inspector/flexbox/actions/index.js b/devtools/client/inspector/flexbox/actions/index.js
new file mode 100644
index 0000000000..bc4e30044e
--- /dev/null
+++ b/devtools/client/inspector/flexbox/actions/index.js
@@ -0,0 +1,24 @@
+/* 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 { createEnum } = require("resource://devtools/client/shared/enum.js");
+
+createEnum(
+ [
+ // 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 color used for the overlay of a flexbox.
+ "UPDATE_FLEXBOX_COLOR",
+
+ // Updates the flexbox highlighted state.
+ "UPDATE_FLEXBOX_HIGHLIGHTED",
+ ],
+ module.exports
+);
diff --git a/devtools/client/inspector/flexbox/actions/moz.build b/devtools/client/inspector/flexbox/actions/moz.build
new file mode 100644
index 0000000000..3e5ab1eda8
--- /dev/null
+++ b/devtools/client/inspector/flexbox/actions/moz.build
@@ -0,0 +1,11 @@
+# -*- 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-highlighter.js",
+ "flexbox.js",
+ "index.js",
+)
diff --git a/devtools/client/inspector/flexbox/components/FlexContainer.js b/devtools/client/inspector/flexbox/components/FlexContainer.js
new file mode 100644
index 0000000000..c8c264defa
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexContainer.js
@@ -0,0 +1,125 @@
+/* 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 {
+ createElement,
+ createRef,
+ Fragment,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getFormatStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+loader.lazyRequireGetter(
+ this,
+ "getNodeRep",
+ "resource://devtools/client/inspector/shared/node-reps.js"
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const {
+ highlightNode,
+ unhighlightNode,
+} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js");
+
+class FlexContainer extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ color: PropTypes.string.isRequired,
+ flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
+ getSwatchColorPickerTooltip: PropTypes.func.isRequired,
+ onSetFlexboxOverlayColor: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.swatchEl = createRef();
+
+ this.setFlexboxColor = this.setFlexboxColor.bind(this);
+ }
+
+ componentDidMount() {
+ const tooltip = this.props.getSwatchColorPickerTooltip();
+
+ let previousColor;
+ tooltip.addSwatch(this.swatchEl.current, {
+ onCommit: this.setFlexboxColor,
+ onPreview: this.setFlexboxColor,
+ onRevert: () => {
+ this.props.onSetFlexboxOverlayColor(previousColor);
+ },
+ onShow: () => {
+ previousColor = this.props.color;
+ },
+ });
+ }
+
+ componentWillUnmount() {
+ const tooltip = this.props.getSwatchColorPickerTooltip();
+ tooltip.removeSwatch(this.swatchEl.current);
+ }
+
+ setFlexboxColor() {
+ const color = this.swatchEl.current.dataset.color;
+ this.props.onSetFlexboxOverlayColor(color);
+ }
+
+ render() {
+ const { color, flexContainer, dispatch } = this.props;
+ const { nodeFront, properties } = flexContainer;
+
+ return createElement(
+ Fragment,
+ null,
+ dom.div(
+ {
+ className: "flex-header-container-label",
+ },
+ getNodeRep(nodeFront, {
+ onDOMNodeMouseOut: () => dispatch(unhighlightNode()),
+ onDOMNodeMouseOver: () => dispatch(highlightNode(nodeFront)),
+ }),
+ dom.button({
+ className: "layout-color-swatch",
+ "data-color": color,
+ ref: this.swatchEl,
+ style: {
+ backgroundColor: color,
+ },
+ title: getFormatStr("layout.colorSwatch.tooltip", color),
+ })
+ ),
+ dom.div(
+ { className: "flex-header-container-properties" },
+ dom.div(
+ {
+ className: "inspector-badge",
+ role: "figure",
+ title: `flex-direction: ${properties["flex-direction"]}`,
+ },
+ properties["flex-direction"]
+ ),
+ dom.div(
+ {
+ className: "inspector-badge",
+ role: "figure",
+ title: `flex-wrap: ${properties["flex-wrap"]}`,
+ },
+ properties["flex-wrap"]
+ )
+ )
+ );
+ }
+}
+
+module.exports = FlexContainer;
diff --git a/devtools/client/inspector/flexbox/components/FlexItem.js b/devtools/client/inspector/flexbox/components/FlexItem.js
new file mode 100644
index 0000000000..b29da8243e
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItem.js
@@ -0,0 +1,60 @@
+/* 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("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+loader.lazyRequireGetter(
+ this,
+ "getNodeRep",
+ "resource://devtools/client/inspector/shared/node-reps.js"
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const {
+ highlightNode,
+ unhighlightNode,
+} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js");
+
+class FlexItem extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ index: PropTypes.number.isRequired,
+ scrollToTop: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ render() {
+ const { dispatch, flexItem, index, scrollToTop, setSelectedNode } =
+ this.props;
+ const { nodeFront } = flexItem;
+
+ return dom.button(
+ {
+ className: "devtools-button devtools-monospace",
+ onClick: e => {
+ e.stopPropagation();
+ scrollToTop();
+ setSelectedNode(nodeFront);
+ dispatch(unhighlightNode());
+ },
+ onMouseOut: () => dispatch(unhighlightNode()),
+ onMouseOver: () => dispatch(highlightNode(nodeFront)),
+ },
+ dom.span({ className: "flex-item-index" }, index),
+ getNodeRep(nodeFront)
+ );
+ }
+}
+
+module.exports = FlexItem;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemList.js b/devtools/client/inspector/flexbox/components/FlexItemList.js
new file mode 100644
index 0000000000..0824ee1175
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemList.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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+const FlexItem = createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItem.js")
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+class FlexItemList extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired,
+ scrollToTop: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ render() {
+ const { dispatch, flexItems, scrollToTop, setSelectedNode } = this.props;
+
+ return dom.div(
+ { className: "flex-item-list" },
+ dom.div(
+ {
+ className: "flex-item-list-header",
+ role: "heading",
+ "aria-level": "3",
+ },
+ !flexItems.length
+ ? getStr("flexbox.noFlexItems")
+ : getStr("flexbox.flexItems")
+ ),
+ flexItems.map((flexItem, index) =>
+ FlexItem({
+ key: flexItem.actorID,
+ dispatch,
+ flexItem,
+ index: index + 1,
+ scrollToTop,
+ setSelectedNode,
+ })
+ )
+ );
+ }
+}
+
+module.exports = FlexItemList;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemSelector.js b/devtools/client/inspector/flexbox/components/FlexItemSelector.js
new file mode 100644
index 0000000000..d8dd2db7a3
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSelector.js
@@ -0,0 +1,81 @@
+/* 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("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getSelectorFromGrip,
+ translateNodeFrontToGrip,
+} = require("resource://devtools/client/inspector/shared/utils.js");
+
+loader.lazyRequireGetter(
+ this,
+ "getNodeRep",
+ "resource://devtools/client/inspector/shared/node-reps.js"
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+loader.lazyRequireGetter(
+ this,
+ "showMenu",
+ "resource://devtools/client/shared/components/menu/utils.js",
+ true
+);
+
+class FlexItemSelector extends PureComponent {
+ static get propTypes() {
+ return {
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+ this.onShowFlexItemMenu = this.onShowFlexItemMenu.bind(this);
+ }
+
+ onShowFlexItemMenu(event) {
+ event.stopPropagation();
+
+ const { flexItem, flexItems, setSelectedNode } = this.props;
+ const menuItems = [];
+
+ for (const item of flexItems) {
+ const grip = translateNodeFrontToGrip(item.nodeFront);
+ menuItems.push({
+ label: getSelectorFromGrip(grip),
+ type: "checkbox",
+ checked: item === flexItem,
+ click: () => setSelectedNode(item.nodeFront),
+ });
+ }
+
+ showMenu(menuItems, {
+ button: event.target,
+ });
+ }
+
+ render() {
+ const { flexItem } = this.props;
+
+ return dom.button(
+ {
+ id: "flex-item-selector",
+ className: "devtools-button devtools-dropdown-button",
+ onClick: this.onShowFlexItemMenu,
+ },
+ getNodeRep(flexItem.nodeFront)
+ );
+ }
+}
+
+module.exports = FlexItemSelector;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
new file mode 100644
index 0000000000..f0c35cd6be
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
@@ -0,0 +1,173 @@
+/* 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("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+class FlexItemSizingOutline extends PureComponent {
+ static get propTypes() {
+ return {
+ flexDirection: PropTypes.string.isRequired,
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ };
+ }
+
+ renderBasisOutline(mainBaseSize) {
+ return dom.div({
+ className: "flex-outline-basis" + (!mainBaseSize ? " zero-basis" : ""),
+ });
+ }
+
+ renderDeltaOutline(mainDeltaSize) {
+ if (!mainDeltaSize) {
+ return null;
+ }
+
+ return dom.div({
+ className: "flex-outline-delta",
+ });
+ }
+
+ renderFinalOutline(isClamped) {
+ return dom.div({
+ className: "flex-outline-final" + (isClamped ? " clamped" : ""),
+ });
+ }
+
+ renderPoint(className, label = className) {
+ return dom.div({
+ key: className,
+ className: `flex-outline-point ${className}`,
+ "data-label": label,
+ });
+ }
+
+ render() {
+ const { flexItemSizing } = this.props.flexItem;
+ const {
+ mainAxisDirection,
+ mainBaseSize,
+ mainDeltaSize,
+ mainMaxSize,
+ mainMinSize,
+ clampState,
+ } = flexItemSizing;
+
+ // Calculate the final size. This is base + delta, then clamped by min or max.
+ let mainFinalSize = mainBaseSize + mainDeltaSize;
+ mainFinalSize = Math.max(mainFinalSize, mainMinSize);
+ mainFinalSize =
+ mainMaxSize === null
+ ? mainFinalSize
+ : Math.min(mainFinalSize, mainMaxSize);
+
+ // Just don't display anything if there isn't anything useful.
+ if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) {
+ return null;
+ }
+
+ // The max size is only interesting to show if it did clamp the item.
+ const showMax = clampState === "clamped_to_max";
+
+ // The min size is only really interesting if it actually clamped the item.
+ // TODO: We might also want to check if the min-size property is defined.
+ const showMin = clampState === "clamped_to_min";
+
+ // Create an array of all of the sizes we want to display that we can use to create
+ // a grid track template.
+ let sizes = [
+ { name: "basis-start", size: 0 },
+ { name: "basis-end", size: mainBaseSize },
+ { name: "final-start", size: 0 },
+ { name: "final-end", size: mainFinalSize },
+ ];
+
+ // Because mainDeltaSize is just a delta from base, make sure to make it absolute like
+ // the other sizes in the array, so we can later sort all sizes in the same way.
+ if (mainDeltaSize > 0) {
+ sizes.push({ name: "delta-start", size: mainBaseSize });
+ sizes.push({ name: "delta-end", size: mainBaseSize + mainDeltaSize });
+ } else {
+ sizes.push({ name: "delta-start", size: mainBaseSize + mainDeltaSize });
+ sizes.push({ name: "delta-end", size: mainBaseSize });
+ }
+
+ if (showMax) {
+ sizes.push({ name: "max", size: mainMaxSize });
+ }
+ if (showMin) {
+ sizes.push({ name: "min", size: mainMinSize });
+ }
+
+ // Sort all of the dimensions so we can create the grid track template correctly.
+ sizes = sizes.sort((a, b) => a.size - b.size);
+
+ // In some cases, the delta-start may be negative (when an item wanted to shrink more
+ // than the item's base size). As a negative value would break the grid track template
+ // offset all values so they're all positive.
+ const offsetBy = sizes.reduce(
+ (acc, curr) => (curr.size < acc ? curr.size : acc),
+ 0
+ );
+ sizes = sizes.map(entry => ({
+ size: entry.size - offsetBy,
+ name: entry.name,
+ }));
+
+ let gridTemplateColumns = "[";
+ let accumulatedSize = 0;
+ for (const { name, size } of sizes) {
+ const breadth = Math.round(size - accumulatedSize);
+ if (breadth === 0) {
+ gridTemplateColumns += ` ${name}`;
+ continue;
+ }
+
+ gridTemplateColumns += `] ${breadth}fr [${name}`;
+ accumulatedSize = size;
+ }
+ gridTemplateColumns += "]";
+
+ // Check the final and basis points to see if they are the same and if so, combine
+ // them into a single rendered point.
+ const renderedBaseAndFinalPoints = [];
+ if (mainFinalSize === mainBaseSize) {
+ renderedBaseAndFinalPoints.push(
+ this.renderPoint("basisfinal", "basis/final")
+ );
+ } else {
+ renderedBaseAndFinalPoints.push(this.renderPoint("basis"));
+ renderedBaseAndFinalPoints.push(this.renderPoint("final"));
+ }
+
+ return dom.div(
+ { className: "flex-outline-container" },
+ dom.div(
+ {
+ className:
+ `flex-outline ${mainAxisDirection}` +
+ (mainDeltaSize > 0 ? " growing" : " shrinking"),
+ style: {
+ gridTemplateColumns,
+ },
+ },
+ renderedBaseAndFinalPoints,
+ showMin ? this.renderPoint("min") : null,
+ showMax ? this.renderPoint("max") : null,
+ this.renderBasisOutline(mainBaseSize),
+ this.renderDeltaOutline(mainDeltaSize),
+ this.renderFinalOutline(clampState !== "unclamped")
+ )
+ );
+ }
+}
+
+module.exports = FlexItemSizingOutline;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
new file mode 100644
index 0000000000..00bea31e57
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
@@ -0,0 +1,326 @@
+/* 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("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const getFlexibilityReasons = ({
+ lineGrowthState,
+ computedFlexGrow,
+ computedFlexShrink,
+ grew,
+ shrank,
+}) => {
+ const reasons = [];
+
+ // Tell users whether the item was set to grow or shrink.
+ if (computedFlexGrow && lineGrowthState === "growing") {
+ reasons.push(getStr("flexbox.itemSizing.setToGrow"));
+ }
+ if (computedFlexShrink && lineGrowthState === "shrinking") {
+ reasons.push(getStr("flexbox.itemSizing.setToShrink"));
+ }
+ if (!computedFlexGrow && !grew && !shrank && lineGrowthState === "growing") {
+ reasons.push(getStr("flexbox.itemSizing.notSetToGrow"));
+ }
+ if (
+ !computedFlexShrink &&
+ !grew &&
+ !shrank &&
+ lineGrowthState === "shrinking"
+ ) {
+ reasons.push(getStr("flexbox.itemSizing.notSetToShrink"));
+ }
+
+ return reasons;
+};
+
+class FlexItemSizingProperties extends PureComponent {
+ static get propTypes() {
+ return {
+ flexDirection: PropTypes.string.isRequired,
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ };
+ }
+
+ /**
+ * Rounds some size in pixels and render it.
+ * The rendered value will end with 'px' (unless the dimension is 0 in which case the
+ * unit will be omitted)
+ *
+ * @param {Number} value
+ * The number to be rounded
+ * @param {Boolean} prependPlusSign
+ * If set to true, the + sign will be printed before a positive value
+ * @return {Object}
+ * The React component representing this rounded size
+ */
+ renderSize(value, prependPlusSign) {
+ if (value == 0) {
+ return dom.span({ className: "value" }, "0");
+ }
+
+ value = Math.round(value * 100) / 100;
+ if (prependPlusSign && value > 0) {
+ value = "+" + value;
+ }
+
+ return dom.span(
+ { className: "value" },
+ value,
+ dom.span({ className: "unit" }, "px")
+ );
+ }
+
+ /**
+ * Render an authored CSS property.
+ *
+ * @param {String} name
+ * The name for this CSS property
+ * @param {String} value
+ * The property value
+ * @param {Booleam} isDefaultValue
+ * Whether the value come from the browser default style
+ * @return {Object}
+ * The React component representing this CSS property
+ */
+ renderCssProperty(name, value, isDefaultValue) {
+ return dom.span({ className: "css-property-link" }, `(${name}: ${value})`);
+ }
+
+ /**
+ * Render a list of sentences to be displayed in the UI as reasons why a certain sizing
+ * value happened.
+ *
+ * @param {Array} sentences
+ * The list of sentences as Strings
+ * @return {Object}
+ * The React component representing these sentences
+ */
+ renderReasons(sentences) {
+ return dom.ul(
+ { className: "reasons" },
+ sentences.map(sentence => dom.li({}, sentence))
+ );
+ }
+
+ renderBaseSizeSection({ mainBaseSize, clampState }, properties, dimension) {
+ const flexBasisValue = properties["flex-basis"];
+ const dimensionValue = properties[dimension];
+
+ let title = getStr("flexbox.itemSizing.baseSizeSectionHeader");
+ let property = null;
+
+ if (flexBasisValue) {
+ // If flex-basis is defined, then that's what is used for the base size.
+ property = this.renderCssProperty("flex-basis", flexBasisValue);
+ } else if (dimensionValue) {
+ // If not and width/height is defined, then that's what defines the base size.
+ property = this.renderCssProperty(dimension, dimensionValue);
+ } else {
+ // Finally, if nothing is set, then the base size is the max-content size.
+ // In this case replace the section's title.
+ title = getStr("flexbox.itemSizing.itemContentSize");
+ }
+
+ const className = "section base";
+ return dom.li(
+ { className: className + (property ? "" : " no-property") },
+ dom.span({ className: "name" }, title, property),
+ this.renderSize(mainBaseSize)
+ );
+ }
+
+ renderFlexibilitySection(
+ flexItemSizing,
+ mainFinalSize,
+ properties,
+ computedStyle
+ ) {
+ const { mainDeltaSize, mainBaseSize, lineGrowthState } = flexItemSizing;
+
+ // Don't display anything if all interesting sizes are 0.
+ if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) {
+ return null;
+ }
+
+ // Also don't display anything if the item did not grow or shrink.
+ const grew = mainDeltaSize > 0;
+ const shrank = mainDeltaSize < 0;
+ if (!grew && !shrank) {
+ return null;
+ }
+
+ const definedFlexGrow = properties["flex-grow"];
+ const computedFlexGrow = computedStyle.flexGrow;
+ const definedFlexShrink = properties["flex-shrink"];
+ const computedFlexShrink = computedStyle.flexShrink;
+
+ const reasons = getFlexibilityReasons({
+ lineGrowthState,
+ computedFlexGrow,
+ computedFlexShrink,
+ grew,
+ shrank,
+ });
+
+ let property = null;
+
+ if (grew && definedFlexGrow && computedFlexGrow) {
+ // If the item grew it's normally because it was set to grow (flex-grow is non 0).
+ property = this.renderCssProperty("flex-grow", definedFlexGrow);
+ } else if (shrank && definedFlexShrink && computedFlexShrink) {
+ // If the item shrank it's either because flex-shrink is non 0.
+ property = this.renderCssProperty("flex-shrink", definedFlexShrink);
+ } else if (shrank && computedFlexShrink) {
+ // Or also because it's default value is 1 anyway.
+ property = this.renderCssProperty(
+ "flex-shrink",
+ computedFlexShrink,
+ true
+ );
+ }
+
+ // Don't display the section at all if there's nothing useful to show users.
+ if (!property && !reasons.length) {
+ return null;
+ }
+
+ const className = "section flexibility";
+ return dom.li(
+ { className: className + (property ? "" : " no-property") },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.flexibilitySectionHeader"),
+ property
+ ),
+ this.renderSize(mainDeltaSize, true),
+ this.renderReasons(reasons)
+ );
+ }
+
+ renderMinimumSizeSection(flexItemSizing, properties, dimension) {
+ const { clampState, mainMinSize, mainDeltaSize } = flexItemSizing;
+ const grew = mainDeltaSize > 0;
+ const shrank = mainDeltaSize < 0;
+ const minDimensionValue = properties[`min-${dimension}`];
+
+ // We only display the minimum size when the item actually violates that size during
+ // layout & is clamped.
+ if (clampState !== "clamped_to_min") {
+ return null;
+ }
+
+ const reasons = [];
+ if (grew || shrank) {
+ // The item may have wanted to grow less, but was min-clamped to a larger size.
+ // Or the item may have wanted to shrink more but was min-clamped to a larger size.
+ reasons.push(getStr("flexbox.itemSizing.clampedToMin"));
+ }
+
+ return dom.li(
+ { className: "section min" },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.minSizeSectionHeader"),
+ minDimensionValue.length
+ ? this.renderCssProperty(`min-${dimension}`, minDimensionValue)
+ : null
+ ),
+ this.renderSize(mainMinSize),
+ this.renderReasons(reasons)
+ );
+ }
+
+ renderMaximumSizeSection(flexItemSizing, properties, dimension) {
+ const { clampState, mainMaxSize, mainDeltaSize } = flexItemSizing;
+ const grew = mainDeltaSize > 0;
+ const shrank = mainDeltaSize < 0;
+ const maxDimensionValue = properties[`max-${dimension}`];
+
+ if (clampState !== "clamped_to_max") {
+ return null;
+ }
+
+ const reasons = [];
+ if (grew || shrank) {
+ // The item may have wanted to grow more than it did, because it was max-clamped.
+ // Or the item may have wanted shrink more, but it was clamped to its max size.
+ reasons.push(getStr("flexbox.itemSizing.clampedToMax"));
+ }
+
+ return dom.li(
+ { className: "section max" },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.maxSizeSectionHeader"),
+ maxDimensionValue.length
+ ? this.renderCssProperty(`max-${dimension}`, maxDimensionValue)
+ : null
+ ),
+ this.renderSize(mainMaxSize),
+ this.renderReasons(reasons)
+ );
+ }
+
+ renderFinalSizeSection(mainFinalSize) {
+ return dom.li(
+ { className: "section final no-property" },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.finalSizeSectionHeader")
+ ),
+ this.renderSize(mainFinalSize)
+ );
+ }
+
+ render() {
+ const { flexItem } = this.props;
+ const { computedStyle, flexItemSizing, properties } = flexItem;
+ const {
+ mainAxisDirection,
+ mainBaseSize,
+ mainDeltaSize,
+ mainMaxSize,
+ mainMinSize,
+ } = flexItemSizing;
+ const dimension = mainAxisDirection.startsWith("horizontal")
+ ? "width"
+ : "height";
+
+ // Calculate the final size. This is base + delta, then clamped by min or max.
+ let mainFinalSize = mainBaseSize + mainDeltaSize;
+ mainFinalSize = Math.max(mainFinalSize, mainMinSize);
+ mainFinalSize =
+ mainMaxSize === null
+ ? mainFinalSize
+ : Math.min(mainFinalSize, mainMaxSize);
+
+ return dom.ul(
+ { className: "flex-item-sizing" },
+ this.renderBaseSizeSection(flexItemSizing, properties, dimension),
+ this.renderFlexibilitySection(
+ flexItemSizing,
+ mainFinalSize,
+ properties,
+ computedStyle
+ ),
+ this.renderMinimumSizeSection(flexItemSizing, properties, dimension),
+ this.renderMaximumSizeSection(flexItemSizing, properties, dimension),
+ this.renderFinalSizeSection(mainFinalSize)
+ );
+ }
+}
+
+module.exports = FlexItemSizingProperties;
diff --git a/devtools/client/inspector/flexbox/components/Flexbox.js b/devtools/client/inspector/flexbox/components/Flexbox.js
new file mode 100644
index 0000000000..a0d79d92eb
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/Flexbox.js
@@ -0,0 +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/. */
+
+"use strict";
+
+const {
+ createElement,
+ createFactory,
+ Fragment,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+loader.lazyGetter(this, "FlexItemList", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemList.js")
+ );
+});
+loader.lazyGetter(this, "FlexItemSizingOutline", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js")
+ );
+});
+loader.lazyGetter(this, "FlexItemSizingProperties", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js")
+ );
+});
+loader.lazyGetter(this, "Header", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/Header.js")
+ );
+});
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+class Flexbox extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ flexbox: PropTypes.shape(Types.flexbox).isRequired,
+ flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
+ getSwatchColorPickerTooltip: PropTypes.func.isRequired,
+ onSetFlexboxOverlayColor: PropTypes.func.isRequired,
+ scrollToTop: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ renderFlexItemList() {
+ const { dispatch, scrollToTop, setSelectedNode } = this.props;
+ const { flexItems } = this.props.flexContainer;
+
+ return FlexItemList({
+ dispatch,
+ flexItems,
+ scrollToTop,
+ setSelectedNode,
+ });
+ }
+
+ renderFlexItemSizing() {
+ const { flexItems, flexItemShown, properties } = this.props.flexContainer;
+
+ const flexItem = flexItems.find(
+ item => item.nodeFront.actorID === flexItemShown
+ );
+ if (!flexItem) {
+ return null;
+ }
+
+ return createElement(
+ Fragment,
+ null,
+ FlexItemSizingOutline({
+ flexDirection: properties["flex-direction"],
+ flexItem,
+ }),
+ FlexItemSizingProperties({
+ flexDirection: properties["flex-direction"],
+ flexItem,
+ })
+ );
+ }
+
+ render() {
+ const {
+ dispatch,
+ flexbox,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ onSetFlexboxOverlayColor,
+ setSelectedNode,
+ } = this.props;
+
+ if (!flexContainer.actorID) {
+ return dom.div(
+ { className: "devtools-sidepanel-no-result" },
+ getStr("flexbox.noFlexboxeOnThisPage")
+ );
+ }
+
+ const { flexItemShown } = flexContainer;
+ const { color, highlighted } = flexbox;
+
+ return dom.div(
+ { className: "layout-flexbox-wrapper" },
+ Header({
+ color,
+ dispatch,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ highlighted,
+ onSetFlexboxOverlayColor,
+ setSelectedNode,
+ }),
+ !flexItemShown ? this.renderFlexItemList() : null,
+ flexItemShown ? this.renderFlexItemSizing() : null
+ );
+ }
+}
+
+module.exports = Flexbox;
diff --git a/devtools/client/inspector/flexbox/components/Header.js b/devtools/client/inspector/flexbox/components/Header.js
new file mode 100644
index 0000000000..44b19703e3
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/Header.js
@@ -0,0 +1,142 @@
+/* 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 {
+ createElement,
+ createFactory,
+ Fragment,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+const FlexContainer = createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexContainer.js")
+);
+const FlexItemSelector = createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemSelector.js")
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const {
+ toggleFlexboxHighlighter,
+} = require("resource://devtools/client/inspector/flexbox/actions/flexbox-highlighter.js");
+
+class Header extends PureComponent {
+ static get propTypes() {
+ return {
+ color: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
+ getSwatchColorPickerTooltip: PropTypes.func.isRequired,
+ highlighted: PropTypes.bool.isRequired,
+ onSetFlexboxOverlayColor: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ renderFlexboxHighlighterToggle() {
+ const { dispatch, flexContainer, highlighted } = this.props;
+ // Don't show the flexbox highlighter toggle for the parent flex container of the
+ // selected element.
+ if (flexContainer.isFlexItemContainer) {
+ return null;
+ }
+
+ return createElement(
+ Fragment,
+ null,
+ dom.div({ className: "devtools-separator" }),
+ dom.input({
+ id: "flexbox-checkbox-toggle",
+ className: "devtools-checkbox-toggle",
+ checked: highlighted,
+ onChange: () =>
+ dispatch(toggleFlexboxHighlighter(flexContainer.nodeFront, "layout")),
+ title: getStr("flexbox.togglesFlexboxHighlighter2"),
+ type: "checkbox",
+ })
+ );
+ }
+
+ renderFlexContainer() {
+ if (this.props.flexContainer.flexItemShown) {
+ return null;
+ }
+
+ const {
+ color,
+ dispatch,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ onSetFlexboxOverlayColor,
+ } = this.props;
+
+ return FlexContainer({
+ color,
+ dispatch,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ onSetFlexboxOverlayColor,
+ });
+ }
+
+ renderFlexItemSelector() {
+ if (!this.props.flexContainer.flexItemShown) {
+ return null;
+ }
+
+ const { flexContainer, setSelectedNode } = this.props;
+ const { flexItems, flexItemShown } = flexContainer;
+ const flexItem = flexItems.find(
+ item => item.nodeFront.actorID === flexItemShown
+ );
+
+ if (!flexItem) {
+ return null;
+ }
+
+ return FlexItemSelector({
+ flexItem,
+ flexItems,
+ setSelectedNode,
+ });
+ }
+
+ render() {
+ const { flexContainer, setSelectedNode } = this.props;
+ const { flexItemShown, nodeFront } = flexContainer;
+
+ return dom.div(
+ { className: "flex-header devtools-monospace" },
+ flexItemShown
+ ? dom.button({
+ className: "flex-header-button-prev devtools-button",
+ "aria-label": getStr("flexbox.backButtonLabel"),
+ onClick: e => {
+ e.stopPropagation();
+ setSelectedNode(nodeFront);
+ },
+ })
+ : null,
+ dom.div(
+ {
+ className:
+ "flex-header-content" + (flexItemShown ? " flex-item-shown" : ""),
+ },
+ this.renderFlexContainer(),
+ this.renderFlexItemSelector()
+ ),
+ this.renderFlexboxHighlighterToggle()
+ );
+ }
+}
+
+module.exports = Header;
diff --git a/devtools/client/inspector/flexbox/components/moz.build b/devtools/client/inspector/flexbox/components/moz.build
new file mode 100644
index 0000000000..3e077a217f
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/moz.build
@@ -0,0 +1,16 @@
+# -*- 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",
+ "FlexContainer.js",
+ "FlexItem.js",
+ "FlexItemList.js",
+ "FlexItemSelector.js",
+ "FlexItemSizingOutline.js",
+ "FlexItemSizingProperties.js",
+ "Header.js",
+)
diff --git a/devtools/client/inspector/flexbox/flexbox.js b/devtools/client/inspector/flexbox/flexbox.js
new file mode 100644
index 0000000000..3947da3cf9
--- /dev/null
+++ b/devtools/client/inspector/flexbox/flexbox.js
@@ -0,0 +1,569 @@
+/* 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("resource://devtools/shared/throttle.js");
+
+const {
+ clearFlexbox,
+ updateFlexbox,
+ updateFlexboxColor,
+ updateFlexboxHighlighted,
+} = require("resource://devtools/client/inspector/flexbox/actions/flexbox.js");
+const flexboxReducer = require("resource://devtools/client/inspector/flexbox/reducers/flexbox.js");
+
+loader.lazyRequireGetter(
+ this,
+ "parseURL",
+ "resource://devtools/client/shared/source-utils.js",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "asyncStorage",
+ "resource://devtools/shared/async-storage.js"
+);
+
+const FLEXBOX_COLOR = "#9400FF";
+
+class FlexboxInspector {
+ constructor(inspector, window) {
+ this.document = window.document;
+ this.inspector = inspector;
+ this.selection = inspector.selection;
+ this.store = inspector.store;
+
+ this.store.injectReducer("flexbox", flexboxReducer);
+
+ this.onHighlighterShown = this.onHighlighterShown.bind(this);
+ this.onHighlighterHidden = this.onHighlighterHidden.bind(this);
+ this.onNavigate = this.onNavigate.bind(this);
+ this.onReflow = throttle(this.onReflow, 500, this);
+ this.onSetFlexboxOverlayColor = this.onSetFlexboxOverlayColor.bind(this);
+ this.onSidebarSelect = this.onSidebarSelect.bind(this);
+ this.onUpdatePanel = this.onUpdatePanel.bind(this);
+
+ this.init();
+ }
+
+ init() {
+ if (!this.inspector) {
+ return;
+ }
+
+ this.inspector.highlighters.on(
+ "highlighter-shown",
+ this.onHighlighterShown
+ );
+ this.inspector.highlighters.on(
+ "highlighter-hidden",
+ this.onHighlighterHidden
+ );
+ this.inspector.sidebar.on("select", this.onSidebarSelect);
+
+ this.onSidebarSelect();
+ }
+
+ destroy() {
+ this.selection.off("new-node-front", this.onUpdatePanel);
+ this.inspector.off("new-root", this.onNavigate);
+ this.inspector.off("reflow-in-selected-target", this.onReflow);
+ this.inspector.highlighters.off(
+ "highlighter-shown",
+ this.onHighlighterShown
+ );
+ this.inspector.highlighters.off(
+ "highlighter-hidden",
+ this.onHighlighterHidden
+ );
+ this.inspector.sidebar.off("select", this.onSidebarSelect);
+
+ this._customHostColors = null;
+ this._overlayColor = null;
+ this.document = null;
+ this.inspector = null;
+ this.selection = null;
+ this.store = null;
+ }
+
+ getComponentProps() {
+ return {
+ onSetFlexboxOverlayColor: this.onSetFlexboxOverlayColor,
+ };
+ }
+
+ /**
+ * Returns an object containing the custom flexbox colors for different hosts.
+ *
+ * @return {Object} that maps a host name to a custom flexbox color for a given host.
+ */
+ async getCustomHostColors() {
+ if (this._customHostColors) {
+ return this._customHostColors;
+ }
+
+ // Cache the custom host colors to avoid refetching from async storage.
+ this._customHostColors =
+ (await asyncStorage.getItem("flexboxInspectorHostColors")) || {};
+ return this._customHostColors;
+ }
+
+ /**
+ * Returns the flex container properties for a given node. If the given node is a flex
+ * item, it attempts to fetch the flex container of the parent node of the given node.
+ *
+ * @param {NodeFront} nodeFront
+ * The NodeFront to fetch the flex container properties.
+ * @param {Boolean} onlyLookAtParents
+ * Whether or not to only consider the parent node of the given node.
+ * @return {Object} consisting of the given node's flex container's properties.
+ */
+ async getFlexContainerProps(nodeFront, onlyLookAtParents = false) {
+ const layoutFront = await nodeFront.walkerFront.getLayoutInspector();
+ const flexboxFront = await layoutFront.getCurrentFlexbox(
+ nodeFront,
+ onlyLookAtParents
+ );
+
+ if (!flexboxFront) {
+ return null;
+ }
+
+ // 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.
+ let containerNodeFront = flexboxFront.containerNodeFront;
+ if (!containerNodeFront) {
+ containerNodeFront = await flexboxFront.walkerFront.getNodeFromActor(
+ flexboxFront.actorID,
+ ["containerEl"]
+ );
+ }
+
+ const flexItems = await this.getFlexItems(flexboxFront);
+
+ // If the current selected node is a flex item, display its flex item sizing
+ // properties.
+ let flexItemShown = null;
+ if (onlyLookAtParents) {
+ flexItemShown = this.selection.nodeFront.actorID;
+ } else {
+ const selectedFlexItem = flexItems.find(
+ item => item.nodeFront === this.selection.nodeFront
+ );
+ if (selectedFlexItem) {
+ flexItemShown = selectedFlexItem.nodeFront.actorID;
+ }
+ }
+
+ return {
+ actorID: flexboxFront.actorID,
+ flexItems,
+ flexItemShown,
+ isFlexItemContainer: onlyLookAtParents,
+ nodeFront: containerNodeFront,
+ properties: flexboxFront.properties,
+ };
+ }
+
+ /**
+ * Returns an array of flex items object for the given flex container front.
+ *
+ * @param {FlexboxFront} flexboxFront
+ * A flex container FlexboxFront.
+ * @return {Array} of objects containing the flex item front properties.
+ */
+ async getFlexItems(flexboxFront) {
+ const flexItemFronts = await flexboxFront.getFlexItems();
+ const flexItems = [];
+
+ for (const flexItemFront of flexItemFronts) {
+ // Fetch the NodeFront of the flex items.
+ let itemNodeFront = flexItemFront.nodeFront;
+ if (!itemNodeFront) {
+ itemNodeFront = await flexItemFront.walkerFront.getNodeFromActor(
+ flexItemFront.actorID,
+ ["element"]
+ );
+ }
+
+ flexItems.push({
+ actorID: flexItemFront.actorID,
+ computedStyle: flexItemFront.computedStyle,
+ flexItemSizing: flexItemFront.flexItemSizing,
+ nodeFront: itemNodeFront,
+ properties: flexItemFront.properties,
+ });
+ }
+
+ return flexItems;
+ }
+
+ /**
+ * Returns the custom overlay color for the current host or the default flexbox color.
+ *
+ * @return {String} overlay color.
+ */
+ async getOverlayColor() {
+ if (this._overlayColor) {
+ return this._overlayColor;
+ }
+
+ // Cache the overlay color for the current host to avoid repeatably parsing the host
+ // and fetching the custom color from async storage.
+ const customColors = await this.getCustomHostColors();
+ const currentUrl = this.inspector.currentTarget.url;
+ // Get the hostname, if there is no hostname, fall back on protocol
+ // ex: `data:` uri, and `about:` pages
+ const hostname =
+ parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
+ this._overlayColor = customColors[hostname]
+ ? customColors[hostname]
+ : FLEXBOX_COLOR;
+ return this._overlayColor;
+ }
+
+ /**
+ * 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 "highlighter-shown" events emitted by HighlightersOverlay.
+ * If the event is dispatched on behalf of a flex highlighter, toggle the
+ * corresponding flex container's highlighted state in the Redux store.
+ *
+ * @param {Object} data
+ * Object with data associated with the highlighter event.
+ * {NodeFront} data.nodeFront
+ * The NodeFront of the flex container element for which the flexbox
+ * highlighter is shown for.
+ * {String} data.type
+ * Highlighter type
+ */
+ onHighlighterShown(data) {
+ if (data.type === this.inspector.highlighters.TYPES.FLEXBOX) {
+ this.onHighlighterChange(true, data.nodeFront);
+ }
+ }
+
+ /**
+ * Handler for "highlighter-shown" events emitted by HighlightersOverlay.
+ * If the event is dispatched on behalf of a flex highlighter, toggle the
+ * corresponding flex container's highlighted state in the Redux store.
+ *
+ * @param {Object} data
+ * Object with data associated with the highlighter event.
+ * {NodeFront} data.nodeFront
+ * The NodeFront of the flex container element for which the flexbox
+ * highlighter was previously shown for.
+ * {String} data.type
+ * Highlighter type
+ */
+ onHighlighterHidden(data) {
+ if (data.type === this.inspector.highlighters.TYPES.FLEXBOX) {
+ this.onHighlighterChange(false, data.nodeFront);
+ }
+ }
+
+ /**
+ * Updates the flex container highlighted state in the Redux store if the provided
+ * NodeFront is the current selected flex container.
+ *
+ * @param {Boolean} highlighted
+ * Whether the change is to highlight or hide the overlay.
+ * @param {NodeFront} nodeFront
+ * The NodeFront of the flex container element for which the flexbox
+ * highlighter is shown for.
+ */
+ onHighlighterChange(highlighted, nodeFront) {
+ const { flexbox } = this.store.getState();
+
+ if (
+ flexbox.flexContainer.nodeFront === nodeFront &&
+ flexbox.highlighted !== highlighted
+ ) {
+ this.store.dispatch(updateFlexboxHighlighted(highlighted));
+ }
+ }
+
+ /**
+ * Handler for the "new-root" event fired by the inspector. Clears the cached overlay
+ * color for the flexbox highlighter and updates the panel.
+ */
+ onNavigate() {
+ this._overlayColor = null;
+ this.onUpdatePanel();
+ }
+
+ /**
+ * Handler for reflow events fired by the inspector when a node is selected. On reflows,
+ * update the flexbox panel because the shape of the flexbox on the page may have
+ * changed.
+ */
+ async onReflow() {
+ if (
+ !this.isPanelVisible() ||
+ !this.store ||
+ !this.selection.nodeFront ||
+ this._isUpdating
+ ) {
+ return;
+ }
+
+ try {
+ const flexContainer = await this.getFlexContainerProps(
+ this.selection.nodeFront
+ );
+
+ // Clear the flexbox panel if there is no flex container for the current node
+ // selection.
+ if (!flexContainer) {
+ this.store.dispatch(clearFlexbox());
+ return;
+ }
+
+ const { flexbox } = this.store.getState();
+
+ // Compare the new flexbox state of the current selected nodeFront with the old
+ // flexbox state to determine if we need to update.
+ if (hasFlexContainerChanged(flexbox.flexContainer, flexContainer)) {
+ this.update(flexContainer);
+ return;
+ }
+
+ let flexItemContainer = null;
+ // If the current selected node is also the flex container node, check if it is
+ // a flex item of a parent flex container.
+ if (flexContainer.nodeFront === this.selection.nodeFront) {
+ flexItemContainer = await this.getFlexContainerProps(
+ this.selection.nodeFront,
+ true
+ );
+ }
+
+ // Compare the new and old state of the parent flex container properties.
+ if (
+ hasFlexContainerChanged(flexbox.flexItemContainer, flexItemContainer)
+ ) {
+ this.update(flexContainer, flexItemContainer);
+ }
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ }
+ }
+
+ /**
+ * Handler for a change in the flexbox overlay color picker for a flex container.
+ *
+ * @param {String} color
+ * A hex string representing the color to use.
+ */
+ async onSetFlexboxOverlayColor(color) {
+ this.store.dispatch(updateFlexboxColor(color));
+
+ const { flexbox } = this.store.getState();
+
+ if (flexbox.highlighted) {
+ this.inspector.highlighters.showFlexboxHighlighter(
+ flexbox.flexContainer.nodeFront
+ );
+ }
+
+ this._overlayColor = color;
+
+ const currentUrl = this.inspector.currentTarget.url;
+ // Get the hostname, if there is no hostname, fall back on protocol
+ // ex: `data:` uri, and `about:` pages
+ const hostname =
+ parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
+ const customColors = await this.getCustomHostColors();
+ customColors[hostname] = color;
+ this._customHostColors = customColors;
+ await asyncStorage.setItem("flexboxInspectorHostColors", customColors);
+ }
+
+ /**
+ * Handler for the inspector sidebar "select" event. Updates the flexbox panel if it
+ * is visible.
+ */
+ onSidebarSelect() {
+ if (!this.isPanelVisible()) {
+ this.inspector.off("reflow-in-selected-target", this.onReflow);
+ this.inspector.off("new-root", this.onNavigate);
+ this.selection.off("new-node-front", this.onUpdatePanel);
+ return;
+ }
+
+ this.inspector.on("reflow-in-selected-target", this.onReflow);
+ this.inspector.on("new-root", this.onNavigate);
+ this.selection.on("new-node-front", this.onUpdatePanel);
+
+ this.update();
+ }
+
+ /**
+ * 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.
+ *
+ * @param {Object}
+ * This callback is sometimes executed on "new-node-front" events which means
+ * that a first param is passed here (the nodeFront), which we don't care about.
+ * @param {String} reason
+ * On "new-node-front" events, a reason is passed here, and we need it to detect
+ * if this update was caused by a node selection from the markup-view.
+ */
+ onUpdatePanel(_, reason) {
+ if (!this.isPanelVisible()) {
+ return;
+ }
+
+ this.update(null, null, reason === "treepanel");
+ }
+
+ /**
+ * 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 {Object|null} flexContainer
+ * An object consisting of the current flex container's flex items and
+ * properties.
+ * @param {Object|null} flexItemContainer
+ * An object consisting of the parent flex container's flex items and
+ * properties.
+ * @param {Boolean} initiatedByMarkupViewSelection
+ * True if the update was due to a node selection in the markup-view.
+ */
+ async update(
+ flexContainer,
+ flexItemContainer,
+ initiatedByMarkupViewSelection
+ ) {
+ this._isUpdating = true;
+
+ // Stop refreshing if the inspector or store is already destroyed or no node is
+ // selected.
+ if (!this.inspector || !this.store || !this.selection.nodeFront) {
+ this._isUpdating = false;
+ return;
+ }
+
+ try {
+ // Fetch the current flexbox if no flexbox front was passed into this update.
+ if (!flexContainer) {
+ flexContainer = await this.getFlexContainerProps(
+ this.selection.nodeFront
+ );
+ }
+
+ // Clear the flexbox panel if there is no flex container for the current node
+ // selection.
+ if (!flexContainer) {
+ this.store.dispatch(clearFlexbox());
+ this._isUpdating = false;
+ return;
+ }
+
+ if (
+ !flexItemContainer &&
+ flexContainer.nodeFront === this.selection.nodeFront
+ ) {
+ flexItemContainer = await this.getFlexContainerProps(
+ this.selection.nodeFront,
+ true
+ );
+ }
+
+ const highlighted =
+ flexContainer.nodeFront ===
+ this.inspector.highlighters.getNodeForActiveHighlighter(
+ this.inspector.highlighters.TYPES.FLEXBOX
+ );
+ const color = await this.getOverlayColor();
+
+ this.store.dispatch(
+ updateFlexbox({
+ color,
+ flexContainer,
+ flexItemContainer,
+ highlighted,
+ initiatedByMarkupViewSelection,
+ })
+ );
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ }
+
+ this._isUpdating = false;
+ }
+}
+
+/**
+ * For a given flex container object, returns the flex container properties that can be
+ * used to check if 2 flex container objects are the same.
+ *
+ * @param {Object|null} flexContainer
+ * Object consisting of the flex container's properties.
+ * @return {Object|null} consisting of the comparable flex container's properties.
+ */
+function getComparableFlexContainerProperties(flexContainer) {
+ if (!flexContainer) {
+ return null;
+ }
+
+ return {
+ flexItems: getComparableFlexItemsProperties(flexContainer.flexItems),
+ nodeFront: flexContainer.nodeFront.actorID,
+ properties: flexContainer.properties,
+ };
+}
+
+/**
+ * Given an array of flex item objects, returns the relevant flex item properties that can
+ * be compared to check if any changes has occurred.
+ *
+ * @param {Array} flexItems
+ * Array of objects containing the flex item properties.
+ * @return {Array} of objects consisting of the comparable flex item's properties.
+ */
+function getComparableFlexItemsProperties(flexItems) {
+ return flexItems.map(item => {
+ return {
+ computedStyle: item.computedStyle,
+ flexItemSizing: item.flexItemSizing,
+ nodeFront: item.nodeFront.actorID,
+ properties: item.properties,
+ };
+ });
+}
+
+/**
+ * Compares the old and new flex container properties
+ *
+ * @param {Object} oldFlexContainer
+ * Object consisting of the old flex container's properties.
+ * @param {Object} newFlexContainer
+ * Object consisting of the new flex container's properties.
+ * @return {Boolean} true if the flex container properties are the same, false otherwise.
+ */
+function hasFlexContainerChanged(oldFlexContainer, newFlexContainer) {
+ return (
+ JSON.stringify(getComparableFlexContainerProperties(oldFlexContainer)) !==
+ JSON.stringify(getComparableFlexContainerProperties(newFlexContainer))
+ );
+}
+
+module.exports = FlexboxInspector;
diff --git a/devtools/client/inspector/flexbox/moz.build b/devtools/client/inspector/flexbox/moz.build
new file mode 100644
index 0000000000..b38f12db55
--- /dev/null
+++ b/devtools/client/inspector/flexbox/moz.build
@@ -0,0 +1,18 @@
+# -*- 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 += [
+ "actions",
+ "components",
+ "reducers",
+]
+
+DevToolsModules(
+ "flexbox.js",
+ "types.js",
+)
+
+BROWSER_CHROME_MANIFESTS += ["test/browser.toml"]
diff --git a/devtools/client/inspector/flexbox/reducers/flexbox.js b/devtools/client/inspector/flexbox/reducers/flexbox.js
new file mode 100644
index 0000000000..856294a549
--- /dev/null
+++ b/devtools/client/inspector/flexbox/reducers/flexbox.js
@@ -0,0 +1,90 @@
+/* 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_COLOR,
+ UPDATE_FLEXBOX_HIGHLIGHTED,
+} = require("resource://devtools/client/inspector/flexbox/actions/index.js");
+
+const INITIAL_FLEXBOX = {
+ // The color of the flexbox highlighter overlay.
+ color: "",
+ // The flex container of the selected element.
+ flexContainer: {
+ // The actor ID of the selected flex container.
+ actorID: "",
+ // An array of flex items belonging to the selected flex container.
+ flexItems: [],
+ // The NodeFront actor ID of the flex item to display in the flex item sizing
+ // properties.
+ flexItemShown: null,
+ // This flag specifies that the flex container data represents the selected flex
+ // container.
+ isFlexItemContainer: false,
+ // The NodeFront of the selected flex container.
+ nodeFront: null,
+ // The computed style properties of the selected flex container.
+ properties: null,
+ },
+ // The selected flex container can also be a flex item. This object contains the
+ // parent flex container properties of the selected element.
+ flexItemContainer: {
+ // The actor ID of the parent flex container.
+ actorID: "",
+ // An array of flex items belonging to the parent flex container.
+ flexItems: [],
+ // The NodeFront actor ID of the flex item to display in the flex item sizing
+ // properties.
+ flexItemShown: null,
+ // This flag specifies that the flex container data represents the parent flex
+ // container of the selected element.
+ isFlexItemContainer: true,
+ // The NodeFront of the parent flex container.
+ nodeFront: null,
+ // The computed styles properties of the parent flex container.
+ properties: null,
+ },
+ // Whether or not the flexbox highlighter is highlighting the flex container.
+ highlighted: false,
+ // Whether or not the node selection that led to the flexbox tool being shown came from
+ // the user selecting a node in the markup-view (whereas, say, selecting in the flex
+ // items list)
+ initiatedByMarkupViewSelection: false,
+};
+
+const reducers = {
+ [CLEAR_FLEXBOX](flexbox, _) {
+ return INITIAL_FLEXBOX;
+ },
+
+ [UPDATE_FLEXBOX](_, { flexbox }) {
+ return flexbox;
+ },
+
+ [UPDATE_FLEXBOX_COLOR](flexbox, { color }) {
+ return {
+ ...flexbox,
+ color,
+ };
+ },
+
+ [UPDATE_FLEXBOX_HIGHLIGHTED](flexbox, { highlighted }) {
+ return {
+ ...flexbox,
+ highlighted,
+ };
+ },
+};
+
+module.exports = function (flexbox = INITIAL_FLEXBOX, action) {
+ const reducer = reducers[action.type];
+ if (!reducer) {
+ return flexbox;
+ }
+ return reducer(flexbox, action);
+};
diff --git a/devtools/client/inspector/flexbox/reducers/index.js b/devtools/client/inspector/flexbox/reducers/index.js
new file mode 100644
index 0000000000..7f415cb163
--- /dev/null
+++ b/devtools/client/inspector/flexbox/reducers/index.js
@@ -0,0 +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.flexbox = require("resource://devtools/client/inspector/flexbox/reducers/flexbox.js");
diff --git a/devtools/client/inspector/flexbox/reducers/moz.build b/devtools/client/inspector/flexbox/reducers/moz.build
new file mode 100644
index 0000000000..4327940888
--- /dev/null
+++ b/devtools/client/inspector/flexbox/reducers/moz.build
@@ -0,0 +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",
+)
diff --git a/devtools/client/inspector/flexbox/test/Ahem.ttf b/devtools/client/inspector/flexbox/test/Ahem.ttf
new file mode 100644
index 0000000000..ac81cb0316
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/Ahem.ttf
Binary files differ
diff --git a/devtools/client/inspector/flexbox/test/browser.toml b/devtools/client/inspector/flexbox/test/browser.toml
new file mode 100644
index 0000000000..764dd1ffba
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser.toml
@@ -0,0 +1,91 @@
+[DEFAULT]
+tags = "devtools"
+subsuite = "devtools"
+support-files = [
+ "Ahem.ttf",
+ "doc_flexbox_CSS_property_with_!important.html",
+ "doc_flexbox_pseudos.html",
+ "doc_flexbox_specific_cases.html",
+ "doc_flexbox_text_nodes.html",
+ "doc_flexbox_unauthored_min_dimension.html",
+ "doc_flexbox_writing_modes.html",
+ "head.js",
+ "!/devtools/client/inspector/test/head.js",
+ "!/devtools/client/inspector/test/shared-head.js",
+ "!/devtools/client/shared/test/shared-head.js",
+ "!/devtools/client/shared/test/telemetry-test-helpers.js",
+ "!/devtools/client/shared/test/highlighter-test-actor.js",
+]
+
+["browser_flexbox_accordion_state.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_flexbox_container_and_item.js"]
+
+["browser_flexbox_container_and_item_accordion_state.js"]
+
+["browser_flexbox_container_and_item_updates_on_change.js"]
+
+["browser_flexbox_container_element_rep.js"]
+
+["browser_flexbox_container_properties.js"]
+
+["browser_flexbox_empty_state.js"]
+
+["browser_flexbox_grand_parent_flex.js"]
+
+["browser_flexbox_highlighter_color_picker_on_ESC.js"]
+
+["browser_flexbox_highlighter_color_picker_on_RETURN.js"]
+
+["browser_flexbox_highlighter_opened_telemetry.js"]
+
+["browser_flexbox_item_list_01.js"]
+
+["browser_flexbox_item_list_02.js"]
+
+["browser_flexbox_item_list_updates_on_change.js"]
+
+["browser_flexbox_item_outline_exists.js"]
+
+["browser_flexbox_item_outline_has_correct_layout.js"]
+
+["browser_flexbox_item_outline_hidden_when_useless.js"]
+
+["browser_flexbox_item_outline_renders_basisfinal_points_correctly.js"]
+
+["browser_flexbox_item_outline_rotates_for_column.js"]
+
+["browser_flexbox_item_outline_rotates_for_different_writing_modes.js"]
+
+["browser_flexbox_non_flex_item_is_not_shown.js"]
+
+["browser_flexbox_pseudo_elements_are_listed.js"]
+
+["browser_flexbox_sizing_flexibility_not_displayed_when_useless.js"]
+
+["browser_flexbox_sizing_info_do_not_show_unspecified_min_dimension.js"]
+
+["browser_flexbox_sizing_info_exists.js"]
+
+["browser_flexbox_sizing_info_for_different_writing_modes.js"]
+
+["browser_flexbox_sizing_info_for_pseudos.js"]
+
+["browser_flexbox_sizing_info_for_text_nodes.js"]
+
+["browser_flexbox_sizing_info_has_correct_sections.js"]
+
+["browser_flexbox_sizing_info_matches_properties_with_!important.js"]
+
+["browser_flexbox_sizing_info_updates_on_change.js"]
+
+["browser_flexbox_sizing_wanted_to_grow_but_was_clamped.js"]
+
+["browser_flexbox_text_nodes_are_listed.js"]
+
+["browser_flexbox_text_nodes_are_not_inlined.js"]
+
+["browser_flexbox_toggle_flexbox_highlighter_01.js"]
+
+["browser_flexbox_toggle_flexbox_highlighter_02.js"]
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_accordion_state.js b/devtools/client/inspector/flexbox/test/browser_flexbox_accordion_state.js
new file mode 100644
index 0000000000..eb4acd8a1b
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_accordion_state.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flexbox accordions state is persistent through hide/show in the layout
+// view.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+const ACCORDION_HEADER_SELECTOR = ".accordion-header";
+const ACCORDION_CONTENT_SELECTOR = ".accordion-content";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+
+ await testAccordionState(
+ ":root",
+ FLEXBOX_OPENED_PREF,
+ "#layout-section-flex"
+ );
+ await testAccordionState(
+ "#container-only",
+ FLEX_CONTAINER_OPENED_PREF,
+ "#layout-section-flex-container"
+ );
+ await testAccordionState(
+ "#item-only",
+ FLEX_ITEM_OPENED_PREF,
+ "#layout-section-flex-item"
+ );
+});
+
+async function testAccordionState(target, pref, selector) {
+ const context = await openLayoutViewAndSelectNode(target);
+
+ await testAccordionStateAfterClickingHeader(pref, selector, context);
+ await testAccordionStateAfterSwitchingSidebars(pref, selector, context);
+ await testAccordionStateAfterReopeningLayoutView(pref, selector, context);
+
+ Services.prefs.clearUserPref(pref);
+}
+
+async function testAccordionStateAfterClickingHeader(pref, selector, { doc }) {
+ info("Checking initial state of the flexbox panel.");
+
+ const item = await waitFor(() => doc.querySelector(selector));
+ const header = item.querySelector(ACCORDION_HEADER_SELECTOR);
+ const content = item.querySelector(ACCORDION_CONTENT_SELECTOR);
+
+ ok(
+ !content.hidden && content.childElementCount > 0,
+ "The flexbox panel content is visible."
+ );
+ ok(Services.prefs.getBoolPref(pref), `${pref} is pref on by default.`);
+
+ info("Clicking the flexbox header to hide the flexbox panel.");
+ header.click();
+
+ info("Checking the new state of the flexbox panel.");
+ ok(content.hidden, "The flexbox panel content is hidden.");
+ ok(!Services.prefs.getBoolPref(pref), `${pref} is pref off.`);
+}
+
+async function testAccordionStateAfterSwitchingSidebars(
+ pref,
+ selector,
+ { doc, inspector }
+) {
+ info(
+ "Checking the flexbox accordion state is persistent after switching sidebars."
+ );
+
+ const item = await waitFor(() => doc.querySelector(selector));
+
+ info("Selecting the computed view.");
+ inspector.sidebar.select("computedview");
+
+ info("Selecting the layout view.");
+ inspector.sidebar.select("layoutview");
+
+ info("Checking the state of the flexbox panel.");
+ const content = item.querySelector(ACCORDION_CONTENT_SELECTOR);
+
+ ok(content.hidden, "The flexbox panel content is hidden.");
+ ok(!Services.prefs.getBoolPref(pref), `${pref} is pref off.`);
+}
+
+async function testAccordionStateAfterReopeningLayoutView(
+ pref,
+ selector,
+ { target, toolbox }
+) {
+ info(
+ "Checking the flexbox accordion state is persistent after closing and re-opening the layout view."
+ );
+
+ info("Closing the toolbox.");
+ await toolbox.destroy();
+
+ info("Re-opening the layout view.");
+ const { doc } = await openLayoutViewAndSelectNode(target);
+ const item = await waitFor(() => doc.querySelector(selector));
+ const content = item.querySelector(ACCORDION_CONTENT_SELECTOR);
+
+ info("Checking the state of the flexbox panel.");
+ ok(content.hidden, "The flexbox panel content is hidden.");
+ ok(!Services.prefs.getBoolPref(pref), `${pref} is pref off.`);
+}
+
+async function openLayoutViewAndSelectNode(target) {
+ const { inspector, flexboxInspector, toolbox } = await openLayoutView();
+ await selectNode(target, inspector);
+
+ return {
+ doc: flexboxInspector.document,
+ inspector,
+ target,
+ toolbox,
+ };
+}
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item.js b/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item.js
new file mode 100644
index 0000000000..d77ec6d9e4
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex container accordion and flex item accordion are both rendered when
+// the selected element is both a flex container and item.
+
+const TEST_URI = `
+ <style type='text/css'>
+ .container {
+ display: flex;
+ }
+ </style>
+ <div id="container" class="container">
+ <div id="item" class="container">
+ <div></div>
+ </div>
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ const onAccordionsRendered = waitForDOM(doc, ".accordion-item", 4);
+ await selectNode("#item", inspector);
+ const [flexItemPane, flexContainerPane] = await onAccordionsRendered;
+
+ ok(flexItemPane, "The flex item accordion pane is rendered.");
+ ok(flexContainerPane, "The flex container accordion pane is rendered.");
+ is(
+ flexItemPane.children[0].textContent,
+ "Flex Item of div#container.container",
+ "Got the correct header for the flex item pane."
+ );
+ is(
+ flexContainerPane.children[0].textContent,
+ "Flex Container",
+ "Got the correct header for the flex container pane."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_accordion_state.js b/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_accordion_state.js
new file mode 100644
index 0000000000..0339845618
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_accordion_state.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the order in which accordion items are shown for container-item elements.
+// For those combined types, the container accordion is shown first if the selection came
+// from the markup-view, because we assume in this case that users do want to see the
+// element selected as a container first.
+// However when users select an item in the list of items in the container accordion (or
+// in the item selector dropdown), then the item accordion should be shown first.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select a flex container-only node");
+ await selectNode("#container-only", inspector);
+ await waitUntil(
+ () => doc.querySelectorAll(".flex-header-container-properties").length
+ );
+
+ info("Check that there is only 1 accordion for displayed");
+ let accordions = doc.querySelectorAll(".flex-accordion");
+ is(accordions.length, 1, "There's only 1 accordion");
+ is(
+ accordions[0].id,
+ "layout-section-flex-container",
+ "The accordion is the container type"
+ );
+
+ info("Select a flex container+item node by clicking in the markup-view");
+ await clickOnNodeInMarkupView("#container-and-item", inspector);
+ await waitUntil(() => doc.querySelectorAll(".flex-accordion").length === 2);
+
+ info(
+ "Check that the 2 accordions are displayed, with container type being first"
+ );
+ accordions = doc.querySelectorAll(".flex-accordion");
+ is(accordions.length, 2, "There are 2 accordions");
+ is(
+ accordions[0].id,
+ "layout-section-flex-container",
+ "The first accordion is the container type"
+ );
+ is(
+ accordions[1].id,
+ "layout-section-flex-item",
+ "The second accordion is the item type"
+ );
+
+ info("Select the container-only node again");
+ await selectNode("#container-only", inspector);
+ await waitUntil(() => doc.querySelectorAll(".flex-accordion").length === 1);
+
+ info("Wait until the accordion item list points to the correct item");
+ await waitUntil(() =>
+ doc
+ .querySelector(".flex-item-list button")
+ .textContent.includes("container-and-item")
+ );
+ info(
+ "Click on the container+item node right there in the accordion item list"
+ );
+ doc.querySelector(".flex-item-list button").click();
+ await waitUntil(() => doc.querySelectorAll(".flex-accordion").length === 2);
+
+ info(
+ "Check that the 2 accordions are displayed again, with item type being first"
+ );
+ accordions = doc.querySelectorAll(".flex-accordion");
+ is(accordions.length, 2, "There are 2 accordions again");
+ is(
+ accordions[0].id,
+ "layout-section-flex-item",
+ "The first accordion is the item type"
+ );
+ is(
+ accordions[1].id,
+ "layout-section-flex-container",
+ "The second accordion is the container type"
+ );
+});
+
+async function clickOnNodeInMarkupView(selector, inspector) {
+ const { selection, markup } = inspector;
+
+ await markup.expandAll(selection.nodeFront);
+ const nodeFront = await getNodeFront(selector, inspector);
+ const markupContainer = markup.getContainer(nodeFront);
+
+ const onSelected = inspector.once("inspector-updated");
+ EventUtils.synthesizeMouseAtCenter(
+ markupContainer.tagLine,
+ { type: "mousedown" },
+ markup.doc.defaultView
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ markupContainer.tagLine,
+ { type: "mouseup" },
+ markup.doc.defaultView
+ );
+ await onSelected;
+}
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_updates_on_change.js b/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_updates_on_change.js
new file mode 100644
index 0000000000..af62ac2ff0
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_container_and_item_updates_on_change.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex container accordion is rendered when a flex item is updated to
+// also be a flex container.
+
+const TEST_URI = `
+ <style>
+ .container {
+ display: flex;
+ }
+ </style>
+ <div id="container" class="container">
+ <div id="item">
+ <div></div>
+ </div>
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ await selectNode("#item", inspector);
+ const [flexSizingContainer] = await onFlexItemSizingRendered;
+
+ ok(flexSizingContainer, "The flex sizing info is rendered.");
+
+ info("Changing the flexbox in the page.");
+ const onAccordionsChanged = waitForDOM(doc, ".accordion-item", 4);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ () => (content.document.getElementById("item").className = "container")
+ );
+ const [flexItemPane, flexContainerPane] = await onAccordionsChanged;
+
+ ok(flexItemPane, "The flex item accordion pane is rendered.");
+ ok(flexContainerPane, "The flex container accordion pane is rendered.");
+ is(
+ flexItemPane.children[0].textContent,
+ "Flex Item of div#container.container",
+ "Got the correct header for the flex item pane."
+ );
+ is(
+ flexContainerPane.children[0].textContent,
+ "Flex Container",
+ "Got the correct header for the flex container pane."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js b/devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js
new file mode 100644
index 0000000000..d2d83527b9
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_container_element_rep.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex container's element rep will display the box model highlighter on
+// hover.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const { waitForHighlighterTypeShown } = getHighlighterTestHelpers(inspector);
+
+ const onFlexContainerRepRendered = waitForDOM(
+ doc,
+ ".flex-header-content .objectBox"
+ );
+ await selectNode("#container", inspector);
+ const [flexContainerRep] = await onFlexContainerRepRendered;
+
+ ok(flexContainerRep, "The flex container element rep is rendered.");
+
+ info("Listen to node-highlight event and mouse over the rep");
+ const onHighlight = waitForHighlighterTypeShown(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+ EventUtils.synthesizeMouse(
+ flexContainerRep,
+ 10,
+ 5,
+ { type: "mouseover" },
+ doc.defaultView
+ );
+ const { nodeFront } = await onHighlight;
+
+ ok(nodeFront, "nodeFront was returned from highlighting the node.");
+ is(nodeFront.tagName, "DIV", "The highlighted node has the correct tagName.");
+ is(
+ nodeFront.attributes[0].name,
+ "id",
+ "The highlighted node has the correct attributes."
+ );
+ is(
+ nodeFront.attributes[0].value,
+ "container",
+ "The highlighted node has the correct id."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_container_properties.js b/devtools/client/inspector/flexbox/test/browser_flexbox_container_properties.js
new file mode 100644
index 0000000000..cf75d74ffe
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_container_properties.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex container properties are shown when a flex container is selected.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #container1 {
+ display: flex;
+ }
+ </style>
+ <div id="container1">
+ <div id="item"></div>
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info(
+ "Selecting the flex container #container1 and checking the values of the flex " +
+ "container properties for #container1."
+ );
+ const onFlexContainerPropertiesRendered = waitForDOM(
+ doc,
+ ".flex-header-container-properties"
+ );
+ await selectNode("#container1", inspector);
+ const [flexContainerProperties] = await onFlexContainerPropertiesRendered;
+
+ ok(flexContainerProperties, "The flex container properties is rendered.");
+ is(
+ flexContainerProperties.children[0].textContent,
+ "row",
+ "Got expected flex-direction."
+ );
+ is(
+ flexContainerProperties.children[1].textContent,
+ "nowrap",
+ "Got expected flex-wrap."
+ );
+
+ info(
+ "Selecting a flex item and expecting the flex container properties to not be " +
+ "shown."
+ );
+ const onFlexHeaderRendered = waitForDOM(doc, ".flex-header");
+ await selectNode("#item", inspector);
+ const [flexHeader] = await onFlexHeaderRendered;
+
+ ok(
+ !flexHeader.querySelector(".flex-header-container-properties"),
+ "The flex container properties is not shown in the header."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_empty_state.js b/devtools/client/inspector/flexbox/test/browser_flexbox_empty_state.js
new file mode 100644
index 0000000000..82e523ebe0
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_empty_state.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a message is displayed when no flex container is selected.
+
+const TEST_URI = `
+ <div></div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Checking the initial state of the Flexbox Inspector.");
+ ok(
+ doc.querySelector(
+ ".flex-accordion .devtools-sidepanel-no-result",
+ "A message is shown when no flex container is selected."
+ )
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_grand_parent_flex.js b/devtools/client/inspector/flexbox/test/browser_flexbox_grand_parent_flex.js
new file mode 100644
index 0000000000..8617def08b
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_grand_parent_flex.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a flex container is not shown as a flex item of its grandparent flex
+// container.
+
+const TEST_URI = `
+<style>
+.flex {
+ display: flex;
+}
+</style>
+<div class="flex">
+ <div>
+ <div id="grandchild" class="flex">
+ This is a flex item of a flex container.
+ Its parent isn't a flex container, but its grandparent is.
+ </div>
+ </div>
+</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select the flex container's grandchild.");
+ const onFlexContainerHeaderRendered = waitForDOM(
+ doc,
+ ".flex-header-container-label"
+ );
+ await selectNode("#grandchild", inspector);
+ await onFlexContainerHeaderRendered;
+
+ info("Check that only the Flex Container accordion item is showing.");
+ const flexPanes = doc.querySelectorAll(".flex-accordion");
+ is(
+ flexPanes.length,
+ 1,
+ "There should only be one flex accordion item showing."
+ );
+
+ info("Check that the container header shows Flex Container.");
+ const flexAccordionHeader = flexPanes[0].querySelector(
+ ".accordion-header-label"
+ );
+ is(
+ flexAccordionHeader.textContent,
+ "Flex Container",
+ "The flexbox pane shows a flex container accordion item."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_ESC.js b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_ESC.js
new file mode 100644
index 0000000000..59b24ba512
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_ESC.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const asyncStorage = require("resource://devtools/shared/async-storage.js");
+
+// Test that the flexbox highlighter color change in the color picker is reverted when
+// ESCAPE is pressed.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ // Make sure there are no custom highlighter colors stored before starting.
+ await asyncStorage.removeItem("flexboxInspectorHostColors");
+
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector, layoutView } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const { store } = inspector;
+ const cPicker = layoutView.swatchColorPickerTooltip;
+ const spectrum = cPicker.spectrum;
+
+ const onColorSwatchRendered = waitForDOM(
+ doc,
+ ".layout-flexbox-wrapper .layout-color-swatch"
+ );
+ await selectNode("#container", inspector);
+ const [swatch] = await onColorSwatchRendered;
+
+ info("Checking the initial state of the Flexbox Inspector color picker.");
+ is(
+ swatch.style.backgroundColor,
+ "rgb(148, 0, 255)",
+ "The color swatch's background is correct."
+ );
+ is(
+ store.getState().flexbox.color,
+ "#9400FF",
+ "The flexbox color state is correct."
+ );
+
+ info("Opening the color picker by clicking on the color swatch.");
+ const onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ await onColorPickerReady;
+
+ await simulateColorPickerChange(cPicker, [0, 255, 0, 0.5]);
+
+ is(
+ swatch.style.backgroundColor,
+ "rgba(0, 255, 0, 0.5)",
+ "The color swatch's background was updated."
+ );
+
+ info("Pressing ESCAPE to close the tooltip.");
+ const onColorUpdate = waitUntilState(
+ store,
+ state => state.flexbox.color === "#9400FF"
+ );
+ const onColorPickerHidden = cPicker.tooltip.once("hidden");
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "ESCAPE");
+ await onColorPickerHidden;
+ await onColorUpdate;
+
+ is(
+ swatch.style.backgroundColor,
+ "rgb(148, 0, 255)",
+ "The color swatch's background was reverted after ESCAPE."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_RETURN.js b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_RETURN.js
new file mode 100644
index 0000000000..46c6a7022c
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_RETURN.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const asyncStorage = require("resource://devtools/shared/async-storage.js");
+
+// Test that the flexbox highlighter color change in the color picker is committed when
+// RETURN is pressed.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ // Make sure there are no custom highlighter colors stored before starting.
+ await asyncStorage.removeItem("flexboxInspectorHostColors");
+
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector, layoutView } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const { store } = inspector;
+ const HIGHLIGHTER_TYPE = inspector.highlighters.TYPES.FLEXBOX;
+ const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
+ getHighlighterTestHelpers(inspector);
+ const cPicker = layoutView.swatchColorPickerTooltip;
+ const spectrum = cPicker.spectrum;
+
+ const onColorSwatchRendered = waitForDOM(
+ doc,
+ ".layout-flexbox-wrapper .layout-color-swatch"
+ );
+ await selectNode("#container", inspector);
+ const [swatch] = await onColorSwatchRendered;
+
+ const checkbox = doc.getElementById("flexbox-checkbox-toggle");
+
+ info("Checking the initial state of the Flexbox Inspector color picker.");
+ ok(!checkbox.checked, "Flexbox highlighter toggle is unchecked.");
+ is(
+ swatch.style.backgroundColor,
+ "rgb(148, 0, 255)",
+ "The color swatch's background is correct."
+ );
+ is(
+ store.getState().flexbox.color,
+ "#9400FF",
+ "The flexbox color state is correct."
+ );
+
+ info("Toggling ON the flexbox highlighter.");
+ const onHighlighterShown = waitForHighlighterTypeShown(HIGHLIGHTER_TYPE);
+ const onCheckboxChange = waitUntilState(
+ store,
+ state => state.flexbox.highlighted
+ );
+ checkbox.click();
+ await onHighlighterShown;
+ await onCheckboxChange;
+
+ info("Opening the color picker by clicking on the color swatch.");
+ const onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ await onColorPickerReady;
+
+ await simulateColorPickerChange(cPicker, [0, 255, 0, 0.5]);
+
+ is(
+ swatch.style.backgroundColor,
+ "rgba(0, 255, 0, 0.5)",
+ "The color swatch's background was updated."
+ );
+
+ info("Pressing RETURN to commit the color change.");
+ const onColorUpdate = waitUntilState(
+ store,
+ state => state.flexbox.color === "#00FF0080"
+ );
+ const onColorPickerHidden = cPicker.tooltip.once("hidden");
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+ await onColorPickerHidden;
+ await onColorUpdate;
+
+ is(
+ swatch.style.backgroundColor,
+ "rgba(0, 255, 0, 0.5)",
+ "The color swatch's background was kept after RETURN."
+ );
+
+ info("Toggling OFF the flexbox highlighter.");
+ const onHighlighterHidden = waitForHighlighterTypeHidden(HIGHLIGHTER_TYPE);
+ checkbox.click();
+ await onHighlighterHidden;
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_opened_telemetry.js b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_opened_telemetry.js
new file mode 100644
index 0000000000..62c63e8b9f
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_opened_telemetry.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the telemetry is correct when the flexbox highlighter is activated from
+// the layout view.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ startTelemetry();
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const onFlexHighlighterToggleRendered = waitForDOM(
+ doc,
+ "#flexbox-checkbox-toggle"
+ );
+ await selectNode("#container", inspector);
+ const [flexHighlighterToggle] = await onFlexHighlighterToggleRendered;
+
+ await toggleHighlighterON(flexHighlighterToggle, inspector);
+ await toggleHighlighterOFF(flexHighlighterToggle, inspector);
+
+ checkResults();
+});
+
+function checkResults() {
+ checkTelemetry("devtools.layout.flexboxhighlighter.opened", "", 1, "scalar");
+ checkTelemetry(
+ "DEVTOOLS_FLEXBOX_HIGHLIGHTER_TIME_ACTIVE_SECONDS",
+ "",
+ null,
+ "hasentries"
+ );
+}
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_01.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_01.js
new file mode 100644
index 0000000000..d7a8ae184a
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_01.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+// Test the flex item list is empty when there are no flex items for the selected flex
+// container.
+
+const TEST_URI = `
+ <div id="container" style="display:flex">
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const HIGHLIGHTER_TYPE = inspector.highlighters.TYPES.FLEXBOX;
+ const { getActiveHighlighter } = getHighlighterTestHelpers(inspector);
+
+ const onFlexHeaderRendered = waitForDOM(doc, ".flex-header");
+ await selectNode("#container", inspector);
+ const [flexHeader] = await onFlexHeaderRendered;
+ const flexHighlighterToggle = flexHeader.querySelector(
+ "#flexbox-checkbox-toggle"
+ );
+ const flexItemListHeader = doc.querySelector(".flex-item-list-header");
+
+ info("Checking the state of the Flexbox Inspector.");
+ ok(flexHeader, "The flex container header is rendered.");
+ ok(flexHighlighterToggle, "The flexbox highlighter toggle is rendered.");
+ is(
+ flexItemListHeader.textContent,
+ getStr("flexbox.noFlexItems"),
+ "The flex item list header shows 'No flex items' when there are no items."
+ );
+ ok(
+ !flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is unchecked."
+ );
+ ok(
+ !getActiveHighlighter(HIGHLIGHTER_TYPE),
+ "No flexbox highlighter is shown."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_02.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_02.js
new file mode 100644
index 0000000000..5433727c6a
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_02.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item list can be used to navigated to the selected flex item.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ const onFlexItemListRendered = waitForDOM(doc, ".flex-item-list");
+ await selectNode("#container", inspector);
+ const [flexItemList] = await onFlexItemListRendered;
+
+ info("Checking the initial state of the flex item list.");
+ ok(flexItemList, "The flex item list is rendered.");
+ is(
+ flexItemList.querySelectorAll("button").length,
+ 1,
+ "Got the correct number of flex items in the list."
+ );
+
+ info("Clicking on the first flex item to navigate to the flex item.");
+ const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+ flexItemList.querySelector("button").click();
+ const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+
+ info("Checking the selected flex item state.");
+ ok(flexOutlineContainer, "The flex outline is rendered.");
+ ok(!doc.querySelector(".flex-item-list"), "The flex item list is not shown.");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_updates_on_change.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_updates_on_change.js
new file mode 100644
index 0000000000..95cbb2da2d
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_updates_on_change.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item list updates on changes to the number of flex items in the
+// flex container.
+
+const TEST_URI = `
+ <style>
+ #container {
+ display: flex;
+ }
+ </style>
+ <div id="container">
+ <div></div>
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ const onFlexItemListRendered = waitForDOM(doc, ".flex-item-list");
+ await selectNode("#container", inspector);
+ const [flexItemList] = await onFlexItemListRendered;
+
+ info("Checking the initial state of the flex item list.");
+ ok(flexItemList, "The flex item list is rendered.");
+ is(
+ flexItemList.querySelectorAll("button").length,
+ 1,
+ "Got the correct number of flex items in the list."
+ );
+
+ info("Changing the flexbox in the page.");
+ const onFlexItemListChanged = waitForDOM(doc, ".flex-item-list > button", 2);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ const div = content.document.createElement("div");
+ content.document.getElementById("container").appendChild(div);
+ });
+ const elements = await onFlexItemListChanged;
+
+ info("Checking the flex item list is correct.");
+ is(elements.length, 2, "Flex item list was changed.");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_exists.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_exists.js
new file mode 100644
index 0000000000..eba6ec03f0
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_exists.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline exists when a flex item is selected.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select a flex item in the test document and wait for the outline to be rendered.
+ const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+ await selectNode(".item", inspector);
+ const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+
+ ok(flexOutlineContainer, "The flex outline exists in the DOM");
+
+ const [basis, final] = [
+ ...flexOutlineContainer.querySelectorAll(
+ ".flex-outline-basis, .flex-outline-final"
+ ),
+ ];
+
+ ok(basis, "The basis outline exists");
+ ok(final, "The final outline exists");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_has_correct_layout.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_has_correct_layout.js
new file mode 100644
index 0000000000..e81d6f5677
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_has_correct_layout.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline has a correct layout. This outline is built using css
+// grid under the hood to position everything. So we want to check that the template for
+// this grid has been correctly generated depending on the item that is currently
+// selected.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+const TEST_DATA = [
+ {
+ selector: ".shrinking .item",
+ expectedGridTemplate:
+ "[basis-start final-start] 300fr [final-end delta-start] " +
+ "200fr [basis-end delta-end]",
+ },
+ {
+ selector: ".shrinking.is-clamped .item",
+ expectedGridTemplate:
+ "[basis-start final-start] 300fr [delta-start] " +
+ "50fr [final-end min] 150fr [basis-end delta-end]",
+ },
+ {
+ selector: ".growing .item",
+ expectedGridTemplate:
+ "[basis-start final-start] 200fr [basis-end delta-start] " +
+ "100fr [final-end delta-end]",
+ },
+ {
+ selector: ".growing.is-clamped .item",
+ expectedGridTemplate:
+ "[basis-start final-start] 200fr [basis-end delta-start] " +
+ "50fr [final-end max] 50fr [delta-end]",
+ },
+ {
+ selector: "#wanted-to-shrink-more-than-basis div:first-child",
+ expectedGridTemplate:
+ "[delta-start] 63fr [basis-start final-start] " +
+ "60fr [final-end min] 140fr [basis-end delta-end]",
+ },
+];
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ for (const { selector, expectedGridTemplate } of TEST_DATA) {
+ info(
+ `Checking the grid template for the flex item outline for ${selector}`
+ );
+
+ await selectNode(selector, inspector);
+ await waitUntil(() => {
+ const flexOutline = doc.querySelector(".flex-outline");
+ return (
+ flexOutline &&
+ flexOutline.style.gridTemplateColumns === expectedGridTemplate
+ );
+ });
+
+ ok(true, "Grid template is correct");
+ }
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_hidden_when_useless.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_hidden_when_useless.js
new file mode 100644
index 0000000000..e0c1cab942
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_hidden_when_useless.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline is not rendered when it isn't useful.
+// For now, that means when the item's base, delta and final sizes are all 0.
+
+const TEST_URI = `
+ <div style="display:flex">
+ <div></div>
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info(
+ "Select the item in the document and wait for the sizing section to appear"
+ );
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ await selectNode("div > div", inspector);
+ const [flexSizingContainer] = await onFlexItemSizingRendered;
+
+ const outlineEls = doc.querySelectorAll(".flex-outline-container");
+ is(outlineEls.length, 0, "The outline has not been rendered for this item");
+
+ info("Also check that the sizing section shows the correct information");
+ const allSections = [
+ ...flexSizingContainer.querySelectorAll(".section .name"),
+ ];
+
+ is(allSections.length, 2, "There are 2 parts in the sizing section");
+ is(
+ allSections[0].textContent,
+ "Content Size",
+ "The first part is the content size"
+ );
+ is(
+ allSections[1].textContent,
+ "Final Size",
+ "The second part is the final size"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_renders_basisfinal_points_correctly.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_renders_basisfinal_points_correctly.js
new file mode 100644
index 0000000000..d2a2ce94fd
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_renders_basisfinal_points_correctly.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline renders the basis and final points as a single point
+// if their sizes are equal. If not, then render as separate points.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc, store } = flexboxInspector;
+
+ info("Select a flex item whose basis size matches its final size.");
+ let onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode(".item", inspector);
+ await onUpdate;
+
+ const [basisFinalPoint] = [
+ ...doc.querySelectorAll(".flex-outline-point.basisfinal"),
+ ];
+
+ ok(basisFinalPoint, "The basis/final point exists");
+
+ info("Select a flex item whose basis size is different than its final size.");
+ onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode(".shrinking .item", inspector);
+ await onUpdate;
+
+ const [basis, final] = [
+ ...doc.querySelectorAll(
+ ".flex-outline-point.basis, .flex-outline-point.final"
+ ),
+ ];
+
+ ok(basis, "The basis point exists");
+ ok(final, "The final point exists");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_column.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_column.js
new file mode 100644
index 0000000000..f26ca751cd
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_column.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline is rotated for flex items in a column flexbox layout.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select a flex item in the row flexbox layout.
+ let onFlexItemOutlineRendered = waitForDOM(
+ doc,
+ ".flex-outline-container .flex-outline"
+ );
+ await selectNode(".container .item", inspector);
+ let [flexOutline] = await onFlexItemOutlineRendered;
+
+ ok(
+ flexOutline.classList.contains("horizontal-lr"),
+ "The flex outline has the horizontal-lr class"
+ );
+
+ // Check that the outline is wider than it is tall in the configuration.
+ let bounds = flexOutline.getBoxQuads()[0].getBounds();
+ Assert.greater(bounds.width, bounds.height, "The outline looks like a row");
+
+ // Select a flex item in the column flexbox layout.
+ onFlexItemOutlineRendered = waitForDOM(
+ doc,
+ ".flex-outline-container .flex-outline"
+ );
+ await selectNode(".container.column .item", inspector);
+ await waitUntil(() => {
+ flexOutline = doc.querySelector(
+ ".flex-outline-container .flex-outline.vertical-tb"
+ );
+ return flexOutline;
+ });
+ ok(true, "The flex outline has the vertical-tb class");
+
+ // Check that the outline is taller than it is wide in the configuration.
+ bounds = flexOutline.getBoxQuads()[0].getBounds();
+ Assert.greater(
+ bounds.height,
+ bounds.width,
+ "The outline looks like a column"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_different_writing_modes.js b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_different_writing_modes.js
new file mode 100644
index 0000000000..860f2c2337
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_outline_rotates_for_different_writing_modes.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item outline is rotated to match its main axis direction.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_writing_modes.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Check that the row flex item rotated to vertical-bt.");
+ let onFlexItemOutlineRendered = waitForDOM(
+ doc,
+ ".flex-outline-container .flex-outline"
+ );
+ await selectNode(".row.vertical-bt.item", inspector);
+ let [flexOutline] = await onFlexItemOutlineRendered;
+
+ ok(
+ flexOutline.classList.contains("vertical-bt"),
+ "Row outline has been rotated to vertical-bt."
+ );
+
+ info("Check that the column flex item rotated to horizontal-rl.");
+ onFlexItemOutlineRendered = waitForDOM(
+ doc,
+ ".flex-outline-container .flex-outline"
+ );
+ await selectNode(".column.horizontal-rl.item", inspector);
+ await waitUntil(() => {
+ flexOutline = doc.querySelector(
+ ".flex-outline-container .flex-outline.horizontal-rl"
+ );
+ return flexOutline;
+ });
+
+ ok(true, "Column outline has been rotated to horizontal-rl.");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_non_flex_item_is_not_shown.js b/devtools/client/inspector/flexbox/test/browser_flexbox_non_flex_item_is_not_shown.js
new file mode 100644
index 0000000000..92422ea490
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_non_flex_item_is_not_shown.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that an element that is the child of a flex container but is not actually a flex
+// item is not shown in the sidebar when selected.
+
+const TEST_URI = `
+<div style="display:flex">
+ <div id="item" style="position:absolute;">test</div>
+</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select the container's supposed flex item.");
+ await selectNode("#item", inspector);
+ const noFlexContainerOrItemSelected = doc.querySelector(
+ ".flex-accordion .devtools-sidepanel-no-result"
+ );
+
+ ok(
+ noFlexContainerOrItemSelected,
+ "The flexbox pane shows a message to select a flex container or item to continue."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_pseudo_elements_are_listed.js b/devtools/client/inspector/flexbox/test/browser_flexbox_pseudo_elements_are_listed.js
new file mode 100644
index 0000000000..c69e4c92b6
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_pseudo_elements_are_listed.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that pseudo-elements that are flex items do appear in the list of items.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_pseudos.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select the flex container in the inspector.
+ const onItemsListRendered = waitForDOM(
+ doc,
+ ".layout-flexbox-wrapper .flex-item-list"
+ );
+ await selectNode(".container", inspector);
+ const [flexItemList] = await onItemsListRendered;
+
+ const items = [...flexItemList.querySelectorAll("button .objectBox")];
+ is(items.length, 2, "There are 2 items displayed in the list");
+
+ is(items[0].textContent, "::before", "The first item is ::before");
+ is(items[1].textContent, "::after", "The second item is ::after");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_flexibility_not_displayed_when_useless.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_flexibility_not_displayed_when_useless.js
new file mode 100644
index 0000000000..e25ebd5a6a
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_flexibility_not_displayed_when_useless.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flexibility section in the flex item sizing properties is not displayed
+// when the item did not grow or shrink.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc, store } = flexboxInspector;
+
+ info(
+ "Select an item with flex:0 and wait for the sizing info to be rendered"
+ );
+ let onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode("#did-not-grow-or-shrink div", inspector);
+ await onUpdate;
+
+ let flexSections = doc.querySelectorAll(
+ ".flex-item-sizing .section.flexibility"
+ );
+ is(
+ flexSections.length,
+ 0,
+ "The flexibility section was not found in the DOM"
+ );
+
+ info(
+ "Select a more complex item which also doesn't flex and wait for the sizing info"
+ );
+ onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode(
+ "#just-enough-space-for-clamped-items div:last-child",
+ inspector
+ );
+ await onUpdate;
+
+ flexSections = doc.querySelectorAll(".flex-item-sizing .section.flexibility");
+ is(
+ flexSections.length,
+ 0,
+ "The flexibility section was not found in the DOM"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_do_not_show_unspecified_min_dimension.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_do_not_show_unspecified_min_dimension.js
new file mode 100644
index 0000000000..45cae70d7b
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_do_not_show_unspecified_min_dimension.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a flex item's min width/height value is not displayed if it's unspecified in
+// the CSS.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_unauthored_min_dimension.html";
+
+async function checkFlexItemCSSProperty(inspector, store, doc, selector) {
+ info("Select the container's flex item sizing info.");
+ const onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode(selector, inspector);
+ await onUpdate;
+
+ info(
+ "Check that the minimum size section does not display minimum dimension text."
+ );
+ const [sectionMinRowItem] = [
+ ...doc.querySelectorAll(".flex-item-sizing .section.min"),
+ ];
+ const minDimension = sectionMinRowItem.querySelector(".css-property-link");
+
+ ok(!minDimension, "Minimum dimension property should not be displayed.");
+}
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc, store } = flexboxInspector;
+
+ await checkFlexItemCSSProperty(
+ inspector,
+ store,
+ doc,
+ "#flex-item-with-unauthored-min-width"
+ );
+ await checkFlexItemCSSProperty(
+ inspector,
+ store,
+ doc,
+ "#flex-item-with-unauthored-min-height"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_exists.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_exists.js
new file mode 100644
index 0000000000..e353eea0db
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_exists.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item sizing information exists when a flex item is selected.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select a flex item in the test document and wait for the sizing info to be rendered.
+ // Note that we select an item that has base, delta and final sizes, so we can check
+ // those sections exists.
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ await selectNode(".container.growing .item", inspector);
+ const [flexSizingContainer] = await onFlexItemSizingRendered;
+
+ ok(flexSizingContainer, "The flex sizing exists in the DOM");
+
+ info("Check that the base, flexibility and final sizes are displayed");
+ const allSections = [
+ ...flexSizingContainer.querySelectorAll(".section .name"),
+ ];
+ const allSectionTitles = allSections.map(el => el.textContent);
+
+ ["Base Size", "Flexibility", "Final Size"].forEach((expectedTitle, i) => {
+ ok(
+ allSectionTitles[i].includes(expectedTitle),
+ `Sizing section #${i + 1} (${expectedTitle}) was found`
+ );
+ });
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_different_writing_modes.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_different_writing_modes.js
new file mode 100644
index 0000000000..91a99d3da5
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_different_writing_modes.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item sizing info shows the correct dimension values for different
+// writing modes. For vertical writing modes, row items should display height values and
+// column items should display width values. The opposite is true for horizontal mode
+// where rows display width values and columns display height.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_writing_modes.html";
+
+async function checkFlexItemDimension(
+ inspector,
+ store,
+ doc,
+ selector,
+ expected
+) {
+ info("Select the container's flex item.");
+ const onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode(selector, inspector);
+ await onUpdate;
+
+ info("Check that the minimum size section shows the correct dimension.");
+ const sectionMinRowItem = doc.querySelector(".flex-item-sizing .section.min");
+ const minDimension = sectionMinRowItem.querySelector(".css-property-link");
+
+ ok(
+ minDimension.textContent.includes(expected),
+ "The flex item sizing has the correct dimension value."
+ );
+}
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc, store } = flexboxInspector;
+
+ await checkFlexItemDimension(
+ inspector,
+ store,
+ doc,
+ ".row.vertical-rl.item",
+ "min-height"
+ );
+ await checkFlexItemDimension(
+ inspector,
+ store,
+ doc,
+ ".column.vertical-tb.item",
+ "min-height"
+ );
+ await checkFlexItemDimension(
+ inspector,
+ store,
+ doc,
+ ".row.vertical-bt.item",
+ "min-height"
+ );
+ await checkFlexItemDimension(
+ inspector,
+ store,
+ doc,
+ ".column.horizontal-rl.item",
+ "min-width"
+ );
+ await checkFlexItemDimension(
+ inspector,
+ store,
+ doc,
+ ".row.horizontal-lr.item",
+ "min-width"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_pseudos.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_pseudos.js
new file mode 100644
index 0000000000..75b3a00a4f
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_pseudos.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item sizing UI also appears for pseudo elements.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_pseudos.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select the ::before pseudo-element in the inspector");
+ const containerNode = await getNodeFront(".container", inspector);
+ const { nodes } = await inspector.walker.children(containerNode);
+ const beforeNode = nodes[0];
+
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+ await selectNode(beforeNode, inspector);
+ const [flexSizingContainer] = await onFlexItemSizingRendered;
+ const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+
+ ok(flexSizingContainer, "The flex sizing exists in the DOM");
+ ok(flexOutlineContainer, "The flex outline exists in the DOM");
+
+ info("Check that the various sizing sections are displayed");
+ const allSections = [...flexSizingContainer.querySelectorAll(".section")];
+ ok(allSections.length, "Sizing sections are displayed");
+
+ info("Check that the various parts of the outline are displayed");
+ const [basis, final] = [
+ ...flexOutlineContainer.querySelectorAll(
+ ".flex-outline-basis, .flex-outline-final"
+ ),
+ ];
+ ok(basis && final, "The final and basis parts of the outline exist");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_text_nodes.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_text_nodes.js
new file mode 100644
index 0000000000..fc96505208
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_text_nodes.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item sizing UI also appears for text nodes.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_text_nodes.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select the first text node in the flex container");
+ const containerNode = await getNodeFront(".container", inspector);
+ const { nodes } = await inspector.walker.children(containerNode);
+ const firstTextNode = nodes[0];
+
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+ await selectNode(firstTextNode, inspector);
+ const [flexSizingContainer] = await onFlexItemSizingRendered;
+ const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+
+ ok(flexSizingContainer, "The flex sizing exists in the DOM");
+ ok(flexOutlineContainer, "The flex outline exists in the DOM");
+
+ info("Check that the various sizing sections are displayed");
+ const allSections = [...flexSizingContainer.querySelectorAll(".section")];
+ ok(allSections.length, "Sizing sections are displayed");
+
+ info("Check that the various parts of the outline are displayed");
+ const [basis, final] = [
+ ...flexOutlineContainer.querySelectorAll(
+ ".flex-outline-basis, .flex-outline-final"
+ ),
+ ];
+ ok(basis && final, "The final and basis parts of the outline exist");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_has_correct_sections.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_has_correct_sections.js
new file mode 100644
index 0000000000..e31366da15
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_has_correct_sections.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item sizing UI contains the right sections, depending on which
+// element is selected. Some items may be clamped, others not, so not all sections are
+// visible at all times.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+const TEST_DATA = [
+ {
+ selector: ".shrinking .item",
+ expectedSections: ["Base Size", "Flexibility", "Final Size"],
+ },
+ {
+ selector: ".shrinking.is-clamped .item",
+ expectedSections: [
+ "Base Size",
+ "Flexibility",
+ "Minimum Size",
+ "Final Size",
+ ],
+ },
+ {
+ selector: ".growing .item",
+ expectedSections: ["Base Size", "Flexibility", "Final Size"],
+ },
+ {
+ selector: ".growing.is-clamped .item",
+ expectedSections: [
+ "Base Size",
+ "Flexibility",
+ "Maximum Size",
+ "Final Size",
+ ],
+ },
+];
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc, store } = flexboxInspector;
+
+ for (const { selector, expectedSections } of TEST_DATA) {
+ info(`Checking the list of sections for the flex item ${selector}`);
+ const sections = await selectNodeAndGetFlexSizingSections(
+ selector,
+ store,
+ inspector,
+ doc
+ );
+
+ is(
+ sections.length,
+ expectedSections.length,
+ "Correct number of sections found"
+ );
+ expectedSections.forEach((expectedSection, i) => {
+ ok(
+ sections[i].includes(expectedSection),
+ `The ${expectedSection} section was found`
+ );
+ });
+ }
+});
+
+async function selectNodeAndGetFlexSizingSections(
+ selector,
+ store,
+ inspector,
+ doc
+) {
+ const onUpdate = waitForDispatch(store, "UPDATE_FLEXBOX");
+ await selectNode(selector, inspector);
+ await onUpdate;
+
+ info(`Getting the list of displayed sections for ${selector}`);
+ const allSections = [
+ ...doc.querySelectorAll("ul.flex-item-sizing .section .name"),
+ ];
+ const allSectionTitles = allSections.map(el => el.textContent);
+
+ return allSectionTitles;
+}
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_matches_properties_with_!important.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_matches_properties_with_!important.js
new file mode 100644
index 0000000000..ba14ef7a62
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_matches_properties_with_!important.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a flex item's sizing section shows the highest value of specificity for its
+// CSS property.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_CSS_property_with_!important.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select the container's flex item sizing info.");
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ await selectNode(".item", inspector);
+ const [flexItemSizingContainer] = await onFlexItemSizingRendered;
+
+ info(
+ "Check that the max and flexibility sections show correct CSS property value."
+ );
+ const flexSection = flexItemSizingContainer.querySelector(
+ ".section.flexibility"
+ );
+ const maxSection = flexItemSizingContainer.querySelector(".section.max");
+ const flexGrow = flexSection.querySelector(".css-property-link");
+ const maxSize = maxSection.querySelector(".css-property-link");
+
+ is(
+ maxSize.textContent,
+ "(max-width: 400px !important)",
+ "Maximum size section shows CSS property value with highest specificity."
+ );
+ is(
+ flexGrow.textContent,
+ "(flex-grow: 5 !important)",
+ "Flexibility size section shows CSS property value with highest specificity."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_updates_on_change.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_updates_on_change.js
new file mode 100644
index 0000000000..9218022b02
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_updates_on_change.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flexbox sizing info updates on changes to the flex item properties.
+
+const TEST_URI = `
+ <style>
+ #container {
+ display: flex;
+ }
+ </style>
+ <div id="container">
+ <div id="item"></div>
+ </div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ await selectNode("#item", inspector);
+ const [flexItemSizingContainer] = await onFlexItemSizingRendered;
+
+ info("Checking the initial state of the flex item list.");
+ is(
+ flexItemSizingContainer.querySelectorAll("li").length,
+ 2,
+ "Got the correct number of flex item sizing properties in the list."
+ );
+
+ info("Changing the flexbox in the page.");
+ const onFlexItemSizingChanged = waitForDOM(
+ doc,
+ "ul.flex-item-sizing > li",
+ 3
+ );
+ SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ () => (content.document.getElementById("item").style.minWidth = "100px")
+ );
+ const elements = await onFlexItemSizingChanged;
+
+ info("Checking the flex item sizing info is correct.");
+ is(elements.length, 3, "Flex item sizing info was changed.");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_wanted_to_grow_but_was_clamped.js b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_wanted_to_grow_but_was_clamped.js
new file mode 100644
index 0000000000..72e2861ef3
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_wanted_to_grow_but_was_clamped.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+// Test the specific max-clamping scenario where an item wants to grow a certain amount
+// but its max-size prevents it from growing that much.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info(
+ "Select the test item in the document and wait for the sizing info to render"
+ );
+ const onRendered = waitForDOM(doc, ".flex-outline, .flex-item-sizing", 2);
+ await selectNode("#want-to-grow-more-than-max div", inspector);
+ const [outlineContainer, sizingContainer] = await onRendered;
+
+ info(
+ "Check that the outline contains the max point and that it's equal to final"
+ );
+ const maxPoint = outlineContainer.querySelector(".flex-outline-point.max");
+ ok(maxPoint, "The max point is displayed");
+ ok(
+ outlineContainer.style.gridTemplateColumns.includes("[final-end max]"),
+ "The final and max points are at the same position"
+ );
+
+ info("Check that the maximum sizing section displays the right info");
+ const reasons = [...sizingContainer.querySelectorAll(".reasons li")];
+ const expectedReason = getStr("flexbox.itemSizing.clampedToMax");
+ ok(
+ reasons.some(r => r.textContent === expectedReason),
+ "The clampedToMax reason was found"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_listed.js b/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_listed.js
new file mode 100644
index 0000000000..374ec77962
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_listed.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that text nodes that are flex items do appear in the list of items.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_text_nodes.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select the flex container in the inspector.
+ const onItemsListRendered = waitForDOM(
+ doc,
+ ".layout-flexbox-wrapper .flex-item-list"
+ );
+ await selectNode(".container", inspector);
+ const [flexItemList] = await onItemsListRendered;
+
+ const items = [...flexItemList.querySelectorAll("button .objectBox")];
+ is(items.length, 3, "There are 3 items displayed in the list");
+
+ is(items[0].textContent, "#text", "The first item is a text node");
+ is(items[2].textContent, "#text", "The third item is a text node");
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_not_inlined.js b/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_not_inlined.js
new file mode 100644
index 0000000000..2494ac1dcb
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_not_inlined.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that single child text nodes that are also flex items can be selected in the
+// flexbox inspector.
+// This means that they are not inlined like normal single child text nodes, since
+// selecting them in the flexbox inspector also means selecting them in the markup view.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_text_nodes.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select the flex container in the inspector.
+ const onItemsListRendered = waitForDOM(
+ doc,
+ ".layout-flexbox-wrapper .flex-item-list"
+ );
+ await selectNode(".container.single-child", inspector);
+ const [flexItemList] = await onItemsListRendered;
+
+ const items = [...flexItemList.querySelectorAll("button .objectBox")];
+ is(items.length, 1, "There is 1 item displayed in the list");
+ is(items[0].textContent, "#text", "The item in the list is a text node");
+
+ info("Click on the item to select it");
+ const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+ items[0].closest("button").click();
+ const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+ ok(
+ flexOutlineContainer,
+ "The flex outline is displayed for a single child short text node too"
+ );
+
+ ok(
+ inspector.selection.isTextNode(),
+ "The current inspector selection is the text node"
+ );
+
+ const markupContainer = inspector.markup.getContainer(
+ inspector.selection.nodeFront
+ );
+ is(
+ markupContainer.elt.textContent,
+ "short text",
+ "This is the right text node"
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_01.js b/devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_01.js
new file mode 100644
index 0000000000..2fbfccb162
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_01.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling ON/OFF the flexbox highlighter from the flexbox inspector panel.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const HIGHLIGHTER_TYPE = inspector.highlighters.TYPES.FLEXBOX;
+ const { getActiveHighlighter } = getHighlighterTestHelpers(inspector);
+
+ const onFlexHighlighterToggleRendered = waitForDOM(
+ doc,
+ "#flexbox-checkbox-toggle"
+ );
+ await selectNode("#container", inspector);
+ const [flexHighlighterToggle] = await onFlexHighlighterToggleRendered;
+
+ info("Checking the initial state of the Flexbox Inspector.");
+ ok(flexHighlighterToggle, "The flexbox highlighter toggle is rendered.");
+ ok(
+ !flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is unchecked."
+ );
+ ok(
+ !getActiveHighlighter(HIGHLIGHTER_TYPE),
+ "No flexbox highlighter is shown."
+ );
+
+ await toggleHighlighterON(flexHighlighterToggle, inspector);
+
+ info("Checking the flexbox highlighter is created.");
+ ok(getActiveHighlighter(HIGHLIGHTER_TYPE), "Flexbox highlighter is shown.");
+ ok(
+ flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is checked."
+ );
+
+ await toggleHighlighterOFF(flexHighlighterToggle, inspector);
+
+ info("Checking the flexbox highlighter is not shown.");
+ ok(
+ !getActiveHighlighter(HIGHLIGHTER_TYPE),
+ "No flexbox highlighter is shown."
+ );
+ ok(
+ !flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is unchecked."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_02.js b/devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_02.js
new file mode 100644
index 0000000000..c7d7c35d9a
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_toggle_flexbox_highlighter_02.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling ON/OFF the flexbox highlighter on different flex containers from the
+// flexbox inspector panel.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+ const { store } = inspector;
+ const HIGHLIGHTER_TYPE = inspector.highlighters.TYPES.FLEXBOX;
+ const { getActiveHighlighter, getNodeForActiveHighlighter } =
+ getHighlighterTestHelpers(inspector);
+
+ const onFlexHighlighterToggleRendered = waitForDOM(
+ doc,
+ "#flexbox-checkbox-toggle"
+ );
+ await selectNode("#container", inspector);
+ const [flexHighlighterToggle] = await onFlexHighlighterToggleRendered;
+
+ info("Checking the #container state of the Flexbox Inspector.");
+ ok(flexHighlighterToggle, "The flexbox highlighter toggle is rendered.");
+ ok(
+ !flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is unchecked."
+ );
+ ok(
+ !getActiveHighlighter(HIGHLIGHTER_TYPE),
+ "No flexbox highlighter is shown."
+ );
+
+ info(
+ "Toggling ON the flexbox highlighter for #container from the layout panel."
+ );
+ await toggleHighlighterON(flexHighlighterToggle, inspector);
+
+ info("Checking the flexbox highlighter is created for #container.");
+ const highlightedNodeFront = store.getState().flexbox.flexContainer.nodeFront;
+ is(
+ getNodeForActiveHighlighter(HIGHLIGHTER_TYPE),
+ highlightedNodeFront,
+ "Flexbox highlighter is shown for #container."
+ );
+ ok(
+ flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is checked."
+ );
+
+ info("Switching the selected flex container to .container.column");
+ const onToggleChange = waitUntilState(
+ store,
+ state => !state.flexbox.highlighted
+ );
+ await selectNode(".container.column", inspector);
+ await onToggleChange;
+
+ info("Checking the .container.column state of the Flexbox Inspector.");
+ ok(
+ !flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is unchecked."
+ );
+ is(
+ getNodeForActiveHighlighter(HIGHLIGHTER_TYPE),
+ highlightedNodeFront,
+ "Flexbox highlighter is still shown for #container."
+ );
+
+ info(
+ "Toggling ON the flexbox highlighter for .container.column from the layout " +
+ "panel."
+ );
+ await toggleHighlighterON(flexHighlighterToggle, inspector);
+
+ info("Checking the flexbox highlighter is created for .container.column");
+ is(
+ getNodeForActiveHighlighter(HIGHLIGHTER_TYPE),
+ store.getState().flexbox.flexContainer.nodeFront,
+ "Flexbox highlighter is shown for .container.column."
+ );
+ ok(
+ flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is checked."
+ );
+
+ await toggleHighlighterOFF(flexHighlighterToggle, inspector);
+
+ info("Checking the flexbox highlighter is not shown.");
+ ok(
+ !getActiveHighlighter(HIGHLIGHTER_TYPE),
+ "No flexbox highlighter is shown."
+ );
+ ok(
+ !flexHighlighterToggle.checked,
+ "The flexbox highlighter toggle is unchecked."
+ );
+});
diff --git a/devtools/client/inspector/flexbox/test/doc_flexbox_CSS_property_with_!important.html b/devtools/client/inspector/flexbox/test/doc_flexbox_CSS_property_with_!important.html
new file mode 100644
index 0000000000..097d91215b
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_CSS_property_with_!important.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.container {
+ display: flex;
+ width: 500px;
+ height: 150px;
+ margin: 10px;
+}
+
+.item {
+ background: yellow;
+ color: red;
+ flex-grow: 1 !important;
+ max-width: 350px;
+ max-width: 400px !important;
+ padding: 10px;
+}
+</style>
+<div class="container">
+ <div class="item" style="flex-grow: 5 !important">Item</div>
+</div>
diff --git a/devtools/client/inspector/flexbox/test/doc_flexbox_pseudos.html b/devtools/client/inspector/flexbox/test/doc_flexbox_pseudos.html
new file mode 100644
index 0000000000..76474cbc37
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_pseudos.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.container {
+ width: 300px;
+ height: 150px;
+ margin: 10px;
+ display: flex;
+}
+
+.container::before,
+.container::after {
+ color: #f06;
+ border: 1px solid #f06;
+ background: gold;
+ padding: 1em;
+}
+
+.container::before {
+ content: "::before pseudo-element item";
+}
+
+.container::after {
+ content: "::after pseudo-element item";
+}
+</style>
+<div class="container"></div>
diff --git a/devtools/client/inspector/flexbox/test/doc_flexbox_specific_cases.html b/devtools/client/inspector/flexbox/test/doc_flexbox_specific_cases.html
new file mode 100644
index 0000000000..f5a6aaad24
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_specific_cases.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+.container {
+ width: 300px;
+ height: 150px;
+ margin: 10px;
+ display: flex;
+}
+.container.column {
+ height: 300px;
+ width: 150px;
+ flex-direction: column;
+}
+.item {
+ background: #0004;
+}
+.shrinking .item {
+ flex-basis: 500px;
+ flex-shrink: 1;
+}
+.shrinking.is-clamped .item {
+ min-width: 350px;
+}
+.growing .item {
+ flex-basis: 200px;
+ flex-grow: 1;
+}
+.growing.is-clamped .item {
+ max-width: 250px;
+}
+
+#want-to-grow-more-than-max {
+ width: 500px;
+ display: flex;
+}
+#want-to-grow-more-than-max div {
+ flex: 1;
+ max-width: 200px;
+}
+
+#did-not-grow-or-shrink {
+ width: 500px;
+ display: flex;
+}
+#did-not-grow-or-shrink div {
+ flex: 0 300px;
+}
+
+#just-enough-space-for-clamped-items {
+ display:flex;
+ width:100px;
+ height:40px
+}
+#just-enough-space-for-clamped-items div:first-child {
+ flex: 1 300px;
+ max-width: 20px;
+ background: teal;
+}
+#just-enough-space-for-clamped-items div:last-child {
+ flex: 1 10px;
+ min-width: 80px;
+ background: salmon;
+}
+
+#wanted-to-shrink-more-than-basis {
+ display: flex;
+ width: 5px;
+}
+#wanted-to-shrink-more-than-basis div:first-child {
+ flex: 0 2 200px;
+ /* Using the Ahem test font to make sure the text has the exact same size on all test
+ platforms */
+ font-family: Ahem;
+ font-size: 10px;
+}
+#wanted-to-shrink-more-than-basis div:last-child {
+ flex: 0 1 200px;
+}
+</style>
+<div id="container" class="container">
+ <div class="item">flex item in a row flex container</div>
+</div>
+<div class="container column">
+ <div class="item">flex item in a column flex container</div>
+</div>
+<div class="container shrinking">
+ <div class="item">Shrinking flex item</div>
+</div>
+<div class="container shrinking is-clamped">
+ <div class="item">Shrinking and clamped flex item</div>
+</div>
+<div class="container growing">
+ <div class="item">Growing flex item</div>
+</div>
+<div class="container growing is-clamped">
+ <div class="item">Growing and clamped flex item</div>
+</div>
+<div id="want-to-grow-more-than-max">
+ <div>item wants to grow more</div>
+</div>
+<div id="did-not-grow-or-shrink">
+ <div>item did not grow or shrink</div>
+</div>
+<div id="just-enough-space-for-clamped-items">
+ <div></div>
+ <div></div>
+</div>
+<div id="wanted-to-shrink-more-than-basis">
+ <div>item wants to shrink more than its basis</div>
+ <div></div>
+</div>
+<div class="container" id="container-only">
+ <div class="container" id="container-and-item">
+ <div id="item-only">This item is inside a container-item element</div>
+ </div>
+</div>
diff --git a/devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html b/devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html
new file mode 100644
index 0000000000..626d3aa47e
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.container {
+ width: 400px;
+ display: flex;
+}
+.container div {
+ flex-basis: 100px;
+ flex-shrink: 0;
+ background: #f06;
+ align-self: stretch;
+}
+</style>
+<div class="container">
+ A text node will be wrapped into an anonymous block container
+ <div></div>
+ Here is yet another text node
+</div>
+<div class="container single-child">short text</div>
diff --git a/devtools/client/inspector/flexbox/test/doc_flexbox_unauthored_min_dimension.html b/devtools/client/inspector/flexbox/test/doc_flexbox_unauthored_min_dimension.html
new file mode 100644
index 0000000000..2f05dbb7f8
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_unauthored_min_dimension.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.flex-container {
+ display: flex;
+ height: 100vh;
+}
+
+.column {
+ flex-direction: column;
+}
+
+.flex-child {
+ height: 100%;
+ width: 100%
+}
+</style>
+<div class="flex-container">
+ <div class="flex-child"></div>
+ <div id="flex-item-with-unauthored-min-width">
+ <h1>AAA</h1>
+ </div>
+</div>
+<div class="flex-container column">
+ <div class="flex-child"></div>
+ <div id="flex-item-with-unauthored-min-height">
+ <h1>BBB</h1>
+ </div>
+</div>
diff --git a/devtools/client/inspector/flexbox/test/doc_flexbox_writing_modes.html b/devtools/client/inspector/flexbox/test/doc_flexbox_writing_modes.html
new file mode 100644
index 0000000000..b518c39631
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_writing_modes.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.flex-container {
+ display: flex;
+}
+
+.flex-container.vertical-rl {
+ writing-mode: vertical-rl;
+}
+
+.flex-container.sideways-lr {
+ writing-mode: sideways-lr;
+}
+
+.column {
+ flex-direction: column;
+}
+
+.item {
+ min-width: 300px;
+ min-height: 300px;
+}
+
+</style>
+<div class="flex-container vertical-rl">
+ <span class="row vertical-rl item">Vertical-tb Row content</span>
+</div>
+<div class="flex-container column">
+ <span class="column vertical-tb item">Vertical-tb Column Content</span>
+</div>
+<div class="flex-container sideways-lr">
+ <span class="row vertical-bt item">Vertical-bt Row Content</span>
+</div>
+<div class="flex-container vertical-rl column">
+ <span class="column horizontal-rl item">Horizontal-rl Column Content</span>
+</div>
+<div class="flex-container">
+ <span class="row horizontal-lr item">Horizontal-lr Row Content</span>
+</div>
diff --git a/devtools/client/inspector/flexbox/test/head.js b/devtools/client/inspector/flexbox/test/head.js
new file mode 100644
index 0000000000..269dcea904
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/head.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+"use strict";
+
+// Import the inspector's head.js first (which itself imports shared-head.js).
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
+ this
+);
+
+const FLEXBOX_OPENED_PREF = "devtools.layout.flexbox.opened";
+const FLEX_CONTAINER_OPENED_PREF = "devtools.layout.flex-container.opened";
+const FLEX_ITEM_OPENED_PREF = "devtools.layout.flex-item.opened";
+const GRID_OPENED_PREF = "devtools.layout.grid.opened";
+const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened";
+
+// Make sure only the flexbox layout accordions are opened, and the others are closed.
+Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF, true);
+Services.prefs.setBoolPref(FLEX_CONTAINER_OPENED_PREF, true);
+Services.prefs.setBoolPref(FLEX_ITEM_OPENED_PREF, true);
+Services.prefs.setBoolPref(BOXMODEL_OPENED_PREF, false);
+Services.prefs.setBoolPref(GRID_OPENED_PREF, false);
+
+// Clear all set prefs.
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(FLEXBOX_OPENED_PREF);
+ Services.prefs.clearUserPref(FLEX_CONTAINER_OPENED_PREF);
+ Services.prefs.clearUserPref(FLEX_ITEM_OPENED_PREF);
+ Services.prefs.clearUserPref(BOXMODEL_OPENED_PREF);
+ Services.prefs.clearUserPref(GRID_OPENED_PREF);
+});
+
+/**
+ * Toggles ON the flexbox highlighter given the flexbox highlighter button from the
+ * layout panel.
+ *
+ * @param {DOMNode} button
+ * The flexbox highlighter toggle button in the flex container panel.
+ * @param {Inspector} inspector
+ * Inspector panel instance.
+ */
+async function toggleHighlighterON(button, inspector) {
+ info("Toggling ON the flexbox highlighter from the layout panel.");
+ const { waitForHighlighterTypeShown } = getHighlighterTestHelpers(inspector);
+ const onHighlighterShown = waitForHighlighterTypeShown(
+ inspector.highlighters.TYPES.FLEXBOX
+ );
+ const { store } = inspector;
+ const onToggleChange = waitUntilState(
+ store,
+ state => state.flexbox.highlighted
+ );
+ button.click();
+ await Promise.all([onHighlighterShown, onToggleChange]);
+}
+
+/**
+ * Toggles OFF the flexbox highlighter given the flexbox highlighter button from the
+ * layout panel.
+ *
+ * @param {DOMNode} button
+ * The flexbox highlighter toggle button in the flex container panel.
+ * @param {Inspector} inspector
+ * Inspector panel instance.
+ */
+async function toggleHighlighterOFF(button, inspector) {
+ info("Toggling OFF the flexbox highlighter from the layout panel.");
+ const { waitForHighlighterTypeHidden } = getHighlighterTestHelpers(inspector);
+ const onHighlighterHidden = waitForHighlighterTypeHidden(
+ inspector.highlighters.TYPES.FLEXBOX
+ );
+ const { store } = inspector;
+ const onToggleChange = waitUntilState(
+ store,
+ state => !state.flexbox.highlighted
+ );
+ button.click();
+ await Promise.all([onHighlighterHidden, onToggleChange]);
+}
diff --git a/devtools/client/inspector/flexbox/types.js b/devtools/client/inspector/flexbox/types.js
new file mode 100644
index 0000000000..34f45ed8ae
--- /dev/null
+++ b/devtools/client/inspector/flexbox/types.js
@@ -0,0 +1,145 @@
+/* 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 PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+/**
+ * A flex item computed style properties.
+ */
+const flexItemProperties = (exports.flexItemProperties = {
+ // The computed value of flex-basis.
+ "flex-basis": PropTypes.string,
+
+ // The computed value of flex-grow.
+ "flex-grow": PropTypes.string,
+
+ // The computed value of min-height.
+ "min-height": PropTypes.string,
+
+ // The computed value of min-width.
+ "min-width": PropTypes.string,
+
+ // The computed value of max-height.
+ "max-height": PropTypes.string,
+
+ // The computed value of max-width.
+ "max-width": PropTypes.string,
+
+ // The computed height of the flex item element.
+ height: PropTypes.string,
+
+ // The computed width of the flex item element.
+ width: PropTypes.string,
+});
+
+/**
+ * A flex item sizing data.
+ */
+const flexItemSizing = (exports.flexItemSizing = {
+ // The max size of the flex item in the cross axis.
+ crossMaxSize: PropTypes.number,
+
+ // The min size of the flex item in the cross axis.
+ crossMinSize: PropTypes.number,
+
+ // The size of the flex item in the main axis before the flex sizing algorithm is
+ // applied. This is also called the "flex base size" in the spec.
+ mainBaseSize: PropTypes.number,
+
+ // The value that the flex sizing algorithm "wants" to use to stretch or shrink the
+ // item, before clamping to the item's main min and max sizes.
+ mainDeltaSize: PropTypes.number,
+
+ // The max size of the flex item in the main axis.
+ mainMaxSize: PropTypes.number,
+
+ // The min size of the flex item in the maxin axis.
+ mainMinSize: PropTypes.number,
+});
+
+/**
+ * A flex item data.
+ */
+const flexItem = (exports.flexItem = {
+ // The actor ID of the flex item.
+ actorID: PropTypes.string,
+
+ // The computed style properties of the flex item.
+ computedStyle: PropTypes.object,
+
+ // The flex item sizing data.
+ flexItemSizing: PropTypes.shape(flexItemSizing),
+
+ // The NodeFront of the flex item.
+ nodeFront: PropTypes.object,
+
+ // The authored style properties of the flex item.
+ properties: PropTypes.shape(flexItemProperties),
+});
+
+/**
+ * A flex container computed style properties.
+ */
+const flexContainerProperties = (exports.flexContainerProperties = {
+ // The computed value of align-content.
+ "align-content": PropTypes.string,
+
+ // The computed value of align-items.
+ "align-items": PropTypes.string,
+
+ // The computed value of flex-direction.
+ "flex-direction": PropTypes.string,
+
+ // The computed value of flex-wrap.
+ "flex-wrap": PropTypes.string,
+
+ // The computed value of justify-content.
+ "justify-content": PropTypes.string,
+});
+
+/**
+ * A flex container data.
+ */
+const flexContainer = (exports.flexContainer = {
+ // The actor ID of the flex container.
+ actorID: PropTypes.string,
+
+ // An array of flex items belonging to the flex container.
+ flexItems: PropTypes.arrayOf(PropTypes.shape(flexItem)),
+
+ // Whether or not the flex container data represents the selected flex container
+ // or the parent flex container. This is true if the flex container data represents
+ // the parent flex container.
+ isFlexItemContainer: PropTypes.bool,
+
+ // The NodeFront actor ID of the flex item to display in the flex item sizing
+ // properties.
+ flexItemShown: PropTypes.string,
+
+ // The NodeFront of the flex container.
+ nodeFront: PropTypes.object,
+
+ // The computed style properties of the flex container.
+ properties: PropTypes.shape(flexContainerProperties),
+});
+
+/**
+ * The Flexbox UI state.
+ */
+exports.flexbox = {
+ // The color of the flexbox highlighter overlay.
+ color: PropTypes.string,
+
+ // The selected flex container.
+ flexContainer: PropTypes.shape(flexContainer),
+
+ // The selected flex container can also be a flex item. This object contains the
+ // parent flex container properties.
+ flexItemContainer: PropTypes.shape(flexContainer),
+
+ // Whether or not the flexbox highlighter is highlighting the flex container.
+ highlighted: PropTypes.bool,
+};