layout/generic/nsGfxScrollFrame.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 /* rendering object to wrap rendering objects that should be scrollable */
     8 #include "nsGfxScrollFrame.h"
    10 #include "base/compiler_specific.h"
    11 #include "DisplayItemClip.h"
    12 #include "nsCOMPtr.h"
    13 #include "nsPresContext.h"
    14 #include "nsView.h"
    15 #include "nsIScrollable.h"
    16 #include "nsContainerFrame.h"
    17 #include "nsGkAtoms.h"
    18 #include "nsNameSpaceManager.h"
    19 #include "nsContentList.h"
    20 #include "nsIDocumentInlines.h"
    21 #include "nsFontMetrics.h"
    22 #include "nsBoxLayoutState.h"
    23 #include "nsINodeInfo.h"
    24 #include "nsScrollbarFrame.h"
    25 #include "nsIScrollbarMediator.h"
    26 #include "nsITextControlFrame.h"
    27 #include "nsIDOMHTMLTextAreaElement.h"
    28 #include "nsNodeInfoManager.h"
    29 #include "nsContentCreatorFunctions.h"
    30 #include "nsAutoPtr.h"
    31 #include "nsPresState.h"
    32 #include "nsIHTMLDocument.h"
    33 #include "nsContentUtils.h"
    34 #include "nsLayoutUtils.h"
    35 #include "nsBidiUtils.h"
    36 #include "mozilla/ContentEvents.h"
    37 #include "mozilla/EventDispatcher.h"
    38 #include "mozilla/Preferences.h"
    39 #include "mozilla/LookAndFeel.h"
    40 #include "mozilla/dom/Element.h"
    41 #include <stdint.h>
    42 #include "mozilla/MathAlgorithms.h"
    43 #include "FrameLayerBuilder.h"
    44 #include "nsSMILKeySpline.h"
    45 #include "nsSubDocumentFrame.h"
    46 #include "nsSVGOuterSVGFrame.h"
    47 #include "mozilla/Attributes.h"
    48 #include "ScrollbarActivity.h"
    49 #include "nsRefreshDriver.h"
    50 #include "nsThemeConstants.h"
    51 #include "nsSVGIntegrationUtils.h"
    52 #include "nsIScrollPositionListener.h"
    53 #include "StickyScrollContainer.h"
    54 #include "nsIFrameInlines.h"
    55 #include "gfxPrefs.h"
    56 #include <algorithm>
    57 #include <cstdlib> // for std::abs(int/long)
    58 #include <cmath> // for std::abs(float/double)
    60 using namespace mozilla;
    61 using namespace mozilla::dom;
    62 using namespace mozilla::layout;
    64 //----------------------------------------------------------------------
    66 //----------nsHTMLScrollFrame-------------------------------------------
    68 nsIFrame*
    69 NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
    70 {
    71   return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot);
    72 }
    74 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
    76 nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, bool aIsRoot)
    77   : nsContainerFrame(aContext),
    78     mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot)
    79 {
    80 }
    82 void
    83 nsHTMLScrollFrame::ScrollbarActivityStarted() const
    84 {
    85   if (mHelper.mScrollbarActivity) {
    86     mHelper.mScrollbarActivity->ActivityStarted();
    87   }
    88 }
    90 void
    91 nsHTMLScrollFrame::ScrollbarActivityStopped() const
    92 {
    93   if (mHelper.mScrollbarActivity) {
    94     mHelper.mScrollbarActivity->ActivityStopped();
    95   }
    96 }
    98 nsresult
    99 nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
   100 {
   101   return mHelper.CreateAnonymousContent(aElements);
   102 }
   104 void
   105 nsHTMLScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
   106                                             uint32_t aFilter)
   107 {
   108   mHelper.AppendAnonymousContentTo(aElements, aFilter);
   109 }
   111 void
   112 nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
   113 {
   114   DestroyAbsoluteFrames(aDestructRoot);
   115   mHelper.Destroy();
   116   nsContainerFrame::DestroyFrom(aDestructRoot);
   117 }
   119 nsresult
   120 nsHTMLScrollFrame::SetInitialChildList(ChildListID  aListID,
   121                                        nsFrameList& aChildList)
   122 {
   123   nsresult rv = nsContainerFrame::SetInitialChildList(aListID, aChildList);
   124   mHelper.ReloadChildFrames();
   125   return rv;
   126 }
   129 nsresult
   130 nsHTMLScrollFrame::AppendFrames(ChildListID  aListID,
   131                                 nsFrameList& aFrameList)
   132 {
   133   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
   134   mFrames.AppendFrames(nullptr, aFrameList);
   135   mHelper.ReloadChildFrames();
   136   return NS_OK;
   137 }
   139 nsresult
   140 nsHTMLScrollFrame::InsertFrames(ChildListID aListID,
   141                                 nsIFrame* aPrevFrame,
   142                                 nsFrameList& aFrameList)
   143 {
   144   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
   145   NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
   146                "inserting after sibling frame with different parent");
   147   mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
   148   mHelper.ReloadChildFrames();
   149   return NS_OK;
   150 }
   152 nsresult
   153 nsHTMLScrollFrame::RemoveFrame(ChildListID aListID,
   154                                nsIFrame* aOldFrame)
   155 {
   156   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
   157   mFrames.DestroyFrame(aOldFrame);
   158   mHelper.ReloadChildFrames();
   159   return NS_OK;
   160 }
   162 nsSplittableType
   163 nsHTMLScrollFrame::GetSplittableType() const
   164 {
   165   return NS_FRAME_NOT_SPLITTABLE;
   166 }
   168 nsIAtom*
   169 nsHTMLScrollFrame::GetType() const
   170 {
   171   return nsGkAtoms::scrollFrame;
   172 }
   174 /**
   175  HTML scrolling implementation
   177  All other things being equal, we prefer layouts with fewer scrollbars showing.
   178 */
   180 struct MOZ_STACK_CLASS ScrollReflowState {
   181   const nsHTMLReflowState& mReflowState;
   182   nsBoxLayoutState mBoxState;
   183   ScrollbarStyles mStyles;
   184   nsMargin mComputedBorder;
   186   // === Filled in by ReflowScrolledFrame ===
   187   nsOverflowAreas mContentsOverflowAreas;
   188   bool mReflowedContentsWithHScrollbar;
   189   bool mReflowedContentsWithVScrollbar;
   191   // === Filled in when TryLayout succeeds ===
   192   // The size of the inside-border area
   193   nsSize mInsideBorderSize;
   194   // Whether we decided to show the horizontal scrollbar
   195   bool mShowHScrollbar;
   196   // Whether we decided to show the vertical scrollbar
   197   bool mShowVScrollbar;
   199   ScrollReflowState(nsIScrollableFrame* aFrame,
   200                     const nsHTMLReflowState& aState) :
   201     mReflowState(aState),
   202     // mBoxState is just used for scrollbars so we don't need to
   203     // worry about the reflow depth here
   204     mBoxState(aState.frame->PresContext(), aState.rendContext, 0),
   205     mStyles(aFrame->GetScrollbarStyles()) {
   206   }
   207 };
   209 // XXXldb Can this go away?
   210 static nsSize ComputeInsideBorderSize(ScrollReflowState* aState,
   211                                       const nsSize& aDesiredInsideBorderSize)
   212 {
   213   // aDesiredInsideBorderSize is the frame size; i.e., it includes
   214   // borders and padding (but the scrolled child doesn't have
   215   // borders). The scrolled child has the same padding as us.
   216   nscoord contentWidth = aState->mReflowState.ComputedWidth();
   217   if (contentWidth == NS_UNCONSTRAINEDSIZE) {
   218     contentWidth = aDesiredInsideBorderSize.width -
   219       aState->mReflowState.ComputedPhysicalPadding().LeftRight();
   220   }
   221   nscoord contentHeight = aState->mReflowState.ComputedHeight();
   222   if (contentHeight == NS_UNCONSTRAINEDSIZE) {
   223     contentHeight = aDesiredInsideBorderSize.height -
   224       aState->mReflowState.ComputedPhysicalPadding().TopBottom();
   225   }
   227   contentWidth  = aState->mReflowState.ApplyMinMaxWidth(contentWidth);
   228   contentHeight = aState->mReflowState.ApplyMinMaxHeight(contentHeight);
   229   return nsSize(contentWidth + aState->mReflowState.ComputedPhysicalPadding().LeftRight(),
   230                 contentHeight + aState->mReflowState.ComputedPhysicalPadding().TopBottom());
   231 }
   233 static void
   234 GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize* aMin,
   235                     nsSize* aPref, bool aVertical)
   236 {
   237   NS_ASSERTION(aState.GetRenderingContext(),
   238                "Must have rendering context in layout state for size "
   239                "computations");
   241   if (aMin) {
   242     *aMin = aBox->GetMinSize(aState);
   243     nsBox::AddMargin(aBox, *aMin);
   244     if (aMin->width < 0) {
   245       aMin->width = 0;
   246     }
   247     if (aMin->height < 0) {
   248       aMin->height = 0;
   249     }
   250   }
   252   if (aPref) {
   253     *aPref = aBox->GetPrefSize(aState);
   254     nsBox::AddMargin(aBox, *aPref);
   255     if (aPref->width < 0) {
   256       aPref->width = 0;
   257     }
   258     if (aPref->height < 0) {
   259       aPref->height = 0;
   260     }
   261   }
   262 }
   264 /**
   265  * Assuming that we know the metrics for our wrapped frame and
   266  * whether the horizontal and/or vertical scrollbars are present,
   267  * compute the resulting layout and return true if the layout is
   268  * consistent. If the layout is consistent then we fill in the
   269  * computed fields of the ScrollReflowState.
   270  *
   271  * The layout is consistent when both scrollbars are showing if and only
   272  * if they should be showing. A horizontal scrollbar should be showing if all
   273  * following conditions are met:
   274  * 1) the style is not HIDDEN
   275  * 2) our inside-border height is at least the scrollbar height (i.e., the
   276  * scrollbar fits vertically)
   277  * 3) our scrollport width (the inside-border width minus the width allocated for a
   278  * vertical scrollbar, if showing) is at least the scrollbar's min-width
   279  * (i.e., the scrollbar fits horizontally)
   280  * 4) the style is SCROLL, or the kid's overflow-area XMost is
   281  * greater than the scrollport width
   282  *
   283  * @param aForce if true, then we just assume the layout is consistent.
   284  */
   285 bool
   286 nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
   287                              nsHTMLReflowMetrics* aKidMetrics,
   288                              bool aAssumeHScroll, bool aAssumeVScroll,
   289                              bool aForce, nsresult* aResult)
   290 {
   291   *aResult = NS_OK;
   293   if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) ||
   294       (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) {
   295     NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
   296     return false;
   297   }
   299   if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
   300       (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
   301        ScrolledContentDependsOnHeight(aState))) {
   302     nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll,
   303                                       aKidMetrics, false);
   304     if (NS_FAILED(rv)) {
   305       *aResult = rv;
   306       return false;
   307     }
   308   }
   310   nsSize vScrollbarMinSize(0, 0);
   311   nsSize vScrollbarPrefSize(0, 0);
   312   if (mHelper.mVScrollbarBox) {
   313     GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
   314                         &vScrollbarMinSize,
   315                         aAssumeVScroll ? &vScrollbarPrefSize : nullptr, true);
   316   }
   317   nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
   318   nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0;
   320   nsSize hScrollbarMinSize(0, 0);
   321   nsSize hScrollbarPrefSize(0, 0);
   322   if (mHelper.mHScrollbarBox) {
   323     GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
   324                         &hScrollbarMinSize,
   325                         aAssumeHScroll ? &hScrollbarPrefSize : nullptr, false);
   326   }
   327   nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
   328   nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0;
   330   // First, compute our inside-border size and scrollport size
   331   // XXXldb Can we depend more on ComputeSize here?
   332   nsSize desiredInsideBorderSize;
   333   desiredInsideBorderSize.width = vScrollbarDesiredWidth +
   334     std::max(aKidMetrics->Width(), hScrollbarMinWidth);
   335   desiredInsideBorderSize.height = hScrollbarDesiredHeight +
   336     std::max(aKidMetrics->Height(), vScrollbarMinHeight);
   337   aState->mInsideBorderSize =
   338     ComputeInsideBorderSize(aState, desiredInsideBorderSize);
   339   nsSize scrollPortSize = nsSize(std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
   340                                  std::max(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
   342   if (!aForce) {
   343     nsRect scrolledRect =
   344       mHelper.GetScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
   345                                      scrollPortSize);
   346     nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
   348     // If the style is HIDDEN then we already know that aAssumeHScroll is false
   349     if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
   350       bool wantHScrollbar =
   351         aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
   352         scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel ||
   353         scrolledRect.x <= -oneDevPixel;
   354       if (scrollPortSize.width < hScrollbarMinSize.width)
   355         wantHScrollbar = false;
   356       if (wantHScrollbar != aAssumeHScroll)
   357         return false;
   358     }
   360     // If the style is HIDDEN then we already know that aAssumeVScroll is false
   361     if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
   362       bool wantVScrollbar =
   363         aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL ||
   364         scrolledRect.YMost() >= scrollPortSize.height + oneDevPixel ||
   365         scrolledRect.y <= -oneDevPixel;
   366       if (scrollPortSize.height < vScrollbarMinSize.height)
   367         wantVScrollbar = false;
   368       if (wantVScrollbar != aAssumeVScroll)
   369         return false;
   370     }
   371   }
   373   nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
   375   aState->mShowHScrollbar = aAssumeHScroll;
   376   aState->mShowVScrollbar = aAssumeVScroll;
   377   nsPoint scrollPortOrigin(aState->mComputedBorder.left,
   378                            aState->mComputedBorder.top);
   379   if (!mHelper.IsScrollbarOnRight()) {
   380     scrollPortOrigin.x += vScrollbarActualWidth;
   381   }
   382   mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize);
   383   return true;
   384 }
   386 bool
   387 nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState)
   388 {
   389   // Return true if ReflowScrolledFrame is going to do something different
   390   // based on the presence of a horizontal scrollbar.
   391   return (mHelper.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) ||
   392     aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE ||
   393     aState->mReflowState.ComputedMinHeight() > 0 ||
   394     aState->mReflowState.ComputedMaxHeight() != NS_UNCONSTRAINEDSIZE;
   395 }
   397 nsresult
   398 nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
   399                                        bool aAssumeHScroll,
   400                                        bool aAssumeVScroll,
   401                                        nsHTMLReflowMetrics* aMetrics,
   402                                        bool aFirstPass)
   403 {
   404   // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
   405   // be OK
   406   const nsMargin& padding = aState->mReflowState.ComputedPhysicalPadding();
   407   nscoord availWidth = aState->mReflowState.ComputedWidth() + padding.LeftRight();
   409   nscoord computedHeight = aState->mReflowState.ComputedHeight();
   410   nscoord computedMinHeight = aState->mReflowState.ComputedMinHeight();
   411   nscoord computedMaxHeight = aState->mReflowState.ComputedMaxHeight();
   412   if (!ShouldPropagateComputedHeightToScrolledContent()) {
   413     computedHeight = NS_UNCONSTRAINEDSIZE;
   414     computedMinHeight = 0;
   415     computedMaxHeight = NS_UNCONSTRAINEDSIZE;
   416   }
   417   if (aAssumeHScroll) {
   418     nsSize hScrollbarPrefSize;
   419     GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
   420                         nullptr, &hScrollbarPrefSize, false);
   421     if (computedHeight != NS_UNCONSTRAINEDSIZE) {
   422       computedHeight = std::max(0, computedHeight - hScrollbarPrefSize.height);
   423     }
   424     computedMinHeight = std::max(0, computedMinHeight - hScrollbarPrefSize.height);
   425     if (computedMaxHeight != NS_UNCONSTRAINEDSIZE) {
   426       computedMaxHeight = std::max(0, computedMaxHeight - hScrollbarPrefSize.height);
   427     }
   428   }
   430   if (aAssumeVScroll) {
   431     nsSize vScrollbarPrefSize;
   432     GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
   433                         nullptr, &vScrollbarPrefSize, true);
   434     availWidth = std::max(0, availWidth - vScrollbarPrefSize.width);
   435   }
   437   nsPresContext* presContext = PresContext();
   439   // Pass false for aInit so we can pass in the correct padding.
   440   nsHTMLReflowState kidReflowState(presContext, aState->mReflowState,
   441                                    mHelper.mScrolledFrame,
   442                                    nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
   443                                    -1, -1, nsHTMLReflowState::CALLER_WILL_INIT);
   444   kidReflowState.Init(presContext, -1, -1, nullptr,
   445                       &padding);
   446   kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll;
   447   kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll;
   448   kidReflowState.SetComputedHeight(computedHeight);
   449   kidReflowState.ComputedMinHeight() = computedMinHeight;
   450   kidReflowState.ComputedMaxHeight() = computedMaxHeight;
   452   // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
   453   // reflect our assumptions while we reflow the child.
   454   bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar;
   455   bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar;
   456   mHelper.mHasHorizontalScrollbar = aAssumeHScroll;
   457   mHelper.mHasVerticalScrollbar = aAssumeVScroll;
   459   nsReflowStatus status;
   460   nsresult rv = ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics,
   461                             kidReflowState, 0, 0,
   462                             NS_FRAME_NO_MOVE_FRAME, status);
   464   mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
   465   mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar;
   467   // Don't resize or position the view (if any) because we're going to resize
   468   // it to the correct size anyway in PlaceScrollArea. Allowing it to
   469   // resize here would size it to the natural height of the frame,
   470   // which will usually be different from the scrollport height;
   471   // invalidating the difference will cause unnecessary repainting.
   472   FinishReflowChild(mHelper.mScrolledFrame, presContext,
   473                     *aMetrics, &kidReflowState, 0, 0,
   474                     NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW);
   476   // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother
   477   // setting their mOverflowArea. This is wrong because every frame should
   478   // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
   479   // support the 'outline' property because of this. Rather than fix the world
   480   // right now, just fix up the overflow area if necessary. Note that we don't
   481   // check HasOverflowRect() because it could be set even though the
   482   // overflow area doesn't include the frame bounds.
   483   aMetrics->UnionOverflowAreasWithDesiredBounds();
   485   if (MOZ_UNLIKELY(StyleDisplay()->mOverflowClipBox ==
   486                      NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) {
   487     nsOverflowAreas childOverflow;
   488     nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow);
   489     nsRect childScrollableOverflow = childOverflow.ScrollableOverflow();
   490     childScrollableOverflow.Inflate(padding);
   491     nsRect contentArea = nsRect(0, 0, availWidth, computedHeight);
   492     if (!contentArea.Contains(childScrollableOverflow)) {
   493       aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow;
   494     }
   495   }
   497   aState->mContentsOverflowAreas = aMetrics->mOverflowAreas;
   498   aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
   499   aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
   501   return rv;
   502 }
   504 bool
   505 nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState)
   506 {
   507   if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO)
   508     // no guessing required
   509     return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL;
   511   return mHelper.mHasHorizontalScrollbar;
   512 }
   514 bool
   515 nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState)
   516 {
   517   if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO)
   518     // no guessing required
   519     return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL;
   521   // If we've had at least one non-initial reflow, then just assume
   522   // the state of the vertical scrollbar will be what we determined
   523   // last time.
   524   if (mHelper.mHadNonInitialReflow) {
   525     return mHelper.mHasVerticalScrollbar;
   526   }
   528   // If this is the initial reflow, guess false because usually
   529   // we have very little content by then.
   530   if (InInitialReflow())
   531     return false;
   533   if (mHelper.mIsRoot) {
   534     nsIFrame *f = mHelper.mScrolledFrame->GetFirstPrincipalChild();
   535     if (f && f->GetType() == nsGkAtoms::svgOuterSVGFrame &&
   536         static_cast<nsSVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) {
   537       // Common SVG case - avoid a bad guess.
   538       return false;
   539     }
   540     // Assume that there will be a scrollbar; it seems to me
   541     // that 'most pages' do have a scrollbar, and anyway, it's cheaper
   542     // to do an extra reflow for the pages that *don't* need a
   543     // scrollbar (because on average they will have less content).
   544     return true;
   545   }
   547   // For non-viewports, just guess that we don't need a scrollbar.
   548   // XXX I wonder if statistically this is the right idea; I'm
   549   // basically guessing that there are a lot of overflow:auto DIVs
   550   // that get their intrinsic size and don't overflow
   551   return false;
   552 }
   554 bool
   555 nsHTMLScrollFrame::InInitialReflow() const
   556 {
   557   // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
   558   // root scrollframe.  In that case we want to skip this clause altogether.
   559   // The guess here is that there are lots of overflow:auto divs out there that
   560   // end up auto-sizing so they don't overflow, and that the root basically
   561   // always needs a scrollbar if it did last time we loaded this page (good
   562   // assumption, because our initial reflow is no longer synchronous).
   563   return !mHelper.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
   564 }
   566 nsresult
   567 nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
   568                                   const nsHTMLReflowMetrics& aDesiredSize)
   569 {
   570   nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.GetWritingMode(), aDesiredSize.mFlags);
   571   nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
   572       GuessVScrollbarNeeded(*aState), &kidDesiredSize, true);
   573   NS_ENSURE_SUCCESS(rv, rv);
   575   // There's an important special case ... if the child appears to fit
   576   // in the inside-border rect (but overflows the scrollport), we
   577   // should try laying it out without a vertical scrollbar. It will
   578   // usually fit because making the available-width wider will not
   579   // normally make the child taller. (The only situation I can think
   580   // of is when you have a line containing %-width inline replaced
   581   // elements whose percentages sum to more than 100%, so increasing
   582   // the available width makes the line break where it was fitting
   583   // before.) If we don't treat this case specially, then we will
   584   // decide that showing scrollbars is OK because the content
   585   // overflows when we're showing scrollbars and we won't try to
   586   // remove the vertical scrollbar.
   588   // Detecting when we enter this special case is important for when
   589   // people design layouts that exactly fit the container "most of the
   590   // time".
   592   // XXX Is this check really sufficient to catch all the incremental cases
   593   // where the ideal case doesn't have a scrollbar?
   594   if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
   595       aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL &&
   596       aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) {
   597     nsSize insideBorderSize =
   598       ComputeInsideBorderSize(aState,
   599                               nsSize(kidDesiredSize.Width(), kidDesiredSize.Height()));
   600     nsRect scrolledRect =
   601       mHelper.GetScrolledRectInternal(kidDesiredSize.ScrollableOverflow(),
   602                                      insideBorderSize);
   603     if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
   604       // Let's pretend we had no scrollbars coming in here
   605       rv = ReflowScrolledFrame(aState, false, false,
   606                                &kidDesiredSize, false);
   607       NS_ENSURE_SUCCESS(rv, rv);
   608     }
   609   }
   611   // Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
   612   // Do this first because changing the vertical scrollbar setting is expensive,
   613   // forcing a reflow always.
   615   // Try leaving the horizontal scrollbar unchanged first. This will be more
   616   // efficient.
   617   if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar,
   618                 aState->mReflowedContentsWithVScrollbar, false, &rv))
   619     return NS_OK;
   620   if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar,
   621                 aState->mReflowedContentsWithVScrollbar, false, &rv))
   622     return NS_OK;
   624   // OK, now try toggling the vertical scrollbar. The performance advantage
   625   // of trying the status-quo horizontal scrollbar state
   626   // does not exist here (we'll have to reflow due to the vertical scrollbar
   627   // change), so always try no horizontal scrollbar first.
   628   bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
   629   if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false, &rv))
   630     return NS_OK;
   631   if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false, &rv))
   632     return NS_OK;
   634   // OK, we're out of ideas. Try again enabling whatever scrollbars we can
   635   // enable and force the layout to stick even if it's inconsistent.
   636   // This just happens sometimes.
   637   TryLayout(aState, &kidDesiredSize,
   638             aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN,
   639             aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN,
   640             true, &rv);
   641   return rv;
   642 }
   644 void
   645 nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState,
   646                                    const nsPoint& aScrollPosition)
   647 {
   648   nsIFrame *scrolledFrame = mHelper.mScrolledFrame;
   649   // Set the x,y of the scrolled frame to the correct value
   650   scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition);
   652   nsRect scrolledArea;
   653   // Preserve the width or height of empty rects
   654   nsSize portSize = mHelper.mScrollPort.Size();
   655   nsRect scrolledRect =
   656     mHelper.GetScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(),
   657                                    portSize);
   658   scrolledArea.UnionRectEdges(scrolledRect,
   659                               nsRect(nsPoint(0,0), portSize));
   661   // Store the new overflow area. Note that this changes where an outline
   662   // of the scrolled frame would be painted, but scrolled frames can't have
   663   // outlines (the outline would go on this scrollframe instead).
   664   // Using FinishAndStoreOverflow is needed so the overflow rect
   665   // gets set correctly.  It also messes with the overflow rect in the
   666   // -moz-hidden-unscrollable case, but scrolled frames can't have
   667   // 'overflow' either.
   668   // This needs to happen before SyncFrameViewAfterReflow so
   669   // HasOverflowRect() will return the correct value.
   670   nsOverflowAreas overflow(scrolledArea, scrolledArea);
   671   scrolledFrame->FinishAndStoreOverflow(overflow,
   672                                         scrolledFrame->GetSize());
   674   // Note that making the view *exactly* the size of the scrolled area
   675   // is critical, since the view scrolling code uses the size of the
   676   // scrolled view to clamp scroll requests.
   677   // Normally the scrolledFrame won't have a view but in some cases it
   678   // might create its own.
   679   nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(),
   680                                              scrolledFrame,
   681                                              scrolledFrame->GetView(),
   682                                              scrolledArea,
   683                                              0);
   684 }
   686 nscoord
   687 nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext)
   688 {
   689   ScrollbarStyles ss = GetScrollbarStyles();
   690   if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mHelper.mVScrollbarBox)
   691     return 0;
   693   // Don't need to worry about reflow depth here since it's
   694   // just for scrollbars
   695   nsBoxLayoutState bls(PresContext(), aRenderingContext, 0);
   696   nsSize vScrollbarPrefSize(0, 0);
   697   GetScrollbarMetrics(bls, mHelper.mVScrollbarBox,
   698                       nullptr, &vScrollbarPrefSize, true);
   699   return vScrollbarPrefSize.width;
   700 }
   702 /* virtual */ nscoord
   703 nsHTMLScrollFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
   704 {
   705   nscoord result = mHelper.mScrolledFrame->GetMinWidth(aRenderingContext);
   706   DISPLAY_MIN_WIDTH(this, result);
   707   return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
   708 }
   710 /* virtual */ nscoord
   711 nsHTMLScrollFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
   712 {
   713   nscoord result = mHelper.mScrolledFrame->GetPrefWidth(aRenderingContext);
   714   DISPLAY_PREF_WIDTH(this, result);
   715   return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext));
   716 }
   718 nsresult
   719 nsHTMLScrollFrame::GetPadding(nsMargin& aMargin)
   720 {
   721   // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
   722   // reaize that.  If we're stuck inside a XUL box, we need to claim no
   723   // padding.
   724   // @see also nsXULScrollFrame::GetPadding.
   725   aMargin.SizeTo(0,0,0,0);
   726   return NS_OK;
   727 }
   729 bool
   730 nsHTMLScrollFrame::IsCollapsed()
   731 {
   732   // We're never collapsed in the box sense.
   733   return false;
   734 }
   736 // Return the <browser> if the scrollframe is for the root frame directly
   737 // inside a <browser>.
   738 static nsIContent*
   739 GetBrowserRoot(nsIContent* aContent)
   740 {
   741   if (aContent) {
   742     nsIDocument* doc = aContent->GetCurrentDoc();
   743     nsPIDOMWindow* win = doc->GetWindow();
   744     if (win) {
   745       nsCOMPtr<nsIContent> frameContent =
   746         do_QueryInterface(win->GetFrameElementInternal());
   747       if (frameContent &&
   748           frameContent->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL))
   749         return frameContent;
   750     }
   751   }
   753   return nullptr;
   754 }
   756 nsresult
   757 nsHTMLScrollFrame::Reflow(nsPresContext*           aPresContext,
   758                           nsHTMLReflowMetrics&     aDesiredSize,
   759                           const nsHTMLReflowState& aReflowState,
   760                           nsReflowStatus&          aStatus)
   761 {
   762   DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
   763   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
   765   mHelper.HandleScrollbarStyleSwitching();
   767   ScrollReflowState state(this, aReflowState);
   768   // sanity check: ensure that if we have no scrollbar, we treat it
   769   // as hidden.
   770   if (!mHelper.mVScrollbarBox || mHelper.mNeverHasVerticalScrollbar)
   771     state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN;
   772   if (!mHelper.mHScrollbarBox || mHelper.mNeverHasHorizontalScrollbar)
   773     state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN;
   775   //------------ Handle Incremental Reflow -----------------
   776   bool reflowHScrollbar = true;
   777   bool reflowVScrollbar = true;
   778   bool reflowScrollCorner = true;
   779   if (!aReflowState.ShouldReflowAllKids()) {
   780     #define NEEDS_REFLOW(frame_) \
   781       ((frame_) && NS_SUBTREE_DIRTY(frame_))
   783     reflowHScrollbar = NEEDS_REFLOW(mHelper.mHScrollbarBox);
   784     reflowVScrollbar = NEEDS_REFLOW(mHelper.mVScrollbarBox);
   785     reflowScrollCorner = NEEDS_REFLOW(mHelper.mScrollCornerBox) ||
   786                          NEEDS_REFLOW(mHelper.mResizerBox);
   788     #undef NEEDS_REFLOW
   789   }
   791   if (mHelper.mIsRoot) {
   792     mHelper.mCollapsedResizer = true;
   794     nsIContent* browserRoot = GetBrowserRoot(mContent);
   795     if (browserRoot) {
   796       bool showResizer = browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer);
   797       reflowScrollCorner = showResizer == mHelper.mCollapsedResizer;
   798       mHelper.mCollapsedResizer = !showResizer;
   799     }
   800   }
   802   nsRect oldScrollAreaBounds = mHelper.mScrollPort;
   803   nsRect oldScrolledAreaBounds =
   804     mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
   805   nsPoint oldScrollPosition = mHelper.GetScrollPosition();
   807   state.mComputedBorder = aReflowState.ComputedPhysicalBorderPadding() -
   808     aReflowState.ComputedPhysicalPadding();
   810   nsresult rv = ReflowContents(&state, aDesiredSize);
   811   if (NS_FAILED(rv))
   812     return rv;
   814   // Restore the old scroll position, for now, even if that's not valid anymore
   815   // because we changed size. We'll fix it up in a post-reflow callback, because
   816   // our current size may only be temporary (e.g. we're compute XUL desired sizes).
   817   PlaceScrollArea(state, oldScrollPosition);
   818   if (!mHelper.mPostedReflowCallback) {
   819     // Make sure we'll try scrolling to restored position
   820     PresContext()->PresShell()->PostReflowCallback(&mHelper);
   821     mHelper.mPostedReflowCallback = true;
   822   }
   824   bool didHaveHScrollbar = mHelper.mHasHorizontalScrollbar;
   825   bool didHaveVScrollbar = mHelper.mHasVerticalScrollbar;
   826   mHelper.mHasHorizontalScrollbar = state.mShowHScrollbar;
   827   mHelper.mHasVerticalScrollbar = state.mShowVScrollbar;
   828   nsRect newScrollAreaBounds = mHelper.mScrollPort;
   829   nsRect newScrolledAreaBounds =
   830     mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
   831   if (mHelper.mSkippedScrollbarLayout ||
   832       reflowHScrollbar || reflowVScrollbar || reflowScrollCorner ||
   833       (GetStateBits() & NS_FRAME_IS_DIRTY) ||
   834       didHaveHScrollbar != state.mShowHScrollbar ||
   835       didHaveVScrollbar != state.mShowVScrollbar ||
   836       !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) ||
   837       !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
   838     if (!mHelper.mSupppressScrollbarUpdate) {
   839       mHelper.mSkippedScrollbarLayout = false;
   840       mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, state.mShowHScrollbar);
   841       mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, state.mShowVScrollbar);
   842       // place and reflow scrollbars
   843       nsRect insideBorderArea =
   844         nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
   845                state.mInsideBorderSize);
   846       mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea,
   847                               oldScrollAreaBounds);
   848     } else {
   849       mHelper.mSkippedScrollbarLayout = true;
   850     }
   851   }
   853   aDesiredSize.Width() = state.mInsideBorderSize.width +
   854     state.mComputedBorder.LeftRight();
   855   aDesiredSize.Height() = state.mInsideBorderSize.height +
   856     state.mComputedBorder.TopBottom();
   858   aDesiredSize.SetOverflowAreasToDesiredBounds();
   859   if (mHelper.IsIgnoringViewportClipping()) {
   860     aDesiredSize.mOverflowAreas.UnionWith(
   861       state.mContentsOverflowAreas + mHelper.mScrolledFrame->GetPosition());
   862   }
   864   mHelper.UpdateSticky();
   865   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus);
   867   if (!InInitialReflow() && !mHelper.mHadNonInitialReflow) {
   868     mHelper.mHadNonInitialReflow = true;
   869   }
   871   if (mHelper.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
   872     mHelper.PostScrolledAreaEvent();
   873   }
   875   aStatus = NS_FRAME_COMPLETE;
   876   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
   877   mHelper.PostOverflowEvent();
   878   return rv;
   879 }
   882 ////////////////////////////////////////////////////////////////////////////////
   884 #ifdef DEBUG_FRAME_DUMP
   885 nsresult
   886 nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const
   887 {
   888   return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
   889 }
   890 #endif
   892 #ifdef ACCESSIBILITY
   893 a11y::AccType
   894 nsHTMLScrollFrame::AccessibleType()
   895 {
   896   // Create an accessible regardless of focusable state because the state can be
   897   // changed during frame life cycle without any notifications to accessibility.
   898   if (mContent->IsRootOfNativeAnonymousSubtree() ||
   899       GetScrollbarStyles() ==
   900         ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN) ) {
   901     return a11y::eNoType;
   902   }
   904   return a11y::eHyperTextType;
   905 }
   906 #endif
   908 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
   909   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
   910   NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
   911   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
   912   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
   913 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
   915 //----------nsXULScrollFrame-------------------------------------------
   917 nsIFrame*
   918 NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext,
   919                      bool aIsRoot, bool aClipAllDescendants)
   920 {
   921   return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot,
   922                                            aClipAllDescendants);
   923 }
   925 NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)
   927 nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext,
   928                                    bool aIsRoot, bool aClipAllDescendants)
   929   : nsBoxFrame(aShell, aContext, aIsRoot),
   930     mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot)
   931 {
   932   SetLayoutManager(nullptr);
   933   mHelper.mClipAllDescendants = aClipAllDescendants;
   934 }
   936 void
   937 nsXULScrollFrame::ScrollbarActivityStarted() const
   938 {
   939   if (mHelper.mScrollbarActivity) {
   940     mHelper.mScrollbarActivity->ActivityStarted();
   941   }
   942 }
   944 void
   945 nsXULScrollFrame::ScrollbarActivityStopped() const
   946 {
   947   if (mHelper.mScrollbarActivity) {
   948     mHelper.mScrollbarActivity->ActivityStopped();
   949   }
   950 }
   952 nsMargin
   953 ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState)
   954 {
   955   NS_ASSERTION(aState && aState->GetRenderingContext(),
   956                "Must have rendering context in layout state for size "
   957                "computations");
   959   nsMargin result(0, 0, 0, 0);
   961   if (mVScrollbarBox) {
   962     nsSize size = mVScrollbarBox->GetPrefSize(*aState);
   963     nsBox::AddMargin(mVScrollbarBox, size);
   964     if (IsScrollbarOnRight())
   965       result.left = size.width;
   966     else
   967       result.right = size.width;
   968   }
   970   if (mHScrollbarBox) {
   971     nsSize size = mHScrollbarBox->GetPrefSize(*aState);
   972     nsBox::AddMargin(mHScrollbarBox, size);
   973     // We don't currently support any scripts that would require a scrollbar
   974     // at the top. (Are there any?)
   975     result.bottom = size.height;
   976   }
   978   return result;
   979 }
   981 nscoord
   982 ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState)
   983 {
   984   NS_ASSERTION(aState && aState->GetRenderingContext(),
   985                "Must have rendering context in layout state for size "
   986                "computations");
   988   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
   989     // We're using overlay scrollbars, so we need to get the width that
   990     // non-disappearing scrollbars would have.
   991     nsITheme* theme = aState->PresContext()->GetTheme();
   992     if (theme &&
   993         theme->ThemeSupportsWidget(aState->PresContext(),
   994                                    mVScrollbarBox,
   995                                    NS_THEME_SCROLLBAR_NON_DISAPPEARING)) {
   996       nsIntSize size;
   997       nsRenderingContext* rendContext = aState->GetRenderingContext();
   998       if (rendContext) {
   999         bool canOverride = true;
  1000         theme->GetMinimumWidgetSize(rendContext,
  1001                                     mVScrollbarBox,
  1002                                     NS_THEME_SCROLLBAR_NON_DISAPPEARING,
  1003                                     &size,
  1004                                     &canOverride);
  1005         if (size.width) {
  1006           return aState->PresContext()->DevPixelsToAppUnits(size.width);
  1012   return GetDesiredScrollbarSizes(aState).LeftRight();
  1015 void
  1016 ScrollFrameHelper::HandleScrollbarStyleSwitching()
  1018   // Check if we switched between scrollbar styles.
  1019   if (mScrollbarActivity &&
  1020       LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) {
  1021     mScrollbarActivity->Destroy();
  1022     mScrollbarActivity = nullptr;
  1023     mOuter->PresContext()->ThemeChanged();
  1025   else if (!mScrollbarActivity &&
  1026            LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
  1027     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(mOuter));
  1028     mOuter->PresContext()->ThemeChanged();
  1032 static bool IsFocused(nsIContent* aContent)
  1034   // Some content elements, like the GetContent() of a scroll frame
  1035   // for a text input field, are inside anonymous subtrees, but the focus
  1036   // manager always reports a non-anonymous element as the focused one, so
  1037   // walk up the tree until we reach a non-anonymous element.
  1038   while (aContent && aContent->IsInAnonymousSubtree()) {
  1039     aContent = aContent->GetParent();
  1042   return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
  1045 bool
  1046 ScrollFrameHelper::WantAsyncScroll() const
  1048   nsRect scrollRange = GetScrollRange();
  1049   ScrollbarStyles styles = GetScrollbarStylesFromFrame();
  1050   bool isFocused = IsFocused(mOuter->GetContent());
  1051   bool isVScrollable = (scrollRange.height > 0)
  1052                     && (styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
  1053   bool isHScrollable = (scrollRange.width > 0)
  1054                     && (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN);
  1055   // The check for scroll bars was added in bug 825692 to prevent layerization
  1056   // of text inputs for performance reasons. However, if a text input is
  1057   // focused we want to layerize it so we can async scroll it (bug 946408).
  1058   bool isVAsyncScrollable = isVScrollable && (mVScrollbarBox || isFocused);
  1059   bool isHAsyncScrollable = isHScrollable && (mHScrollbarBox || isFocused);
  1060   return isVAsyncScrollable || isHAsyncScrollable;
  1063 nsresult
  1064 nsXULScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
  1066   return mHelper.CreateAnonymousContent(aElements);
  1069 void
  1070 nsXULScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
  1071                                            uint32_t aFilter)
  1073   mHelper.AppendAnonymousContentTo(aElements, aFilter);
  1076 void
  1077 nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot)
  1079   mHelper.Destroy();
  1080   nsBoxFrame::DestroyFrom(aDestructRoot);
  1083 nsresult
  1084 nsXULScrollFrame::SetInitialChildList(ChildListID     aListID,
  1085                                       nsFrameList&    aChildList)
  1087   nsresult rv = nsBoxFrame::SetInitialChildList(aListID, aChildList);
  1088   mHelper.ReloadChildFrames();
  1089   return rv;
  1093 nsresult
  1094 nsXULScrollFrame::AppendFrames(ChildListID     aListID,
  1095                                nsFrameList&    aFrameList)
  1097   nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList);
  1098   mHelper.ReloadChildFrames();
  1099   return rv;
  1102 nsresult
  1103 nsXULScrollFrame::InsertFrames(ChildListID     aListID,
  1104                                nsIFrame*       aPrevFrame,
  1105                                nsFrameList&    aFrameList)
  1107   nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
  1108   mHelper.ReloadChildFrames();
  1109   return rv;
  1112 nsresult
  1113 nsXULScrollFrame::RemoveFrame(ChildListID     aListID,
  1114                               nsIFrame*       aOldFrame)
  1116   nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame);
  1117   mHelper.ReloadChildFrames();
  1118   return rv;
  1121 nsSplittableType
  1122 nsXULScrollFrame::GetSplittableType() const
  1124   return NS_FRAME_NOT_SPLITTABLE;
  1127 nsresult
  1128 nsXULScrollFrame::GetPadding(nsMargin& aMargin)
  1130   aMargin.SizeTo(0,0,0,0);
  1131   return NS_OK;
  1134 nsIAtom*
  1135 nsXULScrollFrame::GetType() const
  1137   return nsGkAtoms::scrollFrame;
  1140 nscoord
  1141 nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState)
  1143   if (!mHelper.mScrolledFrame)
  1144     return 0;
  1146   nscoord ascent = mHelper.mScrolledFrame->GetBoxAscent(aState);
  1147   nsMargin m(0,0,0,0);
  1148   GetBorderAndPadding(m);
  1149   ascent += m.top;
  1150   GetMargin(m);
  1151   ascent += m.top;
  1153   return ascent;
  1156 nsSize
  1157 nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState)
  1159 #ifdef DEBUG_LAYOUT
  1160   PropagateDebug(aState);
  1161 #endif
  1163   nsSize pref = mHelper.mScrolledFrame->GetPrefSize(aState);
  1165   ScrollbarStyles styles = GetScrollbarStyles();
  1167   // scrolled frames don't have their own margins
  1169   if (mHelper.mVScrollbarBox &&
  1170       styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
  1171     nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState);
  1172     nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
  1173     pref.width += vSize.width;
  1176   if (mHelper.mHScrollbarBox &&
  1177       styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
  1178     nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState);
  1179     nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
  1180     pref.height += hSize.height;
  1183   AddBorderAndPadding(pref);
  1184   bool widthSet, heightSet;
  1185   nsIFrame::AddCSSPrefSize(this, pref, widthSet, heightSet);
  1186   return pref;
  1189 nsSize
  1190 nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState)
  1192 #ifdef DEBUG_LAYOUT
  1193   PropagateDebug(aState);
  1194 #endif
  1196   nsSize min = mHelper.mScrolledFrame->GetMinSizeForScrollArea(aState);
  1198   ScrollbarStyles styles = GetScrollbarStyles();
  1200   if (mHelper.mVScrollbarBox &&
  1201       styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
  1202      nsSize vSize = mHelper.mVScrollbarBox->GetMinSize(aState);
  1203      AddMargin(mHelper.mVScrollbarBox, vSize);
  1204      min.width += vSize.width;
  1205      if (min.height < vSize.height)
  1206         min.height = vSize.height;
  1209   if (mHelper.mHScrollbarBox &&
  1210       styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
  1211      nsSize hSize = mHelper.mHScrollbarBox->GetMinSize(aState);
  1212      AddMargin(mHelper.mHScrollbarBox, hSize);
  1213      min.height += hSize.height;
  1214      if (min.width < hSize.width)
  1215         min.width = hSize.width;
  1218   AddBorderAndPadding(min);
  1219   bool widthSet, heightSet;
  1220   nsIFrame::AddCSSMinSize(aState, this, min, widthSet, heightSet);
  1221   return min;
  1224 nsSize
  1225 nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState)
  1227 #ifdef DEBUG_LAYOUT
  1228   PropagateDebug(aState);
  1229 #endif
  1231   nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
  1233   AddBorderAndPadding(maxSize);
  1234   bool widthSet, heightSet;
  1235   nsIFrame::AddCSSMaxSize(this, maxSize, widthSet, heightSet);
  1236   return maxSize;
  1239 #ifdef DEBUG_FRAME_DUMP
  1240 nsresult
  1241 nsXULScrollFrame::GetFrameName(nsAString& aResult) const
  1243   return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
  1245 #endif
  1247 NS_IMETHODIMP
  1248 nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState)
  1250   uint32_t flags = aState.LayoutFlags();
  1251   nsresult rv = Layout(aState);
  1252   aState.SetLayoutFlags(flags);
  1254   nsBox::DoLayout(aState);
  1255   return rv;
  1258 NS_QUERYFRAME_HEAD(nsXULScrollFrame)
  1259   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
  1260   NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
  1261   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
  1262   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
  1263 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
  1265 //-------------------- Helper ----------------------
  1267 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
  1269 const double kCurrentVelocityWeighting = 0.25;
  1270 const double kStopDecelerationWeighting = 0.4;
  1272 // AsyncScroll has ref counting.
  1273 class ScrollFrameHelper::AsyncScroll MOZ_FINAL : public nsARefreshObserver {
  1274 public:
  1275   typedef mozilla::TimeStamp TimeStamp;
  1276   typedef mozilla::TimeDuration TimeDuration;
  1278   AsyncScroll(nsPoint aStartPos)
  1279     : mIsFirstIteration(true)
  1280     , mStartPos(aStartPos)
  1281     , mCallee(nullptr)
  1282   {}
  1284 private:
  1285   // Private destructor, to discourage deletion outside of Release():
  1286   ~AsyncScroll() {
  1287     RemoveObserver();
  1290 public:
  1291   nsPoint PositionAt(TimeStamp aTime);
  1292   nsSize VelocityAt(TimeStamp aTime); // In nscoords per second
  1294   void InitSmoothScroll(TimeStamp aTime, nsPoint aDestination,
  1295                         nsIAtom *aOrigin, const nsRect& aRange);
  1296   void Init(const nsRect& aRange) {
  1297     mRange = aRange;
  1300   bool IsFinished(TimeStamp aTime) {
  1301     return aTime > mStartTime + mDuration; // XXX or if we've hit the wall
  1304   TimeStamp mStartTime;
  1306   // mPrevEventTime holds previous 3 timestamps for intervals averaging (to
  1307   // reduce duration fluctuations). When AsyncScroll is constructed and no
  1308   // previous timestamps are available (indicated with mIsFirstIteration),
  1309   // initialize mPrevEventTime using imaginary previous timestamps with maximum
  1310   // relevant intervals between them.
  1311   TimeStamp mPrevEventTime[3];
  1312   bool mIsFirstIteration;
  1314   // Cached Preferences values to avoid re-reading them when extending an existing
  1315   // animation for the same event origin (can be as frequent as every 10(!)ms for
  1316   // a quick roll of the mouse wheel).
  1317   // These values are minimum and maximum animation duration per event origin,
  1318   // and a global ratio which defines how longer is the animation's duration
  1319   // compared to the average recent events intervals (such that for a relatively
  1320   // consistent events rate, the next event arrives before current animation ends)
  1321   nsCOMPtr<nsIAtom> mOrigin;
  1322   int32_t mOriginMinMS;
  1323   int32_t mOriginMaxMS;
  1324   double  mIntervalRatio;
  1326   TimeDuration mDuration;
  1327   nsPoint mStartPos;
  1328   nsPoint mDestination;
  1329   // Allowed destination positions around mDestination
  1330   nsRect mRange;
  1331   nsSMILKeySpline mTimingFunctionX;
  1332   nsSMILKeySpline mTimingFunctionY;
  1333   bool mIsSmoothScroll;
  1335 protected:
  1336   double ProgressAt(TimeStamp aTime) {
  1337     return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
  1340   nscoord VelocityComponent(double aTimeProgress,
  1341                             nsSMILKeySpline& aTimingFunction,
  1342                             nscoord aStart, nscoord aDestination);
  1344   // Initializes the timing function in such a way that the current velocity is
  1345   // preserved.
  1346   void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
  1347                           nscoord aCurrentPos, nscoord aCurrentVelocity,
  1348                           nscoord aDestination);
  1350   TimeDuration CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin);
  1352 // The next section is observer/callback management
  1353 // Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code.
  1354 public:
  1355   NS_INLINE_DECL_REFCOUNTING(AsyncScroll)
  1357   /*
  1358    * Set a refresh observer for smooth scroll iterations (and start observing).
  1359    * Should be used at most once during the lifetime of this object.
  1360    * Return value: true on success, false otherwise.
  1361    */
  1362   bool SetRefreshObserver(ScrollFrameHelper *aCallee) {
  1363     NS_ASSERTION(aCallee && !mCallee, "AsyncScroll::SetRefreshObserver - Invalid usage.");
  1365     if (!RefreshDriver(aCallee)->AddRefreshObserver(this, Flush_Style)) {
  1366       return false;
  1369     mCallee = aCallee;
  1370     return true;
  1373   virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE {
  1374     // The callback may release "this".
  1375     // We don't access members after returning, so no need for KungFuDeathGrip.
  1376     ScrollFrameHelper::AsyncScrollCallback(mCallee, aTime);
  1379 private:
  1380   ScrollFrameHelper *mCallee;
  1382   nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) {
  1383     return aCallee->mOuter->PresContext()->RefreshDriver();
  1386   /*
  1387    * The refresh driver doesn't hold a reference to its observers,
  1388    *   so releasing this object can (and is) used to remove the observer on DTOR.
  1389    * Currently, this object is released once the scrolling ends.
  1390    */
  1391   void RemoveObserver() {
  1392     if (mCallee) {
  1393       RefreshDriver(mCallee)->RemoveRefreshObserver(this, Flush_Style);
  1396 };
  1398 nsPoint
  1399 ScrollFrameHelper::AsyncScroll::PositionAt(TimeStamp aTime) {
  1400   double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
  1401   double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
  1402   return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
  1403                  NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
  1406 nsSize
  1407 ScrollFrameHelper::AsyncScroll::VelocityAt(TimeStamp aTime) {
  1408   double timeProgress = ProgressAt(aTime);
  1409   return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
  1410                                   mStartPos.x, mDestination.x),
  1411                 VelocityComponent(timeProgress, mTimingFunctionY,
  1412                                   mStartPos.y, mDestination.y));
  1415 /*
  1416  * Calculate duration, possibly dynamically according to events rate and event origin.
  1417  * (also maintain previous timestamps - which are only used here).
  1418  */
  1419 TimeDuration
  1420 ScrollFrameHelper::
  1421 AsyncScroll::CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin) {
  1422   if (!aOrigin){
  1423     aOrigin = nsGkAtoms::other;
  1426   // Read preferences only on first iteration or for a different event origin.
  1427   if (mIsFirstIteration || aOrigin != mOrigin) {
  1428     mOrigin = aOrigin;
  1429     mOriginMinMS = mOriginMaxMS = 0;
  1430     bool isOriginSmoothnessEnabled = false;
  1431     mIntervalRatio = 1;
  1433     // Default values for all preferences are defined in all.js
  1434     static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
  1435     static const bool kDefaultIsSmoothEnabled = true;
  1437     nsAutoCString originName;
  1438     aOrigin->ToUTF8String(originName);
  1439     nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
  1441     isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
  1442     if (isOriginSmoothnessEnabled) {
  1443       nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
  1444       nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
  1445       mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
  1446       mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
  1448       static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
  1449       mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
  1450       mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS);
  1453     // Keep the animation duration longer than the average event intervals
  1454     //   (to "connect" consecutive scroll animations before the scroll comes to a stop).
  1455     static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
  1456     mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
  1457                                          kDefaultDurationToIntervalRatio * 100) / 100.0;
  1459     // Duration should be at least as long as the intervals -> ratio is at least 1
  1460     mIntervalRatio = std::max(1.0, mIntervalRatio);
  1462     if (mIsFirstIteration) {
  1463       // Starting a new scroll (i.e. not when extending an existing scroll animation),
  1464       //   create imaginary prev timestamps with maximum relevant intervals between them.
  1466       // Longest relevant interval (which results in maximum duration)
  1467       TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
  1468       mPrevEventTime[0] = aTime              - maxDelta;
  1469       mPrevEventTime[1] = mPrevEventTime[0]  - maxDelta;
  1470       mPrevEventTime[2] = mPrevEventTime[1]  - maxDelta;
  1474   // Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
  1475   int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
  1476   mPrevEventTime[2] = mPrevEventTime[1];
  1477   mPrevEventTime[1] = mPrevEventTime[0];
  1478   mPrevEventTime[0] = aTime;
  1480   // Modulate duration according to events rate (quicker events -> shorter durations).
  1481   // The desired effect is to use longer duration when scrolling slowly, such that
  1482   // it's easier to follow, but reduce the duration to make it feel more snappy when
  1483   // scrolling quickly. To reduce fluctuations of the duration, we average event
  1484   // intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
  1485   int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
  1487   return TimeDuration::FromMilliseconds(durationMS);
  1490 void
  1491 ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
  1492                                                      nsPoint aDestination,
  1493                                                      nsIAtom *aOrigin,
  1494                                                      const nsRect& aRange) {
  1495   mRange = aRange;
  1496   TimeDuration duration = CalcDurationForEventTime(aTime, aOrigin);
  1497   nsSize currentVelocity(0, 0);
  1498   if (!mIsFirstIteration) {
  1499     // If an additional event has not changed the destination, then do not let
  1500     // another minimum duration reset slow things down.  If it would then
  1501     // instead continue with the existing timing function.
  1502     if (aDestination == mDestination &&
  1503         aTime + duration > mStartTime + mDuration)
  1504       return;
  1506     currentVelocity = VelocityAt(aTime);
  1507     mStartPos = PositionAt(aTime);
  1509   mStartTime = aTime;
  1510   mDuration = duration;
  1511   mDestination = aDestination;
  1512   InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
  1513                      aDestination.x);
  1514   InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
  1515                      aDestination.y);
  1516   mIsFirstIteration = false;
  1520 nscoord
  1521 ScrollFrameHelper::AsyncScroll::VelocityComponent(double aTimeProgress,
  1522                                                       nsSMILKeySpline& aTimingFunction,
  1523                                                       nscoord aStart,
  1524                                                       nscoord aDestination)
  1526   double dt, dxy;
  1527   aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
  1528   if (dt == 0)
  1529     return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
  1531   const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
  1532   double slope = dxy / dt;
  1533   return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond));
  1536 void
  1537 ScrollFrameHelper::AsyncScroll::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
  1538                                                        nscoord aCurrentPos,
  1539                                                        nscoord aCurrentVelocity,
  1540                                                        nscoord aDestination)
  1542   if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) {
  1543     aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1);
  1544     return;
  1547   const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
  1548   double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
  1549   double normalization = sqrt(1.0 + slope * slope);
  1550   double dt = 1.0 / normalization * kCurrentVelocityWeighting;
  1551   double dxy = slope / normalization * kCurrentVelocityWeighting;
  1552   aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1);
  1555 static bool
  1556 IsSmoothScrollingEnabled()
  1558   return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false);
  1561 class ScrollFrameActivityTracker MOZ_FINAL : public nsExpirationTracker<ScrollFrameHelper,4> {
  1562 public:
  1563   // Wait for 3-4s between scrolls before we remove our layers.
  1564   // That's 4 generations of 1s each.
  1565   enum { TIMEOUT_MS = 1000 };
  1566   ScrollFrameActivityTracker()
  1567     : nsExpirationTracker<ScrollFrameHelper,4>(TIMEOUT_MS) {}
  1568   ~ScrollFrameActivityTracker() {
  1569     AgeAllGenerations();
  1572   virtual void NotifyExpired(ScrollFrameHelper *aObject) {
  1573     RemoveObject(aObject);
  1574     aObject->MarkInactive();
  1576 };
  1578 static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nullptr;
  1580 // There are situations when a scroll frame is destroyed and then re-created
  1581 // for the same content element. In this case we want to increment the scroll
  1582 // generation between the old and new scrollframes. If the new one knew about
  1583 // the old one then it could steal the old generation counter and increment it
  1584 // but it doesn't have that reference so instead we use a static global to
  1585 // ensure the new one gets a fresh value.
  1586 static uint32_t sScrollGenerationCounter = 0;
  1588 ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
  1589                                              bool aIsRoot)
  1590   : mHScrollbarBox(nullptr)
  1591   , mVScrollbarBox(nullptr)
  1592   , mScrolledFrame(nullptr)
  1593   , mScrollCornerBox(nullptr)
  1594   , mResizerBox(nullptr)
  1595   , mOuter(aOuter)
  1596   , mAsyncScroll(nullptr)
  1597   , mOriginOfLastScroll(nsGkAtoms::other)
  1598   , mScrollGeneration(++sScrollGenerationCounter)
  1599   , mDestination(0, 0)
  1600   , mScrollPosAtLastPaint(0, 0)
  1601   , mRestorePos(-1, -1)
  1602   , mLastPos(-1, -1)
  1603   , mResolution(1.0, 1.0)
  1604   , mScrollPosForLayerPixelAlignment(-1, -1)
  1605   , mLastUpdateImagesPos(-1, -1)
  1606   , mNeverHasVerticalScrollbar(false)
  1607   , mNeverHasHorizontalScrollbar(false)
  1608   , mHasVerticalScrollbar(false)
  1609   , mHasHorizontalScrollbar(false)
  1610   , mFrameIsUpdatingScrollbar(false)
  1611   , mDidHistoryRestore(false)
  1612   , mIsRoot(aIsRoot)
  1613   , mClipAllDescendants(aIsRoot)
  1614   , mSupppressScrollbarUpdate(false)
  1615   , mSkippedScrollbarLayout(false)
  1616   , mHadNonInitialReflow(false)
  1617   , mHorizontalOverflow(false)
  1618   , mVerticalOverflow(false)
  1619   , mPostedReflowCallback(false)
  1620   , mMayHaveDirtyFixedChildren(false)
  1621   , mUpdateScrollbarAttributes(false)
  1622   , mCollapsedResizer(false)
  1623   , mShouldBuildScrollableLayer(false)
  1624   , mHasBeenScrolled(false)
  1625   , mIsResolutionSet(false)
  1627   mScrollingActive = IsAlwaysActive();
  1629   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
  1630     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
  1633   EnsureImageVisPrefsCached();
  1636 ScrollFrameHelper::~ScrollFrameHelper()
  1638   if (mActivityExpirationState.IsTracked()) {
  1639     gScrollFrameActivityTracker->RemoveObject(this);
  1641   if (gScrollFrameActivityTracker &&
  1642       gScrollFrameActivityTracker->IsEmpty()) {
  1643     delete gScrollFrameActivityTracker;
  1644     gScrollFrameActivityTracker = nullptr;
  1647   if (mScrollActivityTimer) {
  1648     mScrollActivityTimer->Cancel();
  1649     mScrollActivityTimer = nullptr;
  1653 /*
  1654  * Callback function from AsyncScroll, used in ScrollFrameHelper::ScrollTo
  1655  */
  1656 void
  1657 ScrollFrameHelper::AsyncScrollCallback(void* anInstance, mozilla::TimeStamp aTime)
  1659   ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance);
  1660   if (!self || !self->mAsyncScroll)
  1661     return;
  1663   nsRect range = self->mAsyncScroll->mRange;
  1664   if (self->mAsyncScroll->mIsSmoothScroll) {
  1665     if (!self->mAsyncScroll->IsFinished(aTime)) {
  1666       nsPoint destination = self->mAsyncScroll->PositionAt(aTime);
  1667       // Allow this scroll operation to land on any pixel boundary between the
  1668       // current position and the final allowed range.  (We don't want
  1669       // intermediate steps to be more constrained than the final step!)
  1670       nsRect intermediateRange =
  1671         nsRect(self->GetScrollPosition(), nsSize()).UnionEdges(range);
  1672       self->ScrollToImpl(destination, intermediateRange);
  1673       // 'self' might be destroyed here
  1674       return;
  1678   // Apply desired destination range since this is the last step of scrolling.
  1679   self->mAsyncScroll = nullptr;
  1680   nsWeakFrame weakFrame(self->mOuter);
  1681   self->ScrollToImpl(self->mDestination, range);
  1682   if (!weakFrame.IsAlive()) {
  1683     return;
  1685   // We are done scrolling, set our destination to wherever we actually ended
  1686   // up scrolling to.
  1687   self->mDestination = self->GetScrollPosition();
  1690 void
  1691 ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition)
  1693   nsPoint current = GetScrollPosition();
  1694   CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
  1695   nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
  1696   nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
  1697   nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1);
  1698   // XXX I don't think the following blocks are needed anymore, now that
  1699   // ScrollToImpl simply tries to scroll an integer number of layer
  1700   // pixels from the current position
  1701   if (currentCSSPixels.x == aScrollPosition.x) {
  1702     pt.x = current.x;
  1703     range.x = pt.x;
  1704     range.width = 0;
  1706   if (currentCSSPixels.y == aScrollPosition.y) {
  1707     pt.y = current.y;
  1708     range.y = pt.y;
  1709     range.height = 0;
  1711   ScrollTo(pt, nsIScrollableFrame::INSTANT, &range);
  1712   // 'this' might be destroyed here
  1715 void
  1716 ScrollFrameHelper::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition,
  1717                                                 nsIAtom *aOrigin)
  1719   nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
  1720   nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
  1721   nsRect range(pt.x - halfRange, pt.y - halfRange, 2*halfRange - 1, 2*halfRange - 1);
  1722   ScrollToWithOrigin(pt, nsIScrollableFrame::INSTANT, aOrigin, &range);
  1723   // 'this' might be destroyed here
  1726 CSSIntPoint
  1727 ScrollFrameHelper::GetScrollPositionCSSPixels()
  1729   return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
  1732 /*
  1733  * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
  1734  *  based on the setting of the smoothness scroll pref
  1735  */
  1736 void
  1737 ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
  1738                                           nsIScrollableFrame::ScrollMode aMode,
  1739                                           nsIAtom *aOrigin,
  1740                                           const nsRect* aRange)
  1742   nsRect scrollRange = GetScrollRangeForClamping();
  1743   mDestination = scrollRange.ClampPoint(aScrollPosition);
  1745   nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0));
  1747   if (aMode == nsIScrollableFrame::INSTANT) {
  1748     // Asynchronous scrolling is not allowed, so we'll kill any existing
  1749     // async-scrolling process and do an instant scroll.
  1750     mAsyncScroll = nullptr;
  1751     nsWeakFrame weakFrame(mOuter);
  1752     ScrollToImpl(mDestination, range, aOrigin);
  1753     if (!weakFrame.IsAlive()) {
  1754       return;
  1756     // We are done scrolling, set our destination to wherever we actually ended
  1757     // up scrolling to.
  1758     mDestination = GetScrollPosition();
  1759     return;
  1762   TimeStamp now = TimeStamp::Now();
  1763   bool isSmoothScroll = (aMode == nsIScrollableFrame::SMOOTH) &&
  1764                           IsSmoothScrollingEnabled();
  1766   if (!mAsyncScroll) {
  1767     mAsyncScroll = new AsyncScroll(GetScrollPosition());
  1768     if (!mAsyncScroll->SetRefreshObserver(this)) {
  1769       mAsyncScroll = nullptr;
  1770       // Observer setup failed. Scroll the normal way.
  1771       nsWeakFrame weakFrame(mOuter);
  1772       ScrollToImpl(mDestination, range, aOrigin);
  1773       if (!weakFrame.IsAlive()) {
  1774         return;
  1776       // We are done scrolling, set our destination to wherever we actually
  1777       // ended up scrolling to.
  1778       mDestination = GetScrollPosition();
  1779       return;
  1783   mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
  1785   if (isSmoothScroll) {
  1786     mAsyncScroll->InitSmoothScroll(now, mDestination, aOrigin, range);
  1787   } else {
  1788     mAsyncScroll->Init(range);
  1792 // We can't use nsContainerFrame::PositionChildViews here because
  1793 // we don't want to invalidate views that have moved.
  1794 static void AdjustViews(nsIFrame* aFrame)
  1796   nsView* view = aFrame->GetView();
  1797   if (view) {
  1798     nsPoint pt;
  1799     aFrame->GetParent()->GetClosestView(&pt);
  1800     pt += aFrame->GetPosition();
  1801     view->SetPosition(pt.x, pt.y);
  1803     return;
  1806   if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
  1807     return;
  1810   // Call AdjustViews recursively for all child frames except the popup list as
  1811   // the views for popups are not scrolled.
  1812   nsIFrame::ChildListIterator lists(aFrame);
  1813   for (; !lists.IsDone(); lists.Next()) {
  1814     if (lists.CurrentID() == nsIFrame::kPopupList) {
  1815       continue;
  1817     nsFrameList::Enumerator childFrames(lists.CurrentList());
  1818     for (; !childFrames.AtEnd(); childFrames.Next()) {
  1819       AdjustViews(childFrames.get());
  1824 static bool
  1825 CanScrollWithBlitting(nsIFrame* aFrame)
  1827   if (aFrame->GetStateBits() & NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL)
  1828     return false;
  1830   for (nsIFrame* f = aFrame; f;
  1831        f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
  1832     if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) ||
  1833         f->IsFrameOfType(nsIFrame::eSVG) ||
  1834         f->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) {
  1835       return false;
  1837     if (nsLayoutUtils::IsPopup(f))
  1838       break;
  1840   return true;
  1843 bool ScrollFrameHelper::IsIgnoringViewportClipping() const
  1845   if (!mIsRoot)
  1846     return false;
  1847   nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
  1848     (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
  1849   return subdocFrame && !subdocFrame->ShouldClipSubdocument();
  1852 bool ScrollFrameHelper::ShouldClampScrollPosition() const
  1854   if (!mIsRoot)
  1855     return true;
  1856   nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
  1857     (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
  1858   return !subdocFrame || subdocFrame->ShouldClampScrollPosition();
  1861 bool ScrollFrameHelper::IsAlwaysActive() const
  1863   if (nsDisplayItem::ForceActiveLayers()) {
  1864     return true;
  1867   const nsStyleDisplay* disp = mOuter->StyleDisplay();
  1868   if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) {
  1869     return true;
  1872   // Unless this is the root scrollframe for a non-chrome document
  1873   // which is the direct child of a chrome document, we default to not
  1874   // being "active".
  1875   if (!(mIsRoot && mOuter->PresContext()->IsRootContentDocument())) {
  1876      return false;
  1879   // If we have scrolled before, then we should stay active.
  1880   if (mHasBeenScrolled) {
  1881     return true;
  1884   // If we're overflow:hidden, then start as inactive until
  1885   // we get scrolled manually.
  1886   ScrollbarStyles styles = GetScrollbarStylesFromFrame();
  1887   return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
  1888           styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
  1891 void ScrollFrameHelper::MarkInactive()
  1893   if (IsAlwaysActive() || !mScrollingActive)
  1894     return;
  1896   mScrollingActive = false;
  1897   mOuter->InvalidateFrameSubtree();
  1900 void ScrollFrameHelper::MarkActive()
  1902   mScrollingActive = true;
  1903   if (IsAlwaysActive())
  1904     return;
  1906   if (mActivityExpirationState.IsTracked()) {
  1907     gScrollFrameActivityTracker->MarkUsed(this);
  1908   } else {
  1909     if (!gScrollFrameActivityTracker) {
  1910       gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
  1912     gScrollFrameActivityTracker->AddObject(this);
  1916 void ScrollFrameHelper::ScrollVisual(nsPoint aOldScrolledFramePos)
  1918   // Mark this frame as having been scrolled. If this is the root
  1919   // scroll frame of a content document, then IsAlwaysActive()
  1920   // will return true from now on and MarkInactive() won't
  1921   // have any effect.
  1922   mHasBeenScrolled = true;
  1924   AdjustViews(mScrolledFrame);
  1925   // We need to call this after fixing up the view positions
  1926   // to be consistent with the frame hierarchy.
  1927   bool canScrollWithBlitting = CanScrollWithBlitting(mOuter);
  1928   mOuter->RemoveStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
  1929   if (IsScrollingActive()) {
  1930     if (!canScrollWithBlitting) {
  1931       MarkInactive();
  1934   if (canScrollWithBlitting) {
  1935     MarkActive();
  1938   mOuter->SchedulePaint();
  1941 /**
  1942  * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper]
  1943  * to [aBoundLower, aBoundUpper] and then select the appunit value from among
  1944  * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) *
  1945  * aRes/aAppUnitsPerPixel is an integer (or as close as we can get
  1946  * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and
  1947  * closest to aDesired.  If no such value exists, return the nearest in
  1948  * [aDestLower, aDestUpper].
  1949  */
  1950 static nscoord
  1951 ClampAndAlignWithPixels(nscoord aDesired,
  1952                         nscoord aBoundLower, nscoord aBoundUpper,
  1953                         nscoord aDestLower, nscoord aDestUpper,
  1954                         nscoord aAppUnitsPerPixel, double aRes,
  1955                         nscoord aCurrent)
  1957   // Intersect scroll range with allowed range, by clamping the ends
  1958   // of aRange to be within bounds
  1959   nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper);
  1960   nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper);
  1962   nscoord desired = clamped(aDesired, destLower, destUpper);
  1964   double currentLayerVal = (aRes*aCurrent)/aAppUnitsPerPixel;
  1965   double desiredLayerVal = (aRes*desired)/aAppUnitsPerPixel;
  1966   double delta = desiredLayerVal - currentLayerVal;
  1967   double nearestLayerVal = NS_round(delta) + currentLayerVal;
  1969   // Convert back from ThebesLayer space to appunits relative to the top-left
  1970   // of the scrolled frame.
  1971   nscoord aligned =
  1972     NSToCoordRoundWithClamp(nearestLayerVal*aAppUnitsPerPixel/aRes);
  1974   // Use a bound if it is within the allowed range and closer to desired than
  1975   // the nearest pixel-aligned value.
  1976   if (aBoundUpper == destUpper &&
  1977       static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <
  1978       Abs(desired - aligned))
  1979     return aBoundUpper;
  1981   if (aBoundLower == destLower &&
  1982       static_cast<decltype(Abs(desired))>(desired - aBoundLower) <
  1983       Abs(aligned - desired))
  1984     return aBoundLower;
  1986   // Accept the nearest pixel-aligned value if it is within the allowed range. 
  1987   if (aligned >= destLower && aligned <= destUpper)
  1988     return aligned;
  1990   // Check if opposite pixel boundary fits into allowed range.
  1991   double oppositeLayerVal =
  1992     nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0);
  1993   nscoord opposite =
  1994     NSToCoordRoundWithClamp(oppositeLayerVal*aAppUnitsPerPixel/aRes);
  1995   if (opposite >= destLower && opposite <= destUpper) {
  1996     return opposite;
  1999   // No alignment available.
  2000   return desired;
  2003 /**
  2004  * Clamp desired scroll position aPt to aBounds and then snap
  2005  * it to the same layer pixel edges as aCurrent, keeping it within aRange
  2006  * during snapping. aCurrent is the current scroll position.
  2007  */
  2008 static nsPoint
  2009 ClampAndAlignWithLayerPixels(const nsPoint& aPt,
  2010                              const nsRect& aBounds,
  2011                              const nsRect& aRange,
  2012                              const nsPoint& aCurrent,
  2013                              nscoord aAppUnitsPerPixel,
  2014                              const gfxSize& aScale)
  2016   return nsPoint(ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(),
  2017                                          aRange.x, aRange.XMost(),
  2018                                          aAppUnitsPerPixel, aScale.width,
  2019                                          aCurrent.x),
  2020                  ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(),
  2021                                          aRange.y, aRange.YMost(),
  2022                                          aAppUnitsPerPixel, aScale.height,
  2023                                          aCurrent.y));
  2026 /* static */ void
  2027 ScrollFrameHelper::ScrollActivityCallback(nsITimer *aTimer, void* anInstance)
  2029   ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance);
  2031   // Fire the synth mouse move.
  2032   self->mScrollActivityTimer->Cancel();
  2033   self->mScrollActivityTimer = nullptr;
  2034   self->mOuter->PresContext()->PresShell()->SynthesizeMouseMove(true);
  2038 void
  2039 ScrollFrameHelper::ScheduleSyntheticMouseMove()
  2041   if (!mScrollActivityTimer) {
  2042     mScrollActivityTimer = do_CreateInstance("@mozilla.org/timer;1");
  2043     if (!mScrollActivityTimer)
  2044       return;
  2047   mScrollActivityTimer->InitWithFuncCallback(
  2048         ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT);
  2051 void
  2052 ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOrigin)
  2054   if (aOrigin == nullptr) {
  2055     // If no origin was specified, we still want to set it to something that's
  2056     // non-null, so that we can use nullness to distinguish if the frame was scrolled
  2057     // at all. Default it to some generic placeholder.
  2058     aOrigin = nsGkAtoms::other;
  2061   nsPresContext* presContext = mOuter->PresContext();
  2062   nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
  2063   // 'scale' is our estimate of the scale factor that will be applied
  2064   // when rendering the scrolled content to its own ThebesLayer.
  2065   gfxSize scale = FrameLayerBuilder::GetThebesLayerScaleForFrame(mScrolledFrame);
  2066   nsPoint curPos = GetScrollPosition();
  2067   nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)
  2068       ? curPos : mScrollPosForLayerPixelAlignment;
  2069   // Try to align aPt with curPos so they have an integer number of layer
  2070   // pixels between them. This gives us the best chance of scrolling without
  2071   // having to invalidate due to changes in subpixel rendering.
  2072   // Note that when we actually draw into a ThebesLayer, the coordinates
  2073   // that get mapped onto the layer buffer pixels are from the display list,
  2074   // which are relative to the display root frame's top-left increasing down,
  2075   // whereas here our coordinates are scroll positions which increase upward
  2076   // and are relative to the scrollport top-left. This difference doesn't actually
  2077   // matter since all we are about is that there be an integer number of
  2078   // layer pixels between pt and curPos.
  2079   nsPoint pt =
  2080     ClampAndAlignWithLayerPixels(aPt,
  2081                                  GetScrollRangeForClamping(),
  2082                                  aRange,
  2083                                  alignWithPos,
  2084                                  appUnitsPerDevPixel,
  2085                                  scale);
  2086   if (pt == curPos) {
  2087     return;
  2090   bool needImageVisibilityUpdate = (mLastUpdateImagesPos == nsPoint(-1,-1));
  2092   nsPoint dist(std::abs(pt.x - mLastUpdateImagesPos.x),
  2093                std::abs(pt.y - mLastUpdateImagesPos.y));
  2094   nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize();
  2095   nscoord horzAllowance = std::max(scrollPortSize.width / std::max(sHorzScrollFraction, 1),
  2096                                    nsPresContext::AppUnitsPerCSSPixel());
  2097   nscoord vertAllowance = std::max(scrollPortSize.height / std::max(sVertScrollFraction, 1),
  2098                                    nsPresContext::AppUnitsPerCSSPixel());
  2099   if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
  2100     needImageVisibilityUpdate = true;
  2103   if (needImageVisibilityUpdate) {
  2104     presContext->PresShell()->ScheduleImageVisibilityUpdate();
  2107   // notify the listeners.
  2108   for (uint32_t i = 0; i < mListeners.Length(); i++) {
  2109     mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
  2112   nsPoint oldScrollFramePos = mScrolledFrame->GetPosition();
  2113   // Update frame position for scrolling
  2114   mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
  2115   mOriginOfLastScroll = aOrigin;
  2116   mScrollGeneration = ++sScrollGenerationCounter;
  2118   // We pass in the amount to move visually
  2119   ScrollVisual(oldScrollFramePos);
  2121   ScheduleSyntheticMouseMove();
  2122   nsWeakFrame weakFrame(mOuter);
  2123   UpdateScrollbarPosition();
  2124   if (!weakFrame.IsAlive()) {
  2125     return;
  2127   PostScrollEvent();
  2129   // notify the listeners.
  2130   for (uint32_t i = 0; i < mListeners.Length(); i++) {
  2131     mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
  2134   nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell();
  2135   if (docShell) {
  2136     docShell->NotifyScrollObservers();
  2140 static int32_t
  2141 MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder)
  2143   int32_t maxZIndex = 0;
  2144   for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
  2145     maxZIndex = std::max(maxZIndex, item->ZIndex());
  2147   return maxZIndex;
  2150 static void
  2151 AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists,
  2152             nsDisplayList* aSource, nsIFrame* aSourceFrame, bool aOwnLayer,
  2153             uint32_t aFlags, mozilla::layers::FrameMetrics::ViewID aScrollTargetId,
  2154             bool aPositioned)
  2156   if (aSource->IsEmpty())
  2157     return;
  2159   nsDisplayWrapList* newItem = aOwnLayer?
  2160     new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource,
  2161                                      aFlags, aScrollTargetId) :
  2162     new (aBuilder) nsDisplayWrapList(aBuilder, aSourceFrame, aSource);
  2164   if (aPositioned) {
  2165     // We want overlay scrollbars to always be on top of the scrolled content,
  2166     // but we don't want them to unnecessarily cover overlapping elements from
  2167     // outside our scroll frame.
  2168     nsDisplayList* positionedDescendants = aLists.PositionedDescendants();
  2169     if (!positionedDescendants->IsEmpty()) {
  2170       newItem->SetOverrideZIndex(MaxZIndexInList(positionedDescendants, aBuilder));
  2171       positionedDescendants->AppendNewToTop(newItem);
  2172     } else {
  2173       aLists.Outlines()->AppendNewToTop(newItem);
  2175   } else {
  2176     aLists.BorderBackground()->AppendNewToTop(newItem);
  2180 struct HoveredStateComparator
  2182   bool Equals(nsIFrame* A, nsIFrame* B) const {
  2183     bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None,
  2184                                              nsGkAtoms::hover);
  2185     bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None,
  2186                                              nsGkAtoms::hover);
  2187     return aHovered == bHovered;
  2189   bool LessThan(nsIFrame* A, nsIFrame* B) const {
  2190     bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None,
  2191                                              nsGkAtoms::hover);
  2192     bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None,
  2193                                              nsGkAtoms::hover);
  2194     return !aHovered && bHovered;
  2196 };
  2198 void
  2199 ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder*   aBuilder,
  2200                                            const nsRect&           aDirtyRect,
  2201                                            const nsDisplayListSet& aLists,
  2202                                            bool&                   aCreateLayer,
  2203                                            bool                    aPositioned)
  2205   nsITheme* theme = mOuter->PresContext()->GetTheme();
  2206   if (theme &&
  2207       theme->ShouldHideScrollbars()) {
  2208     return;
  2211   bool overlayScrollbars =
  2212     LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
  2214   nsAutoTArray<nsIFrame*, 3> scrollParts;
  2215   for (nsIFrame* kid = mOuter->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) {
  2216     if (kid == mScrolledFrame ||
  2217         (kid->IsPositioned() || overlayScrollbars) != aPositioned)
  2218       continue;
  2220     scrollParts.AppendElement(kid);
  2223   mozilla::layers::FrameMetrics::ViewID scrollTargetId = aCreateLayer
  2224     ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
  2225     : mozilla::layers::FrameMetrics::NULL_SCROLL_ID;
  2227   scrollParts.Sort(HoveredStateComparator());
  2229   for (uint32_t i = 0; i < scrollParts.Length(); ++i) {
  2230     nsDisplayListCollection partList;
  2231     mOuter->BuildDisplayListForChild(
  2232       aBuilder, scrollParts[i], aDirtyRect, partList,
  2233       nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
  2235     uint32_t flags = 0;
  2236     if (scrollParts[i] == mVScrollbarBox) {
  2237       flags |= nsDisplayOwnLayer::VERTICAL_SCROLLBAR;
  2239     if (scrollParts[i] == mHScrollbarBox) {
  2240       flags |= nsDisplayOwnLayer::HORIZONTAL_SCROLLBAR;
  2243     // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into
  2244     // partList.PositionedDescendants().
  2245     ::AppendToTop(aBuilder, aLists,
  2246                   partList.PositionedDescendants(), scrollParts[i],
  2247                   aCreateLayer, flags, scrollTargetId, aPositioned);
  2251 class ScrollLayerWrapper : public nsDisplayWrapper
  2253 public:
  2254   ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame)
  2255     : mCount(0)
  2256     , mProps(aScrolledFrame->Properties())
  2257     , mScrollFrame(aScrollFrame)
  2258     , mScrolledFrame(aScrolledFrame)
  2260     SetCount(0);
  2263   virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
  2264                                   nsIFrame* aFrame,
  2265                                   nsDisplayList* aList) MOZ_OVERRIDE {
  2266     SetCount(++mCount);
  2267     return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, mScrolledFrame, mScrolledFrame, mScrollFrame);
  2270   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
  2271                                   nsDisplayItem* aItem) MOZ_OVERRIDE {
  2273     SetCount(++mCount);
  2274     return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame);
  2277 protected:
  2278   void SetCount(intptr_t aCount) {
  2279     mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount));
  2282   intptr_t mCount;
  2283   FrameProperties mProps;
  2284   nsIFrame* mScrollFrame;
  2285   nsIFrame* mScrolledFrame;
  2286 };
  2288 /* static */ bool ScrollFrameHelper::sImageVisPrefsCached = false;
  2289 /* static */ uint32_t ScrollFrameHelper::sHorzExpandScrollPort = 0;
  2290 /* static */ uint32_t ScrollFrameHelper::sVertExpandScrollPort = 1;
  2291 /* static */ int32_t ScrollFrameHelper::sHorzScrollFraction = 2;
  2292 /* static */ int32_t ScrollFrameHelper::sVertScrollFraction = 2;
  2294 /* static */ void
  2295 ScrollFrameHelper::EnsureImageVisPrefsCached()
  2297   if (!sImageVisPrefsCached) {
  2298     Preferences::AddUintVarCache(&sHorzExpandScrollPort,
  2299       "layout.imagevisibility.numscrollportwidths", (uint32_t)0);
  2300     Preferences::AddUintVarCache(&sVertExpandScrollPort,
  2301       "layout.imagevisibility.numscrollportheights", 1);
  2303     Preferences::AddIntVarCache(&sHorzScrollFraction,
  2304       "layout.imagevisibility.amountscrollbeforeupdatehorizontal", 2);
  2305     Preferences::AddIntVarCache(&sVertScrollFraction,
  2306       "layout.imagevisibility.amountscrollbeforeupdatevertical", 2);
  2308     sImageVisPrefsCached = true;
  2312 nsRect
  2313 ScrollFrameHelper::ExpandRect(const nsRect& aRect) const
  2315   // We don't want to expand a rect in a direction that we can't scroll, so we
  2316   // check the scroll range.
  2317   nsRect scrollRange = GetScrollRangeForClamping();
  2318   nsPoint scrollPos = GetScrollPosition();
  2319   nsMargin expand(0, 0, 0, 0);
  2321   nscoord vertShift = sVertExpandScrollPort * aRect.height;
  2322   if (scrollRange.y < scrollPos.y) {
  2323     expand.top = vertShift;
  2325   if (scrollPos.y < scrollRange.YMost()) {
  2326     expand.bottom = vertShift;
  2329   nscoord horzShift = sHorzExpandScrollPort * aRect.width;
  2330   if (scrollRange.x < scrollPos.x) {
  2331     expand.left = horzShift;
  2333   if (scrollPos.x < scrollRange.XMost()) {
  2334     expand.right = horzShift;
  2337   nsRect rect = aRect;
  2338   rect.Inflate(expand);
  2339   return rect;
  2342 static bool
  2343 ShouldBeClippedByFrame(nsIFrame* aClipFrame, nsIFrame* aClippedFrame)
  2345   return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame);
  2348 static void
  2349 ClipItemsExceptCaret(nsDisplayList* aList, nsDisplayListBuilder* aBuilder,
  2350                      nsIFrame* aClipFrame, const DisplayItemClip& aClip)
  2352   nsDisplayItem* i = aList->GetBottom();
  2353   for (; i; i = i->GetAbove()) {
  2354     if (!::ShouldBeClippedByFrame(aClipFrame, i->Frame())) {
  2355       continue;
  2358     bool unused;
  2359     nsRect bounds = i->GetBounds(aBuilder, &unused);
  2360     bool isAffectedByClip = aClip.IsRectAffectedByClip(bounds);
  2361     if (isAffectedByClip && nsDisplayItem::TYPE_CARET == i->GetType()) {
  2362       // Don't clip the caret if it overflows vertically only, and by half
  2363       // its height at most.  This is to avoid clipping it when the line-height
  2364       // is small.
  2365       auto half = bounds.height / 2;
  2366       bounds.y += half;
  2367       bounds.height -= half;
  2368       isAffectedByClip = aClip.IsRectAffectedByClip(bounds);
  2369       if (isAffectedByClip) {
  2370         // Don't clip the caret if it's just outside on the right side.
  2371         nsRect rightSide(bounds.x - 1, bounds.y, 1, bounds.height);
  2372         isAffectedByClip = aClip.IsRectAffectedByClip(rightSide);
  2373         // Also, avoid clipping it in a zero-height line box (heuristic only).
  2374         if (isAffectedByClip) {
  2375           isAffectedByClip = i->Frame()->GetRect().height != 0;
  2379     if (isAffectedByClip) {
  2380       DisplayItemClip newClip;
  2381       newClip.IntersectWith(i->GetClip());
  2382       newClip.IntersectWith(aClip);
  2383       i->SetClip(aBuilder, newClip);
  2385     nsDisplayList* children = i->GetSameCoordinateSystemChildren();
  2386     if (children) {
  2387       ClipItemsExceptCaret(children, aBuilder, aClipFrame, aClip);
  2392 static void
  2393 ClipListsExceptCaret(nsDisplayListCollection* aLists,
  2394                      nsDisplayListBuilder* aBuilder,
  2395                      nsIFrame* aClipFrame,
  2396                      const DisplayItemClip& aClip)
  2398   ::ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame, aClip);
  2399   ::ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame, aClip);
  2400   ::ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aClip);
  2401   ::ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame, aClip);
  2402   ::ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aClip);
  2403   ::ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aClip);
  2406 static bool
  2407 DisplayportExceedsMaxTextureSize(nsPresContext* aPresContext, const nsRect& aDisplayPort)
  2409 #ifdef MOZ_WIDGET_GONK
  2410   // On B2G we actively run into max texture size limits because the displayport-sizing code
  2411   // in the AsyncPanZoomController code is slightly busted (bug 957668 will fix it properly).
  2412   // We sometimes end up requesting displayports for which the corresponding layer will be
  2413   // larger than the max GL texture size (which we assume to be 4096 here).
  2414   // If we run into this case, we should just not use the displayport at all and fall back to
  2415   // just making a ScrollInfoLayer so that we use the APZC's synchronous scrolling fallback
  2416   // mechanism.
  2417   // Note also that if we don't do this here, it is quite likely that the parent B2G process
  2418   // will kill this child process to prevent OOMs (see the patch that landed as part of bug
  2419   // 965945 for details).
  2420   gfxSize resolution = aPresContext->PresShell()->GetCumulativeResolution();
  2421   return (aPresContext->AppUnitsToDevPixels(aDisplayPort.width) * resolution.width > 4096) ||
  2422          (aPresContext->AppUnitsToDevPixels(aDisplayPort.height) * resolution.height > 4096);
  2423 #else
  2424   return false;
  2425 #endif
  2428 void
  2429 ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
  2430                                     const nsRect&           aDirtyRect,
  2431                                     const nsDisplayListSet& aLists)
  2433   if (aBuilder->IsForImageVisibility()) {
  2434     mLastUpdateImagesPos = GetScrollPosition();
  2437   mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
  2439   if (aBuilder->IsPaintingToWindow()) {
  2440     mScrollPosAtLastPaint = GetScrollPosition();
  2441     if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) {
  2442       MarkInactive();
  2444     if (IsScrollingActive()) {
  2445       if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) {
  2446         mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint;
  2448     } else {
  2449       mScrollPosForLayerPixelAlignment = nsPoint(-1,-1);
  2453   // We put scrollbars in their own layers when this is the root scroll
  2454   // frame and we are a toplevel content document. In this situation, the
  2455   // scrollbar(s) would normally be assigned their own layer anyway, since
  2456   // they're not scrolled with the rest of the document. But when both
  2457   // scrollbars are visible, the layer's visible rectangle would be the size
  2458   // of the viewport, so most layer implementations would create a layer buffer
  2459   // that's much larger than necessary. Creating independent layers for each
  2460   // scrollbar works around the problem.
  2461   bool createLayersForScrollbars = mIsRoot &&
  2462     mOuter->PresContext()->IsRootContentDocument();
  2464   if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) {
  2466     // If we are a root scroll frame that has a display port we want to add
  2467     // scrollbars, they will be children of the scrollable layer, but they get
  2468     // adjusted by the APZC automatically.
  2469     bool addScrollBars = mIsRoot &&
  2470       nsLayoutUtils::GetDisplayPort(mOuter->GetContent()) &&
  2471       !aBuilder->IsForEventDelivery();
  2472     // For now, don't add them for display root documents, cause we've never
  2473     // had them there.
  2474     if (aBuilder->RootReferenceFrame()->PresContext() == mOuter->PresContext()) {
  2475       addScrollBars = false;
  2478     if (addScrollBars) {
  2479       // Add classic scrollbars.
  2480       AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
  2481                           false);
  2484     // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
  2485     // The scrolled frame shouldn't have its own background/border, so we
  2486     // can just pass aLists directly.
  2487     mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
  2488                                      aDirtyRect, aLists);
  2490 #ifdef MOZ_WIDGET_GONK
  2491     // TODO: only layerize the overlay scrollbars if this scrollframe can be
  2492     // panned asynchronously. For now just always layerize on B2G because.
  2493     // that's where we want the layerized scrollbars
  2494     createLayersForScrollbars = true;
  2495 #endif
  2496     if (addScrollBars) {
  2497       // Add overlay scrollbars.
  2498       AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
  2499                           true);
  2502     return;
  2505   // Now display the scrollbars and scrollcorner. These parts are drawn
  2506   // in the border-background layer, on top of our own background and
  2507   // borders and underneath borders and backgrounds of later elements
  2508   // in the tree.
  2509   // Note that this does not apply for overlay scrollbars; those are drawn
  2510   // in the positioned-elements layer on top of everything else by the call
  2511   // to AppendScrollPartsTo(..., true) further down.
  2512   AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars,
  2513                       false);
  2515   // Overflow clipping can never clip frames outside our subtree, so there
  2516   // is no need to worry about whether we are a moving frame that might clip
  2517   // non-moving frames.
  2518   // Not all our descendants will be clipped by overflow clipping, but all
  2519   // the ones that aren't clipped will be out of flow frames that have already
  2520   // had dirty rects saved for them by their parent frames calling
  2521   // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
  2522   // dirty rect here.
  2523   nsRect dirtyRect = aDirtyRect.Intersect(mScrollPort);
  2525   nsRect displayPort;
  2526   bool usingDisplayport = false;
  2527   if (!aBuilder->IsForEventDelivery()) {
  2528     if (!mIsRoot) {
  2529       // For a non-root scroll frame, override the value of the display port
  2530       // base rect, and possibly create a display port if there isn't one
  2531       // already. For root scroll frame, nsLayoutUtils::PaintFrame or
  2532       // nsSubDocumentFrame::BuildDisplayList takes care of this.
  2533       nsRect displayportBase = dirtyRect;
  2534       usingDisplayport = nsLayoutUtils::GetOrMaybeCreateDisplayPort(
  2535           *aBuilder, mOuter, displayportBase, &displayPort);
  2536     } else {
  2537       // For a root frmae, just get the value of the existing of the display
  2538       // port, if any.
  2539       usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort);
  2542     if (usingDisplayport && DisplayportExceedsMaxTextureSize(mOuter->PresContext(), displayPort)) {
  2543       usingDisplayport = false;
  2546     // Override the dirty rectangle if the displayport has been set.
  2547     if (usingDisplayport) {
  2548       dirtyRect = displayPort;
  2552   if (aBuilder->IsForImageVisibility()) {
  2553     // We expand the dirty rect to catch images just outside of the scroll port.
  2554     // We use the dirty rect instead of the whole scroll port to prevent
  2555     // too much expansion in the presence of very large (bigger than the
  2556     // viewport) scroll ports.
  2557     dirtyRect = ExpandRect(dirtyRect);
  2560   // Since making new layers is expensive, only use nsDisplayScrollLayer
  2561   // if the area is scrollable and we're the content process (unless we're on
  2562   // B2G, where we support async scrolling for scrollable elements in the
  2563   // parent process as well).
  2564   // When a displayport is being used, force building of a layer so that
  2565   // CompositorParent can always find the scrollable layer for the root content
  2566   // document.
  2567   // If the element is marked 'scrollgrab', also force building of a layer
  2568   // so that APZ can implement scroll grabbing.
  2569   mShouldBuildScrollableLayer = usingDisplayport || nsContentUtils::HasScrollgrab(mOuter->GetContent());
  2570   bool shouldBuildLayer = false;
  2571   if (mShouldBuildScrollableLayer) {
  2572     shouldBuildLayer = true;
  2573   } else {
  2574     shouldBuildLayer =
  2575       nsLayoutUtils::WantSubAPZC() &&
  2576       WantAsyncScroll() &&
  2577       // If we are the root scroll frame for the display root then we don't need a scroll
  2578       // info layer to make a RecordFrameMetrics call for us as
  2579       // nsDisplayList::PaintForFrame already calls RecordFrameMetrics for us.
  2580       (!mIsRoot || aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext());
  2583   nsDisplayListCollection scrolledContent;
  2585     // Note that setting the current scroll parent id here means that positioned children
  2586     // of this scroll info layer will pick up the scroll info layer as their scroll handoff
  2587     // parent. This is intentional because that is what happens for positioned children
  2588     // of scroll layers, and we want to maintain consistent behaviour between scroll layers
  2589     // and scroll info layers.
  2590     nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
  2591         aBuilder,
  2592         shouldBuildLayer && mScrolledFrame->GetContent()
  2593             ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
  2594             : aBuilder->GetCurrentScrollParentId());
  2595     DisplayListClipState::AutoSaveRestore clipState(aBuilder);
  2597     if (usingDisplayport) {
  2598       nsRect clip = displayPort + aBuilder->ToReferenceFrame(mOuter);
  2600       // If we are using a display port, then ignore any pre-existing clip
  2601       // passed down from our parents, and use only the clip computed here
  2602       // based on the display port. The pre-existing clip would just defeat
  2603       // the purpose of a display port which is to paint regions that are not
  2604       // currently visible so that they can be brought into view asynchronously.
  2605       // Notes:
  2606       //   - The pre-existing clip state will be restored when the
  2607       //     AutoSaveRestore goes out of scope, so there is no permanent change
  2608       //     to this clip state.
  2609       //   - We still set a clip to the scroll port further below where we
  2610       //     build the scroll wrapper. This doesn't prevent us from painting
  2611       //     the entire displayport, but it lets the compositor know to
  2612       //     clip to the scroll port after compositing.
  2613       clipState.Clear();
  2615       if (mClipAllDescendants) {
  2616         clipState.ClipContentDescendants(clip);
  2617       } else {
  2618         clipState.ClipContainingBlockDescendants(clip, nullptr);
  2620     } else {
  2621       nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
  2622       nscoord radii[8];
  2623       bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii);
  2624       // Our override of GetBorderRadii ensures we never have a radius at
  2625       // the corners where we have a scrollbar.
  2626       if (mClipAllDescendants) {
  2627         clipState.ClipContentDescendants(clip, haveRadii ? radii : nullptr);
  2628       } else {
  2629         clipState.ClipContainingBlockDescendants(clip, haveRadii ? radii : nullptr);
  2633     mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, scrolledContent);
  2636   if (MOZ_UNLIKELY(mOuter->StyleDisplay()->mOverflowClipBox ==
  2637                      NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) {
  2638     // We only clip if there is *scrollable* overflow, to avoid clipping
  2639     // *visual* overflow unnecessarily.
  2640     nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
  2641     nsRect so = mScrolledFrame->GetScrollableOverflowRect();
  2642     if (clipRect.width != so.width || clipRect.height != so.height ||
  2643         so.x < 0 || so.y < 0) {
  2644       // The 'scrolledContent' items are clipped to the padding-box at this point.
  2645       // Now clip them again to the content-box, except the nsDisplayCaret item
  2646       // which we allow to overflow the content-box in various situations --
  2647       // see ::ClipItemsExceptCaret.
  2648       clipRect.Deflate(mOuter->GetUsedPadding());
  2649       DisplayItemClip clip;
  2650       clip.SetTo(clipRect);
  2651       ::ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame, clip);
  2655   if (shouldBuildLayer) {
  2656     // ScrollLayerWrapper must always be created because it initializes the
  2657     // scroll layer count. The display lists depend on this.
  2658     ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
  2660     if (mShouldBuildScrollableLayer) {
  2661       DisplayListClipState::AutoSaveRestore clipState(aBuilder);
  2663       // For root scrollframes in documents where the CSS viewport has been
  2664       // modified, the CSS viewport no longer corresponds to what is visible,
  2665       // so we don't want to clip the content to it. For root scrollframes
  2666       // in documents where the CSS viewport is NOT modified, the mScrollPort
  2667       // is the same as the CSS viewport, modulo scrollbars.
  2668       if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) {
  2669         nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
  2670         if (mClipAllDescendants) {
  2671           clipState.ClipContentDescendants(clip);
  2672         } else {
  2673           clipState.ClipContainingBlockDescendants(clip);
  2677       // Once a displayport is set, assume that scrolling needs to be fast
  2678       // so create a layer with all the content inside. The compositor
  2679       // process will be able to scroll the content asynchronously.
  2680       wrapper.WrapListsInPlace(aBuilder, mOuter, scrolledContent);
  2683     // In case we are not using displayport or the nsDisplayScrollLayers are
  2684     // flattened during visibility computation, we still need to export the
  2685     // metadata about this scroll box to the compositor process.
  2686     nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
  2687       aBuilder, mScrolledFrame, mOuter);
  2688     scrolledContent.BorderBackground()->AppendNewToBottom(layerItem);
  2690   // Now display overlay scrollbars and the resizer, if we have one.
  2691 #ifdef MOZ_WIDGET_GONK
  2692   // TODO: only layerize the overlay scrollbars if this scrollframe can be
  2693   // panned asynchronously. For now just always layerize on B2G because.
  2694   // that's where we want the layerized scrollbars
  2695   createLayersForScrollbars = true;
  2696 #endif
  2697   AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent,
  2698                       createLayersForScrollbars, true);
  2699   scrolledContent.MoveTo(aLists);
  2702 bool
  2703 ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
  2705   // Use the right rect depending on if a display port is set.
  2706   nsRect displayPort;
  2707   bool usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort);
  2708   return aRect.Intersects(ExpandRect(usingDisplayport ? displayPort : mScrollPort));
  2711 static void HandleScrollPref(nsIScrollable *aScrollable, int32_t aOrientation,
  2712                              uint8_t& aValue)
  2714   int32_t pref;
  2715   aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
  2716   switch (pref) {
  2717     case nsIScrollable::Scrollbar_Auto:
  2718       // leave |aValue| untouched
  2719       break;
  2720     case nsIScrollable::Scrollbar_Never:
  2721       aValue = NS_STYLE_OVERFLOW_HIDDEN;
  2722       break;
  2723     case nsIScrollable::Scrollbar_Always:
  2724       aValue = NS_STYLE_OVERFLOW_SCROLL;
  2725       break;
  2729 ScrollbarStyles
  2730 ScrollFrameHelper::GetScrollbarStylesFromFrame() const
  2732   nsPresContext* presContext = mOuter->PresContext();
  2733   if (!presContext->IsDynamic() &&
  2734       !(mIsRoot && presContext->HasPaginatedScrolling())) {
  2735     return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
  2738   if (!mIsRoot) {
  2739     const nsStyleDisplay* disp = mOuter->StyleDisplay();
  2740     return ScrollbarStyles(disp->mOverflowX, disp->mOverflowY);
  2743   ScrollbarStyles result = presContext->GetViewportOverflowOverride();
  2744   nsCOMPtr<nsISupports> container = presContext->GetContainerWeak();
  2745   nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
  2746   if (scrollable) {
  2747     HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
  2748                      result.mHorizontal);
  2749     HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
  2750                      result.mVertical);
  2752   return result;
  2755 nsRect
  2756 ScrollFrameHelper::GetScrollRange() const
  2758   return GetScrollRange(mScrollPort.width, mScrollPort.height);
  2761 nsRect
  2762 ScrollFrameHelper::GetScrollRange(nscoord aWidth, nscoord aHeight) const
  2764   nsRect range = GetScrolledRect();
  2765   range.width = std::max(range.width - aWidth, 0);
  2766   range.height = std::max(range.height - aHeight, 0);
  2767   return range;
  2770 nsRect
  2771 ScrollFrameHelper::GetScrollRangeForClamping() const
  2773   if (!ShouldClampScrollPosition()) {
  2774     return nsRect(nscoord_MIN/2, nscoord_MIN/2,
  2775                   nscoord_MAX - nscoord_MIN/2, nscoord_MAX - nscoord_MIN/2);
  2777   nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize();
  2778   return GetScrollRange(scrollPortSize.width, scrollPortSize.height);
  2781 nsSize
  2782 ScrollFrameHelper::GetScrollPositionClampingScrollPortSize() const
  2784   nsIPresShell* presShell = mOuter->PresContext()->PresShell();
  2785   if (mIsRoot && presShell->IsScrollPositionClampingScrollPortSizeSet()) {
  2786     return presShell->GetScrollPositionClampingScrollPortSize();
  2788   return mScrollPort.Size();
  2791 gfxSize
  2792 ScrollFrameHelper::GetResolution() const
  2794   return mResolution;
  2797 void
  2798 ScrollFrameHelper::SetResolution(const gfxSize& aResolution)
  2800   mResolution = aResolution;
  2801   mIsResolutionSet = true;
  2804 static void
  2805 AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord)
  2807   if (aDelta < 0) {
  2808     *aCoord = nscoord_MIN;
  2809   } else if (aDelta > 0) {
  2810     *aCoord = nscoord_MAX;
  2814 /**
  2815  * Calculate lower/upper scrollBy range in given direction.
  2816  * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size
  2817  * @param aPos desired destination in AppUnits
  2818  * @param aNeg/PosTolerance defines relative range distance
  2819  *   below and above of aPos point
  2820  * @param aMultiplier used for conversion of tolerance into appUnis
  2821  */
  2822 static void
  2823 CalcRangeForScrollBy(int32_t aDelta, nscoord aPos,
  2824                      float aNegTolerance,
  2825                      float aPosTolerance,
  2826                      nscoord aMultiplier,
  2827                      nscoord* aLower, nscoord* aUpper)
  2829   if (!aDelta) {
  2830     *aLower = *aUpper = aPos;
  2831     return;
  2833   *aLower = aPos - NSToCoordRound(aMultiplier * (aDelta > 0 ? aNegTolerance : aPosTolerance));
  2834   *aUpper = aPos + NSToCoordRound(aMultiplier * (aDelta > 0 ? aPosTolerance : aNegTolerance));
  2837 void
  2838 ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
  2839                                 nsIScrollableFrame::ScrollUnit aUnit,
  2840                                 nsIScrollableFrame::ScrollMode aMode,
  2841                                 nsIntPoint* aOverflow,
  2842                                 nsIAtom *aOrigin)
  2844   nsSize deltaMultiplier;
  2845   float negativeTolerance;
  2846   float positiveTolerance;
  2847   if (!aOrigin){
  2848     aOrigin = nsGkAtoms::other;
  2850   bool isGenericOrigin = (aOrigin == nsGkAtoms::other);
  2851   switch (aUnit) {
  2852   case nsIScrollableFrame::DEVICE_PIXELS: {
  2853     nscoord appUnitsPerDevPixel =
  2854       mOuter->PresContext()->AppUnitsPerDevPixel();
  2855     deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
  2856     if (isGenericOrigin){
  2857       aOrigin = nsGkAtoms::pixels;
  2859     negativeTolerance = positiveTolerance = 0.5f;
  2860     break;
  2862   case nsIScrollableFrame::LINES: {
  2863     deltaMultiplier = GetLineScrollAmount();
  2864     if (isGenericOrigin){
  2865       aOrigin = nsGkAtoms::lines;
  2867     negativeTolerance = positiveTolerance = 0.1f;
  2868     break;
  2870   case nsIScrollableFrame::PAGES: {
  2871     deltaMultiplier = GetPageScrollAmount();
  2872     if (isGenericOrigin){
  2873       aOrigin = nsGkAtoms::pages;
  2875     negativeTolerance = 0.05f;
  2876     positiveTolerance = 0;
  2877     break;
  2879   case nsIScrollableFrame::WHOLE: {
  2880     nsPoint pos = GetScrollPosition();
  2881     AdjustForWholeDelta(aDelta.x, &pos.x);
  2882     AdjustForWholeDelta(aDelta.y, &pos.y);
  2883     ScrollTo(pos, aMode);
  2884     // 'this' might be destroyed here
  2885     if (aOverflow) {
  2886       *aOverflow = nsIntPoint(0, 0);
  2888     return;
  2890   default:
  2891     NS_ERROR("Invalid scroll mode");
  2892     return;
  2895   nsPoint newPos = mDestination +
  2896     nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
  2897   // Calculate desired range values.
  2898   nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
  2899   CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
  2900                        deltaMultiplier.width, &rangeLowerX, &rangeUpperX);
  2901   CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance,
  2902                        deltaMultiplier.height, &rangeLowerY, &rangeUpperY);
  2903   nsRect range(rangeLowerX,
  2904                rangeLowerY,
  2905                rangeUpperX - rangeLowerX,
  2906                rangeUpperY - rangeLowerY);
  2907   nsWeakFrame weakFrame(mOuter);
  2908   ScrollToWithOrigin(newPos, aMode, aOrigin, &range);
  2909   if (!weakFrame.IsAlive()) {
  2910     return;
  2913   if (aOverflow) {
  2914     nsPoint clampAmount = newPos - mDestination;
  2915     float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
  2916     *aOverflow = nsIntPoint(
  2917         NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
  2918         NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
  2922 nsSize
  2923 ScrollFrameHelper::GetLineScrollAmount() const
  2925   nsRefPtr<nsFontMetrics> fm;
  2926   nsLayoutUtils::GetFontMetricsForFrame(mOuter, getter_AddRefs(fm),
  2927     nsLayoutUtils::FontSizeInflationFor(mOuter));
  2928   NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
  2929   static nscoord sMinLineScrollAmountInPixels = -1;
  2930   if (sMinLineScrollAmountInPixels < 0) {
  2931     Preferences::AddIntVarCache(&sMinLineScrollAmountInPixels,
  2932                                 "mousewheel.min_line_scroll_amount", 1);
  2934   int32_t appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
  2935   nscoord minScrollAmountInAppUnits =
  2936     std::max(1, sMinLineScrollAmountInPixels) * appUnitsPerDevPixel;
  2937   nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0;
  2938   nscoord verticalAmount = fm ? fm->MaxHeight() : 0;
  2939   return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits),
  2940                 std::max(verticalAmount, minScrollAmountInAppUnits));
  2943 /**
  2944  * Compute the scrollport size excluding any fixed-pos headers and
  2945  * footers. A header or footer is an box that spans that entire width
  2946  * of the viewport and touches the top (or bottom, respectively) of the
  2947  * viewport. We also want to consider fixed elements that stack or overlap
  2948  * to effectively create a larger header or footer. Headers and footers that
  2949  * cover more than a third of the the viewport are ignored since they
  2950  * probably aren't true headers and footers and we don't want to restrict
  2951  * scrolling too much in such cases. This is a bit conservative --- some
  2952  * pages use elements as headers or footers that don't span the entire width
  2953  * of the viewport --- but it should be a good start.
  2954  */
  2955 struct TopAndBottom
  2957   TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {}
  2959   nscoord top, bottom;
  2960 };
  2961 struct TopComparator
  2963   bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
  2964     return A.top == B.top;
  2966   bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
  2967     return A.top < B.top;
  2969 };
  2970 struct ReverseBottomComparator
  2972   bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
  2973     return A.bottom == B.bottom;
  2975   bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
  2976     return A.bottom > B.bottom;
  2978 };
  2979 static nsSize
  2980 GetScrollPortSizeExcludingHeadersAndFooters(nsIFrame* aViewportFrame,
  2981                                             const nsRect& aScrollPort)
  2983   nsTArray<TopAndBottom> list;
  2984   nsFrameList fixedFrames = aViewportFrame->GetChildList(nsIFrame::kFixedList);
  2985   for (nsFrameList::Enumerator iterator(fixedFrames); !iterator.AtEnd();
  2986        iterator.Next()) {
  2987     nsIFrame* f = iterator.get();
  2988     nsRect r = f->GetRect().Intersect(aScrollPort);
  2989     if (r.x == 0 && r.width == aScrollPort.width &&
  2990         r.height <= aScrollPort.height/3) {
  2991       list.AppendElement(TopAndBottom(r.y, r.YMost()));
  2995   list.Sort(TopComparator());
  2996   nscoord headerBottom = 0;
  2997   for (uint32_t i = 0; i < list.Length(); ++i) {
  2998     if (list[i].top <= headerBottom) {
  2999       headerBottom = std::max(headerBottom, list[i].bottom);
  3003   list.Sort(ReverseBottomComparator());
  3004   nscoord footerTop = aScrollPort.height;
  3005   for (uint32_t i = 0; i < list.Length(); ++i) {
  3006     if (list[i].bottom >= footerTop) {
  3007       footerTop = std::min(footerTop, list[i].top);
  3011   headerBottom = std::min(aScrollPort.height/3, headerBottom);
  3012   footerTop = std::max(aScrollPort.height - aScrollPort.height/3, footerTop);
  3014   return nsSize(aScrollPort.width, footerTop - headerBottom);
  3017 nsSize
  3018 ScrollFrameHelper::GetPageScrollAmount() const
  3020   nsSize lineScrollAmount = GetLineScrollAmount();
  3021   nsSize effectiveScrollPortSize;
  3022   if (mIsRoot) {
  3023     // Reduce effective scrollport height by the height of any fixed-pos
  3024     // headers or footers
  3025     nsIFrame* root = mOuter->PresContext()->PresShell()->GetRootFrame();
  3026     effectiveScrollPortSize =
  3027       GetScrollPortSizeExcludingHeadersAndFooters(root, mScrollPort);
  3028   } else {
  3029     effectiveScrollPortSize = mScrollPort.Size();
  3031   // The page increment is the size of the page, minus the smaller of
  3032   // 10% of the size or 2 lines.
  3033   return nsSize(
  3034     effectiveScrollPortSize.width -
  3035       std::min(effectiveScrollPortSize.width/10, 2*lineScrollAmount.width),
  3036     effectiveScrollPortSize.height -
  3037       std::min(effectiveScrollPortSize.height/10, 2*lineScrollAmount.height));
  3040   /**
  3041    * this code is resposible for restoring the scroll position back to some
  3042    * saved position. if the user has not moved the scroll position manually
  3043    * we keep scrolling down until we get to our original position. keep in
  3044    * mind that content could incrementally be coming in. we only want to stop
  3045    * when we reach our new position.
  3046    */
  3047 void
  3048 ScrollFrameHelper::ScrollToRestoredPosition()
  3050   if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
  3051     return;
  3053   // make sure our scroll position did not change for where we last put
  3054   // it. if it does then the user must have moved it, and we no longer
  3055   // need to restore.
  3056   //
  3057   // In the RTL case, we check whether the scroll position changed using the
  3058   // logical scroll position, but we scroll to the physical scroll position in
  3059   // all cases
  3061   // if we didn't move, we still need to restore
  3062   if (GetLogicalScrollPosition() == mLastPos) {
  3063     // if our desired position is different to the scroll position, scroll.
  3064     // remember that we could be incrementally loading so we may enter
  3065     // and scroll many times.
  3066     if (mRestorePos != mLastPos /* GetLogicalScrollPosition() */) {
  3067       nsPoint scrollToPos = mRestorePos;
  3068       if (!IsLTR())
  3069         // convert from logical to physical scroll position
  3070         scrollToPos.x = mScrollPort.x -
  3071           (mScrollPort.XMost() - scrollToPos.x - mScrolledFrame->GetRect().width);
  3072       nsWeakFrame weakFrame(mOuter);
  3073       ScrollTo(scrollToPos, nsIScrollableFrame::INSTANT);
  3074       if (!weakFrame.IsAlive()) {
  3075         return;
  3077       // Re-get the scroll position, it might not be exactly equal to
  3078       // mRestorePos due to rounding and clamping.
  3079       mLastPos = GetLogicalScrollPosition();
  3080     } else {
  3081       // if we reached the position then stop
  3082       mRestorePos.y = -1;
  3083       mLastPos.x = -1;
  3084       mLastPos.y = -1;
  3086   } else {
  3087     // user moved the position, so we won't need to restore
  3088     mLastPos.x = -1;
  3089     mLastPos.y = -1;
  3093 nsresult
  3094 ScrollFrameHelper::FireScrollPortEvent()
  3096   mAsyncScrollPortEvent.Forget();
  3098   // Keep this in sync with PostOverflowEvent().
  3099   nsSize scrollportSize = mScrollPort.Size();
  3100   nsSize childSize = GetScrolledRect().Size();
  3102   bool newVerticalOverflow = childSize.height > scrollportSize.height;
  3103   bool vertChanged = mVerticalOverflow != newVerticalOverflow;
  3105   bool newHorizontalOverflow = childSize.width > scrollportSize.width;
  3106   bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
  3108   if (!vertChanged && !horizChanged) {
  3109     return NS_OK;
  3112   // If both either overflowed or underflowed then we dispatch only one
  3113   // DOM event.
  3114   bool both = vertChanged && horizChanged &&
  3115                 newVerticalOverflow == newHorizontalOverflow;
  3116   InternalScrollPortEvent::orientType orient;
  3117   if (both) {
  3118     orient = InternalScrollPortEvent::both;
  3119     mHorizontalOverflow = newHorizontalOverflow;
  3120     mVerticalOverflow = newVerticalOverflow;
  3122   else if (vertChanged) {
  3123     orient = InternalScrollPortEvent::vertical;
  3124     mVerticalOverflow = newVerticalOverflow;
  3125     if (horizChanged) {
  3126       // We need to dispatch a separate horizontal DOM event. Do that the next
  3127       // time around since dispatching the vertical DOM event might destroy
  3128       // the frame.
  3129       PostOverflowEvent();
  3132   else {
  3133     orient = InternalScrollPortEvent::horizontal;
  3134     mHorizontalOverflow = newHorizontalOverflow;
  3137   InternalScrollPortEvent event(true,
  3138     (orient == InternalScrollPortEvent::horizontal ? mHorizontalOverflow :
  3139                                                      mVerticalOverflow) ?
  3140     NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW, nullptr);
  3141   event.orient = orient;
  3142   return EventDispatcher::Dispatch(mOuter->GetContent(),
  3143                                    mOuter->PresContext(), &event);
  3146 void
  3147 ScrollFrameHelper::ReloadChildFrames()
  3149   mScrolledFrame = nullptr;
  3150   mHScrollbarBox = nullptr;
  3151   mVScrollbarBox = nullptr;
  3152   mScrollCornerBox = nullptr;
  3153   mResizerBox = nullptr;
  3155   nsIFrame* frame = mOuter->GetFirstPrincipalChild();
  3156   while (frame) {
  3157     nsIContent* content = frame->GetContent();
  3158     if (content == mOuter->GetContent()) {
  3159       NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
  3160       mScrolledFrame = frame;
  3161     } else {
  3162       nsAutoString value;
  3163       content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value);
  3164       if (!value.IsEmpty()) {
  3165         // probably a scrollbar then
  3166         if (value.LowerCaseEqualsLiteral("horizontal")) {
  3167           NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
  3168           mHScrollbarBox = frame;
  3169         } else {
  3170           NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
  3171           mVScrollbarBox = frame;
  3173       } else if (content->Tag() == nsGkAtoms::resizer) {
  3174         NS_ASSERTION(!mResizerBox, "Found multiple resizers");
  3175         mResizerBox = frame;
  3176       } else if (content->Tag() == nsGkAtoms::scrollcorner) {
  3177         // probably a scrollcorner
  3178         NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
  3179         mScrollCornerBox = frame;
  3183     frame = frame->GetNextSibling();
  3187 nsresult
  3188 ScrollFrameHelper::CreateAnonymousContent(
  3189   nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements)
  3191   nsPresContext* presContext = mOuter->PresContext();
  3192   nsIFrame* parent = mOuter->GetParent();
  3194   // Don't create scrollbars if we're an SVG document being used as an image,
  3195   // or if we're printing/print previewing.
  3196   // (In the printing case, we allow scrollbars if this is the child of the
  3197   // viewport & paginated scrolling is enabled, because then we must be the
  3198   // scroll frame for the print preview window, & that does need scrollbars.)
  3199   if (presContext->Document()->IsBeingUsedAsImage() ||
  3200       (!presContext->IsDynamic() &&
  3201        !(mIsRoot && presContext->HasPaginatedScrolling()))) {
  3202     mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
  3203     return NS_OK;
  3206   // Check if the frame is resizable.
  3207   int8_t resizeStyle = mOuter->StyleDisplay()->mResize;
  3208   bool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE;
  3210   nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
  3212   // If we're the scrollframe for the root, then we want to construct
  3213   // our scrollbar frames no matter what.  That way later dynamic
  3214   // changes to propagated overflow styles will show or hide
  3215   // scrollbars on the viewport without requiring frame reconstruction
  3216   // of the viewport (good!).
  3217   bool canHaveHorizontal;
  3218   bool canHaveVertical;
  3219   if (!mIsRoot) {
  3220     ScrollbarStyles styles = scrollable->GetScrollbarStyles();
  3221     canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
  3222     canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
  3223     if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
  3224       // Nothing to do.
  3225       return NS_OK;
  3227   } else {
  3228     canHaveHorizontal = true;
  3229     canHaveVertical = true;
  3232   // The anonymous <div> used by <inputs> never gets scrollbars.
  3233   nsITextControlFrame* textFrame = do_QueryFrame(parent);
  3234   if (textFrame) {
  3235     // Make sure we are not a text area.
  3236     nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
  3237     if (!textAreaElement) {
  3238       mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
  3239       return NS_OK;
  3243   nsNodeInfoManager *nodeInfoManager =
  3244     presContext->Document()->NodeInfoManager();
  3245   nsCOMPtr<nsINodeInfo> nodeInfo;
  3246   nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nullptr,
  3247                                           kNameSpaceID_XUL,
  3248                                           nsIDOMNode::ELEMENT_NODE);
  3249   NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
  3251   if (canHaveHorizontal) {
  3252     nsCOMPtr<nsINodeInfo> ni = nodeInfo;
  3253     NS_TrustedNewXULElement(getter_AddRefs(mHScrollbarContent), ni.forget());
  3254     mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
  3255                                 NS_LITERAL_STRING("horizontal"), false);
  3256     mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
  3257                                 NS_LITERAL_STRING("always"), false);
  3258     if (mIsRoot) {
  3259       mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
  3260                                   NS_LITERAL_STRING("true"), false);
  3262     if (!aElements.AppendElement(mHScrollbarContent))
  3263       return NS_ERROR_OUT_OF_MEMORY;
  3266   if (canHaveVertical) {
  3267     nsCOMPtr<nsINodeInfo> ni = nodeInfo;
  3268     NS_TrustedNewXULElement(getter_AddRefs(mVScrollbarContent), ni.forget());
  3269     mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
  3270                                 NS_LITERAL_STRING("vertical"), false);
  3271     mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
  3272                                 NS_LITERAL_STRING("always"), false);
  3273     if (mIsRoot) {
  3274       mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
  3275                                   NS_LITERAL_STRING("true"), false);
  3277     if (!aElements.AppendElement(mVScrollbarContent))
  3278       return NS_ERROR_OUT_OF_MEMORY;
  3281   if (isResizable) {
  3282     nsCOMPtr<nsINodeInfo> nodeInfo;
  3283     nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nullptr,
  3284                                             kNameSpaceID_XUL,
  3285                                             nsIDOMNode::ELEMENT_NODE);
  3286     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
  3288     NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
  3290     nsAutoString dir;
  3291     switch (resizeStyle) {
  3292       case NS_STYLE_RESIZE_HORIZONTAL:
  3293         if (IsScrollbarOnRight()) {
  3294           dir.AssignLiteral("right");
  3296         else {
  3297           dir.AssignLiteral("left");
  3299         break;
  3300       case NS_STYLE_RESIZE_VERTICAL:
  3301         dir.AssignLiteral("bottom");
  3302         break;
  3303       case NS_STYLE_RESIZE_BOTH:
  3304         dir.AssignLiteral("bottomend");
  3305         break;
  3306       default:
  3307         NS_WARNING("only resizable types should have resizers");
  3309     mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
  3311     if (mIsRoot) {
  3312       nsIContent* browserRoot = GetBrowserRoot(mOuter->GetContent());
  3313       mCollapsedResizer = !(browserRoot &&
  3314                             browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer));
  3316     else {
  3317       mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
  3318                                     NS_LITERAL_STRING("_parent"), false);
  3321     mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
  3322                                   NS_LITERAL_STRING("always"), false);
  3324     if (!aElements.AppendElement(mResizerContent))
  3325       return NS_ERROR_OUT_OF_MEMORY;
  3328   if (canHaveHorizontal && canHaveVertical) {
  3329     nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr,
  3330                                             kNameSpaceID_XUL,
  3331                                             nsIDOMNode::ELEMENT_NODE);
  3332     NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo.forget());
  3333     if (!aElements.AppendElement(mScrollCornerContent))
  3334       return NS_ERROR_OUT_OF_MEMORY;
  3337   return NS_OK;
  3340 void
  3341 ScrollFrameHelper::AppendAnonymousContentTo(nsBaseContentList& aElements,
  3342                                                 uint32_t aFilter)
  3344   aElements.MaybeAppendElement(mHScrollbarContent);
  3345   aElements.MaybeAppendElement(mVScrollbarContent);
  3346   aElements.MaybeAppendElement(mScrollCornerContent);
  3347   aElements.MaybeAppendElement(mResizerContent);
  3350 void
  3351 ScrollFrameHelper::Destroy()
  3353   if (mScrollbarActivity) {
  3354     mScrollbarActivity->Destroy();
  3355     mScrollbarActivity = nullptr;
  3358   // Unbind any content created in CreateAnonymousContent from the tree
  3359   nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent);
  3360   nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent);
  3361   nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent);
  3362   nsContentUtils::DestroyAnonymousContent(&mResizerContent);
  3364   if (mPostedReflowCallback) {
  3365     mOuter->PresContext()->PresShell()->CancelReflowCallback(this);
  3366     mPostedReflowCallback = false;
  3370 /**
  3371  * Called when we want to update the scrollbar position, either because scrolling happened
  3372  * or the user moved the scrollbar position and we need to undo that (e.g., when the user
  3373  * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
  3374  * to its initial position for the start of the smooth sequence).
  3375  */
  3376 void
  3377 ScrollFrameHelper::UpdateScrollbarPosition()
  3379   nsWeakFrame weakFrame(mOuter);
  3380   mFrameIsUpdatingScrollbar = true;
  3382   nsPoint pt = GetScrollPosition();
  3383   if (mVScrollbarBox) {
  3384     SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos,
  3385                       pt.y - GetScrolledRect().y);
  3386     if (!weakFrame.IsAlive()) {
  3387       return;
  3390   if (mHScrollbarBox) {
  3391     SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos,
  3392                       pt.x - GetScrolledRect().x);
  3393     if (!weakFrame.IsAlive()) {
  3394       return;
  3398   mFrameIsUpdatingScrollbar = false;
  3401 void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent)
  3403   NS_ASSERTION(aContent, "aContent must not be null");
  3404   NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
  3405                (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
  3406                "unexpected child");
  3408   // Attribute changes on the scrollbars happen in one of three ways:
  3409   // 1) The scrollbar changed the attribute in response to some user event
  3410   // 2) We changed the attribute in response to a ScrollPositionDidChange
  3411   // callback from the scrolling view
  3412   // 3) We changed the attribute to adjust the scrollbars for the start
  3413   // of a smooth scroll operation
  3414   //
  3415   // In cases 2 and 3 we do not need to scroll because we're just
  3416   // updating our scrollbar.
  3417   if (mFrameIsUpdatingScrollbar)
  3418     return;
  3420   nsRect scrolledRect = GetScrolledRect();
  3422   nsPoint current = GetScrollPosition() - scrolledRect.TopLeft();
  3423   nsPoint dest;
  3424   nsRect allowedRange;
  3425   dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x,
  3426                              &allowedRange.x, &allowedRange.width);
  3427   dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y,
  3428                              &allowedRange.y, &allowedRange.height);
  3429   current += scrolledRect.TopLeft();
  3430   dest += scrolledRect.TopLeft();
  3431   allowedRange += scrolledRect.TopLeft();
  3433   // Don't try to scroll if we're already at an acceptable place.
  3434   // Don't call Contains here since Contains returns false when the point is
  3435   // on the bottom or right edge of the rectangle.
  3436   if (allowedRange.ClampPoint(current) == current) {
  3437     return;
  3440   if (mScrollbarActivity) {
  3441     nsRefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
  3442     scrollbarActivity->ActivityOccurred();
  3445   bool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
  3446   if (isSmooth) {
  3447     // Make sure an attribute-setting callback occurs even if the view
  3448     // didn't actually move yet.  We need to make sure other listeners
  3449     // see that the scroll position is not (yet) what they thought it
  3450     // was.
  3451     nsWeakFrame weakFrame(mOuter);
  3452     UpdateScrollbarPosition();
  3453     if (!weakFrame.IsAlive()) {
  3454       return;
  3457   ScrollToWithOrigin(dest,
  3458                      isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT,
  3459                      nsGkAtoms::scrollbars, &allowedRange);
  3460   // 'this' might be destroyed here
  3463 /* ============= Scroll events ========== */
  3465 NS_IMETHODIMP
  3466 ScrollFrameHelper::ScrollEvent::Run()
  3468   if (mHelper)
  3469     mHelper->FireScrollEvent();
  3470   return NS_OK;
  3473 void
  3474 ScrollFrameHelper::FireScrollEvent()
  3476   mScrollEvent.Forget();
  3478   WidgetGUIEvent event(true, NS_SCROLL_EVENT, nullptr);
  3479   nsEventStatus status = nsEventStatus_eIgnore;
  3480   nsIContent* content = mOuter->GetContent();
  3481   nsPresContext* prescontext = mOuter->PresContext();
  3482   // Fire viewport scroll events at the document (where they
  3483   // will bubble to the window)
  3484   if (mIsRoot) {
  3485     nsIDocument* doc = content->GetCurrentDoc();
  3486     if (doc) {
  3487       EventDispatcher::Dispatch(doc, prescontext, &event, nullptr,  &status);
  3489   } else {
  3490     // scroll events fired at elements don't bubble (although scroll events
  3491     // fired at documents do, to the window)
  3492     event.mFlags.mBubbles = false;
  3493     EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status);
  3497 void
  3498 ScrollFrameHelper::PostScrollEvent()
  3500   if (mScrollEvent.IsPending())
  3501     return;
  3503   nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
  3504   if (!rpc)
  3505     return;
  3506   mScrollEvent = new ScrollEvent(this);
  3507   rpc->AddWillPaintObserver(mScrollEvent.get());
  3510 NS_IMETHODIMP
  3511 ScrollFrameHelper::AsyncScrollPortEvent::Run()
  3513   if (mHelper) {
  3514     mHelper->mOuter->PresContext()->GetPresShell()->
  3515       FlushPendingNotifications(Flush_InterruptibleLayout);
  3517   return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
  3520 bool
  3521 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
  3523   if (!mHelper.mHScrollbarBox)
  3524     return true;
  3526   return AddRemoveScrollbar(aState, aOnBottom, true, true);
  3529 bool
  3530 nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
  3532   if (!mHelper.mVScrollbarBox)
  3533     return true;
  3535   return AddRemoveScrollbar(aState, aOnRight, false, true);
  3538 void
  3539 nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
  3541   // removing a scrollbar should always fit
  3542   DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnBottom, true, false);
  3543   NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
  3546 void
  3547 nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
  3549   // removing a scrollbar should always fit
  3550   DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnRight, false, false);
  3551   NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
  3554 bool
  3555 nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
  3556                                      bool aOnRightOrBottom, bool aHorizontal, bool aAdd)
  3558   if (aHorizontal) {
  3559      if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox)
  3560        return false;
  3562      nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState);
  3563      nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
  3565      mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd);
  3567      bool hasHorizontalScrollbar;
  3568      bool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
  3569                                      mHelper.mScrollPort.y,
  3570                                      mHelper.mScrollPort.height,
  3571                                      hSize.height, aOnRightOrBottom, aAdd);
  3572      mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar;    // because mHasHorizontalScrollbar is a bool
  3573      if (!fit)
  3574         mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd);
  3576      return fit;
  3577   } else {
  3578      if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox)
  3579        return false;
  3581      nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState);
  3582      nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
  3584      mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd);
  3586      bool hasVerticalScrollbar;
  3587      bool fit = AddRemoveScrollbar(hasVerticalScrollbar,
  3588                                      mHelper.mScrollPort.x,
  3589                                      mHelper.mScrollPort.width,
  3590                                      vSize.width, aOnRightOrBottom, aAdd);
  3591      mHelper.mHasVerticalScrollbar = hasVerticalScrollbar;    // because mHasVerticalScrollbar is a bool
  3592      if (!fit)
  3593         mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd);
  3595      return fit;
  3599 bool
  3600 nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY,
  3601                                      nscoord& aSize, nscoord aSbSize,
  3602                                      bool aOnRightOrBottom, bool aAdd)
  3604    nscoord size = aSize;
  3605    nscoord xy = aXY;
  3607    if (size != NS_INTRINSICSIZE) {
  3608      if (aAdd) {
  3609         size -= aSbSize;
  3610         if (!aOnRightOrBottom && size >= 0)
  3611           xy += aSbSize;
  3612      } else {
  3613         size += aSbSize;
  3614         if (!aOnRightOrBottom)
  3615           xy -= aSbSize;
  3619    // not enough room? Yes? Return true.
  3620    if (size >= 0) {
  3621        aHasScrollbar = aAdd;
  3622        aSize = size;
  3623        aXY = xy;
  3624        return true;
  3627    aHasScrollbar = false;
  3628    return false;
  3631 void
  3632 nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
  3633                                    const nsPoint& aScrollPosition)
  3635   uint32_t oldflags = aState.LayoutFlags();
  3636   nsRect childRect = nsRect(mHelper.mScrollPort.TopLeft() - aScrollPosition,
  3637                             mHelper.mScrollPort.Size());
  3638   int32_t flags = NS_FRAME_NO_MOVE_VIEW;
  3640   nsSize minSize = mHelper.mScrolledFrame->GetMinSize(aState);
  3642   if (minSize.height > childRect.height)
  3643     childRect.height = minSize.height;
  3645   if (minSize.width > childRect.width)
  3646     childRect.width = minSize.width;
  3648   aState.SetLayoutFlags(flags);
  3649   ClampAndSetBounds(aState, childRect, aScrollPosition);
  3650   mHelper.mScrolledFrame->Layout(aState);
  3652   childRect = mHelper.mScrolledFrame->GetRect();
  3654   if (childRect.width < mHelper.mScrollPort.width ||
  3655       childRect.height < mHelper.mScrollPort.height)
  3657     childRect.width = std::max(childRect.width, mHelper.mScrollPort.width);
  3658     childRect.height = std::max(childRect.height, mHelper.mScrollPort.height);
  3660     // remove overflow areas when we update the bounds,
  3661     // because we've already accounted for it
  3662     // REVIEW: Have we accounted for both?
  3663     ClampAndSetBounds(aState, childRect, aScrollPosition, true);
  3666   aState.SetLayoutFlags(oldflags);
  3670 void ScrollFrameHelper::PostOverflowEvent()
  3672   if (mAsyncScrollPortEvent.IsPending())
  3673     return;
  3675   // Keep this in sync with FireScrollPortEvent().
  3676   nsSize scrollportSize = mScrollPort.Size();
  3677   nsSize childSize = GetScrolledRect().Size();
  3679   bool newVerticalOverflow = childSize.height > scrollportSize.height;
  3680   bool vertChanged = mVerticalOverflow != newVerticalOverflow;
  3682   bool newHorizontalOverflow = childSize.width > scrollportSize.width;
  3683   bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
  3685   if (!vertChanged && !horizChanged) {
  3686     return;
  3689   nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
  3690   if (!rpc)
  3691     return;
  3693   mAsyncScrollPortEvent = new AsyncScrollPortEvent(this);
  3694   rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get());
  3697 bool
  3698 ScrollFrameHelper::IsLTR() const
  3700   //TODO make bidi code set these from preferences
  3702   nsIFrame *frame = mOuter;
  3703   // XXX This is a bit on the slow side.
  3704   if (mIsRoot) {
  3705     // If we're the root scrollframe, we need the root element's style data.
  3706     nsPresContext *presContext = mOuter->PresContext();
  3707     nsIDocument *document = presContext->Document();
  3708     Element *root = document->GetRootElement();
  3710     // But for HTML and XHTML we want the body element.
  3711     nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
  3712     if (htmlDoc) {
  3713       Element *bodyElement = document->GetBodyElement();
  3714       if (bodyElement)
  3715         root = bodyElement; // we can trust the document to hold on to it
  3718     if (root) {
  3719       nsIFrame *rootsFrame = root->GetPrimaryFrame();
  3720       if (rootsFrame)
  3721         frame = rootsFrame;
  3725   return frame->StyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL;
  3728 bool
  3729 ScrollFrameHelper::IsScrollbarOnRight() const
  3731   nsPresContext *presContext = mOuter->PresContext();
  3733   // The position of the scrollbar in top-level windows depends on the pref
  3734   // layout.scrollbar.side. For non-top-level elements, it depends only on the
  3735   // directionaliy of the element (equivalent to a value of "1" for the pref).
  3736   if (!mIsRoot)
  3737     return IsLTR();
  3738   switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
  3739     default:
  3740     case 0: // UI directionality
  3741       return presContext->GetCachedIntPref(kPresContext_BidiDirection)
  3742              == IBMBIDI_TEXTDIRECTION_LTR;
  3743     case 1: // Document / content directionality
  3744       return IsLTR();
  3745     case 2: // Always right
  3746       return true;
  3747     case 3: // Always left
  3748       return false;
  3752 /**
  3753  * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
  3754  * cause any of the scrollbars to need to be reflowed.
  3755  */
  3756 nsresult
  3757 nsXULScrollFrame::Layout(nsBoxLayoutState& aState)
  3759   bool scrollbarRight = mHelper.IsScrollbarOnRight();
  3760   bool scrollbarBottom = true;
  3762   // get the content rect
  3763   nsRect clientRect(0,0,0,0);
  3764   GetClientRect(clientRect);
  3766   nsRect oldScrollAreaBounds = mHelper.mScrollPort;
  3767   nsPoint oldScrollPosition = mHelper.GetLogicalScrollPosition();
  3769   // the scroll area size starts off as big as our content area
  3770   mHelper.mScrollPort = clientRect;
  3772   /**************
  3773    Our basic strategy here is to first try laying out the content with
  3774    the scrollbars in their current state. We're hoping that that will
  3775    just "work"; the content will overflow wherever there's a scrollbar
  3776    already visible. If that does work, then there's no need to lay out
  3777    the scrollarea. Otherwise we fix up the scrollbars; first we add a
  3778    vertical one to scroll the content if necessary, or remove it if
  3779    it's not needed. Then we reflow the content if the scrollbar
  3780    changed.  Then we add a horizontal scrollbar if necessary (or
  3781    remove if not needed), and if that changed, we reflow the content
  3782    again. At this point, any scrollbars that are needed to scroll the
  3783    content have been added.
  3785    In the second phase we check to see if any scrollbars are too small
  3786    to display, and if so, we remove them. We check the horizontal
  3787    scrollbar first; removing it might make room for the vertical
  3788    scrollbar, and if we have room for just one scrollbar we'll save
  3789    the vertical one.
  3791    Finally we position and size the scrollbars and scrollcorner (the
  3792    square that is needed in the corner of the window when two
  3793    scrollbars are visible), and reflow any fixed position views
  3794    (if we're the viewport and we added or removed a scrollbar).
  3795    **************/
  3797   ScrollbarStyles styles = GetScrollbarStyles();
  3799   // Look at our style do we always have vertical or horizontal scrollbars?
  3800   if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
  3801      mHelper.mHasHorizontalScrollbar = true;
  3802   if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
  3803      mHelper.mHasVerticalScrollbar = true;
  3805   if (mHelper.mHasHorizontalScrollbar)
  3806      AddHorizontalScrollbar(aState, scrollbarBottom);
  3808   if (mHelper.mHasVerticalScrollbar)
  3809      AddVerticalScrollbar(aState, scrollbarRight);
  3811   // layout our the scroll area
  3812   LayoutScrollArea(aState, oldScrollPosition);
  3814   // now look at the content area and see if we need scrollbars or not
  3815   bool needsLayout = false;
  3817   // if we have 'auto' scrollbars look at the vertical case
  3818   if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
  3819     // These are only good until the call to LayoutScrollArea.
  3820     nsRect scrolledRect = mHelper.GetScrolledRect();
  3822     // There are two cases to consider
  3823       if (scrolledRect.height <= mHelper.mScrollPort.height
  3824           || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
  3825         if (mHelper.mHasVerticalScrollbar) {
  3826           // We left room for the vertical scrollbar, but it's not needed;
  3827           // remove it.
  3828           RemoveVerticalScrollbar(aState, scrollbarRight);
  3829           needsLayout = true;
  3831       } else {
  3832         if (!mHelper.mHasVerticalScrollbar) {
  3833           // We didn't leave room for the vertical scrollbar, but it turns
  3834           // out we needed it
  3835           if (AddVerticalScrollbar(aState, scrollbarRight))
  3836             needsLayout = true;
  3840     // ok layout at the right size
  3841     if (needsLayout) {
  3842        nsBoxLayoutState resizeState(aState);
  3843        LayoutScrollArea(resizeState, oldScrollPosition);
  3844        needsLayout = false;
  3849   // if scrollbars are auto look at the horizontal case
  3850   if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
  3852     // These are only good until the call to LayoutScrollArea.
  3853     nsRect scrolledRect = mHelper.GetScrolledRect();
  3855     // if the child is wider that the scroll area
  3856     // and we don't have a scrollbar add one.
  3857     if ((scrolledRect.width > mHelper.mScrollPort.width)
  3858         && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
  3860       if (!mHelper.mHasHorizontalScrollbar) {
  3861            // no scrollbar?
  3862           if (AddHorizontalScrollbar(aState, scrollbarBottom))
  3863              needsLayout = true;
  3865            // if we added a horizontal scrollbar and we did not have a vertical
  3866            // there is a chance that by adding the horizontal scrollbar we will
  3867            // suddenly need a vertical scrollbar. Is a special case but its
  3868            // important.
  3869            //if (!mHasVerticalScrollbar && scrolledRect.height > scrollAreaRect.height - sbSize.height)
  3870            //  printf("****Gfx Scrollbar Special case hit!!*****\n");
  3873     } else {
  3874         // if the area is smaller or equal to and we have a scrollbar then
  3875         // remove it.
  3876       if (mHelper.mHasHorizontalScrollbar) {
  3877         RemoveHorizontalScrollbar(aState, scrollbarBottom);
  3878         needsLayout = true;
  3883   // we only need to set the rect. The inner child stays the same size.
  3884   if (needsLayout) {
  3885      nsBoxLayoutState resizeState(aState);
  3886      LayoutScrollArea(resizeState, oldScrollPosition);
  3887      needsLayout = false;
  3890   // get the preferred size of the scrollbars
  3891   nsSize hMinSize(0, 0);
  3892   if (mHelper.mHScrollbarBox && mHelper.mHasHorizontalScrollbar) {
  3893     GetScrollbarMetrics(aState, mHelper.mHScrollbarBox, &hMinSize, nullptr, false);
  3895   nsSize vMinSize(0, 0);
  3896   if (mHelper.mVScrollbarBox && mHelper.mHasVerticalScrollbar) {
  3897     GetScrollbarMetrics(aState, mHelper.mVScrollbarBox, &vMinSize, nullptr, true);
  3900   // Disable scrollbars that are too small
  3901   // Disable horizontal scrollbar first. If we have to disable only one
  3902   // scrollbar, we'd rather keep the vertical scrollbar.
  3903   // Note that we always give horizontal scrollbars their preferred height,
  3904   // never their min-height. So check that there's room for the preferred height.
  3905   if (mHelper.mHasHorizontalScrollbar &&
  3906       (hMinSize.width > clientRect.width - vMinSize.width
  3907        || hMinSize.height > clientRect.height)) {
  3908     RemoveHorizontalScrollbar(aState, scrollbarBottom);
  3909     needsLayout = true;
  3911   // Now disable vertical scrollbar if necessary
  3912   if (mHelper.mHasVerticalScrollbar &&
  3913       (vMinSize.height > clientRect.height - hMinSize.height
  3914        || vMinSize.width > clientRect.width)) {
  3915     RemoveVerticalScrollbar(aState, scrollbarRight);
  3916     needsLayout = true;
  3919   // we only need to set the rect. The inner child stays the same size.
  3920   if (needsLayout) {
  3921     nsBoxLayoutState resizeState(aState);
  3922     LayoutScrollArea(resizeState, oldScrollPosition);
  3925   if (!mHelper.mSupppressScrollbarUpdate) {
  3926     mHelper.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
  3928   if (!mHelper.mPostedReflowCallback) {
  3929     // Make sure we'll try scrolling to restored position
  3930     PresContext()->PresShell()->PostReflowCallback(&mHelper);
  3931     mHelper.mPostedReflowCallback = true;
  3933   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
  3934     mHelper.mHadNonInitialReflow = true;
  3937   mHelper.UpdateSticky();
  3939   // Set up overflow areas for block frames for the benefit of
  3940   // text-overflow.
  3941   nsIFrame* f = mHelper.mScrolledFrame->GetContentInsertionFrame();
  3942   if (nsLayoutUtils::GetAsBlock(f)) {
  3943     nsRect origRect = f->GetRect();
  3944     nsRect clippedRect = origRect;
  3945     clippedRect.MoveBy(mHelper.mScrollPort.TopLeft());
  3946     clippedRect.IntersectRect(clippedRect, mHelper.mScrollPort);
  3947     nsOverflowAreas overflow = f->GetOverflowAreas();
  3948     f->FinishAndStoreOverflow(overflow, clippedRect.Size());
  3949     clippedRect.MoveTo(origRect.TopLeft());
  3950     f->SetRect(clippedRect);
  3953   mHelper.PostOverflowEvent();
  3954   return NS_OK;
  3957 void
  3958 ScrollFrameHelper::FinishReflowForScrollbar(nsIContent* aContent,
  3959                                                 nscoord aMinXY, nscoord aMaxXY,
  3960                                                 nscoord aCurPosXY,
  3961                                                 nscoord aPageIncrement,
  3962                                                 nscoord aIncrement)
  3964   // Scrollbars assume zero is the minimum position, so translate for them.
  3965   SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY);
  3966   SetScrollbarEnabled(aContent, aMaxXY - aMinXY);
  3967   SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
  3968   SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
  3969   SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
  3972 bool
  3973 ScrollFrameHelper::ReflowFinished()
  3975   nsAutoScriptBlocker scriptBlocker;
  3976   mPostedReflowCallback = false;
  3978   ScrollToRestoredPosition();
  3980   // Clamp current scroll position to new bounds. Normally this won't
  3981   // do anything.
  3982   nsPoint currentScrollPos = GetScrollPosition();
  3983   ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
  3984   if (!mAsyncScroll) {
  3985     // We need to have mDestination track the current scroll position,
  3986     // in case it falls outside the new reflow area. mDestination is used
  3987     // by ScrollBy as its starting position.
  3988     mDestination = GetScrollPosition();
  3991   if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
  3992     return false;
  3994   mUpdateScrollbarAttributes = false;
  3996   // Update scrollbar attributes.
  3997   nsPresContext* presContext = mOuter->PresContext();
  3999   if (mMayHaveDirtyFixedChildren) {
  4000     mMayHaveDirtyFixedChildren = false;
  4001     nsIFrame* parentFrame = mOuter->GetParent();
  4002     for (nsIFrame* fixedChild =
  4003            parentFrame->GetFirstChild(nsIFrame::kFixedList);
  4004          fixedChild; fixedChild = fixedChild->GetNextSibling()) {
  4005       // force a reflow of the fixed child
  4006       presContext->PresShell()->
  4007         FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
  4008                          NS_FRAME_HAS_DIRTY_CHILDREN);
  4012   nsRect scrolledContentRect = GetScrolledRect();
  4013   nscoord minX = scrolledContentRect.x;
  4014   nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width;
  4015   nscoord minY = scrolledContentRect.y;
  4016   nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height;
  4018   // Suppress handling of the curpos attribute changes we make here.
  4019   NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
  4020   mFrameIsUpdatingScrollbar = true;
  4022   nsCOMPtr<nsIContent> vScroll =
  4023     mVScrollbarBox ? mVScrollbarBox->GetContent() : nullptr;
  4024   nsCOMPtr<nsIContent> hScroll =
  4025     mHScrollbarBox ? mHScrollbarBox->GetContent() : nullptr;
  4027   // Note, in some cases mOuter may get deleted while finishing reflow
  4028   // for scrollbars. XXXmats is this still true now that we have a script
  4029   // blocker in this scope? (if not, remove the weak frame checks below).
  4030   if (vScroll || hScroll) {
  4031     nsWeakFrame weakFrame(mOuter);
  4032     nsPoint scrollPos = GetScrollPosition();
  4033     nsSize lineScrollAmount = GetLineScrollAmount();
  4034     if (vScroll) {
  4035       const double kScrollMultiplier =
  4036         Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
  4037                             NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
  4038       nscoord increment = lineScrollAmount.height * kScrollMultiplier;
  4039       // We normally use (scrollArea.height - increment) for height
  4040       // of page scrolling.  However, it is too small when
  4041       // increment is very large. (If increment is larger than
  4042       // scrollArea.height, direction of scrolling will be opposite).
  4043       // To avoid it, we use (float(scrollArea.height) * 0.8) as
  4044       // lower bound value of height of page scrolling. (bug 383267)
  4045       // XXX shouldn't we use GetPageScrollAmount here?
  4046       nscoord pageincrement = nscoord(mScrollPort.height - increment);
  4047       nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8);
  4048       FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
  4049                                std::max(pageincrement, pageincrementMin),
  4050                                increment);
  4052     if (hScroll) {
  4053       const double kScrollMultiplier =
  4054         Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
  4055                             NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
  4056       nscoord increment = lineScrollAmount.width * kScrollMultiplier;
  4057       FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x,
  4058                                nscoord(float(mScrollPort.width) * 0.8),
  4059                                increment);
  4061     NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
  4064   mFrameIsUpdatingScrollbar = false;
  4065   // We used to rely on the curpos attribute changes above to scroll the
  4066   // view.  However, for scrolling to the left of the viewport, we
  4067   // rescale the curpos attribute, which means that operations like
  4068   // resizing the window while it is scrolled all the way to the left
  4069   // hold the curpos attribute constant at 0 while still requiring
  4070   // scrolling.  So we suppress the effect of the changes above with
  4071   // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
  4072   // (It actually even works some of the time without this, thanks to
  4073   // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
  4074   // we hide the scrollbar on a large size change, such as
  4075   // maximization.)
  4076   if (!mHScrollbarBox && !mVScrollbarBox)
  4077     return false;
  4078   CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()
  4079                                         : mHScrollbarBox->GetContent());
  4080   return true;
  4083 void
  4084 ScrollFrameHelper::ReflowCallbackCanceled()
  4086   mPostedReflowCallback = false;
  4089 bool
  4090 ScrollFrameHelper::UpdateOverflow()
  4092   nsIScrollableFrame* sf = do_QueryFrame(mOuter);
  4093   ScrollbarStyles ss = sf->GetScrollbarStyles();
  4095   if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN ||
  4096       ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
  4097       GetScrollPosition() != nsPoint()) {
  4098     // If there are scrollbars, or we're not at the beginning of the pane,
  4099     // the scroll position may change. In this case, mark the frame as
  4100     // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means
  4101     // we have to reflow the frame and all its descendants, and we don't
  4102     // have to do that here. Only this frame needs to be reflowed.
  4103     mOuter->PresContext()->PresShell()->FrameNeedsReflow(
  4104       mOuter, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
  4105     // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip
  4106     // updating the scrollbars. (Because the overflow area of the scrolled
  4107     // frame has probably just been updated, Reflow won't see it change.)
  4108     mSkippedScrollbarLayout = true;
  4109     return false;  // reflowing will update overflow
  4111   PostOverflowEvent();
  4112   return mOuter->nsContainerFrame::UpdateOverflow();
  4115 void
  4116 ScrollFrameHelper::UpdateSticky()
  4118   StickyScrollContainer* ssc = StickyScrollContainer::
  4119     GetStickyScrollContainerForScrollFrame(mOuter);
  4120   if (ssc) {
  4121     nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter);
  4122     ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter);
  4126 void
  4127 ScrollFrameHelper::AdjustScrollbarRectForResizer(
  4128                          nsIFrame* aFrame, nsPresContext* aPresContext,
  4129                          nsRect& aRect, bool aHasResizer, bool aVertical)
  4131   if ((aVertical ? aRect.width : aRect.height) == 0)
  4132     return;
  4134   // if a content resizer is present, use its size. Otherwise, check if the
  4135   // widget has a resizer.
  4136   nsRect resizerRect;
  4137   if (aHasResizer) {
  4138     resizerRect = mResizerBox->GetRect();
  4140   else {
  4141     nsPoint offset;
  4142     nsIWidget* widget = aFrame->GetNearestWidget(offset);
  4143     nsIntRect widgetRect;
  4144     if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
  4145       return;
  4147     resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
  4148                          aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
  4149                          aPresContext->DevPixelsToAppUnits(widgetRect.width),
  4150                          aPresContext->DevPixelsToAppUnits(widgetRect.height));
  4153   if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
  4154     return;
  4156   if (aVertical)
  4157     aRect.height = std::max(0, resizerRect.y - aRect.y);
  4158   else
  4159     aRect.width = std::max(0, resizerRect.x - aRect.x);
  4162 static void
  4163 AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect)
  4165   if (aVRect.IsEmpty() || aHRect.IsEmpty())
  4166     return;
  4168   const nsRect oldVRect = aVRect;
  4169   const nsRect oldHRect = aHRect;
  4170   if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
  4171     aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
  4172   } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
  4173     nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
  4174     aHRect.x += overlap;
  4175     aHRect.width -= overlap;
  4177   if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
  4178     aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
  4182 void
  4183 ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState,
  4184                                         const nsRect& aContentArea,
  4185                                         const nsRect& aOldScrollArea)
  4187   NS_ASSERTION(!mSupppressScrollbarUpdate,
  4188                "This should have been suppressed");
  4190   bool hasResizer = HasResizer();
  4191   bool scrollbarOnLeft = !IsScrollbarOnRight();
  4193   // place the scrollcorner
  4194   if (mScrollCornerBox || mResizerBox) {
  4195     NS_PRECONDITION(!mScrollCornerBox || mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
  4197     nsRect r(0, 0, 0, 0);
  4198     if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
  4199       // scrollbar (if any) on left
  4200       r.x = aContentArea.x;
  4201       r.width = mScrollPort.x - aContentArea.x;
  4202       NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
  4203     } else {
  4204       // scrollbar (if any) on right
  4205       r.width = aContentArea.XMost() - mScrollPort.XMost();
  4206       r.x = aContentArea.XMost() - r.width;
  4207       NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
  4209     if (aContentArea.y != mScrollPort.y) {
  4210       NS_ERROR("top scrollbars not supported");
  4211     } else {
  4212       // scrollbar (if any) on bottom
  4213       r.height = aContentArea.YMost() - mScrollPort.YMost();
  4214       r.y = aContentArea.YMost() - r.height;
  4215       NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
  4218     if (mScrollCornerBox) {
  4219       nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r);
  4222     if (hasResizer) {
  4223       // if a resizer is present, get its size. Assume a default size of 15 pixels.
  4224       nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
  4225       nsSize resizerMinSize = mResizerBox->GetMinSize(aState);
  4227       nscoord vScrollbarWidth = mVScrollbarBox ?
  4228         mVScrollbarBox->GetPrefSize(aState).width : defaultSize;
  4229       r.width = std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width);
  4230       if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) {
  4231         r.x = aContentArea.XMost() - r.width;
  4234       nscoord hScrollbarHeight = mHScrollbarBox ?
  4235         mHScrollbarBox->GetPrefSize(aState).height : defaultSize;
  4236       r.height = std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height);
  4237       if (aContentArea.y == mScrollPort.y) {
  4238         r.y = aContentArea.YMost() - r.height;
  4241       nsBoxFrame::LayoutChildAt(aState, mResizerBox, r);
  4243     else if (mResizerBox) {
  4244       // otherwise lay out the resizer with an empty rectangle
  4245       nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect());
  4249   nsPresContext* presContext = mScrolledFrame->PresContext();
  4250   nsRect vRect;
  4251   if (mVScrollbarBox) {
  4252     NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
  4253     vRect = mScrollPort;
  4254     vRect.width = aContentArea.width - mScrollPort.width;
  4255     vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
  4256     if (mHasVerticalScrollbar) {
  4257       nsMargin margin;
  4258       mVScrollbarBox->GetMargin(margin);
  4259       vRect.Deflate(margin);
  4261     AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true);
  4264   nsRect hRect;
  4265   if (mHScrollbarBox) {
  4266     NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
  4267     hRect = mScrollPort;
  4268     hRect.height = aContentArea.height - mScrollPort.height;
  4269     hRect.y = true ? mScrollPort.YMost() : aContentArea.y;
  4270     if (mHasHorizontalScrollbar) {
  4271       nsMargin margin;
  4272       mHScrollbarBox->GetMargin(margin);
  4273       hRect.Deflate(margin);
  4275     AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
  4278   if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) {
  4279     AdjustOverlappingScrollbars(vRect, hRect);
  4281   if (mVScrollbarBox) {
  4282     nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
  4284   if (mHScrollbarBox) {
  4285     nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect);
  4288   // may need to update fixed position children of the viewport,
  4289   // if the client area changed size because of an incremental
  4290   // reflow of a descendant.  (If the outer frame is dirty, the fixed
  4291   // children will be re-laid out anyway)
  4292   if (aOldScrollArea.Size() != mScrollPort.Size() &&
  4293       !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
  4294       mIsRoot) {
  4295     mMayHaveDirtyFixedChildren = true;
  4298   // post reflow callback to modify scrollbar attributes
  4299   mUpdateScrollbarAttributes = true;
  4300   if (!mPostedReflowCallback) {
  4301     aState.PresContext()->PresShell()->PostReflowCallback(this);
  4302     mPostedReflowCallback = true;
  4306 #if DEBUG
  4307 static bool ShellIsAlive(nsWeakPtr& aWeakPtr)
  4309   nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aWeakPtr));
  4310   return !!shell;
  4312 #endif
  4314 void
  4315 ScrollFrameHelper::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos)
  4317   DebugOnly<nsWeakPtr> weakShell(
  4318     do_GetWeakReference(mOuter->PresContext()->PresShell()));
  4319   if (aMaxPos) {
  4320     aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
  4321   } else {
  4322     aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
  4323                       NS_LITERAL_STRING("true"), true);
  4325   MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
  4328 void
  4329 ScrollFrameHelper::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom,
  4330                                          nscoord aSize)
  4332   DebugOnly<nsWeakPtr> weakShell(
  4333     do_GetWeakReference(mOuter->PresContext()->PresShell()));
  4334   // convert to pixels
  4335   aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
  4337   // only set the attribute if it changed.
  4339   nsAutoString newValue;
  4340   newValue.AppendInt(aSize);
  4342   if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
  4343     return;
  4345   nsWeakFrame weakFrame(mOuter);
  4346   nsCOMPtr<nsIContent> kungFuDeathGrip = aContent;
  4347   aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
  4348   MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
  4349   if (!weakFrame.IsAlive()) {
  4350     return;
  4353   if (mScrollbarActivity) {
  4354     nsRefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
  4355     scrollbarActivity->ActivityOccurred();
  4359 static void
  4360 ReduceRadii(nscoord aXBorder, nscoord aYBorder,
  4361             nscoord& aXRadius, nscoord& aYRadius)
  4363   // In order to ensure that the inside edge of the border has no
  4364   // curvature, we need at least one of its radii to be zero.
  4365   if (aXRadius <= aXBorder || aYRadius <= aYBorder)
  4366     return;
  4368   // For any corner where we reduce the radii, preserve the corner's shape.
  4369   double ratio = std::max(double(aXBorder) / aXRadius,
  4370                         double(aYBorder) / aYRadius);
  4371   aXRadius *= ratio;
  4372   aYRadius *= ratio;
  4375 /**
  4376  * Implement an override for nsIFrame::GetBorderRadii to ensure that
  4377  * the clipping region for the border radius does not clip the scrollbars.
  4379  * In other words, we require that the border radius be reduced until the
  4380  * inner border radius at the inner edge of the border is 0 wherever we
  4381  * have scrollbars.
  4382  */
  4383 bool
  4384 ScrollFrameHelper::GetBorderRadii(nscoord aRadii[8]) const
  4386   if (!mOuter->nsContainerFrame::GetBorderRadii(aRadii))
  4387     return false;
  4389   // Since we can use GetActualScrollbarSizes (rather than
  4390   // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
  4391   // probably should.
  4392   nsMargin sb = GetActualScrollbarSizes();
  4393   nsMargin border = mOuter->GetUsedBorder();
  4395   if (sb.left > 0 || sb.top > 0) {
  4396     ReduceRadii(border.left, border.top,
  4397                 aRadii[NS_CORNER_TOP_LEFT_X],
  4398                 aRadii[NS_CORNER_TOP_LEFT_Y]);
  4401   if (sb.top > 0 || sb.right > 0) {
  4402     ReduceRadii(border.right, border.top,
  4403                 aRadii[NS_CORNER_TOP_RIGHT_X],
  4404                 aRadii[NS_CORNER_TOP_RIGHT_Y]);
  4407   if (sb.right > 0 || sb.bottom > 0) {
  4408     ReduceRadii(border.right, border.bottom,
  4409                 aRadii[NS_CORNER_BOTTOM_RIGHT_X],
  4410                 aRadii[NS_CORNER_BOTTOM_RIGHT_Y]);
  4413   if (sb.bottom > 0 || sb.left > 0) {
  4414     ReduceRadii(border.left, border.bottom,
  4415                 aRadii[NS_CORNER_BOTTOM_LEFT_X],
  4416                 aRadii[NS_CORNER_BOTTOM_LEFT_Y]);
  4419   return true;
  4422 nsRect
  4423 ScrollFrameHelper::GetScrolledRect() const
  4425   nsRect result =
  4426     GetScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
  4427                             mScrollPort.Size());
  4429   if (result.width < mScrollPort.width) {
  4430     NS_WARNING("Scrolled rect smaller than scrollport?");
  4432   if (result.height < mScrollPort.height) {
  4433     NS_WARNING("Scrolled rect smaller than scrollport?");
  4435   return result;
  4438 nsRect
  4439 ScrollFrameHelper::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea,
  4440                                                const nsSize& aScrollPortSize) const
  4442   return nsLayoutUtils::GetScrolledRect(mScrolledFrame,
  4443       aScrolledFrameOverflowArea, aScrollPortSize,
  4444       IsLTR() ? NS_STYLE_DIRECTION_LTR : NS_STYLE_DIRECTION_RTL);
  4447 nsMargin
  4448 ScrollFrameHelper::GetActualScrollbarSizes() const
  4450   nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition();
  4452   return nsMargin(mScrollPort.y - r.y,
  4453                   r.XMost() - mScrollPort.XMost(),
  4454                   r.YMost() - mScrollPort.YMost(),
  4455                   mScrollPort.x - r.x);
  4458 void
  4459 ScrollFrameHelper::SetScrollbarVisibility(nsIFrame* aScrollbar, bool aVisible)
  4461   nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
  4462   if (scrollbar) {
  4463     // See if we have a mediator.
  4464     nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
  4465     if (mediator) {
  4466       // Inform the mediator of the visibility change.
  4467       mediator->VisibilityChanged(aVisible);
  4472 nscoord
  4473 ScrollFrameHelper::GetCoordAttribute(nsIFrame* aBox, nsIAtom* aAtom,
  4474                                          nscoord aDefaultValue,
  4475                                          nscoord* aRangeStart,
  4476                                          nscoord* aRangeLength)
  4478   if (aBox) {
  4479     nsIContent* content = aBox->GetContent();
  4481     nsAutoString value;
  4482     content->GetAttr(kNameSpaceID_None, aAtom, value);
  4483     if (!value.IsEmpty())
  4485       nsresult error;
  4486       // convert it to appunits
  4487       nscoord result = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
  4488       nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
  4489       // Any nscoord value that would round to the attribute value when converted
  4490       // to CSS pixels is allowed.
  4491       *aRangeStart = result - halfPixel;
  4492       *aRangeLength = halfPixel*2 - 1;
  4493       return result;
  4497   // Only this exact default value is allowed.
  4498   *aRangeStart = aDefaultValue;
  4499   *aRangeLength = 0;
  4500   return aDefaultValue;
  4503 nsPresState*
  4504 ScrollFrameHelper::SaveState() const
  4506   nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
  4507   if (mediator) {
  4508     // child handles its own scroll state, so don't bother saving state here
  4509     return nullptr;
  4512   // Don't store a scroll state if we never have been scrolled or restored
  4513   // a previous scroll state.
  4514   if (!mHasBeenScrolled && !mDidHistoryRestore) {
  4515     return nullptr;
  4518   nsPresState* state = new nsPresState();
  4519   // Save mRestorePos instead of our actual current scroll position, if it's
  4520   // valid and we haven't moved since the last update of mLastPos (same check
  4521   // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
  4522   // while we're in the process of loading content to scroll to a restored
  4523   // position, we'll keep trying after the reframe.
  4524   nsPoint pt = GetLogicalScrollPosition();
  4525   if (mRestorePos.y != -1 && pt == mLastPos) {
  4526     pt = mRestorePos;
  4528   state->SetScrollState(pt);
  4529   state->SetResolution(mResolution);
  4530   return state;
  4533 void
  4534 ScrollFrameHelper::RestoreState(nsPresState* aState)
  4536   mRestorePos = aState->GetScrollState();
  4537   mDidHistoryRestore = true;
  4538   mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0);
  4539   mResolution = aState->GetResolution();
  4540   mIsResolutionSet = true;
  4542   if (mIsRoot) {
  4543     mOuter->PresContext()->PresShell()->SetResolution(mResolution.width, mResolution.height);
  4547 void
  4548 ScrollFrameHelper::PostScrolledAreaEvent()
  4550   if (mScrolledAreaEvent.IsPending()) {
  4551     return;
  4553   mScrolledAreaEvent = new ScrolledAreaEvent(this);
  4554   nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
  4557 ////////////////////////////////////////////////////////////////////////////////
  4558 // ScrolledArea change event dispatch
  4560 NS_IMETHODIMP
  4561 ScrollFrameHelper::ScrolledAreaEvent::Run()
  4563   if (mHelper) {
  4564     mHelper->FireScrolledAreaEvent();
  4566   return NS_OK;
  4569 void
  4570 ScrollFrameHelper::FireScrolledAreaEvent()
  4572   mScrolledAreaEvent.Forget();
  4574   InternalScrollAreaEvent event(true, NS_SCROLLEDAREACHANGED, nullptr);
  4575   nsPresContext *prescontext = mOuter->PresContext();
  4576   nsIContent* content = mOuter->GetContent();
  4578   event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
  4580   nsIDocument *doc = content->GetCurrentDoc();
  4581   if (doc) {
  4582     EventDispatcher::Dispatch(doc, prescontext, &event, nullptr);
  4586 uint32_t
  4587 nsIScrollableFrame::GetPerceivedScrollingDirections() const
  4589   nscoord oneDevPixel = GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
  4590   uint32_t directions = GetScrollbarVisibility();
  4591   nsRect scrollRange = GetScrollRange();
  4592   if (scrollRange.width >= oneDevPixel) {
  4593     directions |= HORIZONTAL;
  4595   if (scrollRange.height >= oneDevPixel) {
  4596     directions |= VERTICAL;
  4598   return directions;

mercurial