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