/* -*- 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 using mozilla::StyleDirection; using namespace mozilla; nsBoxLayout* nsSprocketLayout::gInstance = nullptr; static Maybe IterFor(nsIFrame* aBoxFrame) { Maybe ret; if (aBoxFrame->IsXULBoxFrame()) { ret.emplace(aBoxFrame, FrameChildListID::Principal, CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, CSSOrderAwareFrameIterator::OrderState::Unknown, CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup); } return ret; } nsresult NS_NewSprocketLayout(nsCOMPtr& 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) { 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; } 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 } 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; while (limit) { 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); // 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()) { continue; } nsSize pref = child->GetXULPrefSize(aState); AddXULMargin(child, pref); AddLargestSize(vpref, pref, isHorizontal); } // 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); // 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()) { 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; } AddXULMargin(child, min); AddLargestSize(minSize, min, isHorizontal); } // now add our border and padding AddXULBorderAndPadding(aBox, minSize); return minSize; } nsSize nsSprocketLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) { bool isHorizontal = IsXULHorizontal(aBox); 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 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); } // 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) {}