forked from mirrors/gecko-dev
		
	 da97c00bb1
			
		
	
	
		da97c00bb1
		
	
	
	
	
		
			
			Instead, sort stuff using CSSOrderAwareFrameIterator. The current sorting is broken in presence of dynamic insertions, consider the following <Child(order)> combination in the DOM: <A(1000)> <B(0)> That'd look like: <B(0)> <A(1000)> On the frame tree. However when appending a child before B so that the DOM looks like: <A(1000)> <C(0)> <B(0)> The frame constructor will properly insert after A, and the reordering, which is stable, will end up with: <B(0)> <C(0)> <A(1000)> Which is the wrong frame tree order. We only use -moz-box-ordinal-group in regular sprocket layout, so just handle it there rather than everywhere. Similarly, we only rely on it for in-flow stuff, so remove the test for that added in bug 877890 (flex changed behavior afterwards, interestingly enough). Differential Revision: https://phabricator.services.mozilla.com/D94790
		
			
				
	
	
		
			1487 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1487 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* 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/. */
 | |
| 
 | |
| //
 | |
| // Eric Vaughan
 | |
| // Netscape Communications
 | |
| //
 | |
| // See documentation in associated header file
 | |
| //
 | |
| 
 | |
| #include "nsBoxLayoutState.h"
 | |
| #include "nsSprocketLayout.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsIContent.h"
 | |
| #include "nsContainerFrame.h"
 | |
| #include "nsBoxFrame.h"
 | |
| #include "StackArena.h"
 | |
| #include "mozilla/Likely.h"
 | |
| #include "mozilla/CSSOrderAwareFrameIterator.h"
 | |
| #include <algorithm>
 | |
| 
 | |
| using mozilla::StyleDirection;
 | |
| using namespace mozilla;
 | |
| 
 | |
| nsBoxLayout* nsSprocketLayout::gInstance = nullptr;
 | |
| 
 | |
