Bug 1495717 - More user-friendly Flex item sizing information; r=gl
authorPatrick Brosset <pbrosset@mozilla.com>
Fri, 12 Oct 2018 08:46:34 +0000
changeset 499285 6f5483bd51ed13ebd22d1580725e59904f8020c2
parent 499284 cf62153b17ad98e5cac7d5db80fdc5724698b230
child 499286 7640652718bde21308f808970d0cc0662916bfe7
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1495717
milestone64.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 1495717 - More user-friendly Flex item sizing information; r=gl MozReview-Commit-ID: 3cSeShKP6TE Differential Revision: https://phabricator.services.mozilla.com/D8220
devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js
devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
devtools/client/inspector/flexbox/test/browser.ini
devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_exists.js
devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_has_correct_sections.js
devtools/client/inspector/layout/components/LayoutApp.js
devtools/client/locales/en-US/layout.properties
devtools/client/themes/layout.css
devtools/server/actors/layout.js
devtools/shared/fronts/layout.js
--- a/devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js
@@ -26,18 +26,18 @@ add_task(async function() {
   await testAccordionStateAfterClickingHeader(doc);
   await testAccordionStateAfterSwitchingSidebars(inspector, doc);
   await testAccordionStateAfterReopeningLayoutView(toolbox);
 
   Services.prefs.clearUserPref(BOXMODEL_OPENED_PREF);
 });
 
 function testAccordionStateAfterClickingHeader(doc) {
-  const header = doc.querySelector("#layout-container .box-model-pane ._header");
-  const bContent = doc.querySelector("#layout-container .box-model-pane ._content");
+  const header = doc.querySelector(".layout-container .box-model-pane ._header");
+  const bContent = doc.querySelector(".layout-container .box-model-pane ._content");
 
   info("Checking initial state of the box model panel.");
   is(bContent.style.display, "block", "The box model panel content is 'display: block'.");
   ok(Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
     `${BOXMODEL_OPENED_PREF} is pref on by default.`);
 
   info("Clicking the box model header to hide the box model panel.");
   header.click();
@@ -46,17 +46,17 @@ function testAccordionStateAfterClicking
   is(bContent.style.display, "none", "The box model panel content is 'display: none'.");
   ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
     `${BOXMODEL_OPENED_PREF} is pref off.`);
 }
 
 function testAccordionStateAfterSwitchingSidebars(inspector, doc) {
   info("Checking the box model accordion state is persistent after switching sidebars.");
 
-  const bContent = doc.querySelector("#layout-container .box-model-pane ._content");
+  const bContent = doc.querySelector(".layout-container .box-model-pane ._content");
 
   info("Selecting the computed view.");
   inspector.sidebar.select("computedview");
 
   info("Selecting the layout view.");
   inspector.sidebar.select("layoutview");
 
   info("Checking the state of the box model panel.");
@@ -70,15 +70,15 @@ async function testAccordionStateAfterRe
   + "re-opening the layout view.");
 
   info("Closing the toolbox.");
   await toolbox.destroy();
 
   info("Re-opening the layout view.");
   const { boxmodel } = await openLayoutView();
   const { document: doc } = boxmodel;
-  const bContent = doc.querySelector("#layout-container .box-model-pane ._content");
+  const bContent = doc.querySelector(".layout-container .box-model-pane ._content");
 
   info("Checking the state of the box model panel.");
   ok(!bContent, "The box model panel content is not rendered.");
   ok(!Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
     `${BOXMODEL_OPENED_PREF} is pref off.`);
 }
--- a/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
@@ -41,50 +41,60 @@ class FlexItemSizingOutline extends Pure
         className: "flex-outline-delta",
         style: {
           backgroundColor: colorUtils.setAlpha(this.props.color, 0.1)
         }
       })
     );
   }
 
-  renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize) {
-    const isClamped = mainFinalSize === mainMaxSize ||
-                      mainFinalSize === mainMinSize;
-
+  renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize, isClamped) {
     return (
       dom.div({
         className: "flex-outline-final" + (isClamped ? " clamped" : "")
       })
     );
   }
 
   renderPoint(name) {
     return dom.div({ className: `flex-outline-point ${name}`, "data-label": name });
   }
 
   render() {
     const {
+      flexItemSizing,
+      properties,
+    } = this.props.flexItem;
+    const {
       mainBaseSize,
       mainDeltaSize,
       mainMaxSize,
       mainMinSize,
-    } = this.props.flexItem.flexItemSizing;
+    } = flexItemSizing;
+
     const isRow = this.props.flexDirection.startsWith("row");
