Bug 1432599 - Part 2: Display the current flex container element in the flexbox panel and allow for toggling of the flexbox highlighter. r=pbro
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 07 Feb 2018 15:57:46 -0500
changeset 403051 b3eb902f01f4b662856bfc9f9fdbe7830d5d380c
parent 403050 5bc50083d6fd713ccce6fdcb78fca9b7a1f3c625
child 403052 c93aa5c37fd333ddb54d59c2dd3a2f99f4cc3ab1
push id99712
push usergabriel.luong@gmail.com
push dateFri, 09 Feb 2018 05:01:09 +0000
treeherdermozilla-inbound@b3eb902f01f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1432599
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1432599 - Part 2: Display the current flex container element in the flexbox panel and allow for toggling of the flexbox highlighter. r=pbro
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,258 @@
 /* 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;
+    }
+    try {
+      this.hasGetCurrentFlexbox = await this.inspector.target.actorHasMethod("layout",
+        "getCurrentFlexbox");
+      this.layoutInspector = await this.walker.getLayoutInspector();
+    } catch (e) {
+      // These calls might fail if called asynchrously after the toolbox is finished
+      // closing.
+      return;
+    }
+
+    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.selection.off("new-node-front", this.onUpdatePanel);
+    this.inspector.sidebar.off("select", this.onSidebarSelect);
+    this.inspector.off("new-root", this.onUpdatePanel);
+
+    this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
+
     this.document = null;
+    this.hasGetCurrentFlexbox = null;
+    this.highlighters = null;
     this.inspector = 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() || !this.store || !this.inspector.selection.nodeFront) {
+      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.selection.off("new-node-front", this.onUpdatePanel);
+      this.inspector.off("new-root", this.onUpdatePanel);
+      return;
+    }
+
+    this.inspector.reflowTracker.trackReflows(this, this.onReflow);
+    this.inspector.selection.on("new-node-front", this.onUpdatePanel);
+    this.inspector.on("new-root", 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: {