| static Maybe<CSSOrderAwareFrameIterator> IterFor(nsIFrame* aBoxFrame) {
 | |
|   Maybe<CSSOrderAwareFrameIterator> ret;
 | |
|   if (aBoxFrame->IsXULBoxFrame()) {
 | |
|     ret.emplace(aBoxFrame, mozilla::layout::kPrincipalList,
 | |
|                 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
 | |
|                 CSSOrderAwareFrameIterator::OrderState::Unknown,
 | |
|                 CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup);
 | |
|   }
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| nsresult NS_NewSprocketLayout(nsCOMPtr<nsBoxLayout>& aNewLayout) {
 | |
|   if (!nsSprocketLayout::gInstance) {
 | |
|     nsSprocketLayout::gInstance = new nsSprocketLayout();
 | |
|     NS_IF_ADDREF(nsSprocketLayout::gInstance);
 | |
|   }
 | |
|   // we have not instance variables so just return our static one.
 | |
|   aNewLayout = nsSprocketLayout::gInstance;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /*static*/
 | |
| void nsSprocketLayout::Shutdown() { NS_IF_RELEASE(gInstance); }
 | |
| 
 | |
| nsSprocketLayout::nsSprocketLayout() = default;
 | |
| 
 | |
| bool nsSprocketLayout::IsXULHorizontal(nsIFrame* aBox) {
 | |
|   return aBox->HasAnyStateBits(NS_STATE_IS_HORIZONTAL);
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::GetFrameState(nsIFrame* aBox, nsFrameState& aState) {
 | |
|   aState = aBox->GetStateBits();
 | |
| }
 | |
| 
 | |
| static StyleDirection GetFrameDirection(nsIFrame* aBox) {
 | |
|   return aBox->StyleVisibility()->mDirection;
 | |
| }
 | |
| 
 | |
| static void HandleBoxPack(nsIFrame* aBox, const nsFrameState& aFrameState,
 | |
|                           nscoord& aX, nscoord& aY, const nsRect& aOriginalRect,
 | |
|                           const nsRect& aClientRect) {
 | |
|   // In the normal direction we lay out our kids in the positive direction
 | |
|   // (e.g., |x| will get bigger for a horizontal box, and |y| will get bigger
 | |
|   // for a vertical box).  In the reverse direction, the opposite is true. We'll
 | |
|   // be laying out each child at a smaller |x| or |y|.
 | |
|   StyleDirection frameDirection = GetFrameDirection(aBox);
 | |
| 
 | |
|   if (aFrameState & NS_STATE_IS_HORIZONTAL) {
 | |
|     if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) {
 | |
|       // The normal direction. |x| increases as we move through our children.
 | |
|       aX = aClientRect.x;
 | |
|     } else {
 | |
|       // The reverse direction. |x| decreases as we move through our children.
 | |
|       aX = aClientRect.x + aOriginalRect.width;
 | |
|     }
 | |
|     // |y| is always in the normal direction in horizontal boxes
 | |
|     aY = aClientRect.y;
 | |
|   } else {
 | |
|     // take direction property into account for |x| in vertical boxes
 | |
|     if (frameDirection == StyleDirection::Ltr) {
 | |
|       // The normal direction. |x| increases as we move through our children.
 | |
|       aX = aClientRect.x;
 | |
|     } else {
 | |
|       // The reverse direction. |x| decreases as we move through our children.
 | |
|       aX = aClientRect.x + aOriginalRect.width;
 | |
|     }
 | |
|     if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) {
 | |
|       // The normal direction. |y| increases as we move through our children.
 | |
|       aY = aClientRect.y;
 | |
|     } else {
 | |
|       // The reverse direction. |y| decreases as we move through our children.
 | |
|       aY = aClientRect.y + aOriginalRect.height;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Get our pack/alignment information.
 | |
|   nsIFrame::Halignment halign = aBox->GetXULHAlign();
 | |
|   nsIFrame::Valignment valign = aBox->GetXULVAlign();
 | |
| 
 | |
|   // The following code handles box PACKING.  Packing comes into play in the
 | |
|   // case where the computed size for all of our children (now stored in our
 | |
|   // client rect) is smaller than the size available for the box (stored in
 | |
|   // |aOriginalRect|).
 | |
|   //
 | |
|   // Here we adjust our |x| and |y| variables accordingly so that we start at
 | |
|   // the beginning, middle, or end of the box.
 | |
|   //
 | |
|   // XXXdwh JUSTIFY needs to be implemented!
 | |
|   if (aFrameState & NS_STATE_IS_HORIZONTAL) {
 | |
|     switch (halign) {
 | |
|       case nsBoxFrame::hAlign_Left:
 | |
|         break;  // Nothing to do.  The default initialized us properly.
 | |
| 
 | |
|       case nsBoxFrame::hAlign_Center:
 | |
|         if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           aX += (aOriginalRect.width - aClientRect.width) / 2;
 | |
|         else
 | |
|           aX -= (aOriginalRect.width - aClientRect.width) / 2;
 | |
|         break;
 | |
| 
 | |
|       case nsBoxFrame::hAlign_Right:
 | |
|         if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           aX += (aOriginalRect.width - aClientRect.width);
 | |
|         else
 | |
|           aX -= (aOriginalRect.width - aClientRect.width);
 | |
|         break;  // Nothing to do for the reverse dir.  The default initialized
 | |
|                 // us properly.
 | |
|     }
 | |
|   } else {
 | |
|     switch (valign) {
 | |
|       case nsBoxFrame::vAlign_Top:
 | |
|       case nsBoxFrame::vAlign_BaseLine:  // This value is technically impossible
 | |
|                                          // to specify for pack.
 | |
|         break;  // Don't do anything.  We were initialized correctly.
 | |
| 
 | |
|       case nsBoxFrame::vAlign_Middle:
 | |
|         if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           aY += (aOriginalRect.height - aClientRect.height) / 2;
 | |
|         else
 | |
|           aY -= (aOriginalRect.height - aClientRect.height) / 2;
 | |
|         break;
 | |
| 
 | |
|       case nsBoxFrame::vAlign_Bottom:
 | |
|         if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           aY += (aOriginalRect.height - aClientRect.height);
 | |
|         else
 | |
|           aY -= (aOriginalRect.height - aClientRect.height);
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsSprocketLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) {
 | |
|   // See if we are collapsed. If we are, then simply iterate over all our
 | |
|   // children and give them a rect of 0 width and height.
 | |
|   if (aBox->IsXULCollapsed()) {
 | |
|     for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|       nsBoxFrame::LayoutChildAt(aState, iter->get(), nsRect(0, 0, 0, 0));
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsBoxLayoutState::AutoReflowDepth depth(aState);
 | |
|   mozilla::AutoStackArena arena;
 | |
| 
 | |
|   // ----- figure out our size ----------
 | |
|   const nsSize originalSize = aBox->GetSize();
 | |
| 
 | |
|   // -- make sure we remove our border and padding  ----
 | |
|   nsRect clientRect;
 | |
|   aBox->GetXULClientRect(clientRect);
 | |
| 
 | |
|   // |originalClientRect| represents the rect of the entire box (excluding
 | |
|   // borders and padding).  We store it here because we're going to use
 | |
|   // |clientRect| to hold the required size for all our kids.  As an example,
 | |
|   // consider an hbox with a specified width of 300.  If the kids total only 150
 | |
|   // pixels of width, then we have 150 pixels left over.  |clientRect| is going
 | |
|   // to hold a width of 150 and is going to be adjusted based off the value of
 | |
|   // the PACK property.  If flexible objects are in the box, then the two rects
 | |
|   // will match.
 | |
|   nsRect originalClientRect(clientRect);
 | |
| 
 | |
|   // The frame state contains cached knowledge about our box, such as our
 | |
|   // orientation and direction.
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
| 
 | |
|   // Build a list of our children's desired sizes and computed sizes
 | |
|   nsBoxSize* boxSizes = nullptr;
 | |
|   nsComputedBoxSize* computedBoxSizes = nullptr;
 | |
| 
 | |
|   nscoord min = 0;
 | |
|   nscoord max = 0;
 | |
|   int32_t flexes = 0;
 | |
|   PopulateBoxSizes(aBox, aState, boxSizes, min, max, flexes);
 | |
| 
 | |
|   // The |size| variable will hold the total size of children along the axis of
 | |
|   // the box.  Continuing with the example begun in the comment above, size
 | |
|   // would be 150 pixels.
 | |
|   nscoord size = clientRect.width;
 | |
|   if (!IsXULHorizontal(aBox)) size = clientRect.height;
 | |
|   ComputeChildSizes(aBox, aState, size, boxSizes, computedBoxSizes);
 | |
| 
 | |
|   // After the call to ComputeChildSizes, the |size| variable contains the
 | |
|   // total required size of all the children.  We adjust our clientRect in the
 | |
|   // appropriate dimension to match this size.  In our example, we now assign
 | |
|   // 150 pixels into the clientRect.width.
 | |
|   //
 | |
|   // The variables |min| and |max| hold the minimum required size box must be
 | |
|   // in the OPPOSITE orientation, e.g., for a horizontal box, |min| is the
 | |
|   // minimum height we require to enclose our children, and |max| is the maximum
 | |
|   // height required to enclose our children.
 | |
|   if (IsXULHorizontal(aBox)) {
 | |
|     clientRect.width = size;
 | |
|     if (clientRect.height < min) clientRect.height = min;
 | |
| 
 | |
|     if (frameState & NS_STATE_AUTO_STRETCH) {
 | |
|       if (clientRect.height > max) clientRect.height = max;
 | |
|     }
 | |
|   } else {
 | |
|     clientRect.height = size;
 | |
|     if (clientRect.width < min) clientRect.width = min;
 | |
| 
 | |
|     if (frameState & NS_STATE_AUTO_STRETCH) {
 | |
|       if (clientRect.width > max) clientRect.width = max;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // With the sizes computed, now it's time to lay out our children.
 | |
|   bool finished;
 | |
|   nscoord passes = 0;
 | |
| 
 | |
|   // We flow children at their preferred locations (along with the appropriate
 | |
|   // computed flex). After we flow a child, it is possible that the child will
 | |
|   // change its size.  If/when this happens, we have to do another pass.
 | |
|   // Typically only 2 passes are required, but the code is prepared to do as
 | |
|   // many passes as are necessary to achieve equilibrium.
 | |
|   nscoord x = 0;
 | |
|   nscoord y = 0;
 | |
|   nscoord origX = 0;
 | |
|   nscoord origY = 0;
 | |
| 
 | |
|   // |childResized| lets us know if a child changed its size after we attempted
 | |
|   // to lay it out at the specified size.  If this happens, we usually have to
 | |
|   // do another pass.
 | |
|   bool childResized = false;
 | |
| 
 | |
|   // |passes| stores our number of passes.  If for any reason we end up doing
 | |
|   // more than, say, 10 passes, we assert to indicate that something is
 | |
|   // seriously screwed up.
 | |
|   passes = 0;
 | |
|   do {
 | |
|     // Always assume that we're done.  This will change if, for example,
 | |
|     // children don't stay the same size after being flowed.
 | |
|     finished = true;
 | |
| 
 | |
|     // Handle box packing.
 | |
|     HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect);
 | |
| 
 | |
|     // Now that packing is taken care of we set up a few additional
 | |
|     // tracking variables.
 | |
|     origX = x;
 | |
|     origY = y;
 | |
| 
 | |
|     // Now we iterate over our box children and our box size lists in
 | |
|     // parallel.  For each child, we look at its sizes and figure out
 | |
|     // where to place it.
 | |
|     nsComputedBoxSize* childComputedBoxSize = computedBoxSizes;
 | |
|     nsBoxSize* childBoxSize = boxSizes;
 | |
| 
 | |
|     auto iter = IterFor(aBox);
 | |
|     int32_t count = 0;
 | |
|     while ((iter && !iter->AtEnd()) || (childBoxSize && childBoxSize->bogus)) {
 | |
|       // If for some reason, our lists are not the same length, we guard
 | |
|       // by bailing out of the loop.
 | |
|       if (childBoxSize == nullptr) {
 | |
|         MOZ_ASSERT_UNREACHABLE("Lists not the same length.");
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       nscoord width = clientRect.width;
 | |
|       nscoord height = clientRect.height;
 | |
| 
 | |
|       if (!childBoxSize->bogus) {
 | |
|         nsIFrame* child = iter->get();
 | |
| 
 | |
|         // We have a valid box size entry.  This entry already contains
 | |
|         // information about our sizes along the axis of the box (e.g., widths
 | |
|         // in a horizontal box).  If our default ALIGN is not stretch, however,
 | |
|         // then we also need to know the child's size along the opposite axis.
 | |
|         if (!(frameState & NS_STATE_AUTO_STRETCH)) {
 | |
|           nsSize prefSize = child->GetXULPrefSize(aState);
 | |
|           nsSize minSize = child->GetXULMinSize(aState);
 | |
|           nsSize maxSize = child->GetXULMaxSize(aState);
 | |
|           prefSize = nsIFrame::XULBoundsCheck(minSize, prefSize, maxSize);
 | |
| 
 | |
|           AddXULMargin(child, prefSize);
 | |
|           width = std::min(prefSize.width, originalClientRect.width);
 | |
|           height = std::min(prefSize.height, originalClientRect.height);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Obtain the computed size along the axis of the box for this child from
 | |
|       // the computedBoxSize entry. We store the result in |width| for
 | |
|       // horizontal boxes and |height| for vertical boxes.
 | |
|       if (frameState & NS_STATE_IS_HORIZONTAL)
 | |
|         width = childComputedBoxSize->size;
 | |
|       else
 | |
|         height = childComputedBoxSize->size;
 | |
| 
 | |
|       // Adjust our x/y for the left/right spacing.
 | |
|       if (frameState & NS_STATE_IS_HORIZONTAL) {
 | |
|         if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           x += (childBoxSize->left);
 | |
|         else
 | |
|           x -= (childBoxSize->right);
 | |
|       } else {
 | |
|         if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           y += (childBoxSize->left);
 | |
|         else
 | |
|           y -= (childBoxSize->right);
 | |
|       }
 | |
| 
 | |
|       // Now we build a child rect.
 | |
|       nscoord rectX = x;
 | |
|       nscoord rectY = y;
 | |
|       if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) {
 | |
|         if (frameState & NS_STATE_IS_HORIZONTAL)
 | |
|           rectX -= width;
 | |
|         else
 | |
|           rectY -= height;
 | |
|       }
 | |
| 
 | |
|       // We now create an accurate child rect based off our computed size
 | |
|       // information.
 | |
|       nsRect childRect(rectX, rectY, width, height);
 | |
| 
 | |
|       // Sanity check against our clientRect.  It is possible that a child
 | |
|       // specified a size that is too large to fit.  If that happens, then we
 | |
|       // have to grow our client rect.  Remember, clientRect is not the total
 | |
|       // rect of the enclosing box.  It currently holds our perception of how
 | |
|       // big the children needed to be.
 | |
|       if (childRect.width > clientRect.width)
 | |
|         clientRect.width = childRect.width;
 | |
| 
 | |
|       if (childRect.height > clientRect.height)
 | |
|         clientRect.height = childRect.height;
 | |
| 
 | |
|       // Either |nextX| or |nextY| is updated by this function call, according
 | |
|       // to our axis.
 | |
|       nscoord nextX = x;
 | |
|       nscoord nextY = y;
 | |
| 
 | |
|       ComputeChildsNextPosition(aBox, x, y, nextX, nextY, childRect);
 | |
| 
 | |
|       // Now we further update our nextX/Y along our axis.
 | |
|       // We also set childRect.y/x along the opposite axis appropriately for a
 | |
|       // stretch alignment.  (Non-stretch alignment is handled below.)
 | |
|       if (frameState & NS_STATE_IS_HORIZONTAL) {
 | |
|         if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           nextX += (childBoxSize->right);
 | |
|         else
 | |
|           nextX -= (childBoxSize->left);
 | |
|         childRect.y = originalClientRect.y;
 | |
|       } else {
 | |
|         if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|           nextY += (childBoxSize->right);
 | |
|         else
 | |
|           nextY -= (childBoxSize->left);
 | |
|         if (GetFrameDirection(aBox) == StyleDirection::Ltr) {
 | |
|           childRect.x = originalClientRect.x;
 | |
|         } else {
 | |
|           // keep the right edge of the box the same
 | |
|           childRect.x =
 | |
|               clientRect.x + originalClientRect.width - childRect.width;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // If we encounter a completely bogus box size, we just leave this child
 | |
|       // completely alone and continue through the loop to the next child.
 | |
|       if (childBoxSize->bogus) {
 | |
|         childComputedBoxSize = childComputedBoxSize->next;
 | |
|         childBoxSize = childBoxSize->next;
 | |
|         count++;
 | |
|         x = nextX;
 | |
|         y = nextY;
 | |
|         // FIXME(emilio): shouldn't this update `child` / `iter`? This looks
 | |
|         // broken.
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nsIFrame* child = iter->get();
 | |
|       nsMargin margin(0, 0, 0, 0);
 | |
| 
 | |
|       bool layout = true;
 | |
| 
 | |
|       // Deflate the rect of our child by its margin.
 | |
|       child->GetXULMargin(margin);
 | |
|       childRect.Deflate(margin);
 | |
|       if (childRect.width < 0) childRect.width = 0;
 | |
|       if (childRect.height < 0) childRect.height = 0;
 | |
| 
 | |
|       // Now we're trying to figure out if we have to lay out this child, i.e.,
 | |
|       // to call the child's XULLayout method.
 | |
|       if (passes > 0) {
 | |
|         layout = false;
 | |
|       } else {
 | |
|         // Always perform layout if we are dirty or have dirty children
 | |
|         if (!child->IsSubtreeDirty()) {
 | |
|           layout = false;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       nsRect oldRect(child->GetRect());
 | |
| 
 | |
|       // Non-stretch alignment will be handled in AlignChildren(), so don't
 | |
|       // change child out-of-axis positions yet.
 | |
|       if (!(frameState & NS_STATE_AUTO_STRETCH)) {
 | |
|         if (frameState & NS_STATE_IS_HORIZONTAL) {
 | |
|           childRect.y = oldRect.y;
 | |
|         } else {
 | |
|           childRect.x = oldRect.x;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // We computed a childRect.  Now we want to set the bounds of the child to
 | |
|       // be that rect. If our old rect is different, then we know our size
 | |
|       // changed and we cache that fact in the |sizeChanged| variable.
 | |
| 
 | |
|       child->SetXULBounds(aState, childRect);
 | |
|       bool sizeChanged = (childRect.width != oldRect.width ||
 | |
|                           childRect.height != oldRect.height);
 | |
| 
 | |
|       if (sizeChanged) {
 | |
|         // Our size is different.  Sanity check against our maximum allowed size
 | |
|         // to ensure we didn't exceed it.
 | |
|         nsSize minSize = child->GetXULMinSize(aState);
 | |
|         nsSize maxSize = child->GetXULMaxSize(aState);
 | |
|         maxSize = nsIFrame::XULBoundsCheckMinMax(minSize, maxSize);
 | |
| 
 | |
|         // make sure the size is in our max size.
 | |
|         if (childRect.width > maxSize.width) childRect.width = maxSize.width;
 | |
| 
 | |
|         if (childRect.height > maxSize.height)
 | |
|           childRect.height = maxSize.height;
 | |
| 
 | |
|         // set it again
 | |
|         child->SetXULBounds(aState, childRect);
 | |
|       }
 | |
| 
 | |
|       // If we already determined that layout was required or if our size has
 | |
|       // changed, then we make sure to call layout on the child, since its
 | |
|       // children may need to be shifted around as a result of the size change.
 | |
|       if (layout || sizeChanged) child->XULLayout(aState);
 | |
| 
 | |
|       // If the child was a block or inline (e.g., HTML) it may have changed its
 | |
|       // rect *during* layout. We have to check for this.
 | |
|       nsRect newChildRect(child->GetRect());
 | |
| 
 | |
|       if (!newChildRect.IsEqualInterior(childRect)) {
 | |
| #ifdef DEBUG_GROW
 | |
|         printf(" GREW from (%d,%d) -> (%d,%d)\n", childRect.width,
 | |
|                childRect.height, newChildRect.width, newChildRect.height);
 | |
| #endif
 | |
|         newChildRect.Inflate(margin);
 | |
|         childRect.Inflate(margin);
 | |
| 
 | |
|         // The child changed size during layout.  The ChildResized method
 | |
|         // handles this scenario.
 | |
|         ChildResized(aBox, aState, child, childBoxSize, childComputedBoxSize,
 | |
|                      boxSizes, computedBoxSizes, childRect, newChildRect,
 | |
|                      clientRect, flexes, finished);
 | |
| 
 | |
|         // We note that a child changed size, which means that another pass will
 | |
|         // be required.
 | |
|         childResized = true;
 | |
| 
 | |
|         // Now that a child resized, it's entirely possible that OUR rect is too
 | |
|         // small.  Now we ensure that |originalClientRect| is grown to
 | |
|         // accommodate the size of |clientRect|.
 | |
|         if (clientRect.width > originalClientRect.width)
 | |
|           originalClientRect.width = clientRect.width;
 | |
| 
 | |
|         if (clientRect.height > originalClientRect.height)
 | |
|           originalClientRect.height = clientRect.height;
 | |
| 
 | |
|         if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) {
 | |
|           // Our childRect had its XMost() or YMost() (depending on our layout
 | |
|           // direction), positioned at a certain point.  Ensure that the
 | |
|           // newChildRect satisfies the same constraint.  Note that this is
 | |
|           // just equivalent to adjusting the x/y by the difference in
 | |
|           // width/height between childRect and newChildRect.  So we don't need
 | |
|           // to reaccount for the left and right of the box layout state again.
 | |
|           if (frameState & NS_STATE_IS_HORIZONTAL)
 | |
|             newChildRect.x = childRect.XMost() - newChildRect.width;
 | |
|           else
 | |
|             newChildRect.y = childRect.YMost() - newChildRect.height;
 | |
|         }
 | |
| 
 | |
|         if (!(frameState & NS_STATE_IS_HORIZONTAL)) {
 | |
|           if (GetFrameDirection(aBox) != StyleDirection::Ltr) {
 | |
|             // keep the right edge the same
 | |
|             newChildRect.x = childRect.XMost() - newChildRect.width;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // If the child resized then recompute its position.
 | |
|         ComputeChildsNextPosition(aBox, x, y, nextX, nextY, newChildRect);
 | |
| 
 | |
|         if (newChildRect.width >= margin.left + margin.right &&
 | |
|             newChildRect.height >= margin.top + margin.bottom)
 | |
|           newChildRect.Deflate(margin);
 | |
| 
 | |
|         if (childRect.width >= margin.left + margin.right &&
 | |
|             childRect.height >= margin.top + margin.bottom)
 | |
|           childRect.Deflate(margin);
 | |
| 
 | |
|         child->SetXULBounds(aState, newChildRect);
 | |
| 
 | |
|         // If we are the first box that changed size, then we don't need to do a
 | |
|         // second pass
 | |
|         if (count == 0) finished = true;
 | |
|       }
 | |
| 
 | |
|       // Now update our x/y finally.
 | |
|       x = nextX;
 | |
|       y = nextY;
 | |
| 
 | |
|       // Move to the next child.
 | |
|       childComputedBoxSize = childComputedBoxSize->next;
 | |
|       childBoxSize = childBoxSize->next;
 | |
| 
 | |
|       iter->Next();
 | |
|       count++;
 | |
|     }
 | |
| 
 | |
|     // Sanity-checking code to ensure we don't do an infinite # of passes.
 | |
|     passes++;
 | |
|     NS_ASSERTION(passes < 10, "A Box's child is constantly growing!!!!!");
 | |
|     if (passes >= 10) break;
 | |
|   } while (false == finished);
 | |
| 
 | |
|   // Get rid of our size lists.
 | |
|   while (boxSizes) {
 | |
|     nsBoxSize* toDelete = boxSizes;
 | |
|     boxSizes = boxSizes->next;
 | |
|     delete toDelete;
 | |
|   }
 | |
| 
 | |
|   while (computedBoxSizes) {
 | |
|     nsComputedBoxSize* toDelete = computedBoxSizes;
 | |
|     computedBoxSizes = computedBoxSizes->next;
 | |
|     delete toDelete;
 | |
|   }
 | |
| 
 | |
|   if (childResized) {
 | |
|     // See if one of our children forced us to get bigger
 | |
|     nsRect tmpClientRect(originalClientRect);
 | |
|     nsMargin bp(0, 0, 0, 0);
 | |
|     aBox->GetXULBorderAndPadding(bp);
 | |
|     tmpClientRect.Inflate(bp);
 | |
| 
 | |
|     if (tmpClientRect.width > originalSize.width ||
 | |
|         tmpClientRect.height > originalSize.height) {
 | |
|       // if it did reset our bounds.
 | |
|       nsRect bounds(aBox->GetRect());
 | |
|       if (tmpClientRect.width > originalSize.width)
 | |
|         bounds.width = tmpClientRect.width;
 | |
| 
 | |
|       if (tmpClientRect.height > originalSize.height)
 | |
|         bounds.height = tmpClientRect.height;
 | |
| 
 | |
|       aBox->SetXULBounds(aState, bounds);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Because our size grew, we now have to readjust because of box packing.
 | |
|   // Repack in order to update our x and y to the correct values.
 | |
|   HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect);
 | |
| 
 | |
|   // Compare against our original x and y and only worry about adjusting the
 | |
|   // children if we really did have to change the positions because of packing
 | |
|   // (typically for 'center' or 'end' pack values).
 | |
|   if (x != origX || y != origY) {
 | |
|     // reposition all our children
 | |
|     for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|       nsIFrame* child = iter->get();
 | |
|       nsRect childRect(child->GetRect());
 | |
|       childRect.x += (x - origX);
 | |
|       childRect.y += (y - origY);
 | |
|       child->SetXULBounds(aState, childRect);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Perform out-of-axis alignment for non-stretch alignments
 | |
|   if (!(frameState & NS_STATE_AUTO_STRETCH)) {
 | |
|     AlignChildren(aBox, aState);
 | |
|   }
 | |
| 
 | |
|   // That's it!  If you made it this far without having a nervous breakdown,
 | |
|   // congratulations!  Go get yourself a beer.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::PopulateBoxSizes(nsIFrame* aBox,
 | |
|                                         nsBoxLayoutState& aState,
 | |
|                                         nsBoxSize*& aBoxSizes,
 | |
|                                         nscoord& aMinSize, nscoord& aMaxSize,
 | |
|                                         int32_t& aFlexes) {
 | |
|   // used for the equal size flag
 | |
|   nscoord biggestPrefWidth = 0;
 | |
|   nscoord biggestMinWidth = 0;
 | |
|   nscoord smallestMaxWidth = NS_UNCONSTRAINEDSIZE;
 | |
| 
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
| 
 | |
|   aMinSize = 0;
 | |
|   aMaxSize = NS_UNCONSTRAINEDSIZE;
 | |
| 
 | |
|   bool isHorizontal;
 | |
| 
 | |
|   if (IsXULHorizontal(aBox))
 | |
|     isHorizontal = true;
 | |
|   else
 | |
|     isHorizontal = false;
 | |
| 
 | |
|   // this is a nice little optimization
 | |
|   // it turns out that if we only have 1 flexable child
 | |
|   // then it does not matter what its preferred size is
 | |
|   // there is nothing to flex it relative. This is great
 | |
|   // because we can avoid asking for a preferred size in this
 | |
|   // case. Why is this good? Well you might have html inside it
 | |
|   // and asking html for its preferred size is rather expensive.
 | |
|   // so we can just optimize it out this way.
 | |
| 
 | |
|   // set flexes
 | |
|   aFlexes = 0;
 | |
|   nsBoxSize* currentBox = aBoxSizes;
 | |
|   nsBoxSize* last = nullptr;
 | |
| 
 | |
|   nscoord maxFlex = 0;
 | |
|   int32_t childCount = 0;
 | |
| 
 | |
|   for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|     nsIFrame* child = iter->get();
 | |
|     while (currentBox && currentBox->bogus) {
 | |
|       last = currentBox;
 | |
|       currentBox = currentBox->next;
 | |
|     }
 | |
|     ++childCount;
 | |
|     nsSize pref(0, 0);
 | |
|     nsSize minSize(0, 0);
 | |
|     nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 | |
|     bool collapsed = child->IsXULCollapsed();
 | |
| 
 | |
|     if (!collapsed) {
 | |
|       // only one flexible child? Cool we will just make its preferred size
 | |
|       // 0 then and not even have to ask for it.
 | |
|       // if (flexes != 1)  {
 | |
| 
 | |
|       pref = child->GetXULPrefSize(aState);
 | |
|       minSize = child->GetXULMinSize(aState);
 | |
|       maxSize =
 | |
|           nsIFrame::XULBoundsCheckMinMax(minSize, child->GetXULMaxSize(aState));
 | |
|       child->GetXULBoxAscent(aState);
 | |
|       //}
 | |
| 
 | |
|       pref = nsIFrame::XULBoundsCheck(minSize, pref, maxSize);
 | |
| 
 | |
|       AddXULMargin(child, pref);
 | |
|       AddXULMargin(child, minSize);
 | |
|       AddXULMargin(child, maxSize);
 | |
|     }
 | |
| 
 | |
|     if (!currentBox) {
 | |
|       // create one.
 | |
|       currentBox = new (aState) nsBoxSize();
 | |
|       if (!aBoxSizes) {
 | |
|         aBoxSizes = currentBox;
 | |
|         last = aBoxSizes;
 | |
|       } else {
 | |
|         last->next = currentBox;
 | |
|         last = currentBox;
 | |
|       }
 | |
| 
 | |
|       nscoord minWidth;
 | |
|       nscoord maxWidth;
 | |
|       nscoord prefWidth;
 | |
| 
 | |
|       // get sizes from child
 | |
|       if (isHorizontal) {
 | |
|         minWidth = minSize.width;
 | |
|         maxWidth = maxSize.width;
 | |
|         prefWidth = pref.width;
 | |
|       } else {
 | |
|         minWidth = minSize.height;
 | |
|         maxWidth = maxSize.height;
 | |
|         prefWidth = pref.height;
 | |
|       }
 | |
| 
 | |
|       nscoord flex = child->GetXULFlex();
 | |
| 
 | |
|       // set them if you collapsed you are not flexible.
 | |
|       if (collapsed) {
 | |
|         currentBox->flex = 0;
 | |
|       } else {
 | |
|         if (flex > maxFlex) {
 | |
|           maxFlex = flex;
 | |
|         }
 | |
|         currentBox->flex = flex;
 | |
|       }
 | |
| 
 | |
|       // we specified all our children are equal size;
 | |
|       if (frameState & NS_STATE_EQUAL_SIZE) {
 | |
|         if (prefWidth > biggestPrefWidth) biggestPrefWidth = prefWidth;
 | |
| 
 | |
|         if (minWidth > biggestMinWidth) biggestMinWidth = minWidth;
 | |
| 
 | |
|         if (maxWidth < smallestMaxWidth) smallestMaxWidth = maxWidth;
 | |
|       } else {  // not we can set our children right now.
 | |
|         currentBox->pref = prefWidth;
 | |
|         currentBox->min = minWidth;
 | |
|         currentBox->max = maxWidth;
 | |
|       }
 | |
| 
 | |
|       NS_ASSERTION(minWidth <= prefWidth && prefWidth <= maxWidth,
 | |
|                    "Bad min, pref, max widths!");
 | |
|     }
 | |
| 
 | |
|     if (!isHorizontal) {
 | |
|       if (minSize.width > aMinSize) aMinSize = minSize.width;
 | |
| 
 | |
|       if (maxSize.width < aMaxSize) aMaxSize = maxSize.width;
 | |
| 
 | |
|     } else {
 | |
|       if (minSize.height > aMinSize) aMinSize = minSize.height;
 | |
| 
 | |
|       if (maxSize.height < aMaxSize) aMaxSize = maxSize.height;
 | |
|     }
 | |
| 
 | |
|     currentBox->collapsed = collapsed;
 | |
|     aFlexes += currentBox->flex;
 | |
| 
 | |
|     last = currentBox;
 | |
|     currentBox = currentBox->next;
 | |
|   }
 | |
| 
 | |
|   if (childCount > 0) {
 | |
|     nscoord maxAllowedFlex = nscoord_MAX / childCount;
 | |
| 
 | |
|     if (MOZ_UNLIKELY(maxFlex > maxAllowedFlex)) {
 | |
|       // clamp all the flexes
 | |
|       currentBox = aBoxSizes;
 | |
|       while (currentBox) {
 | |
|         currentBox->flex = std::min(currentBox->flex, maxAllowedFlex);
 | |
|         currentBox = currentBox->next;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #ifdef DEBUG
 | |
|   else {
 | |
|     NS_ASSERTION(maxFlex == 0, "How did that happen?");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // we specified all our children are equal size;
 | |
|   if (frameState & NS_STATE_EQUAL_SIZE) {
 | |
|     smallestMaxWidth = std::max(smallestMaxWidth, biggestMinWidth);
 | |
|     biggestPrefWidth = nsIFrame::XULBoundsCheck(
 | |
|         biggestMinWidth, biggestPrefWidth, smallestMaxWidth);
 | |
| 
 | |
|     currentBox = aBoxSizes;
 | |
| 
 | |
|     while (currentBox) {
 | |
|       if (!currentBox->collapsed) {
 | |
|         currentBox->pref = biggestPrefWidth;
 | |
|         currentBox->min = biggestMinWidth;
 | |
|         currentBox->max = smallestMaxWidth;
 | |
|       } else {
 | |
|         currentBox->pref = 0;
 | |
|         currentBox->min = 0;
 | |
|         currentBox->max = 0;
 | |
|       }
 | |
|       currentBox = currentBox->next;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::ComputeChildsNextPosition(
 | |
|     nsIFrame* aBox, const nscoord& aCurX, const nscoord& aCurY, nscoord& aNextX,
 | |
|     nscoord& aNextY, const nsRect& aCurrentChildSize) {
 | |
|   // Get the position along the box axis for the child.
 | |
|   // The out-of-axis position is not set.
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
| 
 | |
|   if (IsXULHorizontal(aBox)) {
 | |
|     // horizontal box's children.
 | |
|     if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|       aNextX = aCurX + aCurrentChildSize.width;
 | |
|     else
 | |
|       aNextX = aCurX - aCurrentChildSize.width;
 | |
| 
 | |
|   } else {
 | |
|     // vertical box's children.
 | |
|     if (frameState & NS_STATE_IS_DIRECTION_NORMAL)
 | |
|       aNextY = aCurY + aCurrentChildSize.height;
 | |
|     else
 | |
|       aNextY = aCurY - aCurrentChildSize.height;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::AlignChildren(nsIFrame* aBox, nsBoxLayoutState& aState) {
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
|   bool isHorizontal = (frameState & NS_STATE_IS_HORIZONTAL) != 0;
 | |
|   nsRect clientRect;
 | |
|   aBox->GetXULClientRect(clientRect);
 | |
| 
 | |
|   MOZ_ASSERT(!(frameState & NS_STATE_AUTO_STRETCH),
 | |
|              "Only AlignChildren() with non-stretch alignment");
 | |
| 
 | |
|   // These are only calculated if needed
 | |
|   nsIFrame::Halignment halign;
 | |
|   nsIFrame::Valignment valign;
 | |
|   nscoord maxAscent = 0;
 | |
|   bool isLTR;
 | |
| 
 | |
|   if (isHorizontal) {
 | |
|     valign = aBox->GetXULVAlign();
 | |
|     if (valign == nsBoxFrame::vAlign_BaseLine) {
 | |
|       maxAscent = aBox->GetXULBoxAscent(aState);
 | |
|     }
 | |
|   } else {
 | |
|     isLTR = GetFrameDirection(aBox) == StyleDirection::Ltr;
 | |
|     halign = aBox->GetXULHAlign();
 | |
|   }
 | |
| 
 | |
|   for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|     nsIFrame* child = iter->get();
 | |
|     nsMargin margin;
 | |
|     child->GetXULMargin(margin);
 | |
|     nsRect childRect = child->GetRect();
 | |
| 
 | |
|     if (isHorizontal) {
 | |
|       const nscoord startAlign = clientRect.y + margin.top;
 | |
|       const nscoord endAlign =
 | |
|           clientRect.YMost() - margin.bottom - childRect.height;
 | |
| 
 | |
|       nscoord y = 0;
 | |
|       switch (valign) {
 | |
|         case nsBoxFrame::vAlign_Top:
 | |
|           y = startAlign;
 | |
|           break;
 | |
|         case nsBoxFrame::vAlign_Middle:
 | |
|           // Should this center the border box?
 | |
|           // This centers the margin box, the historical behavior.
 | |
|           y = (startAlign + endAlign) / 2;
 | |
|           break;
 | |
|         case nsBoxFrame::vAlign_Bottom:
 | |
|           y = endAlign;
 | |
|           break;
 | |
|         case nsBoxFrame::vAlign_BaseLine:
 | |
|           // Alignments don't force the box to grow (only sizes do),
 | |
|           // so keep the children within the box.
 | |
|           y = maxAscent - child->GetXULBoxAscent(aState);
 | |
|           y = std::max(startAlign, y);
 | |
|           y = std::min(y, endAlign);
 | |
|           break;
 | |
|       }
 | |
| 
 | |
|       childRect.y = y;
 | |
| 
 | |
|     } else {  // vertical box
 | |
|       const nscoord leftAlign = clientRect.x + margin.left;
 | |
|       const nscoord rightAlign =
 | |
|           clientRect.XMost() - margin.right - childRect.width;
 | |
| 
 | |
|       nscoord x = 0;
 | |
|       switch (halign) {
 | |
|         case nsBoxFrame::hAlign_Left:  // start
 | |
|           x = isLTR ? leftAlign : rightAlign;
 | |
|           break;
 | |
|         case nsBoxFrame::hAlign_Center:
 | |
|           x = (leftAlign + rightAlign) / 2;
 | |
|           break;
 | |
|         case nsBoxFrame::hAlign_Right:  // end
 | |
|           x = isLTR ? rightAlign : leftAlign;
 | |
|           break;
 | |
|       }
 | |
| 
 | |
|       childRect.x = x;
 | |
|     }
 | |
| 
 | |
|     if (childRect.TopLeft() != child->GetPosition()) {
 | |
|       child->SetXULBounds(aState, childRect);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::ChildResized(
 | |
|     nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChild,
 | |
|     nsBoxSize* aChildBoxSize, nsComputedBoxSize* aChildComputedSize,
 | |
|     nsBoxSize* aBoxSizes, nsComputedBoxSize* aComputedBoxSizes,
 | |
|     const nsRect& aChildLayoutRect, nsRect& aChildActualRect,
 | |
|     nsRect& aContainingRect, int32_t aFlexes, bool& aFinished)
 | |
| 
 | |
| {
 | |
|   nsRect childCurrentRect(aChildLayoutRect);
 | |
| 
 | |
|   bool isHorizontal = IsXULHorizontal(aBox);
 | |
|   nscoord childLayoutWidth = GET_WIDTH(aChildLayoutRect, isHorizontal);
 | |
|   nscoord& childActualWidth = GET_WIDTH(aChildActualRect, isHorizontal);
 | |
|   nscoord& containingWidth = GET_WIDTH(aContainingRect, isHorizontal);
 | |
| 
 | |
|   // nscoord childLayoutHeight = GET_HEIGHT(aChildLayoutRect,isHorizontal);
 | |
|   nscoord& childActualHeight = GET_HEIGHT(aChildActualRect, isHorizontal);
 | |
|   nscoord& containingHeight = GET_HEIGHT(aContainingRect, isHorizontal);
 | |
| 
 | |
|   bool recompute = false;
 | |
| 
 | |
|   // if we are a horizontal box see if the child will fit inside us.
 | |
|   if (childActualHeight > containingHeight) {
 | |
|     // if we are a horizontal box and the child is bigger than our height
 | |
| 
 | |
|     // ok if the height changed then we need to reflow everyone but us at the
 | |
|     // new height so we will set the changed index to be us. And signal that we
 | |
|     // need a new pass.
 | |
| 
 | |
|     nsSize min = aChild->GetXULMinSize(aState);
 | |
|     nsSize max =
 | |
|         nsIFrame::XULBoundsCheckMinMax(min, aChild->GetXULMaxSize(aState));
 | |
|     AddXULMargin(aChild, max);
 | |
| 
 | |
|     if (isHorizontal)
 | |
|       childActualHeight =
 | |
|           max.height < childActualHeight ? max.height : childActualHeight;
 | |
|     else
 | |
|       childActualHeight =
 | |
|           max.width < childActualHeight ? max.width : childActualHeight;
 | |
| 
 | |
|     // only set if it changes
 | |
|     if (childActualHeight > containingHeight) {
 | |
|       containingHeight = childActualHeight;
 | |
| 
 | |
|       // remember we do not need to clear the resized list because changing the
 | |
|       // height of a horizontal box will not affect the width of any of its
 | |
|       // children because block flow left to right, top to bottom. Just trust me
 | |
|       // on this one.
 | |
|       aFinished = false;
 | |
| 
 | |
|       // only recompute if there are flexes.
 | |
|       if (aFlexes > 0) {
 | |
|         // relayout everything
 | |
|         recompute = true;
 | |
|         InvalidateComputedSizes(aComputedBoxSizes);
 | |
|         nsComputedBoxSize* node = aComputedBoxSizes;
 | |
| 
 | |
|         while (node) {
 | |
|           node->resized = false;
 | |
|           node = node->next;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (childActualWidth > childLayoutWidth) {
 | |
|     nsSize min = aChild->GetXULMinSize(aState);
 | |
|     nsSize max =
 | |
|         nsIFrame::XULBoundsCheckMinMax(min, aChild->GetXULMaxSize(aState));
 | |
| 
 | |
|     AddXULMargin(aChild, max);
 | |
| 
 | |
|     // our width now becomes the new size
 | |
| 
 | |
|     if (isHorizontal)
 | |
|       childActualWidth =
 | |
|           max.width < childActualWidth ? max.width : childActualWidth;
 | |
|     else
 | |
|       childActualWidth =
 | |
|           max.height < childActualWidth ? max.height : childActualWidth;
 | |
| 
 | |
|     if (childActualWidth > childLayoutWidth) {
 | |
|       aChildComputedSize->size = childActualWidth;
 | |
|       aChildBoxSize->min = childActualWidth;
 | |
|       if (aChildBoxSize->pref < childActualWidth)
 | |
|         aChildBoxSize->pref = childActualWidth;
 | |
|       if (aChildBoxSize->max < childActualWidth)
 | |
|         aChildBoxSize->max = childActualWidth;
 | |
| 
 | |
|       // if we have flexible elements with us then reflex things. Otherwise we
 | |
|       // can skip doing it.
 | |
|       if (aFlexes > 0) {
 | |
|         InvalidateComputedSizes(aComputedBoxSizes);
 | |
| 
 | |
|         nsComputedBoxSize* node = aComputedBoxSizes;
 | |
|         aChildComputedSize->resized = true;
 | |
| 
 | |
|         while (node) {
 | |
|           if (node->resized) node->valid = true;
 | |
| 
 | |
|           node = node->next;
 | |
|         }
 | |
| 
 | |
|         recompute = true;
 | |
|         aFinished = false;
 | |
|       } else {
 | |
|         containingWidth += aChildComputedSize->size - childLayoutWidth;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (recompute)
 | |
|     ComputeChildSizes(aBox, aState, containingWidth, aBoxSizes,
 | |
|                       aComputedBoxSizes);
 | |
| 
 | |
|   if (!childCurrentRect.IsEqualInterior(aChildActualRect)) {
 | |
|     // the childRect includes the margin
 | |
|     // make sure we remove it before setting
 | |
|     // the bounds.
 | |
|     nsMargin margin(0, 0, 0, 0);
 | |
|     aChild->GetXULMargin(margin);
 | |
|     nsRect rect(aChildActualRect);
 | |
|     if (rect.width >= margin.left + margin.right &&
 | |
|         rect.height >= margin.top + margin.bottom)
 | |
|       rect.Deflate(margin);
 | |
| 
 | |
|     aChild->SetXULBounds(aState, rect);
 | |
|     aChild->XULLayout(aState);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::InvalidateComputedSizes(
 | |
|     nsComputedBoxSize* aComputedBoxSizes) {
 | |
|   while (aComputedBoxSizes) {
 | |
|     aComputedBoxSizes->valid = false;
 | |
|     aComputedBoxSizes = aComputedBoxSizes->next;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::ComputeChildSizes(
 | |
|     nsIFrame* aBox, nsBoxLayoutState& aState, nscoord& aGivenSize,
 | |
|     nsBoxSize* aBoxSizes, nsComputedBoxSize*& aComputedBoxSizes) {
 | |
|   // nscoord onePixel = aState.PresContext()->IntScaledPixelsToTwips(1);
 | |
| 
 | |
|   int32_t sizeRemaining = aGivenSize;
 | |
|   int32_t spacerConstantsRemaining = 0;
 | |
| 
 | |
|   // ----- calculate the spacers constants and the size remaining -----
 | |
| 
 | |
|   if (!aComputedBoxSizes) aComputedBoxSizes = new (aState) nsComputedBoxSize();
 | |
| 
 | |
|   nsBoxSize* boxSizes = aBoxSizes;
 | |
|   nsComputedBoxSize* computedBoxSizes = aComputedBoxSizes;
 | |
|   int32_t count = 0;
 | |
|   int32_t validCount = 0;
 | |
| 
 | |
|   while (boxSizes) {
 | |
|     NS_ASSERTION(
 | |
|         (boxSizes->min <= boxSizes->pref && boxSizes->pref <= boxSizes->max),
 | |
|         "bad pref, min, max size");
 | |
| 
 | |
|     // ignore collapsed children
 | |
|     //  if (boxSizes->collapsed)
 | |
|     //  {
 | |
|     //  computedBoxSizes->valid = true;
 | |
|     //  computedBoxSizes->size = boxSizes->pref;
 | |
|     // validCount++;
 | |
|     //      boxSizes->flex = 0;
 | |
|     // }// else {
 | |
| 
 | |
|     if (computedBoxSizes->valid) {
 | |
|       sizeRemaining -= computedBoxSizes->size;
 | |
|       validCount++;
 | |
|     } else {
 | |
|       if (boxSizes->flex == 0) {
 | |
|         computedBoxSizes->valid = true;
 | |
|         computedBoxSizes->size = boxSizes->pref;
 | |
|         validCount++;
 | |
|       }
 | |
| 
 | |
|       spacerConstantsRemaining += boxSizes->flex;
 | |
|       sizeRemaining -= boxSizes->pref;
 | |
|     }
 | |
| 
 | |
|     sizeRemaining -= (boxSizes->left + boxSizes->right);
 | |
| 
 | |
|     //}
 | |
| 
 | |
|     boxSizes = boxSizes->next;
 | |
| 
 | |
|     if (boxSizes && !computedBoxSizes->next)
 | |
|       computedBoxSizes->next = new (aState) nsComputedBoxSize();
 | |
| 
 | |
|     computedBoxSizes = computedBoxSizes->next;
 | |
|     count++;
 | |
|   }
 | |
| 
 | |
|   // everything accounted for?
 | |
|   if (validCount < count) {
 | |
|     // ----- Ok we are give a size to fit into so stretch or squeeze to fit
 | |
|     // ----- Make sure we look at our min and max size
 | |
|     bool limit = true;
 | |
|     for (int pass = 1; true == limit; pass++) {
 | |
|       limit = false;
 | |
|       boxSizes = aBoxSizes;
 | |
|       computedBoxSizes = aComputedBoxSizes;
 | |
| 
 | |
|       while (boxSizes) {
 | |
|         // ignore collapsed spacers
 | |
| 
 | |
|         //    if (!boxSizes->collapsed) {
 | |
| 
 | |
|         nscoord pref = 0;
 | |
|         nscoord max = NS_UNCONSTRAINEDSIZE;
 | |
|         nscoord min = 0;
 | |
|         nscoord flex = 0;
 | |
| 
 | |
|         pref = boxSizes->pref;
 | |
|         min = boxSizes->min;
 | |
|         max = boxSizes->max;
 | |
|         flex = boxSizes->flex;
 | |
| 
 | |
|         // ----- look at our min and max limits make sure we aren't too small or
 | |
|         // too big -----
 | |
|         if (!computedBoxSizes->valid) {
 | |
|           int32_t newSize = pref + int32_t(int64_t(sizeRemaining) * flex /
 | |
|                                            spacerConstantsRemaining);
 | |
| 
 | |
|           if (newSize <= min) {
 | |
|             computedBoxSizes->size = min;
 | |
|             computedBoxSizes->valid = true;
 | |
|             spacerConstantsRemaining -= flex;
 | |
|             sizeRemaining += pref;
 | |
|             sizeRemaining -= min;
 | |
|             limit = true;
 | |
|           } else if (newSize >= max) {
 | |
|             computedBoxSizes->size = max;
 | |
|             computedBoxSizes->valid = true;
 | |
|             spacerConstantsRemaining -= flex;
 | |
|             sizeRemaining += pref;
 | |
|             sizeRemaining -= max;
 | |
|             limit = true;
 | |
|           }
 | |
|         }
 | |
|         // }
 | |
|         boxSizes = boxSizes->next;
 | |
|         computedBoxSizes = computedBoxSizes->next;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // ---- once we have removed and min and max issues just stretch us out in the
 | |
|   // remaining space
 | |
|   // ---- or shrink us. Depends on the size remaining and the spacer constants
 | |
|   aGivenSize = 0;
 | |
|   boxSizes = aBoxSizes;
 | |
|   computedBoxSizes = aComputedBoxSizes;
 | |
| 
 | |
|   while (boxSizes) {
 | |
|     // ignore collapsed spacers
 | |
|     //  if (!(boxSizes && boxSizes->collapsed)) {
 | |
| 
 | |
|     nscoord pref = 0;
 | |
|     nscoord flex = 0;
 | |
|     pref = boxSizes->pref;
 | |
|     flex = boxSizes->flex;
 | |
| 
 | |
|     if (!computedBoxSizes->valid) {
 | |
|       computedBoxSizes->size = pref + int32_t(int64_t(sizeRemaining) * flex /
 | |
|                                               spacerConstantsRemaining);
 | |
|       computedBoxSizes->valid = true;
 | |
|     }
 | |
| 
 | |
|     aGivenSize += (boxSizes->left + boxSizes->right);
 | |
|     aGivenSize += computedBoxSizes->size;
 | |
| 
 | |
|     // }
 | |
| 
 | |
|     boxSizes = boxSizes->next;
 | |
|     computedBoxSizes = computedBoxSizes->next;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsSize nsSprocketLayout::GetXULPrefSize(nsIFrame* aBox,
 | |
|                                         nsBoxLayoutState& aState) {
 | |
|   nsSize vpref(0, 0);
 | |
|   bool isHorizontal = IsXULHorizontal(aBox);
 | |
| 
 | |
|   nscoord biggestPref = 0;
 | |
| 
 | |
|   // run through all the children and get their min, max, and preferred sizes
 | |
|   // return us the size of the box
 | |
| 
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
|   bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE);
 | |
|   int32_t count = 0;
 | |
| 
 | |
|   for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|     nsIFrame* child = iter->get();
 | |
|     // ignore collapsed children
 | |
|     if (child->IsXULCollapsed()) {
 | |
|       continue;
 | |
|     }
 | |
|     nsSize pref = child->GetXULPrefSize(aState);
 | |
|     AddXULMargin(child, pref);
 | |
| 
 | |
|     if (isEqual) {
 | |
|       if (isHorizontal) {
 | |
|         if (pref.width > biggestPref) biggestPref = pref.width;
 | |
|       } else {
 | |
|         if (pref.height > biggestPref) biggestPref = pref.height;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     AddLargestSize(vpref, pref, isHorizontal);
 | |
|     count++;
 | |
|   }
 | |
| 
 | |
|   if (isEqual) {
 | |
|     if (isHorizontal)
 | |
|       vpref.width = biggestPref * count;
 | |
|     else
 | |
|       vpref.height = biggestPref * count;
 | |
|   }
 | |
| 
 | |
|   // now add our border and padding
 | |
|   AddXULBorderAndPadding(aBox, vpref);
 | |
| 
 | |
|   return vpref;
 | |
| }
 | |
| 
 | |
| nsSize nsSprocketLayout::GetXULMinSize(nsIFrame* aBox,
 | |
|                                        nsBoxLayoutState& aState) {
 | |
|   nsSize minSize(0, 0);
 | |
|   bool isHorizontal = IsXULHorizontal(aBox);
 | |
| 
 | |
|   nscoord biggestMin = 0;
 | |
| 
 | |
|   // run through all the children and get their min, max, and preferred sizes
 | |
|   // return us the size of the box
 | |
| 
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
|   bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE);
 | |
|   int32_t count = 0;
 | |
| 
 | |
|   for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|     nsIFrame* child = iter->get();
 | |
| 
 | |
|     // ignore collapsed children
 | |
|     if (child->IsXULCollapsed()) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsSize min = child->GetXULMinSize(aState);
 | |
|     nsSize pref(0, 0);
 | |
| 
 | |
|     // if the child is not flexible then
 | |
|     // its min size is its pref size.
 | |
|     if (child->GetXULFlex() == 0) {
 | |
|       pref = child->GetXULPrefSize(aState);
 | |
|       if (isHorizontal)
 | |
|         min.width = pref.width;
 | |
|       else
 | |
|         min.height = pref.height;
 | |
|     }
 | |
| 
 | |
|     if (isEqual) {
 | |
|       if (isHorizontal) {
 | |
|         if (min.width > biggestMin) biggestMin = min.width;
 | |
|       } else {
 | |
|         if (min.height > biggestMin) biggestMin = min.height;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     AddXULMargin(child, min);
 | |
|     AddLargestSize(minSize, min, isHorizontal);
 | |
|     count++;
 | |
|   }
 | |
| 
 | |
|   if (isEqual) {
 | |
|     if (isHorizontal)
 | |
|       minSize.width = biggestMin * count;
 | |
|     else
 | |
|       minSize.height = biggestMin * count;
 | |
|   }
 | |
| 
 | |
|   // now add our border and padding
 | |
|   AddXULBorderAndPadding(aBox, minSize);
 | |
| 
 | |
|   return minSize;
 | |
| }
 | |
| 
 | |
| nsSize nsSprocketLayout::GetXULMaxSize(nsIFrame* aBox,
 | |
|                                        nsBoxLayoutState& aState) {
 | |
|   bool isHorizontal = IsXULHorizontal(aBox);
 | |
| 
 | |
|   nscoord smallestMax = NS_UNCONSTRAINEDSIZE;
 | |
|   nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 | |
| 
 | |
|   // run through all the children and get their min, max, and preferred sizes
 | |
|   // return us the size of the box
 | |
| 
 | |
|   nsFrameState frameState = nsFrameState(0);
 | |
|   GetFrameState(aBox, frameState);
 | |
|   bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE);
 | |
|   int32_t count = 0;
 | |
| 
 | |
|   for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|     nsIFrame* child = iter->get();
 | |
| 
 | |
|     // ignore collapsed children
 | |
|     if (child->IsXULCollapsed()) {
 | |
|       continue;
 | |
|     }
 | |
|     // if completely redefined don't even ask our child for its size.
 | |
|     nsSize min = child->GetXULMinSize(aState);
 | |
|     nsSize max =
 | |
|         nsIFrame::XULBoundsCheckMinMax(min, child->GetXULMaxSize(aState));
 | |
| 
 | |
|     AddXULMargin(child, max);
 | |
|     AddSmallestSize(maxSize, max, isHorizontal);
 | |
| 
 | |
|     if (isEqual) {
 | |
|       if (isHorizontal) {
 | |
|         if (max.width < smallestMax) smallestMax = max.width;
 | |
|       } else {
 | |
|         if (max.height < smallestMax) smallestMax = max.height;
 | |
|       }
 | |
|     }
 | |
|     count++;
 | |
|   }
 | |
| 
 | |
|   if (isEqual) {
 | |
|     if (isHorizontal) {
 | |
|       if (smallestMax != NS_UNCONSTRAINEDSIZE)
 | |
|         maxSize.width = smallestMax * count;
 | |
|       else
 | |
|         maxSize.width = NS_UNCONSTRAINEDSIZE;
 | |
|     } else {
 | |
|       if (smallestMax != NS_UNCONSTRAINEDSIZE)
 | |
|         maxSize.height = smallestMax * count;
 | |
|       else
 | |
|         maxSize.height = NS_UNCONSTRAINEDSIZE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // now add our border and padding
 | |
|   AddXULBorderAndPadding(aBox, maxSize);
 | |
| 
 | |
|   return maxSize;
 | |
| }
 | |
| 
 | |
| nscoord nsSprocketLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) {
 | |
|   nscoord vAscent = 0;
 | |
| 
 | |
|   bool isHorizontal = IsXULHorizontal(aBox);
 | |
| 
 | |
|   // run through all the children and get their min, max, and preferred sizes
 | |
|   // return us the size of the box
 | |
| 
 | |
|   for (auto iter = IterFor(aBox); iter && !iter->AtEnd(); iter->Next()) {
 | |
|     nsIFrame* child = iter->get();
 | |
| 
 | |
|     // ignore collapsed children
 | |
|     // if (!child->IsXULCollapsed())
 | |
|     //{
 | |
|     // if completely redefined don't even ask our child for its size.
 | |
|     nscoord ascent = child->GetXULBoxAscent(aState);
 | |
| 
 | |
|     nsMargin margin;
 | |
|     child->GetXULMargin(margin);
 | |
|     ascent += margin.top;
 | |
| 
 | |
|     if (isHorizontal) {
 | |
|       if (ascent > vAscent) vAscent = ascent;
 | |
|     } else {
 | |
|       if (vAscent == 0) vAscent = ascent;
 | |
|     }
 | |
|     //}
 | |
| 
 | |
|     child = nsIFrame::GetNextXULBox(child);
 | |
|   }
 | |
| 
 | |
|   nsMargin borderPadding;
 | |
|   aBox->GetXULBorderAndPadding(borderPadding);
 | |
| 
 | |
|   return vAscent + borderPadding.top;
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::SetLargestSize(nsSize& aSize1, const nsSize& aSize2,
 | |
|                                       bool aIsHorizontal) {
 | |
|   if (aIsHorizontal) {
 | |
|     if (aSize1.height < aSize2.height) aSize1.height = aSize2.height;
 | |
|   } else {
 | |
|     if (aSize1.width < aSize2.width) aSize1.width = aSize2.width;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::SetSmallestSize(nsSize& aSize1, const nsSize& aSize2,
 | |
|                                        bool aIsHorizontal) {
 | |
|   if (aIsHorizontal) {
 | |
|     if (aSize1.height > aSize2.height) aSize1.height = aSize2.height;
 | |
|   } else {
 | |
|     if (aSize1.width > aSize2.width) aSize1.width = aSize2.width;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::AddLargestSize(nsSize& aSize, const nsSize& aSizeToAdd,
 | |
|                                       bool aIsHorizontal) {
 | |
|   if (aIsHorizontal)
 | |
|     AddCoord(aSize.width, aSizeToAdd.width);
 | |
|   else
 | |
|     AddCoord(aSize.height, aSizeToAdd.height);
 | |
| 
 | |
|   SetLargestSize(aSize, aSizeToAdd, aIsHorizontal);
 | |
| }
 | |
| 
 | |
| void nsSprocketLayout::AddCoord(nscoord& aCoord, nscoord aCoordToAdd) {
 | |
|   if (aCoord != NS_UNCONSTRAINEDSIZE) {
 | |
|     if (aCoordToAdd == NS_UNCONSTRAINEDSIZE)
 | |
|       aCoord = aCoordToAdd;
 | |
|     else
 | |
|       aCoord += aCoordToAdd;
 | |
|   }
 | |
| }
 | |
| void nsSprocketLayout::AddSmallestSize(nsSize& aSize, const nsSize& aSizeToAdd,
 | |
|                                        bool aIsHorizontal) {
 | |
|   if (aIsHorizontal)
 | |
|     AddCoord(aSize.width, aSizeToAdd.width);
 | |
|   else
 | |
|     AddCoord(aSize.height, aSizeToAdd.height);
 | |
| 
 | |
|   SetSmallestSize(aSize, aSizeToAdd, aIsHorizontal);
 | |
| }
 | |
| 
 | |
| bool nsSprocketLayout::GetDefaultFlex(int32_t& aFlex) {
 | |
|   aFlex = 0;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsComputedBoxSize::nsComputedBoxSize() {
 | |
|   resized = false;
 | |
|   valid = false;
 | |
|   size = 0;
 | |
|   next = nullptr;
 | |
| }
 | |
| 
 | |
| nsBoxSize::nsBoxSize() {
 | |
|   pref = 0;
 | |
|   min = 0;
 | |
|   max = NS_UNCONSTRAINEDSIZE;
 | |
|   collapsed = false;
 | |
|   left = 0;
 | |
|   right = 0;
 | |
|   flex = 0;
 | |
|   next = nullptr;
 | |
|   bogus = false;
 | |
| }
 | |
| 
 | |
| void* nsBoxSize::operator new(size_t sz,
 | |
|                               nsBoxLayoutState& aState) noexcept(true) {
 | |
|   return mozilla::AutoStackArena::Allocate(sz);
 | |
| }
 | |
| 
 | |
| void nsBoxSize::operator delete(void* aPtr, size_t sz) {}
 | |
| 
 | |
| void* nsComputedBoxSize::operator new(size_t sz,
 | |
|                                       nsBoxLayoutState& aState) noexcept(true) {
 | |
|   return mozilla::AutoStackArena::Allocate(sz);
 | |
| }
 | |
| 
 | |
| void nsComputedBoxSize::operator delete(void* aPtr, size_t sz) {}
 |