+    const dimension = isRow ? "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 = Math.min(mainFinalSize, mainMaxSize);
 
     // The max size is only interesting to show if it did clamp the item
+    // TODO: replace this with the new clamping state that the API will return once bug
+    // 1498273 is fixed.
     const showMax = mainMaxSize === mainFinalSize;
 
     // The min size is only really interesting if it actually clamped the item.
-    const showMin = mainMinSize === mainFinalSize;
+    // Just checking that the main size = final size isn't enough because this may be true
+    // if the max content size is the final size. So also check that min-width/height is
+    // set.
+    // TODO: replace this with the new clamping state that the API will return once bug
+    // 1498273 is fixed.
+    const showMin = mainMinSize === mainFinalSize && properties[`min-${dimension}`];
 
     // Sort all of the dimensions in order to come up with a grid track template.
     // Make mainDeltaSize start from the same point as the other ones so we can compare.
     let sizes = [
       { name: "basis-end", size: mainBaseSize },
       { name: "final-end", size: mainFinalSize }
     ];
 
@@ -132,16 +142,17 @@ class FlexItemSizingOutline extends Pure
             }
           },
           this.renderPoint("basis"),
           this.renderPoint("final"),
           showMin ? this.renderPoint("min") : null,
           showMax ? this.renderPoint("max") : null,
           this.renderBasisOutline(mainBaseSize),
           this.renderDeltaOutline(mainDeltaSize),
-          this.renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize)
+          this.renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize,
+                                  showMin || showMax)
         )
       )
     );
   }
 }
 
 module.exports = FlexItemSizingOutline;
