From 0cfb95c93ef06c9dfad0f285bcadeeaf5730740d Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Wed, 19 Feb 2014 14:14:52 +0000 Subject: [PATCH] Bug 966992 - Implement layout for the overflow-clip-box property. r=roc --- layout/base/nsPresShell.cpp | 8 +- layout/generic/nsFrame.cpp | 17 +++- layout/generic/nsGfxScrollFrame.cpp | 116 +++++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 2cbda233f51..24c564acc43 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -3471,7 +3471,13 @@ PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, nsIScrollableFrame* sf = do_QueryFrame(container); if (sf) { nsPoint oldPosition = sf->GetScrollPosition(); - ScrollToShowRect(container, sf, rect - sf->GetScrolledFrame()->GetPosition(), + nsRect targetRect = rect; + if (container->StyleDisplay()->mOverflowClipBox == + NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX) { + nsMargin padding = container->GetUsedPadding(); + targetRect.Inflate(padding); + } + ScrollToShowRect(container, sf, targetRect - sf->GetScrolledFrame()->GetPosition(), aVertical, aHorizontal, aFlags); nsPoint newPosition = sf->GetScrollPosition(); // If the scroll position increased, that means our content moved up, diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index a06eb503b49..430a5a22dc7 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -1609,11 +1609,20 @@ ApplyOverflowClipping(nsDisplayListBuilder* aBuilder, if (!nsFrame::ShouldApplyOverflowClipping(aFrame, aDisp)) { return; } - nsRect rect = aFrame->GetPaddingRectRelativeToSelf() + - aBuilder->ToReferenceFrame(aFrame); + nsRect clipRect; + bool haveRadii = false; nscoord radii[8]; - bool haveRadii = aFrame->GetPaddingBoxBorderRadii(radii); - aClipState.ClipContainingBlockDescendantsExtra(rect, haveRadii ? radii : nullptr); + if (aFrame->StyleDisplay()->mOverflowClipBox == + NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX) { + clipRect = aFrame->GetPaddingRectRelativeToSelf() + + aBuilder->ToReferenceFrame(aFrame); + haveRadii = aFrame->GetPaddingBoxBorderRadii(radii); + } else { + clipRect = aFrame->GetContentRectRelativeToSelf() + + aBuilder->ToReferenceFrame(aFrame); + // XXX border-radius + } + aClipState.ClipContainingBlockDescendantsExtra(clipRect, haveRadii ? radii : nullptr); } #ifdef DEBUG diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 3a4a09a1cb1..5b7fb24a634 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -8,6 +8,7 @@ #include "nsGfxScrollFrame.h" #include "base/compiler_specific.h" +#include "DisplayItemClip.h" #include "nsCOMPtr.h" #include "nsPresContext.h" #include "nsView.h" @@ -401,9 +402,8 @@ nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState, { // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should // be OK - nscoord paddingLR = aState->mReflowState.ComputedPhysicalPadding().LeftRight(); - - nscoord availWidth = aState->mReflowState.ComputedWidth() + paddingLR; + const nsMargin& padding = aState->mReflowState.ComputedPhysicalPadding(); + nscoord availWidth = aState->mReflowState.ComputedWidth() + padding.LeftRight(); nscoord computedHeight = aState->mReflowState.ComputedHeight(); nscoord computedMinHeight = aState->mReflowState.ComputedMinHeight(); @@ -417,11 +417,13 @@ nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState, nsSize hScrollbarPrefSize; GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, nullptr, &hScrollbarPrefSize, false); - if (computedHeight != NS_UNCONSTRAINEDSIZE) + if (computedHeight != NS_UNCONSTRAINEDSIZE) { computedHeight = std::max(0, computedHeight - hScrollbarPrefSize.height); + } computedMinHeight = std::max(0, computedMinHeight - hScrollbarPrefSize.height); - if (computedMaxHeight != NS_UNCONSTRAINEDSIZE) + if (computedMaxHeight != NS_UNCONSTRAINEDSIZE) { computedMaxHeight = std::max(0, computedMaxHeight - hScrollbarPrefSize.height); + } } if (aAssumeVScroll) { @@ -439,7 +441,7 @@ nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState, nsSize(availWidth, NS_UNCONSTRAINEDSIZE), -1, -1, nsHTMLReflowState::CALLER_WILL_INIT); kidReflowState.Init(presContext, -1, -1, nullptr, - &aState->mReflowState.ComputedPhysicalPadding()); + &padding); kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll; kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll; kidReflowState.SetComputedHeight(computedHeight); @@ -479,6 +481,18 @@ nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState, // overflow area doesn't include the frame bounds. aMetrics->UnionOverflowAreasWithDesiredBounds(); + if (MOZ_UNLIKELY(StyleDisplay()->mOverflowClipBox == + NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) { + nsOverflowAreas childOverflow; + nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow); + nsRect childScrollableOverflow = childOverflow.ScrollableOverflow(); + childScrollableOverflow.Inflate(padding); + nsRect contentArea = nsRect(0, 0, availWidth, computedHeight); + if (!contentArea.Contains(childScrollableOverflow)) { + aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow; + } + } + aState->mContentsOverflowAreas = aMetrics->mOverflowAreas; aState->mReflowedContentsWithHScrollbar = aAssumeHScroll; aState->mReflowedContentsWithVScrollbar = aAssumeVScroll; @@ -2272,10 +2286,74 @@ static bool IsFocused(nsIContent* aContent) return aContent ? nsContentUtils::IsFocusedContent(aContent) : false; } +static bool +ShouldBeClippedByFrame(nsIFrame* aClipFrame, nsIFrame* aClippedFrame) +{ + return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame); +} + +static void +ClipItemsExceptCaret(nsDisplayList* aList, nsDisplayListBuilder* aBuilder, + nsIFrame* aClipFrame, const DisplayItemClip& aClip) +{ + nsDisplayItem* i = aList->GetBottom(); + for (; i; i = i->GetAbove()) { + if (!::ShouldBeClippedByFrame(aClipFrame, i->Frame())) { + continue; + } + + bool unused; + nsRect bounds = i->GetBounds(aBuilder, &unused); + bool isAffectedByClip = aClip.IsRectAffectedByClip(bounds); + if (isAffectedByClip && nsDisplayItem::TYPE_CARET == i->GetType()) { + // Don't clip the caret if it overflows vertically only, and by half + // its height at most. This is to avoid clipping it when the line-height + // is small. + auto half = bounds.height / 2; + bounds.y += half; + bounds.height -= half; + isAffectedByClip = aClip.IsRectAffectedByClip(bounds); + if (isAffectedByClip) { + // Don't clip the caret if it's just outside on the right side. + nsRect rightSide(bounds.x - 1, bounds.y, 1, bounds.height); + isAffectedByClip = aClip.IsRectAffectedByClip(rightSide); + // Also, avoid clipping it in a zero-height line box (heuristic only). + if (isAffectedByClip) { + isAffectedByClip = i->Frame()->GetRect().height != 0; + } + } + } + if (isAffectedByClip) { + DisplayItemClip newClip; + newClip.IntersectWith(i->GetClip()); + newClip.IntersectWith(aClip); + i->SetClip(aBuilder, newClip); + } + nsDisplayList* children = i->GetSameCoordinateSystemChildren(); + if (children) { + ClipItemsExceptCaret(children, aBuilder, aClipFrame, aClip); + } + } +} + +static void +ClipListsExceptCaret(nsDisplayListCollection* aLists, + nsDisplayListBuilder* aBuilder, + nsIFrame* aClipFrame, + const DisplayItemClip& aClip) +{ + ::ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame, aClip); + ::ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame, aClip); + ::ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aClip); + ::ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame, aClip); + ::ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aClip); + ::ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aClip); +} + void ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, - const nsRect& aDirtyRect, - const nsDisplayListSet& aLists) + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) { if (aBuilder->IsForImageVisibility()) { mLastUpdateImagesPos = GetScrollPosition(); @@ -2330,13 +2408,12 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, // Overflow clipping can never clip frames outside our subtree, so there // is no need to worry about whether we are a moving frame that might clip // non-moving frames. - nsRect dirtyRect; // Not all our descendants will be clipped by overflow clipping, but all // the ones that aren't clipped will be out of flow frames that have already // had dirty rects saved for them by their parent frames calling // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our // dirty rect here. - dirtyRect.IntersectRect(aDirtyRect, mScrollPort); + nsRect dirtyRect = aDirtyRect.Intersect(mScrollPort); // Override the dirty rectangle if the displayport has been set. nsRect displayPort; @@ -2398,6 +2475,25 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, scrolledContent); } + if (MOZ_UNLIKELY(mOuter->StyleDisplay()->mOverflowClipBox == + NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) { + // We only clip if there is *scrollable* overflow, to avoid clipping + // *visual* overflow unnecessarily. + nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter); + nsRect so = mScrolledFrame->GetScrollableOverflowRect(); + if (clipRect.width != so.width || clipRect.height != so.height || + so.x < 0 || so.y < 0) { + // The 'scrolledContent' items are clipped to the padding-box at this point. + // Now clip them again to the content-box, except the nsDisplayCaret item + // which we allow to overflow the content-box in various situations -- + // see ::ClipItemsExceptCaret. + clipRect.Deflate(mOuter->GetUsedPadding()); + DisplayItemClip clip; + clip.SetTo(clipRect); + ::ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame, clip); + } + } + // Since making new layers is expensive, only use nsDisplayScrollLayer // if the area is scrollable and we're the content process (unless we're on // B2G, where we support async scrolling for scrollable elements in the