static void ScheduleReflow(nsIPresShell* aShell, nsIFrame* aFrame) { nsIFrame* f = aFrame; if (f->IsFrameOfType(nsIFrame::eSVG) || f->IsSVGText()) { // SVG frames (and the non-SVG descendants of an SVGTextFrame) need special // reflow handling. We need to search upwards for the first displayed // nsSVGOuterSVGFrame or non-SVG frame, which is the frame we can call // FrameNeedsReflow on. (This logic is based on // nsSVGUtils::ScheduleReflowSVG and // SVGTextFrame::ScheduleReflowSVGNonDisplayText.) if (f->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { while (f) { if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { if (NS_SUBTREE_DIRTY(f)) { // This is a displayed frame, so if it is already dirty, we // will be reflowed soon anyway. No need to call // FrameNeedsReflow again, then. return; } if (f->GetStateBits() & NS_STATE_IS_OUTER_SVG || !(f->IsFrameOfType(nsIFrame::eSVG) || f->IsSVGText())) { break; } f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } f = f->GetParent(); } MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); } } aShell->FrameNeedsReflow(f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); }
nscoord nsFormControlFrame::GetBaseline() const { NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "frame must not be dirty"); // Treat radio buttons and checkboxes as having an intrinsic baseline // at the bottom of the control (use the bottom content edge rather // than the bottom margin edge). return mRect.height - GetUsedBorderAndPadding().bottom; }
nsresult nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState) { // All we want out of it later on, really, is the height of a row, so we // don't even need to cache mDropdownFrame's ascent or anything. If we don't // need to reflow it, just bail out here. if (!aReflowState.ShouldReflowAllKids() && !NS_SUBTREE_DIRTY(mDropdownFrame)) { return NS_OK; } // XXXbz this will, for small-height dropdowns, have extra space on the right // edge for the scrollbar we don't show... but that's the best we can do here // for now. nsSize availSize(aReflowState.availableWidth, NS_UNCONSTRAINEDSIZE); nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame, availSize); // If the dropdown's intrinsic width is narrower than our specified width, // then expand it out. We want our border-box width to end up the same as // the dropdown's so account for both sets of mComputedBorderPadding. nscoord forcedWidth = aReflowState.ComputedWidth() + aReflowState.mComputedBorderPadding.LeftRight() - kidReflowState.mComputedBorderPadding.LeftRight(); kidReflowState.SetComputedWidth(PR_MAX(kidReflowState.ComputedWidth(), forcedWidth)); // ensure we start off hidden if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { nsIView* view = mDropdownFrame->GetView(); nsIViewManager* viewManager = view->GetViewManager(); viewManager->SetViewVisibility(view, nsViewVisibility_kHide); nsRect emptyRect(0, 0, 0, 0); viewManager->ResizeView(view, emptyRect); } // Allow the child to move/size/change-visibility its view if it's currently // dropped down PRInt32 flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW; if (mDroppedDown) { flags = 0; } nsRect rect = mDropdownFrame->GetRect(); nsHTMLReflowMetrics desiredSize; nsReflowStatus ignoredStatus; nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize, kidReflowState, rect.x, rect.y, flags, ignoredStatus); // Set the child's width and height to it's desired size FinishReflowChild(mDropdownFrame, aPresContext, &kidReflowState, desiredSize, rect.x, rect.y, flags); return rv; }
NS_IMETHODIMP nsStackLayout::Layout(nsIBox* aBox, nsBoxLayoutState& aState) { nsRect clientRect; aBox->GetClientRect(clientRect); PRBool grow; do { nsIBox* child = aBox->GetChildBox(); grow = PR_FALSE; while (child) { nsMargin margin; child->GetMargin(margin); nsRect childRect(clientRect); childRect.Deflate(margin); if (childRect.width < 0) childRect.width = 0; if (childRect.height < 0) childRect.height = 0; nsRect oldRect(child->GetRect()); PRBool sizeChanged = (oldRect != childRect); // only lay out dirty children or children whose sizes have changed if (sizeChanged || NS_SUBTREE_DIRTY(child)) { // add in the child's margin nsMargin margin; child->GetMargin(margin); // obtain our offset from the top left border of the stack's content box. nsSize offset(0,0); PRBool offsetSpecified = AddOffset(aState, child, offset); // Correct the child's x/y position by adding in both the margins // and the left/top offset. childRect.x = clientRect.x + offset.width + margin.left; childRect.y = clientRect.y + offset.height + margin.top; // If we have an offset, we don't stretch the child. Just use // its preferred size. if (offsetSpecified) { nsSize pref = child->GetPrefSize(aState); childRect.width = pref.width; childRect.height = pref.height; } // Now place the child. child->SetBounds(aState, childRect); // Flow the child. child->Layout(aState); // Get the child's new rect. nsRect childRectNoMargin; childRectNoMargin = childRect = child->GetRect(); childRect.Inflate(margin); // Did the child push back on us and get bigger? if (offset.width + childRect.width > clientRect.width) { clientRect.width = childRect.width + offset.width; grow = PR_TRUE; } if (offset.height + childRect.height > clientRect.height) { clientRect.height = childRect.height + offset.height; grow = PR_TRUE; } if (childRectNoMargin != oldRect) { // redraw the new and old positions if the // child moved or resized. // if the new and old rect intersect meaning we just moved a little // then just redraw the union. If they don't intersect (meaning // we moved a good distance) redraw both separately. if (childRectNoMargin.Intersects(oldRect)) { nsRect u; u.UnionRect(oldRect, childRectNoMargin); aBox->Redraw(aState, &u); } else { aBox->Redraw(aState, &oldRect); aBox->Redraw(aState, &childRectNoMargin); } } } child = child->GetNextBox(); } } while (grow); // if some HTML inside us got bigger we need to force ourselves to // get bigger nsRect bounds(aBox->GetRect()); nsMargin bp; aBox->GetBorderAndPadding(bp); clientRect.Inflate(bp); if (clientRect.width > bounds.width || clientRect.height > bounds.height) { if (clientRect.width > bounds.width) bounds.width = clientRect.width; if (clientRect.height > bounds.height) bounds.height = clientRect.height; aBox->SetBounds(aState, bounds); } return NS_OK; }
nsresult nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsReflowStatus& aReflowStatus, nscoord aContainingBlockWidth, nscoord aContainingBlockHeight, PRBool aConstrainHeight, PRBool aCBWidthChanged, PRBool aCBHeightChanged, nsOverflowAreas* aOverflowAreas) { nsReflowStatus reflowStatus = NS_FRAME_COMPLETE; PRBool reflowAll = aReflowState.ShouldReflowAllKids(); nsIFrame* kidFrame; nsOverflowContinuationTracker tracker(aPresContext, aDelegatingFrame, PR_TRUE); for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame; kidFrame = kidFrame->GetNextSibling()) { PRBool kidNeedsReflow = reflowAll || NS_SUBTREE_DIRTY(kidFrame) || FrameDependsOnContainer(kidFrame, aCBWidthChanged, aCBHeightChanged); if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) { // Reflow the frame nsReflowStatus kidStatus = NS_FRAME_COMPLETE; ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowState, aContainingBlockWidth, aContainingBlockHeight, aConstrainHeight, kidFrame, kidStatus, aOverflowAreas); nsIFrame* nextFrame = kidFrame->GetNextInFlow(); if (!NS_FRAME_IS_FULLY_COMPLETE(kidStatus)) { // Need a continuation if (!nextFrame) { nsresult rv = aPresContext->PresShell()->FrameConstructor()-> CreateContinuingFrame(aPresContext, kidFrame, aDelegatingFrame, &nextFrame); NS_ENSURE_SUCCESS(rv, rv); } // Add it as an overflow container. //XXXfr This is a hack to fix some of our printing dataloss. // See bug 154892. Not sure how to do it "right" yet; probably want // to keep continuations within an nsAbsoluteContainingBlock eventually. tracker.Insert(nextFrame, kidStatus); NS_MergeReflowStatusInto(&reflowStatus, kidStatus); } else { // Delete any continuations if (nextFrame) { tracker.Finish(kidFrame); static_cast<nsContainerFrame*>(nextFrame->GetParent()) ->DeleteNextInFlowChild(aPresContext, nextFrame, PR_TRUE); } } } else { tracker.Skip(kidFrame, reflowStatus); if (aOverflowAreas) { aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame); } } // Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That // will make sure that we end up reflowing aDelegatingFrame in cases when // one of our kids interrupted. Otherwise we'd set the dirty or // dirty-children bit on the kid in the condition below, and then when // reflow completes and we go to mark dirty bits on all ancestors of that // kid we'll immediately bail out, because the kid already has a dirty bit. // In particular, we won't set any dirty bits on aDelegatingFrame, so when // the following reflow happens we won't reflow the kid in question. This // might be slightly suboptimal in cases where |kidFrame| itself did not // interrupt, since we'll trigger a reflow of it too when it's not strictly // needed. But the logic to not do that is enough more complicated, and // the case enough of an edge case, that this is probably better. if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) { if (aDelegatingFrame->GetStateBits() & NS_FRAME_IS_DIRTY) { kidFrame->AddStateBits(NS_FRAME_IS_DIRTY); } else { kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } } } // Abspos frames can't cause their parent to be incomplete, // only overflow incomplete. if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus)) NS_FRAME_SET_OVERFLOW_INCOMPLETE(reflowStatus); NS_MergeReflowStatusInto(&aReflowStatus, reflowStatus); return NS_OK; }
/** * Called to layout our our children. Does no frame construction */ NS_IMETHODIMP nsListBoxLayout::LayoutInternal(nsIFrame* aBox, nsBoxLayoutState& aState) { int32_t redrawStart = -1; // Get the start y position. nsListBoxBodyFrame* body = static_cast<nsListBoxBodyFrame*>(aBox); if (!body) { NS_ERROR("Frame encountered that isn't a listboxbody!"); return NS_ERROR_FAILURE; } nsMargin margin; // Get our client rect. nsRect clientRect; aBox->GetClientRect(clientRect); // Get the starting y position and the remaining available // height. nscoord availableHeight = body->GetAvailableHeight(); nscoord yOffset = body->GetYPosition(); if (availableHeight <= 0) { bool fixed = (body->GetFixedRowSize() != -1); if (fixed) availableHeight = 10; else return NS_OK; } // run through all our currently created children nsIFrame* box = body->GetChildBox(); // if the reason is resize or initial we must relayout. nscoord rowHeight = body->GetRowHeightAppUnits(); while (box) { // If this box is dirty or if it has dirty children, we // call layout on it. nsRect childRect(box->GetRect()); box->GetMargin(margin); // relayout if we must or we are dirty or some of our children are dirty // or the client area is wider than us // XXXldb There should probably be a resize check here too! if (NS_SUBTREE_DIRTY(box) || childRect.width < clientRect.width) { childRect.x = 0; childRect.y = yOffset; childRect.width = clientRect.width; nsSize size = box->GetPrefSize(aState); body->SetRowHeight(size.height); childRect.height = rowHeight; childRect.Deflate(margin); box->SetBounds(aState, childRect); box->Layout(aState); } else { // if the child did not need to be relayed out. Then its easy. // Place the child by just grabbing its rect and adjusting the y. int32_t newPos = yOffset+margin.top; // are we pushing down or pulling up any rows? // Then we may have to redraw everything below the moved // rows. if (redrawStart == -1 && childRect.y != newPos) redrawStart = newPos; childRect.y = newPos; box->SetBounds(aState, childRect); } // Ok now the available size gets smaller and we move the // starting position of the next child down some. nscoord size = childRect.height + margin.top + margin.bottom; yOffset += size; availableHeight -= size; box = box->GetNextBox(); } // We have enough available height left to add some more rows // Since we can't do this during layout, we post a callback // that will be processed after the reflow completes. body->PostReflowCallback(); // if rows were pushed down or pulled up because some rows were added // before them then redraw everything under the inserted rows. The inserted // rows will automatically be redrawn because the were marked dirty on insertion. if (redrawStart > -1) { aBox->Redraw(aState); } return NS_OK; }
NS_IMETHODIMP nsSprocketLayout::Layout(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->IsCollapsed()) { nsIFrame* child = nsBox::GetChildBox(aBox); while(child) { nsBoxFrame::LayoutChildAt(aState, child, nsRect(0,0,0,0)); child = nsBox::GetNextBox(child); } 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->GetClientRect(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 (!IsHorizontal(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 (IsHorizontal(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 { #ifdef DEBUG_REFLOW if (passes > 0) { AddIndents(); printf("ChildResized doing pass: %d\n", passes); } #endif // 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; nsIFrame* child = nsBox::GetChildBox(aBox); int32_t count = 0; while (child || (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) { NS_NOTREACHED("Lists not the same length."); break; } nscoord width = clientRect.width; nscoord height = clientRect.height; if (!childBoxSize->bogus) { // 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->GetPrefSize(aState); nsSize minSize = child->GetMinSize(aState); nsSize maxSize = child->GetMaxSize(aState); prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize); AddMargin(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); childRect.x = originalClientRect.x; } // 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; continue; } nsMargin margin(0,0,0,0); bool layout = true; // Deflate the rect of our child by its margin. child->GetMargin(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 Layout method. if (passes > 0) { layout = false; } else { // Always perform layout if we are dirty or have dirty children if (!NS_SUBTREE_DIRTY(child)) 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->SetBounds(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->GetMinSize(aState); nsSize maxSize = child->GetMaxSize(aState); maxSize = nsBox::BoundsCheckMinMax(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->SetBounds(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->Layout(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 child->DumpBox(stdout); 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 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->SetBounds(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; child = nsBox::GetNextBox(child); 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->GetBorderAndPadding(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->SetBounds(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) { nsIFrame* child = nsBox::GetChildBox(aBox); // reposition all our children while (child) { nsRect childRect(child->GetRect()); childRect.x += (x - origX); childRect.y += (y - origY); child->SetBounds(aState, childRect); child = nsBox::GetNextBox(child); } } // 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; }
bool nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus, const ReflowConfig& aConfig, bool aUnboundedLastColumn, nsCollapsingMargin* aBottomMarginCarriedOut, ColumnBalanceData& aColData) { aColData.Reset(); bool allFit = true; bool RTL = GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; bool shrinkingHeightOnly = !NS_SUBTREE_DIRTY(this) && mLastBalanceHeight > aConfig.mColMaxHeight; #ifdef DEBUG_roc printf("*** Doing column reflow pass: mLastBalanceHeight=%d, mColMaxHeight=%d, RTL=%d\n, mBalanceColCount=%d, mColWidth=%d, mColGap=%d\n", mLastBalanceHeight, aConfig.mColMaxHeight, RTL, aConfig.mBalanceColCount, aConfig.mColWidth, aConfig.mColGap); #endif DrainOverflowColumns(); if (mLastBalanceHeight != aConfig.mColMaxHeight) { mLastBalanceHeight = aConfig.mColMaxHeight; // XXX Seems like this could fire if incremental reflow pushed the column set // down so we reflow incrementally with a different available height. // We need a way to do an incremental reflow and be sure availableHeight // changes are taken account of! Right now I think block frames with absolute // children might exit early. //NS_ASSERTION(aKidReason != eReflowReason_Incremental, // "incremental reflow should not have changed the balance height"); } // get our border and padding const nsMargin &borderPadding = aReflowState.mComputedBorderPadding; nsRect contentRect(0, 0, 0, 0); nsOverflowAreas overflowRects; nsIFrame* child = mFrames.FirstChild(); nsPoint childOrigin = nsPoint(borderPadding.left, borderPadding.top); // For RTL, figure out where the last column's left edge should be. Since the // columns might not fill the frame exactly, we need to account for the // slop. Otherwise we'll waste time moving the columns by some tiny // amount unnecessarily. nscoord targetX = borderPadding.left; if (RTL) { nscoord availWidth = aReflowState.availableWidth; if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) { availWidth = aReflowState.ComputedWidth(); } if (availWidth != NS_INTRINSICSIZE) { childOrigin.x += availWidth - aConfig.mColWidth; targetX += aConfig.mExpectedWidthLeftOver; #ifdef DEBUG_roc printf("*** childOrigin.x = %d\n", childOrigin.x); #endif } } int columnCount = 0; int contentBottom = 0; bool reflowNext = false; while (child) { // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't // skip if the next column is dirty, because the next column's first line(s) // might be pullable back to this column. We can't skip if it's the last child // because we need to obtain the bottom margin. We can't skip // if this is the last column and we're supposed to assign unbounded // height to it, because that could change the available height from // the last time we reflowed it and we should try to pull all the // content from its next sibling. (Note that it might be the last // column, but not be the last child because the desired number of columns // has changed.) bool skipIncremental = !aReflowState.ShouldReflowAllKids() && !NS_SUBTREE_DIRTY(child) && child->GetNextSibling() && !(aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) && !NS_SUBTREE_DIRTY(child->GetNextSibling()); // If we need to pull up content from the prev-in-flow then this is not just // a height shrink. The prev in flow will have set the dirty bit. // Check the overflow rect YMost instead of just the child's content height. The child // may have overflowing content that cares about the available height boundary. // (It may also have overflowing content that doesn't care about the available height // boundary, but if so, too bad, this optimization is defeated.) // We want scrollable overflow here since this is a calculation that // affects layout. bool skipResizeHeightShrink = shrinkingHeightOnly && child->GetScrollableOverflowRect().YMost() <= aConfig.mColMaxHeight; nscoord childContentBottom = 0; if (!reflowNext && (skipIncremental || skipResizeHeightShrink)) { // This child does not need to be reflowed, but we may need to move it MoveChildTo(this, child, childOrigin); // If this is the last frame then make sure we get the right status nsIFrame* kidNext = child->GetNextSibling(); if (kidNext) { aStatus = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) ? NS_FRAME_OVERFLOW_INCOMPLETE : NS_FRAME_NOT_COMPLETE; } else { aStatus = mLastFrameStatus; } childContentBottom = nsLayoutUtils::CalculateContentBottom(child); #ifdef DEBUG_roc printf("*** Skipping child #%d %p (incremental %d, resize height shrink %d): status = %d\n", columnCount, (void*)child, skipIncremental, skipResizeHeightShrink, aStatus); #endif } else { nsSize availSize(aConfig.mColWidth, aConfig.mColMaxHeight); if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) { availSize.height = GetAvailableContentHeight(aReflowState); } if (reflowNext) child->AddStateBits(NS_FRAME_IS_DIRTY); nsHTMLReflowState kidReflowState(PresContext(), aReflowState, child, availSize, availSize.width, aReflowState.ComputedHeight()); kidReflowState.mFlags.mIsTopOfPage = PR_TRUE; kidReflowState.mFlags.mTableIsSplittable = PR_FALSE; #ifdef DEBUG_roc printf("*** Reflowing child #%d %p: availHeight=%d\n", columnCount, (void*)child,availSize.height); #endif // Note if the column's next in flow is not being changed by this incremental reflow. // This may allow the current column to avoid trying to pull lines from the next column. if (child->GetNextSibling() && !(GetStateBits() & NS_FRAME_IS_DIRTY) && !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) { kidReflowState.mFlags.mNextInFlowUntouched = PR_TRUE; } nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags); // XXX it would be cool to consult the float manager for the // previous block to figure out the region of floats from the // previous column that extend into this column, and subtract // that region from the new float manager. So you could stick a // really big float in the first column and text in following // columns would flow around it. // Reflow the frame ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState, childOrigin.x + kidReflowState.mComputedMargin.left, childOrigin.y + kidReflowState.mComputedMargin.top, 0, aStatus); reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0; #ifdef DEBUG_roc printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d\n", columnCount, (void*)child, aStatus, kidDesiredSize.width, kidDesiredSize.height); #endif NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus); *aBottomMarginCarriedOut = kidDesiredSize.mCarriedOutBottomMargin; FinishReflowChild(child, PresContext(), &kidReflowState, kidDesiredSize, childOrigin.x, childOrigin.y, 0); childContentBottom = nsLayoutUtils::CalculateContentBottom(child); if (childContentBottom > aConfig.mColMaxHeight) { allFit = PR_FALSE; } if (childContentBottom > availSize.height) { aColData.mMaxOverflowingHeight = NS_MAX(childContentBottom, aColData.mMaxOverflowingHeight); } } contentRect.UnionRect(contentRect, child->GetRect()); ConsiderChildOverflow(overflowRects, child); contentBottom = NS_MAX(contentBottom, childContentBottom); aColData.mLastHeight = childContentBottom; aColData.mSumHeight += childContentBottom; // Build a continuation column if necessary nsIFrame* kidNextInFlow = child->GetNextInFlow(); if (NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) { NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted"); child = nsnull; break; } else { ++columnCount; // Make sure that the column has a next-in-flow. If not, we must // create one to hold the overflowing stuff, even if we're just // going to put it on our overflow list and let *our* // next in flow handle it. if (!kidNextInFlow) { NS_ASSERTION(aStatus & NS_FRAME_REFLOW_NEXTINFLOW, "We have to create a continuation, but the block doesn't want us to reflow it?"); // We need to create a continuing column nsresult rv = CreateNextInFlow(PresContext(), child, kidNextInFlow); if (NS_FAILED(rv)) { NS_NOTREACHED("Couldn't create continuation"); child = nsnull; break; } } // Make sure we reflow a next-in-flow when it switches between being // normal or overflow container if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) { if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) { aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; reflowNext = PR_TRUE; kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); } } else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) { aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; reflowNext = PR_TRUE; kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); } if (columnCount >= aConfig.mBalanceColCount) { // No more columns allowed here. Stop. aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY); // Move any of our leftover columns to our overflow list. Our // next-in-flow will eventually pick them up. const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child); if (continuationColumns.NotEmpty()) { SetOverflowFrames(PresContext(), continuationColumns); } child = nsnull; break; } } if (PresContext()->HasPendingInterrupt()) { // Stop the loop now while |child| still points to the frame that bailed // out. We could keep going here and condition a bunch of the code in // this loop on whether there's an interrupt, or even just keep going and // trying to reflow the blocks (even though we know they'll interrupt // right after their first line), but stopping now is conceptually the // simplest (and probably fastest) thing. break; } // Advance to the next column child = child->GetNextSibling(); if (child) { if (!RTL) { childOrigin.x += aConfig.mColWidth + aConfig.mColGap; } else { childOrigin.x -= aConfig.mColWidth + aConfig.mColGap; } #ifdef DEBUG_roc printf("*** NEXT CHILD ORIGIN.x = %d\n", childOrigin.x); #endif } } if (PresContext()->CheckForInterrupt(this) && (GetStateBits() & NS_FRAME_IS_DIRTY)) { // Mark all our kids starting with |child| dirty // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt, // because we might have interrupted while reflowing |child|, and since // we're about to add a dirty bit to |child| we need to make sure that // |this| is scheduled to have dirty bits marked on it and its ancestors. // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll // bail out immediately, since it'll already have a dirty bit. for (; child; child = child->GetNextSibling()) { child->AddStateBits(NS_FRAME_IS_DIRTY); } } // If we're doing RTL, we need to make sure our last column is at the left-hand side of the frame. if (RTL && childOrigin.x != targetX) { overflowRects.Clear(); contentRect = nsRect(0, 0, 0, 0); PRInt32 deltaX = targetX - childOrigin.x; #ifdef DEBUG_roc printf("*** CHILDORIGIN.x = %d, targetX = %d, DELTAX = %d\n", childOrigin.x, targetX, deltaX); #endif for (child = mFrames.FirstChild(); child; child = child->GetNextSibling()) { MoveChildTo(this, child, child->GetPosition() + nsPoint(deltaX, 0)); ConsiderChildOverflow(overflowRects, child); contentRect.UnionRect(contentRect, child->GetRect()); } } aColData.mMaxHeight = contentBottom; contentRect.height = NS_MAX(contentRect.height, contentBottom); mLastFrameStatus = aStatus; // contentRect included the borderPadding.left,borderPadding.top of the child rects contentRect -= nsPoint(borderPadding.left, borderPadding.top); nsSize contentSize = nsSize(contentRect.XMost(), contentRect.YMost()); // Apply computed and min/max values if (aReflowState.ComputedHeight() != NS_INTRINSICSIZE) { contentSize.height = aReflowState.ComputedHeight(); } else { if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) { contentSize.height = NS_MIN(aReflowState.mComputedMaxHeight, contentSize.height); } if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) { contentSize.height = NS_MAX(aReflowState.mComputedMinHeight, contentSize.height); } } if (aReflowState.ComputedWidth() != NS_INTRINSICSIZE) { contentSize.width = aReflowState.ComputedWidth(); } else { if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) { contentSize.width = NS_MIN(aReflowState.mComputedMaxWidth, contentSize.width); } if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) { contentSize.width = NS_MAX(aReflowState.mComputedMinWidth, contentSize.width); } } aDesiredSize.height = borderPadding.top + contentSize.height + borderPadding.bottom; aDesiredSize.width = contentSize.width + borderPadding.left + borderPadding.right; aDesiredSize.mOverflowAreas = overflowRects; aDesiredSize.UnionOverflowAreasWithDesiredBounds(); #ifdef DEBUG_roc printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)); #endif return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus); }