--- a/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
@@ -14,63 +14,338 @@ const Types = require("../types");
 class FlexItemSizingProperties extends PureComponent {
   static get propTypes() {
     return {
       flexDirection: PropTypes.string.isRequired,
       flexItem: PropTypes.shape(Types.flexItem).isRequired,
     };
   }
 
+  /**
+   * Rounds some dimension in pixels and returns a string to be displayed to the user.
+   * The string will end with 'px'. If the number is 0, the string "0" is returned.
+   *
+   * @param  {Number} value
+   *         The number to be rounded
+   * @return {String}
+   *         Representation of the rounded number
+   */
+  getRoundedDimension(value) {
+    if (value == 0) {
+      return "0";
+    }
+    return (Math.round(value * 100) / 100) + "px";
+  }
+
+  /**
+   * Format the flexibility value into a meaningful value for the UI.
+   * If the item grew, then prepend a + sign, if it shrank, prepend a - sign.
+   * If it didn't flex, return "0".
+   *
+   * @param  {Boolean} grew
+   *         Whether the item grew or not
+   * @param  {Number} value
+   *         The amount of pixels the item flexed
+   * @return {String}
+   *         Representation of the flexibility value
+   */
+  getFlexibilityValueString(grew, mainDeltaSize) {
+    const value = this.getRoundedDimension(mainDeltaSize);
+
+    if (grew) {
+      return "+" + value;
+    }
+
+    return value;
+  }
+
+  /**
+   * 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" },
+        dom.span({ className: "theme-fg-color5" }, name),
+        ": ",
+        dom.span({ className: "theme-fg-color1" }, 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, mainMinSize }, properties, dimension) {
+    const flexBasisValue = properties["flex-basis"];
+    const dimensionValue = properties[dimension];
+    const minDimensionValue = properties[`min-${dimension}`];
+    const hasMinClamping = mainMinSize && mainMinSize === mainBaseSize;
+
+    let property = null;
+    let reason = null;
+
+    if (hasMinClamping && minDimensionValue) {
+      // If min clamping happened, then the base size is going to be that value.
+      // TODO: this isn't going to be necessarily true after bug 1498273 is fixed.
+      property = this.renderCssProperty(`min-${dimension}`, minDimensionValue);
+    } else if (flexBasisValue && !hasMinClamping) {
+      // 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.
+      reason = this.renderReasons(
+        [getStr("flexbox.itemSizing.itemBaseSizeFromContent")]);
+    }
+
+    return (
+      dom.li({ className: property ? "section" : "section no-property" },
+        dom.span({ className: "name" },
+          getStr("flexbox.itemSizing.baseSizeSectionHeader")
+        ),
+        dom.span({ className: "value theme-fg-color1" },
+          this.getRoundedDimension(mainBaseSize)
+        ),
+        property,
+        reason
+      )
+    );
+  }
+
+  renderFlexibilitySection(flexItemSizing, properties) {
+    const {
+      mainDeltaSize,
+      mainBaseSize,
+      mainFinalSize,
+      lineGrowthState
+    } = flexItemSizing;
+
+    const flexGrow = properties["flex-grow"];
+    const flexGrow0 = parseFloat(flexGrow) === 0;
+    const flexShrink = properties["flex-shrink"];
+    const flexShrink0 = parseFloat(flexShrink) === 0;
+    const grew = mainDeltaSize > 0;
+    const shrank = mainDeltaSize < 0;
+    // TODO: replace this with the new clamping state that the API will return once bug
+    // 1498273 is fixed.
+    const wasClamped = mainDeltaSize + mainBaseSize !== mainFinalSize;
+
+    const reasons = [];
+
+    // First output a sentence for telling users about whether there was enough room or
+    // not on the line.
+    if (lineGrowthState === "growing") {
+      reasons.push(getStr("flexbox.itemSizing.extraRoomOnLine"));
+    } else if (lineGrowthState === "shrinking") {
+      reasons.push(getStr("flexbox.itemSizing.notEnoughRoomOnLine"));
+    }
+
+    // Then tell users whether the item was set to grow, shrink or none of them.
+    if (flexGrow && !flexGrow0 && lineGrowthState !== "shrinking") {
+      reasons.push(getStr("flexbox.itemSizing.setToGrow"));
+    }
+    if (flexShrink && !flexShrink0 && lineGrowthState !== "growing") {
+      reasons.push(getStr("flexbox.itemSizing.setToShrink"));
+    }
+    if (!grew && !shrank && lineGrowthState === "growing") {
+      reasons.push(getStr("flexbox.itemSizing.notSetToGrow"));
+    }
+    if (!grew && !shrank && lineGrowthState === "shrinking") {
+      reasons.push(getStr("flexbox.itemSizing.notSetToShrink"));
+    }
+
+    let property = null;
+
+    if (grew) {
+      // If the item grew.
+      if (flexGrow) {
+        // It's normally because it was set to grow (flex-grow is non 0).
+        property = this.renderCssProperty("flex-grow", flexGrow);
+      }
+
+      if (wasClamped) {
+        // It may have wanted to grow more than it did, because it was later max-clamped.
+        reasons.push(getStr("flexbox.itemSizing.growthAttemptWhenClamped"));
+      }
+    } else if (shrank) {
+      // If the item shrank.
+      if (flexShrink && !flexShrink0) {
+        // It's either because flex-shrink is non 0.
+        property = this.renderCssProperty("flex-shrink", flexShrink);
+      } else {
+        // Or also because it's default value is 1 anyway.
+        property = this.renderCssProperty("flex-shrink", "1", true);
+      }
+
+      if (wasClamped) {
+        // It might have wanted to shrink more (to accomodate all items) but couldn't
+        // because it was later min-clamped.
+        reasons.push(getStr("flexbox.itemSizing.shrinkAttemptWhenClamped"));
+      }
+    } else if (lineGrowthState === "growing" && flexGrow && !flexGrow0) {
+      // The item did not grow or shrink. There was room on the line and flex-grow was
+      // set, other items have likely used up all of the space.
+      property = this.renderCssProperty("flex-grow", flexGrow);
+      reasons.push(getStr("flexbox.itemSizing.growthAttemptButSiblings"));
+    } else if (lineGrowthState === "shrinking") {
+      // The item did not grow or shrink and there wasn't enough room on the line.
+      if (!flexShrink0) {
+        // flex-shrink was set (either defined in CSS, or via its default value of 1).
+        // but the item didn't shrink.
+        if (flexShrink) {
+          property = this.renderCssProperty("flex-shrink", flexShrink);
+        } else {
+          property = this.renderCssProperty("flex-shrink", 1, true);
+        }
+
+        reasons.push(getStr("flexbox.itemSizing.shrinkAttemptButCouldnt"));
+
+        if (wasClamped) {
+          // Maybe it was clamped.
+          reasons.push(getStr("flexbox.itemSizing.shrinkAttemptWhenClamped"));
+        }
+      } else {
+        // flex-shrink was set to 0, so it didn't shrink.
+        property = this.renderCssProperty("flex-shrink", flexShrink);
+      }
+    }
+
+    // Don't display the section at all if there's nothing useful to show users.
+    if (!property && !reasons.length) {
+      return null;
+    }
+
+    return (
+      dom.li({ className: property ? "section" : "section no-property" },
+        dom.span({ className: "name" },
+          getStr("flexbox.itemSizing.flexibilitySectionHeader")
+        ),
+        dom.span({ className: "value theme-fg-color1" },
+          this.getFlexibilityValueString(grew, mainDeltaSize)
+        ),
+        property,
+        this.renderReasons(reasons)
+      )
+    );
+  }
+
+  renderMinimumSizeSection({ mainMinSize, mainFinalSize }, properties, dimension) {
+    // We only display the minimum size when the item actually violates that size during
+    // layout & is clamped.
+    // For now, we detect this by checking that the min-size is the same as the final size
+    // and that a min-size is actually defined in CSS.
+    // TODO: replace this with the new clamping state that the API will return once bug
+    // 1498273 is fixed.
+    const minDimensionValue = properties[`min-${dimension}`];
+    if (mainMinSize !== mainFinalSize || !minDimensionValue) {
+      return null;
+    }
+
+    return (
+      dom.li({ className: "section" },
+        dom.span({ className: "name" },
+          getStr("flexbox.itemSizing.minSizeSectionHeader")
+        ),
+        dom.span({ className: "value theme-fg-color1" },
+          this.getRoundedDimension(mainMinSize)
+        ),
+        this.renderCssProperty(`min-${dimension}`, minDimensionValue)
+      )
+    );
+  }
+
+  renderMaximumSizeSection({ mainMaxSize, mainFinalSize }, properties, dimension) {
+    // TODO: replace this with the new clamping state that the API will return once bug
+    // 1498273 is fixed.
+    if (mainMaxSize !== mainFinalSize) {
+      return null;
+    }
+
+    const maxDimensionValue = properties[`max-${dimension}`];
+
+    return (
+      dom.li({ className: "section" },
+        dom.span({ className: "name" },
+          getStr("flexbox.itemSizing.maxSizeSectionHeader")
+        ),
+        dom.span({ className: "value theme-fg-color1" },
+          this.getRoundedDimension(mainMaxSize)
+        ),
+        this.renderCssProperty(`max-${dimension}`, maxDimensionValue)
+      )
+    );
+  }
+
+  renderFinalSizeSection({ mainFinalSize }) {
+    return (
+      dom.li({ className: "section no-property" },
+        dom.span({ className: "name" },
+          getStr("flexbox.itemSizing.finalSizeSectionHeader")
+        ),
+        dom.span({ className: "value theme-fg-color1" },
+          this.getRoundedDimension(mainFinalSize)
+        )
+      )
+    );
+  }
+
   render() {
     const {
       flexDirection,
       flexItem,
     } = this.props;
     const {
       flexItemSizing,
       properties,
     } = flexItem;
+    const {
+      mainBaseSize,
+      mainDeltaSize,
+      mainMaxSize,
+      mainMinSize,
+    } = flexItemSizing;
     const dimension = flexDirection.startsWith("row") ? "width" : "height";
-    const contentStr = dimension === "width" ?
-      getStr("flexbox.contentWidth") : getStr("flexbox.contentHeight");
-    const finalStr = dimension === "width" ?
-      getStr("flexbox.finalWidth") : getStr("flexbox.finalHeight");
+
+    // Calculate the final size. This is base + delta, then clamped by min or max.
+    let mainFinalSize = mainBaseSize + mainDeltaSize;
+    mainFinalSize = Math.max(mainFinalSize, mainMinSize);
+    mainFinalSize = Math.min(mainFinalSize, mainMaxSize);
+    flexItemSizing.mainFinalSize = mainFinalSize;
 
     return (
-      dom.ol(
-        {
-          id: "flex-item-sizing-properties",
-          className: "flex-item-list",
-        },
-        dom.li({},
-          dom.span({}, "flex-basis: "),
-          properties["flex-basis"]
-        ),
-        dom.li({},
-          dom.span({}, "flex-grow: "),
-          properties["flex-grow"]
-        ),
-        dom.li({},
-          dom.span({}, "flex-shrink: "),
-          properties["flex-shrink"]
-        ),
-        dom.li({},
-          dom.span({}, `${contentStr} `),
-          `${parseFloat(flexItemSizing.mainBaseSize.toPrecision(6))}px`
-        ),
-        dom.li({},
-          dom.span({}, `Min-${dimension}: `),
-          properties["min-" + dimension]
-        ),
-        dom.li({},
-          dom.span({}, `Max-${dimension}: `),
-          properties["max-" + dimension]
-        ),
-        dom.li({},
-          dom.span({}, `${finalStr} `),
-          `${parseFloat(properties[dimension].toPrecision(6))}px`
-        )
+      dom.ul({ className: "flex-item-sizing" },
+        this.renderBaseSizeSection(flexItemSizing, properties, dimension),
+        this.renderFlexibilitySection(flexItemSizing, properties),
+        this.renderMinimumSizeSection(flexItemSizing, properties, dimension),
+        this.renderMaximumSizeSection(flexItemSizing, properties, dimension),
+        this.renderFinalSizeSection(flexItemSizing)
       )
     );
   }
 }
 
 module.exports = FlexItemSizingProperties;
--- a/devtools/client/inspector/flexbox/test/browser.ini
+++ b/devtools/client/inspector/flexbox/test/browser.ini
@@ -4,10 +4,12 @@ subsuite = devtools
 support-files =
   doc_flexbox_simple.html
   head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
 
 [browser_flexbox_item_outline_exists.js]
+[browser_flexbox_item_outline_has_correct_layout.js]
 [browser_flexbox_item_outline_rotates_for_column.js]
-[browser_flexbox_item_outline_has_correct_layout.js]
+[browser_flexbox_sizing_info_exists.js]
+[browser_flexbox_sizing_info_has_correct_sections.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_exists.js
@@ -0,0 +1,32 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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_simple.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);
+  const expectedTitles = ["Base Size", "Flexibility", "Final Size"];
+
+  ok(expectedTitles.every(title => allSectionTitles.includes(title)),
+     "The 3 main sizing sections where found");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_has_correct_sections.js
@@ -0,0 +1,53 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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_simple.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 } = flexboxInspector;
+
+  for (const { selector, expectedSections } of TEST_DATA) {
+    info(`Checking the list of sections for the flex item ${selector}`);
+    const sections = await selectNodeAndGetFlexSizingSections(selector, inspector, doc);
+
+    is(sections.length, expectedSections.length, "Correct number of sections found");
+    expectedSections.forEach((expectedSection, i) => {
+      is(sections[i], expectedSection, `The ${expectedSection} section was found`);
+    });
+  }
+});
+
+async function selectNodeAndGetFlexSizingSections(selector, inspector, doc) {
+  const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+  await selectNode(selector, inspector);
+  const [flexSizingContainer] = await onFlexItemSizingRendered;
+
+  info(`Getting the list of displayed sections for ${selector}`);
+  const allSections = [...flexSizingContainer.querySelectorAll(".section .name")];
+  const allSectionTitles = allSections.map(el => el.textContent);
+
+  return allSectionTitles;
+}
--- a/devtools/client/inspector/layout/components/LayoutApp.js
+++ b/devtools/client/inspector/layout/components/LayoutApp.js
@@ -134,16 +134,16 @@ class LayoutApp extends PureComponent {
             const opened =  Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF);
             Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF, !opened);
           }
         });
       }
     }
 
     return (
-      dom.div({ id: "layout-container" },
+      dom.div({ className: "layout-container" },
         Accordion({ items })
       )
     );
   }
 }
 
 module.exports = connect(state => state)(LayoutApp);
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -27,16 +27,97 @@ flexbox.flexContainerProperties=Flex Con
 
 # LOCALIZATION NOTE (flexbox.contentWidth, flexbox.contentHeight, flexbox.finalWidth,
 # flexbox.finalHeight): Labels for the flex item sizing properties in the Flexbox panel.
 flexbox.contentWidth=Content width:
 flexbox.contentHeight=Content height:
 flexbox.finalWidth=Final width:
 flexbox.finalHeight=Final height:
 
+# LOCALIZATION NOTE (flexbox.itemSizing.baseSizeSectionHeader): Header label displayed
+# at the start of the flex item sizing Base Size section.
+flexbox.itemSizing.baseSizeSectionHeader=Base Size
+
+# LOCALIZATION NOTE (flexbox.itemSizing.flexibilitySectionHeader): Header label displayed
+# at the start of the flex item sizing Flexibility section.
+flexbox.itemSizing.flexibilitySectionHeader=Flexibility
+
+# LOCALIZATION NOTE (flexbox.itemSizing.minSizeSectionHeader): Header label displayed
+# at the start of the flex item sizing Minimum Size section.
+flexbox.itemSizing.minSizeSectionHeader=Minimum Size
+
+# LOCALIZATION NOTE (flexbox.itemSizing.maxSizeSectionHeader): Header label displayed at
+# the start of the flex item sizing Maximum Size section.
+flexbox.itemSizing.maxSizeSectionHeader=Maximum Size
+
+# LOCALIZATION NOTE (flexbox.itemSizing.finalSizeSectionHeader): Header label displayed at
+# the start of the flex item sizing Final Size section.
+flexbox.itemSizing.finalSizeSectionHeader=Final Size
+
+# LOCALIZATION NOTE (flexbox.itemSizing.itemBaseSizeFromContent): Label shown in the flex
+# item sizing panel. It tells users that a given item’s base size was calculated from its
+# content size when unconstrained.
+flexbox.itemSizing.itemBaseSizeFromContent=The item’s content size when unconstrained.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.itemMinSizeFromItemMinContent): Label shown in the
+# flex item sizing panel. It tells users that a given item’s minimum size is coming from
+# its min-content size.
+flexbox.itemSizing.itemMinSizeFromItemMinContent=This is the element’s minimum content size.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.extraRoomOnLine): Label shown in the flexbox item
+# sizing panel. It tells users that there was extra room to distribute inside a given flex
+# line.
+flexbox.itemSizing.extraRoomOnLine=There was extra room available on the flex line.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.notEnoughRoomOnLine): Label shown in the flexbox
+# item sizing panel. It tells users that there wasn’t enough room inside a given flex line
+# for all of its items.
+flexbox.itemSizing.notEnoughRoomOnLine=There wasn’t enough room available on the flex line.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.growthAttemptWhenClamped): Label shown in the
+# flexbox item sizing panel. It tells users that a given item attempted to grow by a
+# certain amount but ended up being clamped by a max size.
+# (note that clamp is a common word in flexbox terminology. It refers to constraining an
+# item's size to some defined min/max-width/height set on the element, even though there
+# might have been room for it to grow, or reason for it to shrink more).
+flexbox.itemSizing.growthAttemptWhenClamped=The item wanted to grow, but it was clamped.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.shrinkAttemptWhenClamped): Label shown in the
+# flexbox item sizing panel. It tells users that a given item attempted to shrink by a
+# certain amount but ended up being clamped by a min size.
+flexbox.itemSizing.shrinkAttemptWhenClamped=The item wanted to shrink, but it was clamped.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.shrinkAttemptButCouldnt): Label shown in the
+# flexbox item sizing panel. It tells users that a given item attempted to shrink by a
+# certain amount but could not
+flexbox.itemSizing.shrinkAttemptButCouldnt=Item was set to shrink but could not.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.growthAttemptButSiblings): Label shown in the
+# flexbox item sizing panel. It tells users that a given item could not grow to occupy
+# extra space because its siblings have likely already used it.
+flexbox.itemSizing.growthAttemptButSiblings=Item could not grow, siblings have likely used the extra space.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.setToGrow): Label shown in the flex item sizing
+# panel. It tells users that a given item was set to grow.
+flexbox.itemSizing.setToGrow=Item was set to grow.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.setToShrink): Label shown in the flexbox item
+# sizing panel. It tells users that a given item was set to shrink.
+flexbox.itemSizing.setToShrink=Item was set to shrink.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.notSetToGrow): Label shown in the
+# flexbox item sizing panel. It tells users that a given item was not set to grow, even
+# though there might have been space on the flex line for it to grow.
+flexbox.itemSizing.notSetToGrow=Item was not set to grow.
+
+# LOCALIZATION NOTE (flexbox.itemSizing.notSetToShrink): Label shown in the
+# flexbox item sizing panel. It tells users that a given item did not shrink even though
+# there might not have been enough space on the flex line for all items to fit.
+flexbox.itemSizing.notSetToShrink=Item was not set to shrink.
+
 # 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 panel.
 layout.displayAreaNames=Display area names
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,45 +1,45 @@
 /* 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/. */
 
-#layout-container {
+.layout-container {
   height: 100%;
   width: 100%;
   overflow-y: auto;
   overflow-x: auto;
   min-width: 200px;
 }
 
-#layout-container .accordion ._content {
+.layout-container .accordion ._content {
   padding: 0;
 }
 
 #layout-container .accordion ._header {
   white-space: nowrap;
   text-overflow: ellipsis;
   overflow: hidden;
 }
 
 /**
  * Common styles for the layout container
  */
 
