--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -943,17 +943,18 @@ public:
* If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
*/
nscoord GetLastBaselineOffset() const {
return mLastBaselineOffset;
}
// Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
// CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
- void ResolveFlexibleLengths(nscoord aFlexContainerMainSize);
+ void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
+ ComputedFlexLineInfo* aLineInfo);
void PositionItemsInMainAxis(uint8_t aJustifyContent,
nscoord aContentBoxMainSize,
const FlexboxAxisTracker& aAxisTracker);
void PositionItemsInCrossAxis(nscoord aLineStartPosition,
const FlexboxAxisTracker& aAxisTracker);
@@ -2375,33 +2376,36 @@ FlexLine::FreezeOrRestoreEachFlexibleSiz
// Clear this item's violation(s), now that we've dealt with them
item->ClearViolationFlags();
}
}
}
void
-FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize)
+FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
+ ComputedFlexLineInfo* aLineInfo)
{
MOZ_LOG(gFlexContainerLog, LogLevel::Debug, ("ResolveFlexibleLengths\n"));
// Determine whether we're going to be growing or shrinking items.
const bool isUsingFlexGrow =
(mTotalOuterHypotheticalMainSize < aFlexContainerMainSize);
// Do an "early freeze" for flex items that obviously can't flex in the
// direction we've chosen:
FreezeItemsEarly(isUsingFlexGrow);
- if (mNumFrozenItems == mNumItems) {
- // All our items are frozen, so we have no flexible lengths to resolve.
+ if ((mNumFrozenItems == mNumItems) && !aLineInfo) {
+ // All our items are frozen, so we have no flexible lengths to resolve,
+ // and we aren't being asked to generate computed line info.
return;
}
- MOZ_ASSERT(!IsEmpty(), "empty lines should take the early-return above");
+ MOZ_ASSERT(!IsEmpty() || aLineInfo,
+ "empty lines should take the early-return above");
// Subtract space occupied by our items' margins/borders/padding, so we can
// just be dealing with the space available for our flex items' content
// boxes.
nscoord spaceReservedForMarginBorderPadding =
mTotalOuterHypotheticalMainSize - mTotalInnerHypotheticalMainSize;
nscoord spaceAvailableForFlexItemsContentBoxes =
@@ -2425,16 +2429,31 @@ FlexLine::ResolveFlexibleLengths(nscoord
nscoord availableFreeSpace = spaceAvailableForFlexItemsContentBoxes;
for (FlexItem* item = mItems.getFirst(); item; item = item->getNext()) {
if (!item->IsFrozen()) {
item->SetMainSize(item->GetFlexBaseSize());
}
availableFreeSpace -= item->GetMainSize();
}
+ // If we have an aLineInfo structure to fill out, and this is the
+ // first time through the loop, capture these sizes as mainBaseSizes.
+ // We only care about the first iteration, because additional
+ // iterations will only reset item base sizes to these values.
+ // We also set a 0 mainDeltaSize. This will be modified later if
+ // the item is stretched or shrunk.
+ if (aLineInfo && (iterationCounter == 0)) {
+ uint32_t itemIndex = 0;
+ for (FlexItem* item = mItems.getFirst(); item; item = item->getNext(),
+ ++itemIndex) {
+ aLineInfo->mItems[itemIndex].mMainBaseSize = item->GetMainSize();
+ aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
+ }
+ }
+
MOZ_LOG(gFlexContainerLog, LogLevel::Debug,
(" available free space = %d\n", availableFreeSpace));
// The sign of our free space should agree with the type of flexing
// (grow/shrink) that we're doing (except if we've had integer overflow;
// then, all bets are off). Any disagreement should've made us use the
// other type of flexing, or should've been resolved in FreezeItemsEarly.
@@ -2590,16 +2609,55 @@ FlexLine::ResolveFlexibleLengths(nscoord
availableFreeSpace -= sizeDelta;
item->SetMainSize(item->GetMainSize() + sizeDelta);
MOZ_LOG(gFlexContainerLog, LogLevel::Debug,
(" child %p receives %d, for a total of %d\n",
item, sizeDelta, item->GetMainSize()));
}
}
+
+ // If we have an aLineInfo structure to fill out, capture any
+ // size changes that may have occurred in the previous loop.
+ // We don't do this inside the previous loop, because we don't
+ // want to burden layout when aLineInfo is null.
+ if (aLineInfo) {
+ uint32_t itemIndex = 0;
+ for (FlexItem* item = mItems.getFirst(); item; item = item->getNext(),
+ ++itemIndex) {
+ // Calculate a deltaSize that represents how much the
+ // flex sizing algorithm "wants" to stretch or shrink this
+ // item during this pass through the algorithm. Later
+ // passes through the algorithm may overwrite this value.
+ // Also, this value may not reflect how much the size of
+ // the item is actually changed, since the size of the
+ // item will be clamped to min and max values later in
+ // this pass. That's intentional, since we want to report
+ // the value that the sizing algorithm tried to stretch
+ // or shrink the item.
+ nscoord deltaSize = item->GetMainSize() -
+ aLineInfo->mItems[itemIndex].mMainBaseSize;
+
+ aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
+ // If any item on the line is growing, mark the aLineInfo
+ // structure; likewise if any item is shrinking. Items in
+ // a line can't be both growing and shrinking.
+ if (deltaSize > 0) {
+ MOZ_ASSERT(aLineInfo->mGrowthState !=
+ ComputedFlexLineInfo::GrowthState::SHRINKING);
+ aLineInfo->mGrowthState =
+ ComputedFlexLineInfo::GrowthState::GROWING;
+ } else if (deltaSize < 0) {
+ MOZ_ASSERT(aLineInfo->mGrowthState !=
+ ComputedFlexLineInfo::GrowthState::GROWING);
+ aLineInfo->mGrowthState =
+ ComputedFlexLineInfo::GrowthState::SHRINKING;
+ }
+ }
+ }
}
}
// Fix min/max violations:
nscoord totalViolation = 0; // keeps track of adjustments for min/max
MOZ_LOG(gFlexContainerLog, LogLevel::Debug,
(" Checking for violations:"));
@@ -3969,16 +4027,34 @@ nsFlexContainerFrame::Reflow(nsPresConte
eStyleUnit_Auto != stylePos->mOffset.GetBEndUnit(wm))) {
AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
}
RenumberList();
const FlexboxAxisTracker axisTracker(this, aReflowInput.GetWritingMode());
+ // Check to see if we need to create a computed info structure, to
+ // be filled out for use by devtools.
+ if (HasAnyStateBits(NS_STATE_FLEX_GENERATE_COMPUTED_VALUES)) {
+ // This state bit will never be cleared. That's acceptable because
+ // it's only set in a Chrome API invoked by devtools, and won't
+ // impact normal browsing.
+
+ // Re-use the ComputedFlexContainerInfo, if it exists.
+ ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
+ if (info) {
+ // We can reuse, as long as we clear out old data.
+ info->mLines.Clear();
+ } else {
+ info = new ComputedFlexContainerInfo();
+ SetProperty(FlexContainerInfo(), info);
+ }
+ }
+
// If we're being fragmented into a constrained BSize, then subtract off
// borderpadding BStart from that constrained BSize, to get the available
// BSize for our content box. (No need to subtract the borderpadding BStart
// if we're already skipping it via GetLogicalSkipSides, though.)
nscoord availableBSizeForContent = aReflowInput.AvailableBSize();
if (availableBSizeForContent != NS_UNCONSTRAINEDSIZE &&
!(GetLogicalSkipSides(&aReflowInput).BStart())) {
availableBSizeForContent -=
@@ -4112,16 +4188,69 @@ nsFlexContainerFrame::CalculatePackingSp
// Use half of that edge packing space right now:
*aFirstSubjectOffset += totalEdgePackingSpace / 2;
// ...but we need to subtract all of it right away, so that we won't
// hand out any of it to intermediate packing spaces.
*aPackingSpaceRemaining -= totalEdgePackingSpace;
}
+nsFlexContainerFrame*
+nsFlexContainerFrame::GetFlexFrameWithComputedInfo(nsIFrame* aFrame)
+{
+ // Prepare a lambda function that we may need to call multiple times.
+ auto GetFlexContainerFrame = [](nsIFrame *aFrame) {
+ // Return the aFrame's content insertion frame, iff it is
+ // a flex container frame.
+ nsFlexContainerFrame* flexFrame = nullptr;
+
+ if (aFrame) {
+ nsIFrame* contentFrame = aFrame->GetContentInsertionFrame();
+ if (contentFrame && (contentFrame->IsFlexContainerFrame())) {
+ flexFrame = static_cast<nsFlexContainerFrame*>(contentFrame);
+ }
+ }
+ return flexFrame;
+ };
+
+ nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame);
+ if (flexFrame) {
+ // Generate the FlexContainerInfo data, if it's not already there.
+ bool reflowNeeded = !flexFrame->HasProperty(FlexContainerInfo());
+
+ if (reflowNeeded) {
+ // Trigger a reflow that generates additional flex property data.
+ // Hold onto aFrame while we do this, in case reflow destroys it.
+ AutoWeakFrame weakFrameRef(aFrame);
+
+ nsIPresShell* shell = flexFrame->PresContext()->PresShell();
+ flexFrame->AddStateBits(NS_STATE_FLEX_GENERATE_COMPUTED_VALUES);
+ shell->FrameNeedsReflow(flexFrame,
+ nsIPresShell::eResize,
+ NS_FRAME_IS_DIRTY);
+ shell->FlushPendingNotifications(FlushType::Layout);
+
+ // Since the reflow may have side effects, get the flex frame
+ // again. But if the weakFrameRef is no longer valid, then we
+ // must bail out.
+ if (!weakFrameRef.IsAlive()) {
+ return nullptr;
+ }
+
+ flexFrame = GetFlexContainerFrame(weakFrameRef.GetFrame());
+
+ MOZ_ASSERT(!flexFrame ||
+ flexFrame->HasProperty(FlexContainerInfo()),
+ "The state bit should've made our forced-reflow "
+ "generate a FlexContainerInfo object");
+ }
+ }
+ return flexFrame;
+}
+
void
nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus,
nscoord aContentBoxMainSize,
nscoord aAvailableBSizeForContent,
nsTArray<StrutInfo>& aStruts,
@@ -4142,23 +4271,80 @@ nsFlexContainerFrame::DoFlexLayout(nsPre
if (lines.getFirst()->IsEmpty() &&
!lines.getFirst()->getNext()) {
// We have no flex items, our parent should synthesize a baseline if needed.
AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
} else {
RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
}
+ // Construct our computed info if we've been asked to do so. This is
+ // necessary to do now so we can capture some computed values for
+ // FlexItems during layout that would not otherwise be saved (like
+ // size adjustments). We'll later fix up the line properties,
+ // because the correct values aren't available yet.
+ ComputedFlexContainerInfo* containerInfo = nullptr;
+ if (HasAnyStateBits(NS_STATE_FLEX_GENERATE_COMPUTED_VALUES)) {
+ containerInfo = GetProperty(FlexContainerInfo());
+ MOZ_ASSERT(containerInfo,
+ "::Reflow() should have created container info.");
+
+ if (!aStruts.IsEmpty()) {
+ // We restarted DoFlexLayout, and may have stale mLines to clear:
+ containerInfo->mLines.Clear();
+ } else {
+ MOZ_ASSERT(containerInfo->mLines.IsEmpty(),
+ "Shouldn't have lines yet.");
+ }
+
+ for (const FlexLine* line = lines.getFirst(); line;
+ line = line->getNext()) {
+ ComputedFlexLineInfo* lineInfo =
+ containerInfo->mLines.AppendElement();
+ // Most lineInfo properties will be set later, but we set
+ // mGrowthState to UNCHANGED here because it may be later
+ // modified by ResolveFlexibleLengths().
+ lineInfo->mGrowthState =
+ ComputedFlexLineInfo::GrowthState::UNCHANGED;
+
+ // The remaining lineInfo properties will be filled out at the
+ // end of this function, when we have real values. But we still
+ // add all the items here, so we can capture computed data for
+ // each item.
+ for (const FlexItem* item = line->GetFirstItem(); item;
+ item = item->getNext()) {
+ ComputedFlexItemInfo* itemInfo =
+ lineInfo->mItems.AppendElement();
+
+ itemInfo->mNode = item->Frame()->GetContent();
+
+ // mMainBaseSize and itemInfo->mMainDeltaSize will
+ // be filled out in ResolveFlexibleLengths().
+
+ // Other FlexItem properties can be captured now.
+ itemInfo->mMainMinSize = item->GetMainMinSize();
+ itemInfo->mMainMaxSize = item->GetMainMaxSize();
+ itemInfo->mCrossMinSize = item->GetCrossMinSize();
+ itemInfo->mCrossMaxSize = item->GetCrossMaxSize();
+ }
+ }
+ }
+
aContentBoxMainSize =
ResolveFlexContainerMainSize(aReflowInput, aAxisTracker,
aContentBoxMainSize, aAvailableBSizeForContent,
lines.getFirst(), aStatus);
- for (FlexLine* line = lines.getFirst(); line; line = line->getNext()) {
- line->ResolveFlexibleLengths(aContentBoxMainSize);
+ uint32_t lineIndex = 0;
+ for (FlexLine* line = lines.getFirst(); line; line = line->getNext(),
+ ++lineIndex) {
+ ComputedFlexLineInfo* lineInfo = containerInfo ?
+ &containerInfo->mLines[lineIndex] :
+ nullptr;
+ line->ResolveFlexibleLengths(aContentBoxMainSize, lineInfo);
}
// Cross Size Determination - Flexbox spec section 9.4
// ===================================================
// Calculate the hypothetical cross size of each item:
nscoord sumLineCrossSizes = 0;
for (FlexLine* line = lines.getFirst(); line; line = line->getNext()) {
for (FlexItem* item = line->GetFirstItem(); item; item = item->getNext()) {
@@ -4458,16 +4644,29 @@ nsFlexContainerFrame::DoFlexLayout(nsPre
for (nsIFrame* childFrame : mFrames) {
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, childFrame);
}
FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
aReflowInput, aStatus);
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize)
+
+ // Finally update our line sizing values in our containerInfo.
+ if (containerInfo) {
+ uint32_t lineIndex = 0;
+ for (const FlexLine* line = lines.getFirst(); line;
+ line = line->getNext(), ++lineIndex) {
+ ComputedFlexLineInfo* lineInfo = &containerInfo->mLines[lineIndex];
+
+ lineInfo->mCrossSize = line->GetLineCrossSize();
+ lineInfo->mFirstBaselineOffset = line->GetFirstBaselineOffset();
+ lineInfo->mLastBaselineOffset = line->GetLastBaselineOffset();
+ }
+ }
}
void
nsFlexContainerFrame::MoveFlexItemToFinalPosition(
const ReflowInput& aReflowInput,
const FlexItem& aItem,
LogicalPoint& aFramePos,
const nsSize& aContainerSize)
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -15,16 +15,64 @@
namespace mozilla {
template <class T> class LinkedList;
class LogicalPoint;
} // namespace mozilla
nsContainerFrame* NS_NewFlexContainerFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext);
+/**
+ * These structures are used to capture data during reflow to be
+ * extracted by devtools via Chrome APIs. The structures are only
+ * created when requested in GetFlexFrameWithComputedInfo(), and
+ * the structures are attached to the nsFlexContainerFrame via the
+ * FlexContainerInfo property.
+ */
+struct ComputedFlexItemInfo
+{
+ nsCOMPtr<nsINode> mNode;
+ /**
+ * mMainBaseSize is a measure of the size of the item in the main
+ * axis before the flex sizing algorithm is applied. In the spec,
+ * this is called "flex base size", but we use this name to connect
+ * the value to the other main axis sizes.
+ */
+ nscoord mMainBaseSize;
+ /**
+ * mMainDeltaSize is the value that the flex sizing algorithm
+ * "wants" to use to stretch or shrink the item, before clamping to
+ * the item's main min and max sizes. Since the flex sizing
+ * algorithm proceeds linearly, the mMainDeltaSize for an item only
+ * respects the resolved size of items already frozen.
+ */
+ nscoord mMainDeltaSize;
+ nscoord mMainMinSize;
+ nscoord mMainMaxSize;
+ nscoord mCrossMinSize;
+ nscoord mCrossMaxSize;
+};
+
+struct ComputedFlexLineInfo
+{
+ nsTArray<ComputedFlexItemInfo> mItems;
+ nscoord mCrossSize;
+ nscoord mFirstBaselineOffset;
+ nscoord mLastBaselineOffset;
+ enum GrowthState {
+ UNCHANGED,
+ SHRINKING,
+ GROWING,
+ } mGrowthState;
+};
+
+struct ComputedFlexContainerInfo
+{
+ nsTArray<ComputedFlexLineInfo> mLines;
+};
/**
* This is the rendering object used for laying out elements with
* "display: flex" or "display: inline-flex".
*
* We also use this class for elements with "display: -webkit-box" or
* "display: -webkit-inline-box" (but not "-moz-box" / "-moz-inline-box" --
* those are rendered with old-school XUL frame classes).
@@ -103,35 +151,59 @@ public:
uint16_t CSSAlignmentForAbsPosChild(
const ReflowInput& aChildRI,
mozilla::LogicalAxis aLogicalAxis) const override;
// Flexbox-specific public methods
bool IsHorizontal();
/**
- * Helper function to calculate packing space and initial offset of alignment
- * subjects in MainAxisPositionTracker() and CrossAxisPositionTracker() for
- * space-between, space-around, and space-evenly.
- *
- * @param aNumThingsToPack Number of alignment subjects.
- * @param aAlignVal Value for align-self or justify-self.
- * @param aFirstSubjectOffset Outparam for first subject offset.
- * @param aNumPackingSpacesRemaining Outparam for number of equal-sized
- * packing spaces to apply between each
- * alignment subject.
- * @param aPackingSpaceRemaining Outparam for total amount of packing
- * space to be divided up.
- */
+ * Helper function to calculate packing space and initial offset of alignment
+ * subjects in MainAxisPositionTracker() and CrossAxisPositionTracker() for
+ * space-between, space-around, and space-evenly.
+ * * @param aNumThingsToPack Number of alignment subjects.
+ * @param aAlignVal Value for align-self or justify-self.
+ * @param aFirstSubjectOffset Outparam for first subject offset.
+ * @param aNumPackingSpacesRemaining Outparam for number of equal-sized
+ * packing spaces to apply between each
+ * alignment subject.
+ * @param aPackingSpaceRemaining Outparam for total amount of packing
+ * space to be divided up.
+ */
static void CalculatePackingSpace(uint32_t aNumThingsToPack,
uint8_t aAlignVal,
nscoord* aFirstSubjectOffset,
uint32_t* aNumPackingSpacesRemaining,
nscoord* aPackingSpaceRemaining);
+ /**
+ * This property is created by a call to
+ * nsFlexContainerFrame::GetFlexFrameWithComputedInfo.
+ */
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(FlexContainerInfo, ComputedFlexContainerInfo)
+ /**
+ * This function should only be called on a nsFlexContainerFrame
+ * that has just been returned by a call to
+ * GetFlexFrameWithComputedInfo.
+ */
+ const ComputedFlexContainerInfo* GetFlexContainerInfo()
+ {
+ const ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
+ MOZ_ASSERT(info, "Property generation wasn't requested.");
+ return info;
+ }
+
+ /**
+ * Return aFrame as a flex frame after ensuring it has computed flex info.
+ * @return nullptr if aFrame is null or doesn't have a flex frame
+ * as its content insertion frame.
+ * @note this might destroy layout/style data since it may flush layout.
+ */
+ static nsFlexContainerFrame* GetFlexFrameWithComputedInfo(nsIFrame* aFrame);
+
protected:
// Protected constructor & destructor
explicit nsFlexContainerFrame(nsStyleContext* aContext)
: nsContainerFrame(aContext, kClassID)
, mBaselineFromLastReflow(NS_INTRINSIC_WIDTH_UNKNOWN)
, mLastBaselineFromLastReflow(NS_INTRINSIC_WIDTH_UNKNOWN)
{}
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -318,18 +318,21 @@ FRAME_STATE_GROUP(FlexContainer, nsFlexC
// True iff the normal flow children are already in CSS 'order' in the
// order they occur in the child frame list.
FRAME_STATE_BIT(FlexContainer, 20, NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
// Set for a flex container that is emulating a legacy
// 'display:-webkit-{inline-}box' container.
FRAME_STATE_BIT(FlexContainer, 21, NS_STATE_FLEX_IS_LEGACY_WEBKIT_BOX)
+// True iff computed flex values should be generated on the next reflow
+FRAME_STATE_BIT(FlexContainer, 22, NS_STATE_FLEX_GENERATE_COMPUTED_VALUES)
+
// True if the container has no flex items; may lie if there is a pending reflow
-FRAME_STATE_BIT(FlexContainer, 22, NS_STATE_FLEX_SYNTHESIZE_BASELINE)
+FRAME_STATE_BIT(FlexContainer, 23, NS_STATE_FLEX_SYNTHESIZE_BASELINE)
// == Frame state bits that apply to grid container frames ====================
FRAME_STATE_GROUP(GridContainer, nsGridContainerFrame)
// True iff the normal flow children are already in CSS 'order' in the
// order they occur in the child frame list.
FRAME_STATE_BIT(GridContainer, 20, NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)