layout/generic/nsAbsoluteContainingBlock.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /*
     7  * code for managing absolutely positioned children of a rendering
     8  * object that is a containing block for them
     9  */
    11 #include "nsAbsoluteContainingBlock.h"
    13 #include "nsContainerFrame.h"
    14 #include "nsGkAtoms.h"
    15 #include "nsIPresShell.h"
    16 #include "nsHTMLReflowState.h"
    17 #include "nsPresContext.h"
    18 #include "nsCSSFrameConstructor.h"
    20 #ifdef DEBUG
    21 #include "nsBlockFrame.h"
    23 static void PrettyUC(nscoord aSize, char* aBuf)
    24 {
    25   if (NS_UNCONSTRAINEDSIZE == aSize) {
    26     strcpy(aBuf, "UC");
    27   } else {
    28     if((int32_t)0xdeadbeef == aSize) {
    29       strcpy(aBuf, "deadbeef");
    30     } else {
    31       sprintf(aBuf, "%d", aSize);
    32     }
    33   }
    34 }
    35 #endif
    37 nsresult
    38 nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame*       aDelegatingFrame,
    39                                                ChildListID     aListID,
    40                                                nsFrameList&    aChildList)
    41 {
    42   NS_PRECONDITION(mChildListID == aListID, "unexpected child list name");
    43 #ifdef DEBUG
    44   nsFrame::VerifyDirtyBitSet(aChildList);
    45 #endif
    46   mAbsoluteFrames.SetFrames(aChildList);
    47   return NS_OK;
    48 }
    50 nsresult
    51 nsAbsoluteContainingBlock::AppendFrames(nsIFrame*      aDelegatingFrame,
    52                                         ChildListID    aListID,
    53                                         nsFrameList&   aFrameList)
    54 {
    55   NS_ASSERTION(mChildListID == aListID, "unexpected child list");
    57   // Append the frames to our list of absolutely positioned frames
    58 #ifdef DEBUG
    59   nsFrame::VerifyDirtyBitSet(aFrameList);
    60 #endif
    61   mAbsoluteFrames.AppendFrames(nullptr, aFrameList);
    63   // no damage to intrinsic widths, since absolutely positioned frames can't
    64   // change them
    65   aDelegatingFrame->PresContext()->PresShell()->
    66     FrameNeedsReflow(aDelegatingFrame, nsIPresShell::eResize,
    67                      NS_FRAME_HAS_DIRTY_CHILDREN);
    69   return NS_OK;
    70 }
    72 nsresult
    73 nsAbsoluteContainingBlock::InsertFrames(nsIFrame*      aDelegatingFrame,
    74                                         ChildListID    aListID,
    75                                         nsIFrame*      aPrevFrame,
    76                                         nsFrameList&   aFrameList)
    77 {
    78   NS_ASSERTION(mChildListID == aListID, "unexpected child list");
    79   NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
    80                "inserting after sibling frame with different parent");
    82 #ifdef DEBUG
    83   nsFrame::VerifyDirtyBitSet(aFrameList);
    84 #endif
    85   mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
    87   // no damage to intrinsic widths, since absolutely positioned frames can't
    88   // change them
    89   aDelegatingFrame->PresContext()->PresShell()->
    90     FrameNeedsReflow(aDelegatingFrame, nsIPresShell::eResize,
    91                      NS_FRAME_HAS_DIRTY_CHILDREN);
    93   return NS_OK;
    94 }
    96 void
    97 nsAbsoluteContainingBlock::RemoveFrame(nsIFrame*       aDelegatingFrame,
    98                                        ChildListID     aListID,
    99                                        nsIFrame*       aOldFrame)
   100 {
   101   NS_ASSERTION(mChildListID == aListID, "unexpected child list");
   102   nsIFrame* nif = aOldFrame->GetNextInFlow();
   103   if (nif) {
   104     static_cast<nsContainerFrame*>(nif->GetParent())
   105       ->DeleteNextInFlowChild(nif, false);
   106   }
   108   mAbsoluteFrames.DestroyFrame(aOldFrame);
   109 }
   111 nsresult
   112 nsAbsoluteContainingBlock::Reflow(nsContainerFrame*        aDelegatingFrame,
   113                                   nsPresContext*           aPresContext,
   114                                   const nsHTMLReflowState& aReflowState,
   115                                   nsReflowStatus&          aReflowStatus,
   116                                   const nsRect&            aContainingBlock,
   117                                   bool                     aConstrainHeight,
   118                                   bool                     aCBWidthChanged,
   119                                   bool                     aCBHeightChanged,
   120                                   nsOverflowAreas*         aOverflowAreas)
   121 {
   122   nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
   124   bool reflowAll = aReflowState.ShouldReflowAllKids();
   126   nsIFrame* kidFrame;
   127   nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
   128   for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame; kidFrame = kidFrame->GetNextSibling()) {
   129     bool kidNeedsReflow = reflowAll || NS_SUBTREE_DIRTY(kidFrame) ||
   130       FrameDependsOnContainer(kidFrame, aCBWidthChanged, aCBHeightChanged);
   131     if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
   132       // Reflow the frame
   133       nsReflowStatus  kidStatus = NS_FRAME_COMPLETE;
   134       ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowState,
   135                           aContainingBlock,
   136                           aConstrainHeight, kidFrame, kidStatus,
   137                           aOverflowAreas);
   138       nsIFrame* nextFrame = kidFrame->GetNextInFlow();
   139       if (!NS_FRAME_IS_FULLY_COMPLETE(kidStatus)) {
   140         // Need a continuation
   141         if (!nextFrame) {
   142           nextFrame =
   143             aPresContext->PresShell()->FrameConstructor()->
   144               CreateContinuingFrame(aPresContext, kidFrame, aDelegatingFrame);
   145         }
   146         // Add it as an overflow container.
   147         //XXXfr This is a hack to fix some of our printing dataloss.
   148         // See bug 154892. Not sure how to do it "right" yet; probably want
   149         // to keep continuations within an nsAbsoluteContainingBlock eventually.
   150         tracker.Insert(nextFrame, kidStatus);
   151         NS_MergeReflowStatusInto(&reflowStatus, kidStatus);
   152       }
   153       else {
   154         // Delete any continuations
   155         if (nextFrame) {
   156           nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
   157           static_cast<nsContainerFrame*>(nextFrame->GetParent())
   158             ->DeleteNextInFlowChild(nextFrame, true);
   159         }
   160       }
   161     }
   162     else {
   163       tracker.Skip(kidFrame, reflowStatus);
   164       if (aOverflowAreas) {
   165         aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
   166       }
   167     }
   169     // Make a CheckForInterrupt call, here, not just HasPendingInterrupt.  That
   170     // will make sure that we end up reflowing aDelegatingFrame in cases when
   171     // one of our kids interrupted.  Otherwise we'd set the dirty or
   172     // dirty-children bit on the kid in the condition below, and then when
   173     // reflow completes and we go to mark dirty bits on all ancestors of that
   174     // kid we'll immediately bail out, because the kid already has a dirty bit.
   175     // In particular, we won't set any dirty bits on aDelegatingFrame, so when
   176     // the following reflow happens we won't reflow the kid in question.  This
   177     // might be slightly suboptimal in cases where |kidFrame| itself did not
   178     // interrupt, since we'll trigger a reflow of it too when it's not strictly
   179     // needed.  But the logic to not do that is enough more complicated, and
   180     // the case enough of an edge case, that this is probably better.
   181     if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
   182       if (aDelegatingFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
   183         kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
   184       } else {
   185         kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   186       }
   187     }
   188   }
   190   // Abspos frames can't cause their parent to be incomplete,
   191   // only overflow incomplete.
   192   if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus))
   193     NS_FRAME_SET_OVERFLOW_INCOMPLETE(reflowStatus);
   195   NS_MergeReflowStatusInto(&aReflowStatus, reflowStatus);
   196   return NS_OK;
   197 }
   199 static inline bool IsFixedPaddingSize(const nsStyleCoord& aCoord)
   200   { return aCoord.ConvertsToLength(); }
   201 static inline bool IsFixedMarginSize(const nsStyleCoord& aCoord)
   202   { return aCoord.ConvertsToLength(); }
   203 static inline bool IsFixedOffset(const nsStyleCoord& aCoord)
   204   { return aCoord.ConvertsToLength(); }
   206 bool
   207 nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
   208                                                    bool aCBWidthChanged,
   209                                                    bool aCBHeightChanged)
   210 {
   211   const nsStylePosition* pos = f->StylePosition();
   212   // See if f's position might have changed because it depends on a
   213   // placeholder's position
   214   // This can happen in the following cases:
   215   // 1) Vertical positioning.  "top" must be auto and "bottom" must be auto
   216   //    (otherwise the vertical position is completely determined by
   217   //    whichever of them is not auto and the height).
   218   // 2) Horizontal positioning.  "left" must be auto and "right" must be auto
   219   //    (otherwise the horizontal position is completely determined by
   220   //    whichever of them is not auto and the width).
   221   // See nsHTMLReflowState::InitAbsoluteConstraints -- these are the
   222   // only cases when we call CalculateHypotheticalBox().
   223   if ((pos->mOffset.GetTopUnit() == eStyleUnit_Auto &&
   224        pos->mOffset.GetBottomUnit() == eStyleUnit_Auto) ||
   225       (pos->mOffset.GetLeftUnit() == eStyleUnit_Auto &&
   226        pos->mOffset.GetRightUnit() == eStyleUnit_Auto)) {
   227     return true;
   228   }
   229   if (!aCBWidthChanged && !aCBHeightChanged) {
   230     // skip getting style data
   231     return false;
   232   }
   233   const nsStylePadding* padding = f->StylePadding();
   234   const nsStyleMargin* margin = f->StyleMargin();
   235   if (aCBWidthChanged) {
   236     // See if f's width might have changed.
   237     // If border-left, border-right, padding-left, padding-right,
   238     // width, min-width, and max-width are all lengths, 'none', or enumerated,
   239     // then our frame width does not depend on the parent width.
   240     // Note that borders never depend on the parent width
   241     // XXX All of the enumerated values except -moz-available are ok too.
   242     if (pos->WidthDependsOnContainer() ||
   243         pos->MinWidthDependsOnContainer() ||
   244         pos->MaxWidthDependsOnContainer() ||
   245         !IsFixedPaddingSize(padding->mPadding.GetLeft()) ||
   246         !IsFixedPaddingSize(padding->mPadding.GetRight())) {
   247       return true;
   248     }
   250     // See if f's position might have changed. If we're RTL then the
   251     // rules are slightly different. We'll assume percentage or auto
   252     // margins will always induce a dependency on the size
   253     if (!IsFixedMarginSize(margin->mMargin.GetLeft()) ||
   254         !IsFixedMarginSize(margin->mMargin.GetRight())) {
   255       return true;
   256     }
   257     if (f->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
   258       // Note that even if 'left' is a length, our position can
   259       // still depend on the containing block width, because if
   260       // 'right' is also a length we will discard 'left' and be
   261       // positioned relative to the containing block right edge.
   262       // 'left' length and 'right' auto is the only combination
   263       // we can be sure of.
   264       if (!IsFixedOffset(pos->mOffset.GetLeft()) ||
   265           pos->mOffset.GetRightUnit() != eStyleUnit_Auto) {
   266         return true;
   267       }
   268     } else {
   269       if (!IsFixedOffset(pos->mOffset.GetLeft())) {
   270         return true;
   271       }
   272     }
   273   }
   274   if (aCBHeightChanged) {
   275     // See if f's height might have changed.
   276     // If border-top, border-bottom, padding-top, padding-bottom,
   277     // min-height, and max-height are all lengths or 'none',
   278     // and height is a length or height and bottom are auto and top is not auto,
   279     // then our frame height does not depend on the parent height.
   280     // Note that borders never depend on the parent height
   281     if ((pos->HeightDependsOnContainer() &&
   282          !(pos->mHeight.GetUnit() == eStyleUnit_Auto &&
   283            pos->mOffset.GetBottomUnit() == eStyleUnit_Auto &&
   284            pos->mOffset.GetTopUnit() != eStyleUnit_Auto)) ||
   285         pos->MinHeightDependsOnContainer() ||
   286         pos->MaxHeightDependsOnContainer() ||
   287         !IsFixedPaddingSize(padding->mPadding.GetTop()) ||
   288         !IsFixedPaddingSize(padding->mPadding.GetBottom())) { 
   289       return true;
   290     }
   292     // See if f's position might have changed.
   293     if (!IsFixedMarginSize(margin->mMargin.GetTop()) ||
   294         !IsFixedMarginSize(margin->mMargin.GetBottom())) {
   295       return true;
   296     }
   297     if (!IsFixedOffset(pos->mOffset.GetTop())) {
   298       return true;
   299     }
   300   }
   301   return false;
   302 }
   304 void
   305 nsAbsoluteContainingBlock::DestroyFrames(nsIFrame* aDelegatingFrame,
   306                                          nsIFrame* aDestructRoot)
   307 {
   308   mAbsoluteFrames.DestroyFramesFrom(aDestructRoot);
   309 }
   311 void
   312 nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty()
   313 {
   314   DoMarkFramesDirty(false);
   315 }
   317 void
   318 nsAbsoluteContainingBlock::MarkAllFramesDirty()
   319 {
   320   DoMarkFramesDirty(true);
   321 }
   323 void
   324 nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty)
   325 {
   326   for (nsIFrame* kidFrame = mAbsoluteFrames.FirstChild();
   327        kidFrame;
   328        kidFrame = kidFrame->GetNextSibling()) {
   329     if (aMarkAllDirty) {
   330       kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
   331     } else if (FrameDependsOnContainer(kidFrame, true, true)) {
   332       // Add the weakest flags that will make sure we reflow this frame later
   333       kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   334     }
   335   }
   336 }
   338 // XXX Optimize the case where it's a resize reflow and the absolutely
   339 // positioned child has the exact same size and position and skip the
   340 // reflow...
   342 // When bug 154892 is checked in, make sure that when 
   343 // mChildListID == kFixedList, the height is unconstrained.
   344 // since we don't allow replicated frames to split.
   346 nsresult
   347 nsAbsoluteContainingBlock::ReflowAbsoluteFrame(nsIFrame*                aDelegatingFrame,
   348                                                nsPresContext*           aPresContext,
   349                                                const nsHTMLReflowState& aReflowState,
   350                                                const nsRect&            aContainingBlock,
   351                                                bool                     aConstrainHeight,
   352                                                nsIFrame*                aKidFrame,
   353                                                nsReflowStatus&          aStatus,
   354                                                nsOverflowAreas*         aOverflowAreas)
   355 {
   356 #ifdef DEBUG
   357   if (nsBlockFrame::gNoisyReflow) {
   358     nsFrame::IndentBy(stdout,nsBlockFrame::gNoiseIndent);
   359     printf("abs pos ");
   360     if (aKidFrame) {
   361       nsAutoString name;
   362       aKidFrame->GetFrameName(name);
   363       printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
   364     }
   366     char width[16];
   367     char height[16];
   368     PrettyUC(aReflowState.AvailableWidth(), width);
   369     PrettyUC(aReflowState.AvailableHeight(), height);
   370     printf(" a=%s,%s ", width, height);
   371     PrettyUC(aReflowState.ComputedWidth(), width);
   372     PrettyUC(aReflowState.ComputedHeight(), height);
   373     printf("c=%s,%s \n", width, height);
   374   }
   375   AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
   376 #endif // DEBUG
   378   nscoord availWidth = aContainingBlock.width;
   379   if (availWidth == -1) {
   380     NS_ASSERTION(aReflowState.ComputedWidth() != NS_UNCONSTRAINEDSIZE,
   381                  "Must have a useful width _somewhere_");
   382     availWidth =
   383       aReflowState.ComputedWidth() + aReflowState.ComputedPhysicalPadding().LeftRight();
   384   }
   386   nsHTMLReflowMetrics kidDesiredSize(aReflowState);
   387   nsHTMLReflowState kidReflowState(aPresContext, aReflowState, aKidFrame,
   388                                    nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
   389                                    aContainingBlock.width,
   390                                    aContainingBlock.height);
   392   // Send the WillReflow() notification and position the frame
   393   aKidFrame->WillReflow(aPresContext);
   395   // Get the border values
   396   const nsMargin& border = aReflowState.mStyleBorder->GetComputedBorder();
   398   bool constrainHeight = (aReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE)
   399     && aConstrainHeight
   400        // Don't split if told not to (e.g. for fixed frames)
   401     && (aDelegatingFrame->GetType() != nsGkAtoms::inlineFrame)
   402        //XXX we don't handle splitting frames for inline absolute containing blocks yet
   403     && (aKidFrame->GetRect().y <= aReflowState.AvailableHeight());
   404        // Don't split things below the fold. (Ideally we shouldn't *have*
   405        // anything totally below the fold, but we can't position frames
   406        // across next-in-flow breaks yet.
   407   if (constrainHeight) {
   408     kidReflowState.AvailableHeight() = aReflowState.AvailableHeight() - border.top
   409                                      - kidReflowState.ComputedPhysicalMargin().top;
   410     if (NS_AUTOOFFSET != kidReflowState.ComputedPhysicalOffsets().top)
   411       kidReflowState.AvailableHeight() -= kidReflowState.ComputedPhysicalOffsets().top;
   412   }
   414   // Do the reflow
   415   nsresult rv = aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowState, aStatus);
   417   // If we're solving for 'left' or 'top', then compute it now that we know the
   418   // width/height
   419   if ((NS_AUTOOFFSET == kidReflowState.ComputedPhysicalOffsets().left) ||
   420       (NS_AUTOOFFSET == kidReflowState.ComputedPhysicalOffsets().top)) {
   421     nscoord aContainingBlockWidth = aContainingBlock.width;
   422     nscoord aContainingBlockHeight = aContainingBlock.height;
   424     if (-1 == aContainingBlockWidth) {
   425       // Get the containing block width/height
   426       kidReflowState.ComputeContainingBlockRectangle(aPresContext,
   427                                                      &aReflowState,
   428                                                      aContainingBlockWidth,
   429                                                      aContainingBlockHeight);
   430     }
   432     if (NS_AUTOOFFSET == kidReflowState.ComputedPhysicalOffsets().left) {
   433       NS_ASSERTION(NS_AUTOOFFSET != kidReflowState.ComputedPhysicalOffsets().right,
   434                    "Can't solve for both left and right");
   435       kidReflowState.ComputedPhysicalOffsets().left = aContainingBlockWidth -
   436                                              kidReflowState.ComputedPhysicalOffsets().right -
   437                                              kidReflowState.ComputedPhysicalMargin().right -
   438                                              kidDesiredSize.Width() -
   439                                              kidReflowState.ComputedPhysicalMargin().left;
   440     }
   441     if (NS_AUTOOFFSET == kidReflowState.ComputedPhysicalOffsets().top) {
   442       kidReflowState.ComputedPhysicalOffsets().top = aContainingBlockHeight -
   443                                             kidReflowState.ComputedPhysicalOffsets().bottom -
   444                                             kidReflowState.ComputedPhysicalMargin().bottom -
   445                                             kidDesiredSize.Height() -
   446                                             kidReflowState.ComputedPhysicalMargin().top;
   447     }
   448   }
   450   // Position the child relative to our padding edge
   451   nsRect  rect(border.left + kidReflowState.ComputedPhysicalOffsets().left + kidReflowState.ComputedPhysicalMargin().left,
   452                border.top + kidReflowState.ComputedPhysicalOffsets().top + kidReflowState.ComputedPhysicalMargin().top,
   453                kidDesiredSize.Width(), kidDesiredSize.Height());
   455   // Offset the frame rect by the given origin of the absolute containing block.
   456   // If the frame is auto-positioned on both sides of an axis, it will be
   457   // positioned based on its containing block and we don't need to offset.
   458   if (aContainingBlock.TopLeft() != nsPoint(0, 0)) {
   459     if (!(kidReflowState.mStylePosition->mOffset.GetLeftUnit() == eStyleUnit_Auto &&
   460           kidReflowState.mStylePosition->mOffset.GetRightUnit() == eStyleUnit_Auto)) {
   461       rect.x += aContainingBlock.x;
   462     }
   463     if (!(kidReflowState.mStylePosition->mOffset.GetTopUnit() == eStyleUnit_Auto &&
   464           kidReflowState.mStylePosition->mOffset.GetBottomUnit() == eStyleUnit_Auto)) {
   465       rect.y += aContainingBlock.y;
   466     }
   467   }
   469   aKidFrame->SetRect(rect);
   471   nsView* view = aKidFrame->GetView();
   472   if (view) {
   473     // Size and position the view and set its opacity, visibility, content
   474     // transparency, and clip
   475     nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
   476                                                kidDesiredSize.VisualOverflow());
   477   } else {
   478     nsContainerFrame::PositionChildViews(aKidFrame);
   479   }
   481   aKidFrame->DidReflow(aPresContext, &kidReflowState, nsDidReflowStatus::FINISHED);
   483 #ifdef DEBUG
   484   if (nsBlockFrame::gNoisyReflow) {
   485     nsFrame::IndentBy(stdout,nsBlockFrame::gNoiseIndent - 1);
   486     printf("abs pos ");
   487     if (aKidFrame) {
   488       nsAutoString name;
   489       aKidFrame->GetFrameName(name);
   490       printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
   491     }
   492     printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame),
   493            rect.x, rect.y, rect.width, rect.height);
   494   }
   495 #endif
   497   if (aOverflowAreas) {
   498     aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + rect.TopLeft());
   499   }
   501   return rv;
   502 }

mercurial