-#layout-container li {
+.layout-container li {
   padding: 3px 0;
   -moz-user-select: none;
 }
 
-#layout-container input {
+.layout-container input {
   margin-inline-end: 7px;
   vertical-align: middle;
 }
 
-#layout-container label {
+.layout-container label {
   margin-inline-start: -3px;
 }
 
 .layout-color-swatch {
   width: 12px;
   height: 12px;
   margin-inline-start: -1px;
   border: 1px solid var(--theme-highlight-gray);
@@ -358,22 +358,57 @@
 .flex-outline.shrinking .flex-outline-point.final::before {
   border-width: 0 1px 0 0;
 }
 
 /**
  * Flex Item Sizing Properties
  */
 
-#flex-item-sizing-properties {
-  padding-top: 0;
+.flex-item-sizing {
+  margin: 20px;
+  padding: 0;
+  list-style: none;
+}
+
+.flex-item-sizing .section {
+  --padding: 10px;
+  margin-block-start: var(--padding);
+  padding: var(--padding) 0 0 0;
+  border-block-start: 1px solid var(--theme-splitter-color);
+  display: grid;
+  grid-template-columns: 1fr max-content;
+  grid-column-gap: var(--padding);
+}
+
+.flex-item-sizing .section:first-child {
+  margin: 0;
 }
 
-#flex-item-sizing-properties span {
- font-weight: 600;
+.flex-item-sizing .name {
+  font-weight: 600;
+  grid-column: 1;
+}
+
+.flex-item-sizing .value {
+  text-align: end;
+  font-weight: 600;
+}
+
+.flex-item-sizing .css-property-link {
+  grid-column: 2;
+  text-align: end;
+}
+
+.flex-item-sizing .reasons,
+.flex-item-sizing .reasons li {
+  grid-column: 1 / 3;
+  margin: 0;
+  padding: 0;
+  list-style: none;
 }
 
 /**
  * Flex Container Properties
  */
 
 #flex-container-properties {
   border-block-start: 1px solid var(--theme-splitter-color);
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -7,21 +7,21 @@
 const { Cu } = require("chrome");
 const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const {
   flexboxSpec,
   flexItemSpec,
   gridSpec,
   layoutSpec,
 } = require("devtools/shared/specs/layout");
-const { ELEMENT_NODE } = require("devtools/shared/dom-node-constants");
 const { SHOW_ELEMENT } = require("devtools/shared/dom-node-filter-constants");
 const { getStringifiableFragments } =
   require("devtools/server/actors/utils/css-grid-utils");
 
+loader.lazyRequireGetter(this, "getCSSStyleRules", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/actors/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 
 /**
  * Set of actors the expose the CSS layout information to the devtools protocol clients.
  *
  * The |Layout| actor is the main entry point. It is used to get various CSS
  * layout-related information from the document.
@@ -98,27 +98,24 @@ const FlexboxActor = ActorClassWithSpec(
     if (!flex) {
       return [];
     }
 
     const flexItemActors = [];
 
     for (const line of flex.getLines()) {
       for (const item of line.getItems()) {
-        if (item.node.nodeType !== ELEMENT_NODE) {
-          continue;
-        }
-
         flexItemActors.push(new FlexItemActor(this, item.node, {
           crossMaxSize: item.crossMaxSize,
           crossMinSize: item.crossMinSize,
           mainBaseSize: item.mainBaseSize,
           mainDeltaSize: item.mainDeltaSize,
           mainMaxSize: item.mainMaxSize,
           mainMinSize: item.mainMinSize,
+          lineGrowthState: line.growthState,
         }));
       }
     }
 
     return flexItemActors;
   },
 });
 
@@ -152,42 +149,60 @@ const FlexItemActor = ActorClassWithSpec
     this.walker = null;
   },
 
   form(detail) {
     if (detail === "actorid") {
       return this.actorID;
     }
 
+    const { flexDirection } = CssLogic.getComputedStyle(this.containerEl);
+    const dimension = flexDirection.startsWith("row") ? "width" : "height";
+
+    // Find the authored sizing properties for this item.
+    const properties = {
+      "flex-basis": "",
+      "flex-grow": "",
+      "flex-shrink": "",
+      [`min-${dimension}`]: "",
+      [`max-${dimension}`]: "",
+      [dimension]: ""
+    };
+
+    if (this.element.nodeType === this.element.ELEMENT_NODE) {
+      for (const name in properties) {
+        let value = "";
+        // Look first on the element style.
+        if (this.element.style[name] && this.element.style[name] !== "auto") {
+          value = this.element.style[name];
+        } else {
+          // And then on the rules that apply to the element.
+          // getCSSStyleRules returns rules from least to most specific, so override
+          // values as we find them.
+          const cssRules = getCSSStyleRules(this.element);
+          for (const rule of cssRules) {
+            const rulePropertyValue = rule.style.getPropertyValue(name);
+            if (rulePropertyValue && rulePropertyValue !== "auto") {
+              value = rulePropertyValue;
+            }
+          }
+        }
+
+        properties[name] = value;
+      }
+    }
+
     const form = {
       actor: this.actorID,
       // The flex item sizing data.
       flexItemSizing: this.flexItemSizing,
+      // The authored style properties of the flex item.
+      properties,
     };
 
-    if (this.element.nodeType === ELEMENT_NODE) {
-      const { flexDirection } = CssLogic.getComputedStyle(this.containerEl);
-      const styles = CssLogic.getComputedStyle(this.element);
-      const clientRect = this.element.getBoundingClientRect();
-      const dimension = flexDirection.startsWith("row") ? "width" : "height";
-
-      // The computed style properties of the flex item.
-      form.properties = {
-        "flex-basis": styles.flexBasis,
-        "flex-grow": styles.flexGrow,
-        "flex-shrink": styles.flexShrink,
-        // min-width/height computed style.
-        [`min-${dimension}`]: styles[`min-${dimension}`],
-        // max-width/height computed style.
-        [`max-${dimension}`]: styles[`max-${dimension}`],
-        // Computed width/height of the flex item element.
-        [dimension]: parseFloat(clientRect[dimension.toLowerCase()].toPrecision(6)),
-      };
-    }
-
     // If the WalkerActor already knows the flex item element, then also return its
     // ActorID so we avoid the client from doing another round trip to get it in many
     // cases.
     if (this.walker.hasNode(this.element)) {
       form.nodeActorID = this.walker.getNode(this.element).actorID;
     }
 
     return form;
--- a/devtools/shared/fronts/layout.js
+++ b/devtools/shared/fronts/layout.js
@@ -65,17 +65,17 @@ const FlexItemFront = FrontClassWithSpec
     if (!this._form.nodeActorID) {
       return null;
     }
 
     return this.conn.getActor(this._form.nodeActorID);
   },
 
   /**
-   * Get the computed style properties for the flex item.
+   * Get the style properties for the flex item.
    */
   get properties() {
     return this._form.properties;
   },
 });
 
 const GridFront = FrontClassWithSpec(gridSpec, {
   form: function(form, detail) {