1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/nsGfxScrollFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,4599 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* rendering object to wrap rendering objects that should be scrollable */ 1.10 + 1.11 +#include "nsGfxScrollFrame.h" 1.12 + 1.13 +#include "base/compiler_specific.h" 1.14 +#include "DisplayItemClip.h" 1.15 +#include "nsCOMPtr.h" 1.16 +#include "nsPresContext.h" 1.17 +#include "nsView.h" 1.18 +#include "nsIScrollable.h" 1.19 +#include "nsContainerFrame.h" 1.20 +#include "nsGkAtoms.h" 1.21 +#include "nsNameSpaceManager.h" 1.22 +#include "nsContentList.h" 1.23 +#include "nsIDocumentInlines.h" 1.24 +#include "nsFontMetrics.h" 1.25 +#include "nsBoxLayoutState.h" 1.26 +#include "nsINodeInfo.h" 1.27 +#include "nsScrollbarFrame.h" 1.28 +#include "nsIScrollbarMediator.h" 1.29 +#include "nsITextControlFrame.h" 1.30 +#include "nsIDOMHTMLTextAreaElement.h" 1.31 +#include "nsNodeInfoManager.h" 1.32 +#include "nsContentCreatorFunctions.h" 1.33 +#include "nsAutoPtr.h" 1.34 +#include "nsPresState.h" 1.35 +#include "nsIHTMLDocument.h" 1.36 +#include "nsContentUtils.h" 1.37 +#include "nsLayoutUtils.h" 1.38 +#include "nsBidiUtils.h" 1.39 +#include "mozilla/ContentEvents.h" 1.40 +#include "mozilla/EventDispatcher.h" 1.41 +#include "mozilla/Preferences.h" 1.42 +#include "mozilla/LookAndFeel.h" 1.43 +#include "mozilla/dom/Element.h" 1.44 +#include <stdint.h> 1.45 +#include "mozilla/MathAlgorithms.h" 1.46 +#include "FrameLayerBuilder.h" 1.47 +#include "nsSMILKeySpline.h" 1.48 +#include "nsSubDocumentFrame.h" 1.49 +#include "nsSVGOuterSVGFrame.h" 1.50 +#include "mozilla/Attributes.h" 1.51 +#include "ScrollbarActivity.h" 1.52 +#include "nsRefreshDriver.h" 1.53 +#include "nsThemeConstants.h" 1.54 +#include "nsSVGIntegrationUtils.h" 1.55 +#include "nsIScrollPositionListener.h" 1.56 +#include "StickyScrollContainer.h" 1.57 +#include "nsIFrameInlines.h" 1.58 +#include "gfxPrefs.h" 1.59 +#include <algorithm> 1.60 +#include <cstdlib> // for std::abs(int/long) 1.61 +#include <cmath> // for std::abs(float/double) 1.62 + 1.63 +using namespace mozilla; 1.64 +using namespace mozilla::dom; 1.65 +using namespace mozilla::layout; 1.66 + 1.67 +//---------------------------------------------------------------------- 1.68 + 1.69 +//----------nsHTMLScrollFrame------------------------------------------- 1.70 + 1.71 +nsIFrame* 1.72 +NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot) 1.73 +{ 1.74 + return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot); 1.75 +} 1.76 + 1.77 +NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame) 1.78 + 1.79 +nsHTMLScrollFrame::nsHTMLScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, bool aIsRoot) 1.80 + : nsContainerFrame(aContext), 1.81 + mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) 1.82 +{ 1.83 +} 1.84 + 1.85 +void 1.86 +nsHTMLScrollFrame::ScrollbarActivityStarted() const 1.87 +{ 1.88 + if (mHelper.mScrollbarActivity) { 1.89 + mHelper.mScrollbarActivity->ActivityStarted(); 1.90 + } 1.91 +} 1.92 + 1.93 +void 1.94 +nsHTMLScrollFrame::ScrollbarActivityStopped() const 1.95 +{ 1.96 + if (mHelper.mScrollbarActivity) { 1.97 + mHelper.mScrollbarActivity->ActivityStopped(); 1.98 + } 1.99 +} 1.100 + 1.101 +nsresult 1.102 +nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) 1.103 +{ 1.104 + return mHelper.CreateAnonymousContent(aElements); 1.105 +} 1.106 + 1.107 +void 1.108 +nsHTMLScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, 1.109 + uint32_t aFilter) 1.110 +{ 1.111 + mHelper.AppendAnonymousContentTo(aElements, aFilter); 1.112 +} 1.113 + 1.114 +void 1.115 +nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.116 +{ 1.117 + DestroyAbsoluteFrames(aDestructRoot); 1.118 + mHelper.Destroy(); 1.119 + nsContainerFrame::DestroyFrom(aDestructRoot); 1.120 +} 1.121 + 1.122 +nsresult 1.123 +nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID, 1.124 + nsFrameList& aChildList) 1.125 +{ 1.126 + nsresult rv = nsContainerFrame::SetInitialChildList(aListID, aChildList); 1.127 + mHelper.ReloadChildFrames(); 1.128 + return rv; 1.129 +} 1.130 + 1.131 + 1.132 +nsresult 1.133 +nsHTMLScrollFrame::AppendFrames(ChildListID aListID, 1.134 + nsFrameList& aFrameList) 1.135 +{ 1.136 + NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); 1.137 + mFrames.AppendFrames(nullptr, aFrameList); 1.138 + mHelper.ReloadChildFrames(); 1.139 + return NS_OK; 1.140 +} 1.141 + 1.142 +nsresult 1.143 +nsHTMLScrollFrame::InsertFrames(ChildListID aListID, 1.144 + nsIFrame* aPrevFrame, 1.145 + nsFrameList& aFrameList) 1.146 +{ 1.147 + NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); 1.148 + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 1.149 + "inserting after sibling frame with different parent"); 1.150 + mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); 1.151 + mHelper.ReloadChildFrames(); 1.152 + return NS_OK; 1.153 +} 1.154 + 1.155 +nsresult 1.156 +nsHTMLScrollFrame::RemoveFrame(ChildListID aListID, 1.157 + nsIFrame* aOldFrame) 1.158 +{ 1.159 + NS_ASSERTION(aListID == kPrincipalList, "Only main list supported"); 1.160 + mFrames.DestroyFrame(aOldFrame); 1.161 + mHelper.ReloadChildFrames(); 1.162 + return NS_OK; 1.163 +} 1.164 + 1.165 +nsSplittableType 1.166 +nsHTMLScrollFrame::GetSplittableType() const 1.167 +{ 1.168 + return NS_FRAME_NOT_SPLITTABLE; 1.169 +} 1.170 + 1.171 +nsIAtom* 1.172 +nsHTMLScrollFrame::GetType() const 1.173 +{ 1.174 + return nsGkAtoms::scrollFrame; 1.175 +} 1.176 + 1.177 +/** 1.178 + HTML scrolling implementation 1.179 + 1.180 + All other things being equal, we prefer layouts with fewer scrollbars showing. 1.181 +*/ 1.182 + 1.183 +struct MOZ_STACK_CLASS ScrollReflowState { 1.184 + const nsHTMLReflowState& mReflowState; 1.185 + nsBoxLayoutState mBoxState; 1.186 + ScrollbarStyles mStyles; 1.187 + nsMargin mComputedBorder; 1.188 + 1.189 + // === Filled in by ReflowScrolledFrame === 1.190 + nsOverflowAreas mContentsOverflowAreas; 1.191 + bool mReflowedContentsWithHScrollbar; 1.192 + bool mReflowedContentsWithVScrollbar; 1.193 + 1.194 + // === Filled in when TryLayout succeeds === 1.195 + // The size of the inside-border area 1.196 + nsSize mInsideBorderSize; 1.197 + // Whether we decided to show the horizontal scrollbar 1.198 + bool mShowHScrollbar; 1.199 + // Whether we decided to show the vertical scrollbar 1.200 + bool mShowVScrollbar; 1.201 + 1.202 + ScrollReflowState(nsIScrollableFrame* aFrame, 1.203 + const nsHTMLReflowState& aState) : 1.204 + mReflowState(aState), 1.205 + // mBoxState is just used for scrollbars so we don't need to 1.206 + // worry about the reflow depth here 1.207 + mBoxState(aState.frame->PresContext(), aState.rendContext, 0), 1.208 + mStyles(aFrame->GetScrollbarStyles()) { 1.209 + } 1.210 +}; 1.211 + 1.212 +// XXXldb Can this go away? 1.213 +static nsSize ComputeInsideBorderSize(ScrollReflowState* aState, 1.214 + const nsSize& aDesiredInsideBorderSize) 1.215 +{ 1.216 + // aDesiredInsideBorderSize is the frame size; i.e., it includes 1.217 + // borders and padding (but the scrolled child doesn't have 1.218 + // borders). The scrolled child has the same padding as us. 1.219 + nscoord contentWidth = aState->mReflowState.ComputedWidth(); 1.220 + if (contentWidth == NS_UNCONSTRAINEDSIZE) { 1.221 + contentWidth = aDesiredInsideBorderSize.width - 1.222 + aState->mReflowState.ComputedPhysicalPadding().LeftRight(); 1.223 + } 1.224 + nscoord contentHeight = aState->mReflowState.ComputedHeight(); 1.225 + if (contentHeight == NS_UNCONSTRAINEDSIZE) { 1.226 + contentHeight = aDesiredInsideBorderSize.height - 1.227 + aState->mReflowState.ComputedPhysicalPadding().TopBottom(); 1.228 + } 1.229 + 1.230 + contentWidth = aState->mReflowState.ApplyMinMaxWidth(contentWidth); 1.231 + contentHeight = aState->mReflowState.ApplyMinMaxHeight(contentHeight); 1.232 + return nsSize(contentWidth + aState->mReflowState.ComputedPhysicalPadding().LeftRight(), 1.233 + contentHeight + aState->mReflowState.ComputedPhysicalPadding().TopBottom()); 1.234 +} 1.235 + 1.236 +static void 1.237 +GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize* aMin, 1.238 + nsSize* aPref, bool aVertical) 1.239 +{ 1.240 + NS_ASSERTION(aState.GetRenderingContext(), 1.241 + "Must have rendering context in layout state for size " 1.242 + "computations"); 1.243 + 1.244 + if (aMin) { 1.245 + *aMin = aBox->GetMinSize(aState); 1.246 + nsBox::AddMargin(aBox, *aMin); 1.247 + if (aMin->width < 0) { 1.248 + aMin->width = 0; 1.249 + } 1.250 + if (aMin->height < 0) { 1.251 + aMin->height = 0; 1.252 + } 1.253 + } 1.254 + 1.255 + if (aPref) { 1.256 + *aPref = aBox->GetPrefSize(aState); 1.257 + nsBox::AddMargin(aBox, *aPref); 1.258 + if (aPref->width < 0) { 1.259 + aPref->width = 0; 1.260 + } 1.261 + if (aPref->height < 0) { 1.262 + aPref->height = 0; 1.263 + } 1.264 + } 1.265 +} 1.266 + 1.267 +/** 1.268 + * Assuming that we know the metrics for our wrapped frame and 1.269 + * whether the horizontal and/or vertical scrollbars are present, 1.270 + * compute the resulting layout and return true if the layout is 1.271 + * consistent. If the layout is consistent then we fill in the 1.272 + * computed fields of the ScrollReflowState. 1.273 + * 1.274 + * The layout is consistent when both scrollbars are showing if and only 1.275 + * if they should be showing. A horizontal scrollbar should be showing if all 1.276 + * following conditions are met: 1.277 + * 1) the style is not HIDDEN 1.278 + * 2) our inside-border height is at least the scrollbar height (i.e., the 1.279 + * scrollbar fits vertically) 1.280 + * 3) our scrollport width (the inside-border width minus the width allocated for a 1.281 + * vertical scrollbar, if showing) is at least the scrollbar's min-width 1.282 + * (i.e., the scrollbar fits horizontally) 1.283 + * 4) the style is SCROLL, or the kid's overflow-area XMost is 1.284 + * greater than the scrollport width 1.285 + * 1.286 + * @param aForce if true, then we just assume the layout is consistent. 1.287 + */ 1.288 +bool 1.289 +nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState, 1.290 + nsHTMLReflowMetrics* aKidMetrics, 1.291 + bool aAssumeHScroll, bool aAssumeVScroll, 1.292 + bool aForce, nsresult* aResult) 1.293 +{ 1.294 + *aResult = NS_OK; 1.295 + 1.296 + if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) || 1.297 + (aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) { 1.298 + NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!"); 1.299 + return false; 1.300 + } 1.301 + 1.302 + if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar || 1.303 + (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar && 1.304 + ScrolledContentDependsOnHeight(aState))) { 1.305 + nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, 1.306 + aKidMetrics, false); 1.307 + if (NS_FAILED(rv)) { 1.308 + *aResult = rv; 1.309 + return false; 1.310 + } 1.311 + } 1.312 + 1.313 + nsSize vScrollbarMinSize(0, 0); 1.314 + nsSize vScrollbarPrefSize(0, 0); 1.315 + if (mHelper.mVScrollbarBox) { 1.316 + GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, 1.317 + &vScrollbarMinSize, 1.318 + aAssumeVScroll ? &vScrollbarPrefSize : nullptr, true); 1.319 + } 1.320 + nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0; 1.321 + nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0; 1.322 + 1.323 + nsSize hScrollbarMinSize(0, 0); 1.324 + nsSize hScrollbarPrefSize(0, 0); 1.325 + if (mHelper.mHScrollbarBox) { 1.326 + GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, 1.327 + &hScrollbarMinSize, 1.328 + aAssumeHScroll ? &hScrollbarPrefSize : nullptr, false); 1.329 + } 1.330 + nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0; 1.331 + nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0; 1.332 + 1.333 + // First, compute our inside-border size and scrollport size 1.334 + // XXXldb Can we depend more on ComputeSize here? 1.335 + nsSize desiredInsideBorderSize; 1.336 + desiredInsideBorderSize.width = vScrollbarDesiredWidth + 1.337 + std::max(aKidMetrics->Width(), hScrollbarMinWidth); 1.338 + desiredInsideBorderSize.height = hScrollbarDesiredHeight + 1.339 + std::max(aKidMetrics->Height(), vScrollbarMinHeight); 1.340 + aState->mInsideBorderSize = 1.341 + ComputeInsideBorderSize(aState, desiredInsideBorderSize); 1.342 + nsSize scrollPortSize = nsSize(std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth), 1.343 + std::max(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight)); 1.344 + 1.345 + if (!aForce) { 1.346 + nsRect scrolledRect = 1.347 + mHelper.GetScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(), 1.348 + scrollPortSize); 1.349 + nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1); 1.350 + 1.351 + // If the style is HIDDEN then we already know that aAssumeHScroll is false 1.352 + if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) { 1.353 + bool wantHScrollbar = 1.354 + aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL || 1.355 + scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel || 1.356 + scrolledRect.x <= -oneDevPixel; 1.357 + if (scrollPortSize.width < hScrollbarMinSize.width) 1.358 + wantHScrollbar = false; 1.359 + if (wantHScrollbar != aAssumeHScroll) 1.360 + return false; 1.361 + } 1.362 + 1.363 + // If the style is HIDDEN then we already know that aAssumeVScroll is false 1.364 + if (aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) { 1.365 + bool wantVScrollbar = 1.366 + aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL || 1.367 + scrolledRect.YMost() >= scrollPortSize.height + oneDevPixel || 1.368 + scrolledRect.y <= -oneDevPixel; 1.369 + if (scrollPortSize.height < vScrollbarMinSize.height) 1.370 + wantVScrollbar = false; 1.371 + if (wantVScrollbar != aAssumeVScroll) 1.372 + return false; 1.373 + } 1.374 + } 1.375 + 1.376 + nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width; 1.377 + 1.378 + aState->mShowHScrollbar = aAssumeHScroll; 1.379 + aState->mShowVScrollbar = aAssumeVScroll; 1.380 + nsPoint scrollPortOrigin(aState->mComputedBorder.left, 1.381 + aState->mComputedBorder.top); 1.382 + if (!mHelper.IsScrollbarOnRight()) { 1.383 + scrollPortOrigin.x += vScrollbarActualWidth; 1.384 + } 1.385 + mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize); 1.386 + return true; 1.387 +} 1.388 + 1.389 +bool 1.390 +nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowState* aState) 1.391 +{ 1.392 + // Return true if ReflowScrolledFrame is going to do something different 1.393 + // based on the presence of a horizontal scrollbar. 1.394 + return (mHelper.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) || 1.395 + aState->mReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE || 1.396 + aState->mReflowState.ComputedMinHeight() > 0 || 1.397 + aState->mReflowState.ComputedMaxHeight() != NS_UNCONSTRAINEDSIZE; 1.398 +} 1.399 + 1.400 +nsresult 1.401 +nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState, 1.402 + bool aAssumeHScroll, 1.403 + bool aAssumeVScroll, 1.404 + nsHTMLReflowMetrics* aMetrics, 1.405 + bool aFirstPass) 1.406 +{ 1.407 + // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should 1.408 + // be OK 1.409 + const nsMargin& padding = aState->mReflowState.ComputedPhysicalPadding(); 1.410 + nscoord availWidth = aState->mReflowState.ComputedWidth() + padding.LeftRight(); 1.411 + 1.412 + nscoord computedHeight = aState->mReflowState.ComputedHeight(); 1.413 + nscoord computedMinHeight = aState->mReflowState.ComputedMinHeight(); 1.414 + nscoord computedMaxHeight = aState->mReflowState.ComputedMaxHeight(); 1.415 + if (!ShouldPropagateComputedHeightToScrolledContent()) { 1.416 + computedHeight = NS_UNCONSTRAINEDSIZE; 1.417 + computedMinHeight = 0; 1.418 + computedMaxHeight = NS_UNCONSTRAINEDSIZE; 1.419 + } 1.420 + if (aAssumeHScroll) { 1.421 + nsSize hScrollbarPrefSize; 1.422 + GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox, 1.423 + nullptr, &hScrollbarPrefSize, false); 1.424 + if (computedHeight != NS_UNCONSTRAINEDSIZE) { 1.425 + computedHeight = std::max(0, computedHeight - hScrollbarPrefSize.height); 1.426 + } 1.427 + computedMinHeight = std::max(0, computedMinHeight - hScrollbarPrefSize.height); 1.428 + if (computedMaxHeight != NS_UNCONSTRAINEDSIZE) { 1.429 + computedMaxHeight = std::max(0, computedMaxHeight - hScrollbarPrefSize.height); 1.430 + } 1.431 + } 1.432 + 1.433 + if (aAssumeVScroll) { 1.434 + nsSize vScrollbarPrefSize; 1.435 + GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox, 1.436 + nullptr, &vScrollbarPrefSize, true); 1.437 + availWidth = std::max(0, availWidth - vScrollbarPrefSize.width); 1.438 + } 1.439 + 1.440 + nsPresContext* presContext = PresContext(); 1.441 + 1.442 + // Pass false for aInit so we can pass in the correct padding. 1.443 + nsHTMLReflowState kidReflowState(presContext, aState->mReflowState, 1.444 + mHelper.mScrolledFrame, 1.445 + nsSize(availWidth, NS_UNCONSTRAINEDSIZE), 1.446 + -1, -1, nsHTMLReflowState::CALLER_WILL_INIT); 1.447 + kidReflowState.Init(presContext, -1, -1, nullptr, 1.448 + &padding); 1.449 + kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll; 1.450 + kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll; 1.451 + kidReflowState.SetComputedHeight(computedHeight); 1.452 + kidReflowState.ComputedMinHeight() = computedMinHeight; 1.453 + kidReflowState.ComputedMaxHeight() = computedMaxHeight; 1.454 + 1.455 + // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to 1.456 + // reflect our assumptions while we reflow the child. 1.457 + bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar; 1.458 + bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar; 1.459 + mHelper.mHasHorizontalScrollbar = aAssumeHScroll; 1.460 + mHelper.mHasVerticalScrollbar = aAssumeVScroll; 1.461 + 1.462 + nsReflowStatus status; 1.463 + nsresult rv = ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics, 1.464 + kidReflowState, 0, 0, 1.465 + NS_FRAME_NO_MOVE_FRAME, status); 1.466 + 1.467 + mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar; 1.468 + mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar; 1.469 + 1.470 + // Don't resize or position the view (if any) because we're going to resize 1.471 + // it to the correct size anyway in PlaceScrollArea. Allowing it to 1.472 + // resize here would size it to the natural height of the frame, 1.473 + // which will usually be different from the scrollport height; 1.474 + // invalidating the difference will cause unnecessary repainting. 1.475 + FinishReflowChild(mHelper.mScrolledFrame, presContext, 1.476 + *aMetrics, &kidReflowState, 0, 0, 1.477 + NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); 1.478 + 1.479 + // XXX Some frames (e.g., nsObjectFrame, nsFrameFrame, nsTextFrame) don't bother 1.480 + // setting their mOverflowArea. This is wrong because every frame should 1.481 + // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't 1.482 + // support the 'outline' property because of this. Rather than fix the world 1.483 + // right now, just fix up the overflow area if necessary. Note that we don't 1.484 + // check HasOverflowRect() because it could be set even though the 1.485 + // overflow area doesn't include the frame bounds. 1.486 + aMetrics->UnionOverflowAreasWithDesiredBounds(); 1.487 + 1.488 + if (MOZ_UNLIKELY(StyleDisplay()->mOverflowClipBox == 1.489 + NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) { 1.490 + nsOverflowAreas childOverflow; 1.491 + nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow); 1.492 + nsRect childScrollableOverflow = childOverflow.ScrollableOverflow(); 1.493 + childScrollableOverflow.Inflate(padding); 1.494 + nsRect contentArea = nsRect(0, 0, availWidth, computedHeight); 1.495 + if (!contentArea.Contains(childScrollableOverflow)) { 1.496 + aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow; 1.497 + } 1.498 + } 1.499 + 1.500 + aState->mContentsOverflowAreas = aMetrics->mOverflowAreas; 1.501 + aState->mReflowedContentsWithHScrollbar = aAssumeHScroll; 1.502 + aState->mReflowedContentsWithVScrollbar = aAssumeVScroll; 1.503 + 1.504 + return rv; 1.505 +} 1.506 + 1.507 +bool 1.508 +nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState) 1.509 +{ 1.510 + if (aState.mStyles.mHorizontal != NS_STYLE_OVERFLOW_AUTO) 1.511 + // no guessing required 1.512 + return aState.mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL; 1.513 + 1.514 + return mHelper.mHasHorizontalScrollbar; 1.515 +} 1.516 + 1.517 +bool 1.518 +nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState) 1.519 +{ 1.520 + if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO) 1.521 + // no guessing required 1.522 + return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL; 1.523 + 1.524 + // If we've had at least one non-initial reflow, then just assume 1.525 + // the state of the vertical scrollbar will be what we determined 1.526 + // last time. 1.527 + if (mHelper.mHadNonInitialReflow) { 1.528 + return mHelper.mHasVerticalScrollbar; 1.529 + } 1.530 + 1.531 + // If this is the initial reflow, guess false because usually 1.532 + // we have very little content by then. 1.533 + if (InInitialReflow()) 1.534 + return false; 1.535 + 1.536 + if (mHelper.mIsRoot) { 1.537 + nsIFrame *f = mHelper.mScrolledFrame->GetFirstPrincipalChild(); 1.538 + if (f && f->GetType() == nsGkAtoms::svgOuterSVGFrame && 1.539 + static_cast<nsSVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) { 1.540 + // Common SVG case - avoid a bad guess. 1.541 + return false; 1.542 + } 1.543 + // Assume that there will be a scrollbar; it seems to me 1.544 + // that 'most pages' do have a scrollbar, and anyway, it's cheaper 1.545 + // to do an extra reflow for the pages that *don't* need a 1.546 + // scrollbar (because on average they will have less content). 1.547 + return true; 1.548 + } 1.549 + 1.550 + // For non-viewports, just guess that we don't need a scrollbar. 1.551 + // XXX I wonder if statistically this is the right idea; I'm 1.552 + // basically guessing that there are a lot of overflow:auto DIVs 1.553 + // that get their intrinsic size and don't overflow 1.554 + return false; 1.555 +} 1.556 + 1.557 +bool 1.558 +nsHTMLScrollFrame::InInitialReflow() const 1.559 +{ 1.560 + // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a 1.561 + // root scrollframe. In that case we want to skip this clause altogether. 1.562 + // The guess here is that there are lots of overflow:auto divs out there that 1.563 + // end up auto-sizing so they don't overflow, and that the root basically 1.564 + // always needs a scrollbar if it did last time we loaded this page (good 1.565 + // assumption, because our initial reflow is no longer synchronous). 1.566 + return !mHelper.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW); 1.567 +} 1.568 + 1.569 +nsresult 1.570 +nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState, 1.571 + const nsHTMLReflowMetrics& aDesiredSize) 1.572 +{ 1.573 + nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.GetWritingMode(), aDesiredSize.mFlags); 1.574 + nsresult rv = ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState), 1.575 + GuessVScrollbarNeeded(*aState), &kidDesiredSize, true); 1.576 + NS_ENSURE_SUCCESS(rv, rv); 1.577 + 1.578 + // There's an important special case ... if the child appears to fit 1.579 + // in the inside-border rect (but overflows the scrollport), we 1.580 + // should try laying it out without a vertical scrollbar. It will 1.581 + // usually fit because making the available-width wider will not 1.582 + // normally make the child taller. (The only situation I can think 1.583 + // of is when you have a line containing %-width inline replaced 1.584 + // elements whose percentages sum to more than 100%, so increasing 1.585 + // the available width makes the line break where it was fitting 1.586 + // before.) If we don't treat this case specially, then we will 1.587 + // decide that showing scrollbars is OK because the content 1.588 + // overflows when we're showing scrollbars and we won't try to 1.589 + // remove the vertical scrollbar. 1.590 + 1.591 + // Detecting when we enter this special case is important for when 1.592 + // people design layouts that exactly fit the container "most of the 1.593 + // time". 1.594 + 1.595 + // XXX Is this check really sufficient to catch all the incremental cases 1.596 + // where the ideal case doesn't have a scrollbar? 1.597 + if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) && 1.598 + aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL && 1.599 + aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) { 1.600 + nsSize insideBorderSize = 1.601 + ComputeInsideBorderSize(aState, 1.602 + nsSize(kidDesiredSize.Width(), kidDesiredSize.Height())); 1.603 + nsRect scrolledRect = 1.604 + mHelper.GetScrolledRectInternal(kidDesiredSize.ScrollableOverflow(), 1.605 + insideBorderSize); 1.606 + if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) { 1.607 + // Let's pretend we had no scrollbars coming in here 1.608 + rv = ReflowScrolledFrame(aState, false, false, 1.609 + &kidDesiredSize, false); 1.610 + NS_ENSURE_SUCCESS(rv, rv); 1.611 + } 1.612 + } 1.613 + 1.614 + // Try vertical scrollbar settings that leave the vertical scrollbar unchanged. 1.615 + // Do this first because changing the vertical scrollbar setting is expensive, 1.616 + // forcing a reflow always. 1.617 + 1.618 + // Try leaving the horizontal scrollbar unchanged first. This will be more 1.619 + // efficient. 1.620 + if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar, 1.621 + aState->mReflowedContentsWithVScrollbar, false, &rv)) 1.622 + return NS_OK; 1.623 + if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar, 1.624 + aState->mReflowedContentsWithVScrollbar, false, &rv)) 1.625 + return NS_OK; 1.626 + 1.627 + // OK, now try toggling the vertical scrollbar. The performance advantage 1.628 + // of trying the status-quo horizontal scrollbar state 1.629 + // does not exist here (we'll have to reflow due to the vertical scrollbar 1.630 + // change), so always try no horizontal scrollbar first. 1.631 + bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar; 1.632 + if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false, &rv)) 1.633 + return NS_OK; 1.634 + if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false, &rv)) 1.635 + return NS_OK; 1.636 + 1.637 + // OK, we're out of ideas. Try again enabling whatever scrollbars we can 1.638 + // enable and force the layout to stick even if it's inconsistent. 1.639 + // This just happens sometimes. 1.640 + TryLayout(aState, &kidDesiredSize, 1.641 + aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN, 1.642 + aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN, 1.643 + true, &rv); 1.644 + return rv; 1.645 +} 1.646 + 1.647 +void 1.648 +nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState, 1.649 + const nsPoint& aScrollPosition) 1.650 +{ 1.651 + nsIFrame *scrolledFrame = mHelper.mScrolledFrame; 1.652 + // Set the x,y of the scrolled frame to the correct value 1.653 + scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition); 1.654 + 1.655 + nsRect scrolledArea; 1.656 + // Preserve the width or height of empty rects 1.657 + nsSize portSize = mHelper.mScrollPort.Size(); 1.658 + nsRect scrolledRect = 1.659 + mHelper.GetScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(), 1.660 + portSize); 1.661 + scrolledArea.UnionRectEdges(scrolledRect, 1.662 + nsRect(nsPoint(0,0), portSize)); 1.663 + 1.664 + // Store the new overflow area. Note that this changes where an outline 1.665 + // of the scrolled frame would be painted, but scrolled frames can't have 1.666 + // outlines (the outline would go on this scrollframe instead). 1.667 + // Using FinishAndStoreOverflow is needed so the overflow rect 1.668 + // gets set correctly. It also messes with the overflow rect in the 1.669 + // -moz-hidden-unscrollable case, but scrolled frames can't have 1.670 + // 'overflow' either. 1.671 + // This needs to happen before SyncFrameViewAfterReflow so 1.672 + // HasOverflowRect() will return the correct value. 1.673 + nsOverflowAreas overflow(scrolledArea, scrolledArea); 1.674 + scrolledFrame->FinishAndStoreOverflow(overflow, 1.675 + scrolledFrame->GetSize()); 1.676 + 1.677 + // Note that making the view *exactly* the size of the scrolled area 1.678 + // is critical, since the view scrolling code uses the size of the 1.679 + // scrolled view to clamp scroll requests. 1.680 + // Normally the scrolledFrame won't have a view but in some cases it 1.681 + // might create its own. 1.682 + nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(), 1.683 + scrolledFrame, 1.684 + scrolledFrame->GetView(), 1.685 + scrolledArea, 1.686 + 0); 1.687 +} 1.688 + 1.689 +nscoord 1.690 +nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext) 1.691 +{ 1.692 + ScrollbarStyles ss = GetScrollbarStyles(); 1.693 + if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mHelper.mVScrollbarBox) 1.694 + return 0; 1.695 + 1.696 + // Don't need to worry about reflow depth here since it's 1.697 + // just for scrollbars 1.698 + nsBoxLayoutState bls(PresContext(), aRenderingContext, 0); 1.699 + nsSize vScrollbarPrefSize(0, 0); 1.700 + GetScrollbarMetrics(bls, mHelper.mVScrollbarBox, 1.701 + nullptr, &vScrollbarPrefSize, true); 1.702 + return vScrollbarPrefSize.width; 1.703 +} 1.704 + 1.705 +/* virtual */ nscoord 1.706 +nsHTMLScrollFrame::GetMinWidth(nsRenderingContext *aRenderingContext) 1.707 +{ 1.708 + nscoord result = mHelper.mScrolledFrame->GetMinWidth(aRenderingContext); 1.709 + DISPLAY_MIN_WIDTH(this, result); 1.710 + return result + GetIntrinsicVScrollbarWidth(aRenderingContext); 1.711 +} 1.712 + 1.713 +/* virtual */ nscoord 1.714 +nsHTMLScrollFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) 1.715 +{ 1.716 + nscoord result = mHelper.mScrolledFrame->GetPrefWidth(aRenderingContext); 1.717 + DISPLAY_PREF_WIDTH(this, result); 1.718 + return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext)); 1.719 +} 1.720 + 1.721 +nsresult 1.722 +nsHTMLScrollFrame::GetPadding(nsMargin& aMargin) 1.723 +{ 1.724 + // Our padding hangs out on the inside of the scrollframe, but XUL doesn't 1.725 + // reaize that. If we're stuck inside a XUL box, we need to claim no 1.726 + // padding. 1.727 + // @see also nsXULScrollFrame::GetPadding. 1.728 + aMargin.SizeTo(0,0,0,0); 1.729 + return NS_OK; 1.730 +} 1.731 + 1.732 +bool 1.733 +nsHTMLScrollFrame::IsCollapsed() 1.734 +{ 1.735 + // We're never collapsed in the box sense. 1.736 + return false; 1.737 +} 1.738 + 1.739 +// Return the <browser> if the scrollframe is for the root frame directly 1.740 +// inside a <browser>. 1.741 +static nsIContent* 1.742 +GetBrowserRoot(nsIContent* aContent) 1.743 +{ 1.744 + if (aContent) { 1.745 + nsIDocument* doc = aContent->GetCurrentDoc(); 1.746 + nsPIDOMWindow* win = doc->GetWindow(); 1.747 + if (win) { 1.748 + nsCOMPtr<nsIContent> frameContent = 1.749 + do_QueryInterface(win->GetFrameElementInternal()); 1.750 + if (frameContent && 1.751 + frameContent->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL)) 1.752 + return frameContent; 1.753 + } 1.754 + } 1.755 + 1.756 + return nullptr; 1.757 +} 1.758 + 1.759 +nsresult 1.760 +nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext, 1.761 + nsHTMLReflowMetrics& aDesiredSize, 1.762 + const nsHTMLReflowState& aReflowState, 1.763 + nsReflowStatus& aStatus) 1.764 +{ 1.765 + DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame"); 1.766 + DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); 1.767 + 1.768 + mHelper.HandleScrollbarStyleSwitching(); 1.769 + 1.770 + ScrollReflowState state(this, aReflowState); 1.771 + // sanity check: ensure that if we have no scrollbar, we treat it 1.772 + // as hidden. 1.773 + if (!mHelper.mVScrollbarBox || mHelper.mNeverHasVerticalScrollbar) 1.774 + state.mStyles.mVertical = NS_STYLE_OVERFLOW_HIDDEN; 1.775 + if (!mHelper.mHScrollbarBox || mHelper.mNeverHasHorizontalScrollbar) 1.776 + state.mStyles.mHorizontal = NS_STYLE_OVERFLOW_HIDDEN; 1.777 + 1.778 + //------------ Handle Incremental Reflow ----------------- 1.779 + bool reflowHScrollbar = true; 1.780 + bool reflowVScrollbar = true; 1.781 + bool reflowScrollCorner = true; 1.782 + if (!aReflowState.ShouldReflowAllKids()) { 1.783 + #define NEEDS_REFLOW(frame_) \ 1.784 + ((frame_) && NS_SUBTREE_DIRTY(frame_)) 1.785 + 1.786 + reflowHScrollbar = NEEDS_REFLOW(mHelper.mHScrollbarBox); 1.787 + reflowVScrollbar = NEEDS_REFLOW(mHelper.mVScrollbarBox); 1.788 + reflowScrollCorner = NEEDS_REFLOW(mHelper.mScrollCornerBox) || 1.789 + NEEDS_REFLOW(mHelper.mResizerBox); 1.790 + 1.791 + #undef NEEDS_REFLOW 1.792 + } 1.793 + 1.794 + if (mHelper.mIsRoot) { 1.795 + mHelper.mCollapsedResizer = true; 1.796 + 1.797 + nsIContent* browserRoot = GetBrowserRoot(mContent); 1.798 + if (browserRoot) { 1.799 + bool showResizer = browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer); 1.800 + reflowScrollCorner = showResizer == mHelper.mCollapsedResizer; 1.801 + mHelper.mCollapsedResizer = !showResizer; 1.802 + } 1.803 + } 1.804 + 1.805 + nsRect oldScrollAreaBounds = mHelper.mScrollPort; 1.806 + nsRect oldScrolledAreaBounds = 1.807 + mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent(); 1.808 + nsPoint oldScrollPosition = mHelper.GetScrollPosition(); 1.809 + 1.810 + state.mComputedBorder = aReflowState.ComputedPhysicalBorderPadding() - 1.811 + aReflowState.ComputedPhysicalPadding(); 1.812 + 1.813 + nsresult rv = ReflowContents(&state, aDesiredSize); 1.814 + if (NS_FAILED(rv)) 1.815 + return rv; 1.816 + 1.817 + // Restore the old scroll position, for now, even if that's not valid anymore 1.818 + // because we changed size. We'll fix it up in a post-reflow callback, because 1.819 + // our current size may only be temporary (e.g. we're compute XUL desired sizes). 1.820 + PlaceScrollArea(state, oldScrollPosition); 1.821 + if (!mHelper.mPostedReflowCallback) { 1.822 + // Make sure we'll try scrolling to restored position 1.823 + PresContext()->PresShell()->PostReflowCallback(&mHelper); 1.824 + mHelper.mPostedReflowCallback = true; 1.825 + } 1.826 + 1.827 + bool didHaveHScrollbar = mHelper.mHasHorizontalScrollbar; 1.828 + bool didHaveVScrollbar = mHelper.mHasVerticalScrollbar; 1.829 + mHelper.mHasHorizontalScrollbar = state.mShowHScrollbar; 1.830 + mHelper.mHasVerticalScrollbar = state.mShowVScrollbar; 1.831 + nsRect newScrollAreaBounds = mHelper.mScrollPort; 1.832 + nsRect newScrolledAreaBounds = 1.833 + mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent(); 1.834 + if (mHelper.mSkippedScrollbarLayout || 1.835 + reflowHScrollbar || reflowVScrollbar || reflowScrollCorner || 1.836 + (GetStateBits() & NS_FRAME_IS_DIRTY) || 1.837 + didHaveHScrollbar != state.mShowHScrollbar || 1.838 + didHaveVScrollbar != state.mShowVScrollbar || 1.839 + !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) || 1.840 + !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) { 1.841 + if (!mHelper.mSupppressScrollbarUpdate) { 1.842 + mHelper.mSkippedScrollbarLayout = false; 1.843 + mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, state.mShowHScrollbar); 1.844 + mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, state.mShowVScrollbar); 1.845 + // place and reflow scrollbars 1.846 + nsRect insideBorderArea = 1.847 + nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top), 1.848 + state.mInsideBorderSize); 1.849 + mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea, 1.850 + oldScrollAreaBounds); 1.851 + } else { 1.852 + mHelper.mSkippedScrollbarLayout = true; 1.853 + } 1.854 + } 1.855 + 1.856 + aDesiredSize.Width() = state.mInsideBorderSize.width + 1.857 + state.mComputedBorder.LeftRight(); 1.858 + aDesiredSize.Height() = state.mInsideBorderSize.height + 1.859 + state.mComputedBorder.TopBottom(); 1.860 + 1.861 + aDesiredSize.SetOverflowAreasToDesiredBounds(); 1.862 + if (mHelper.IsIgnoringViewportClipping()) { 1.863 + aDesiredSize.mOverflowAreas.UnionWith( 1.864 + state.mContentsOverflowAreas + mHelper.mScrolledFrame->GetPosition()); 1.865 + } 1.866 + 1.867 + mHelper.UpdateSticky(); 1.868 + FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus); 1.869 + 1.870 + if (!InInitialReflow() && !mHelper.mHadNonInitialReflow) { 1.871 + mHelper.mHadNonInitialReflow = true; 1.872 + } 1.873 + 1.874 + if (mHelper.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) { 1.875 + mHelper.PostScrolledAreaEvent(); 1.876 + } 1.877 + 1.878 + aStatus = NS_FRAME_COMPLETE; 1.879 + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); 1.880 + mHelper.PostOverflowEvent(); 1.881 + return rv; 1.882 +} 1.883 + 1.884 + 1.885 +//////////////////////////////////////////////////////////////////////////////// 1.886 + 1.887 +#ifdef DEBUG_FRAME_DUMP 1.888 +nsresult 1.889 +nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const 1.890 +{ 1.891 + return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult); 1.892 +} 1.893 +#endif 1.894 + 1.895 +#ifdef ACCESSIBILITY 1.896 +a11y::AccType 1.897 +nsHTMLScrollFrame::AccessibleType() 1.898 +{ 1.899 + // Create an accessible regardless of focusable state because the state can be 1.900 + // changed during frame life cycle without any notifications to accessibility. 1.901 + if (mContent->IsRootOfNativeAnonymousSubtree() || 1.902 + GetScrollbarStyles() == 1.903 + ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN) ) { 1.904 + return a11y::eNoType; 1.905 + } 1.906 + 1.907 + return a11y::eHyperTextType; 1.908 +} 1.909 +#endif 1.910 + 1.911 +NS_QUERYFRAME_HEAD(nsHTMLScrollFrame) 1.912 + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 1.913 + NS_QUERYFRAME_ENTRY(nsIScrollbarOwner) 1.914 + NS_QUERYFRAME_ENTRY(nsIScrollableFrame) 1.915 + NS_QUERYFRAME_ENTRY(nsIStatefulFrame) 1.916 +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 1.917 + 1.918 +//----------nsXULScrollFrame------------------------------------------- 1.919 + 1.920 +nsIFrame* 1.921 +NS_NewXULScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, 1.922 + bool aIsRoot, bool aClipAllDescendants) 1.923 +{ 1.924 + return new (aPresShell) nsXULScrollFrame(aPresShell, aContext, aIsRoot, 1.925 + aClipAllDescendants); 1.926 +} 1.927 + 1.928 +NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame) 1.929 + 1.930 +nsXULScrollFrame::nsXULScrollFrame(nsIPresShell* aShell, nsStyleContext* aContext, 1.931 + bool aIsRoot, bool aClipAllDescendants) 1.932 + : nsBoxFrame(aShell, aContext, aIsRoot), 1.933 + mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot) 1.934 +{ 1.935 + SetLayoutManager(nullptr); 1.936 + mHelper.mClipAllDescendants = aClipAllDescendants; 1.937 +} 1.938 + 1.939 +void 1.940 +nsXULScrollFrame::ScrollbarActivityStarted() const 1.941 +{ 1.942 + if (mHelper.mScrollbarActivity) { 1.943 + mHelper.mScrollbarActivity->ActivityStarted(); 1.944 + } 1.945 +} 1.946 + 1.947 +void 1.948 +nsXULScrollFrame::ScrollbarActivityStopped() const 1.949 +{ 1.950 + if (mHelper.mScrollbarActivity) { 1.951 + mHelper.mScrollbarActivity->ActivityStopped(); 1.952 + } 1.953 +} 1.954 + 1.955 +nsMargin 1.956 +ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) 1.957 +{ 1.958 + NS_ASSERTION(aState && aState->GetRenderingContext(), 1.959 + "Must have rendering context in layout state for size " 1.960 + "computations"); 1.961 + 1.962 + nsMargin result(0, 0, 0, 0); 1.963 + 1.964 + if (mVScrollbarBox) { 1.965 + nsSize size = mVScrollbarBox->GetPrefSize(*aState); 1.966 + nsBox::AddMargin(mVScrollbarBox, size); 1.967 + if (IsScrollbarOnRight()) 1.968 + result.left = size.width; 1.969 + else 1.970 + result.right = size.width; 1.971 + } 1.972 + 1.973 + if (mHScrollbarBox) { 1.974 + nsSize size = mHScrollbarBox->GetPrefSize(*aState); 1.975 + nsBox::AddMargin(mHScrollbarBox, size); 1.976 + // We don't currently support any scripts that would require a scrollbar 1.977 + // at the top. (Are there any?) 1.978 + result.bottom = size.height; 1.979 + } 1.980 + 1.981 + return result; 1.982 +} 1.983 + 1.984 +nscoord 1.985 +ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState) 1.986 +{ 1.987 + NS_ASSERTION(aState && aState->GetRenderingContext(), 1.988 + "Must have rendering context in layout state for size " 1.989 + "computations"); 1.990 + 1.991 + if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { 1.992 + // We're using overlay scrollbars, so we need to get the width that 1.993 + // non-disappearing scrollbars would have. 1.994 + nsITheme* theme = aState->PresContext()->GetTheme(); 1.995 + if (theme && 1.996 + theme->ThemeSupportsWidget(aState->PresContext(), 1.997 + mVScrollbarBox, 1.998 + NS_THEME_SCROLLBAR_NON_DISAPPEARING)) { 1.999 + nsIntSize size; 1.1000 + nsRenderingContext* rendContext = aState->GetRenderingContext(); 1.1001 + if (rendContext) { 1.1002 + bool canOverride = true; 1.1003 + theme->GetMinimumWidgetSize(rendContext, 1.1004 + mVScrollbarBox, 1.1005 + NS_THEME_SCROLLBAR_NON_DISAPPEARING, 1.1006 + &size, 1.1007 + &canOverride); 1.1008 + if (size.width) { 1.1009 + return aState->PresContext()->DevPixelsToAppUnits(size.width); 1.1010 + } 1.1011 + } 1.1012 + } 1.1013 + } 1.1014 + 1.1015 + return GetDesiredScrollbarSizes(aState).LeftRight(); 1.1016 +} 1.1017 + 1.1018 +void 1.1019 +ScrollFrameHelper::HandleScrollbarStyleSwitching() 1.1020 +{ 1.1021 + // Check if we switched between scrollbar styles. 1.1022 + if (mScrollbarActivity && 1.1023 + LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) { 1.1024 + mScrollbarActivity->Destroy(); 1.1025 + mScrollbarActivity = nullptr; 1.1026 + mOuter->PresContext()->ThemeChanged(); 1.1027 + } 1.1028 + else if (!mScrollbarActivity && 1.1029 + LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { 1.1030 + mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(mOuter)); 1.1031 + mOuter->PresContext()->ThemeChanged(); 1.1032 + } 1.1033 +} 1.1034 + 1.1035 +static bool IsFocused(nsIContent* aContent) 1.1036 +{ 1.1037 + // Some content elements, like the GetContent() of a scroll frame 1.1038 + // for a text input field, are inside anonymous subtrees, but the focus 1.1039 + // manager always reports a non-anonymous element as the focused one, so 1.1040 + // walk up the tree until we reach a non-anonymous element. 1.1041 + while (aContent && aContent->IsInAnonymousSubtree()) { 1.1042 + aContent = aContent->GetParent(); 1.1043 + } 1.1044 + 1.1045 + return aContent ? nsContentUtils::IsFocusedContent(aContent) : false; 1.1046 +} 1.1047 + 1.1048 +bool 1.1049 +ScrollFrameHelper::WantAsyncScroll() const 1.1050 +{ 1.1051 + nsRect scrollRange = GetScrollRange(); 1.1052 + ScrollbarStyles styles = GetScrollbarStylesFromFrame(); 1.1053 + bool isFocused = IsFocused(mOuter->GetContent()); 1.1054 + bool isVScrollable = (scrollRange.height > 0) 1.1055 + && (styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN); 1.1056 + bool isHScrollable = (scrollRange.width > 0) 1.1057 + && (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN); 1.1058 + // The check for scroll bars was added in bug 825692 to prevent layerization 1.1059 + // of text inputs for performance reasons. However, if a text input is 1.1060 + // focused we want to layerize it so we can async scroll it (bug 946408). 1.1061 + bool isVAsyncScrollable = isVScrollable && (mVScrollbarBox || isFocused); 1.1062 + bool isHAsyncScrollable = isHScrollable && (mHScrollbarBox || isFocused); 1.1063 + return isVAsyncScrollable || isHAsyncScrollable; 1.1064 +} 1.1065 + 1.1066 +nsresult 1.1067 +nsXULScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) 1.1068 +{ 1.1069 + return mHelper.CreateAnonymousContent(aElements); 1.1070 +} 1.1071 + 1.1072 +void 1.1073 +nsXULScrollFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, 1.1074 + uint32_t aFilter) 1.1075 +{ 1.1076 + mHelper.AppendAnonymousContentTo(aElements, aFilter); 1.1077 +} 1.1078 + 1.1079 +void 1.1080 +nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.1081 +{ 1.1082 + mHelper.Destroy(); 1.1083 + nsBoxFrame::DestroyFrom(aDestructRoot); 1.1084 +} 1.1085 + 1.1086 +nsresult 1.1087 +nsXULScrollFrame::SetInitialChildList(ChildListID aListID, 1.1088 + nsFrameList& aChildList) 1.1089 +{ 1.1090 + nsresult rv = nsBoxFrame::SetInitialChildList(aListID, aChildList); 1.1091 + mHelper.ReloadChildFrames(); 1.1092 + return rv; 1.1093 +} 1.1094 + 1.1095 + 1.1096 +nsresult 1.1097 +nsXULScrollFrame::AppendFrames(ChildListID aListID, 1.1098 + nsFrameList& aFrameList) 1.1099 +{ 1.1100 + nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList); 1.1101 + mHelper.ReloadChildFrames(); 1.1102 + return rv; 1.1103 +} 1.1104 + 1.1105 +nsresult 1.1106 +nsXULScrollFrame::InsertFrames(ChildListID aListID, 1.1107 + nsIFrame* aPrevFrame, 1.1108 + nsFrameList& aFrameList) 1.1109 +{ 1.1110 + nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); 1.1111 + mHelper.ReloadChildFrames(); 1.1112 + return rv; 1.1113 +} 1.1114 + 1.1115 +nsresult 1.1116 +nsXULScrollFrame::RemoveFrame(ChildListID aListID, 1.1117 + nsIFrame* aOldFrame) 1.1118 +{ 1.1119 + nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame); 1.1120 + mHelper.ReloadChildFrames(); 1.1121 + return rv; 1.1122 +} 1.1123 + 1.1124 +nsSplittableType 1.1125 +nsXULScrollFrame::GetSplittableType() const 1.1126 +{ 1.1127 + return NS_FRAME_NOT_SPLITTABLE; 1.1128 +} 1.1129 + 1.1130 +nsresult 1.1131 +nsXULScrollFrame::GetPadding(nsMargin& aMargin) 1.1132 +{ 1.1133 + aMargin.SizeTo(0,0,0,0); 1.1134 + return NS_OK; 1.1135 +} 1.1136 + 1.1137 +nsIAtom* 1.1138 +nsXULScrollFrame::GetType() const 1.1139 +{ 1.1140 + return nsGkAtoms::scrollFrame; 1.1141 +} 1.1142 + 1.1143 +nscoord 1.1144 +nsXULScrollFrame::GetBoxAscent(nsBoxLayoutState& aState) 1.1145 +{ 1.1146 + if (!mHelper.mScrolledFrame) 1.1147 + return 0; 1.1148 + 1.1149 + nscoord ascent = mHelper.mScrolledFrame->GetBoxAscent(aState); 1.1150 + nsMargin m(0,0,0,0); 1.1151 + GetBorderAndPadding(m); 1.1152 + ascent += m.top; 1.1153 + GetMargin(m); 1.1154 + ascent += m.top; 1.1155 + 1.1156 + return ascent; 1.1157 +} 1.1158 + 1.1159 +nsSize 1.1160 +nsXULScrollFrame::GetPrefSize(nsBoxLayoutState& aState) 1.1161 +{ 1.1162 +#ifdef DEBUG_LAYOUT 1.1163 + PropagateDebug(aState); 1.1164 +#endif 1.1165 + 1.1166 + nsSize pref = mHelper.mScrolledFrame->GetPrefSize(aState); 1.1167 + 1.1168 + ScrollbarStyles styles = GetScrollbarStyles(); 1.1169 + 1.1170 + // scrolled frames don't have their own margins 1.1171 + 1.1172 + if (mHelper.mVScrollbarBox && 1.1173 + styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { 1.1174 + nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState); 1.1175 + nsBox::AddMargin(mHelper.mVScrollbarBox, vSize); 1.1176 + pref.width += vSize.width; 1.1177 + } 1.1178 + 1.1179 + if (mHelper.mHScrollbarBox && 1.1180 + styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) { 1.1181 + nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState); 1.1182 + nsBox::AddMargin(mHelper.mHScrollbarBox, hSize); 1.1183 + pref.height += hSize.height; 1.1184 + } 1.1185 + 1.1186 + AddBorderAndPadding(pref); 1.1187 + bool widthSet, heightSet; 1.1188 + nsIFrame::AddCSSPrefSize(this, pref, widthSet, heightSet); 1.1189 + return pref; 1.1190 +} 1.1191 + 1.1192 +nsSize 1.1193 +nsXULScrollFrame::GetMinSize(nsBoxLayoutState& aState) 1.1194 +{ 1.1195 +#ifdef DEBUG_LAYOUT 1.1196 + PropagateDebug(aState); 1.1197 +#endif 1.1198 + 1.1199 + nsSize min = mHelper.mScrolledFrame->GetMinSizeForScrollArea(aState); 1.1200 + 1.1201 + ScrollbarStyles styles = GetScrollbarStyles(); 1.1202 + 1.1203 + if (mHelper.mVScrollbarBox && 1.1204 + styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { 1.1205 + nsSize vSize = mHelper.mVScrollbarBox->GetMinSize(aState); 1.1206 + AddMargin(mHelper.mVScrollbarBox, vSize); 1.1207 + min.width += vSize.width; 1.1208 + if (min.height < vSize.height) 1.1209 + min.height = vSize.height; 1.1210 + } 1.1211 + 1.1212 + if (mHelper.mHScrollbarBox && 1.1213 + styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) { 1.1214 + nsSize hSize = mHelper.mHScrollbarBox->GetMinSize(aState); 1.1215 + AddMargin(mHelper.mHScrollbarBox, hSize); 1.1216 + min.height += hSize.height; 1.1217 + if (min.width < hSize.width) 1.1218 + min.width = hSize.width; 1.1219 + } 1.1220 + 1.1221 + AddBorderAndPadding(min); 1.1222 + bool widthSet, heightSet; 1.1223 + nsIFrame::AddCSSMinSize(aState, this, min, widthSet, heightSet); 1.1224 + return min; 1.1225 +} 1.1226 + 1.1227 +nsSize 1.1228 +nsXULScrollFrame::GetMaxSize(nsBoxLayoutState& aState) 1.1229 +{ 1.1230 +#ifdef DEBUG_LAYOUT 1.1231 + PropagateDebug(aState); 1.1232 +#endif 1.1233 + 1.1234 + nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE); 1.1235 + 1.1236 + AddBorderAndPadding(maxSize); 1.1237 + bool widthSet, heightSet; 1.1238 + nsIFrame::AddCSSMaxSize(this, maxSize, widthSet, heightSet); 1.1239 + return maxSize; 1.1240 +} 1.1241 + 1.1242 +#ifdef DEBUG_FRAME_DUMP 1.1243 +nsresult 1.1244 +nsXULScrollFrame::GetFrameName(nsAString& aResult) const 1.1245 +{ 1.1246 + return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult); 1.1247 +} 1.1248 +#endif 1.1249 + 1.1250 +NS_IMETHODIMP 1.1251 +nsXULScrollFrame::DoLayout(nsBoxLayoutState& aState) 1.1252 +{ 1.1253 + uint32_t flags = aState.LayoutFlags(); 1.1254 + nsresult rv = Layout(aState); 1.1255 + aState.SetLayoutFlags(flags); 1.1256 + 1.1257 + nsBox::DoLayout(aState); 1.1258 + return rv; 1.1259 +} 1.1260 + 1.1261 +NS_QUERYFRAME_HEAD(nsXULScrollFrame) 1.1262 + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 1.1263 + NS_QUERYFRAME_ENTRY(nsIScrollbarOwner) 1.1264 + NS_QUERYFRAME_ENTRY(nsIScrollableFrame) 1.1265 + NS_QUERYFRAME_ENTRY(nsIStatefulFrame) 1.1266 +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) 1.1267 + 1.1268 +//-------------------- Helper ---------------------- 1.1269 + 1.1270 +#define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll" 1.1271 + 1.1272 +const double kCurrentVelocityWeighting = 0.25; 1.1273 +const double kStopDecelerationWeighting = 0.4; 1.1274 + 1.1275 +// AsyncScroll has ref counting. 1.1276 +class ScrollFrameHelper::AsyncScroll MOZ_FINAL : public nsARefreshObserver { 1.1277 +public: 1.1278 + typedef mozilla::TimeStamp TimeStamp; 1.1279 + typedef mozilla::TimeDuration TimeDuration; 1.1280 + 1.1281 + AsyncScroll(nsPoint aStartPos) 1.1282 + : mIsFirstIteration(true) 1.1283 + , mStartPos(aStartPos) 1.1284 + , mCallee(nullptr) 1.1285 + {} 1.1286 + 1.1287 +private: 1.1288 + // Private destructor, to discourage deletion outside of Release(): 1.1289 + ~AsyncScroll() { 1.1290 + RemoveObserver(); 1.1291 + } 1.1292 + 1.1293 +public: 1.1294 + nsPoint PositionAt(TimeStamp aTime); 1.1295 + nsSize VelocityAt(TimeStamp aTime); // In nscoords per second 1.1296 + 1.1297 + void InitSmoothScroll(TimeStamp aTime, nsPoint aDestination, 1.1298 + nsIAtom *aOrigin, const nsRect& aRange); 1.1299 + void Init(const nsRect& aRange) { 1.1300 + mRange = aRange; 1.1301 + } 1.1302 + 1.1303 + bool IsFinished(TimeStamp aTime) { 1.1304 + return aTime > mStartTime + mDuration; // XXX or if we've hit the wall 1.1305 + } 1.1306 + 1.1307 + TimeStamp mStartTime; 1.1308 + 1.1309 + // mPrevEventTime holds previous 3 timestamps for intervals averaging (to 1.1310 + // reduce duration fluctuations). When AsyncScroll is constructed and no 1.1311 + // previous timestamps are available (indicated with mIsFirstIteration), 1.1312 + // initialize mPrevEventTime using imaginary previous timestamps with maximum 1.1313 + // relevant intervals between them. 1.1314 + TimeStamp mPrevEventTime[3]; 1.1315 + bool mIsFirstIteration; 1.1316 + 1.1317 + // Cached Preferences values to avoid re-reading them when extending an existing 1.1318 + // animation for the same event origin (can be as frequent as every 10(!)ms for 1.1319 + // a quick roll of the mouse wheel). 1.1320 + // These values are minimum and maximum animation duration per event origin, 1.1321 + // and a global ratio which defines how longer is the animation's duration 1.1322 + // compared to the average recent events intervals (such that for a relatively 1.1323 + // consistent events rate, the next event arrives before current animation ends) 1.1324 + nsCOMPtr<nsIAtom> mOrigin; 1.1325 + int32_t mOriginMinMS; 1.1326 + int32_t mOriginMaxMS; 1.1327 + double mIntervalRatio; 1.1328 + 1.1329 + TimeDuration mDuration; 1.1330 + nsPoint mStartPos; 1.1331 + nsPoint mDestination; 1.1332 + // Allowed destination positions around mDestination 1.1333 + nsRect mRange; 1.1334 + nsSMILKeySpline mTimingFunctionX; 1.1335 + nsSMILKeySpline mTimingFunctionY; 1.1336 + bool mIsSmoothScroll; 1.1337 + 1.1338 +protected: 1.1339 + double ProgressAt(TimeStamp aTime) { 1.1340 + return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0); 1.1341 + } 1.1342 + 1.1343 + nscoord VelocityComponent(double aTimeProgress, 1.1344 + nsSMILKeySpline& aTimingFunction, 1.1345 + nscoord aStart, nscoord aDestination); 1.1346 + 1.1347 + // Initializes the timing function in such a way that the current velocity is 1.1348 + // preserved. 1.1349 + void InitTimingFunction(nsSMILKeySpline& aTimingFunction, 1.1350 + nscoord aCurrentPos, nscoord aCurrentVelocity, 1.1351 + nscoord aDestination); 1.1352 + 1.1353 + TimeDuration CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin); 1.1354 + 1.1355 +// The next section is observer/callback management 1.1356 +// Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code. 1.1357 +public: 1.1358 + NS_INLINE_DECL_REFCOUNTING(AsyncScroll) 1.1359 + 1.1360 + /* 1.1361 + * Set a refresh observer for smooth scroll iterations (and start observing). 1.1362 + * Should be used at most once during the lifetime of this object. 1.1363 + * Return value: true on success, false otherwise. 1.1364 + */ 1.1365 + bool SetRefreshObserver(ScrollFrameHelper *aCallee) { 1.1366 + NS_ASSERTION(aCallee && !mCallee, "AsyncScroll::SetRefreshObserver - Invalid usage."); 1.1367 + 1.1368 + if (!RefreshDriver(aCallee)->AddRefreshObserver(this, Flush_Style)) { 1.1369 + return false; 1.1370 + } 1.1371 + 1.1372 + mCallee = aCallee; 1.1373 + return true; 1.1374 + } 1.1375 + 1.1376 + virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE { 1.1377 + // The callback may release "this". 1.1378 + // We don't access members after returning, so no need for KungFuDeathGrip. 1.1379 + ScrollFrameHelper::AsyncScrollCallback(mCallee, aTime); 1.1380 + } 1.1381 + 1.1382 +private: 1.1383 + ScrollFrameHelper *mCallee; 1.1384 + 1.1385 + nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) { 1.1386 + return aCallee->mOuter->PresContext()->RefreshDriver(); 1.1387 + } 1.1388 + 1.1389 + /* 1.1390 + * The refresh driver doesn't hold a reference to its observers, 1.1391 + * so releasing this object can (and is) used to remove the observer on DTOR. 1.1392 + * Currently, this object is released once the scrolling ends. 1.1393 + */ 1.1394 + void RemoveObserver() { 1.1395 + if (mCallee) { 1.1396 + RefreshDriver(mCallee)->RemoveRefreshObserver(this, Flush_Style); 1.1397 + } 1.1398 + } 1.1399 +}; 1.1400 + 1.1401 +nsPoint 1.1402 +ScrollFrameHelper::AsyncScroll::PositionAt(TimeStamp aTime) { 1.1403 + double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime)); 1.1404 + double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime)); 1.1405 + return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x), 1.1406 + NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y)); 1.1407 +} 1.1408 + 1.1409 +nsSize 1.1410 +ScrollFrameHelper::AsyncScroll::VelocityAt(TimeStamp aTime) { 1.1411 + double timeProgress = ProgressAt(aTime); 1.1412 + return nsSize(VelocityComponent(timeProgress, mTimingFunctionX, 1.1413 + mStartPos.x, mDestination.x), 1.1414 + VelocityComponent(timeProgress, mTimingFunctionY, 1.1415 + mStartPos.y, mDestination.y)); 1.1416 +} 1.1417 + 1.1418 +/* 1.1419 + * Calculate duration, possibly dynamically according to events rate and event origin. 1.1420 + * (also maintain previous timestamps - which are only used here). 1.1421 + */ 1.1422 +TimeDuration 1.1423 +ScrollFrameHelper:: 1.1424 +AsyncScroll::CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin) { 1.1425 + if (!aOrigin){ 1.1426 + aOrigin = nsGkAtoms::other; 1.1427 + } 1.1428 + 1.1429 + // Read preferences only on first iteration or for a different event origin. 1.1430 + if (mIsFirstIteration || aOrigin != mOrigin) { 1.1431 + mOrigin = aOrigin; 1.1432 + mOriginMinMS = mOriginMaxMS = 0; 1.1433 + bool isOriginSmoothnessEnabled = false; 1.1434 + mIntervalRatio = 1; 1.1435 + 1.1436 + // Default values for all preferences are defined in all.js 1.1437 + static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150; 1.1438 + static const bool kDefaultIsSmoothEnabled = true; 1.1439 + 1.1440 + nsAutoCString originName; 1.1441 + aOrigin->ToUTF8String(originName); 1.1442 + nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName; 1.1443 + 1.1444 + isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled); 1.1445 + if (isOriginSmoothnessEnabled) { 1.1446 + nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS"); 1.1447 + nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS"); 1.1448 + mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS); 1.1449 + mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS); 1.1450 + 1.1451 + static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000; 1.1452 + mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS); 1.1453 + mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS); 1.1454 + } 1.1455 + 1.1456 + // Keep the animation duration longer than the average event intervals 1.1457 + // (to "connect" consecutive scroll animations before the scroll comes to a stop). 1.1458 + static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js 1.1459 + mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio", 1.1460 + kDefaultDurationToIntervalRatio * 100) / 100.0; 1.1461 + 1.1462 + // Duration should be at least as long as the intervals -> ratio is at least 1 1.1463 + mIntervalRatio = std::max(1.0, mIntervalRatio); 1.1464 + 1.1465 + if (mIsFirstIteration) { 1.1466 + // Starting a new scroll (i.e. not when extending an existing scroll animation), 1.1467 + // create imaginary prev timestamps with maximum relevant intervals between them. 1.1468 + 1.1469 + // Longest relevant interval (which results in maximum duration) 1.1470 + TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio); 1.1471 + mPrevEventTime[0] = aTime - maxDelta; 1.1472 + mPrevEventTime[1] = mPrevEventTime[0] - maxDelta; 1.1473 + mPrevEventTime[2] = mPrevEventTime[1] - maxDelta; 1.1474 + } 1.1475 + } 1.1476 + 1.1477 + // Average last 3 delta durations (rounding errors up to 2ms are negligible for us) 1.1478 + int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3; 1.1479 + mPrevEventTime[2] = mPrevEventTime[1]; 1.1480 + mPrevEventTime[1] = mPrevEventTime[0]; 1.1481 + mPrevEventTime[0] = aTime; 1.1482 + 1.1483 + // Modulate duration according to events rate (quicker events -> shorter durations). 1.1484 + // The desired effect is to use longer duration when scrolling slowly, such that 1.1485 + // it's easier to follow, but reduce the duration to make it feel more snappy when 1.1486 + // scrolling quickly. To reduce fluctuations of the duration, we average event 1.1487 + // intervals using the recent 4 timestamps (now + three prev -> 3 intervals). 1.1488 + int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS); 1.1489 + 1.1490 + return TimeDuration::FromMilliseconds(durationMS); 1.1491 +} 1.1492 + 1.1493 +void 1.1494 +ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime, 1.1495 + nsPoint aDestination, 1.1496 + nsIAtom *aOrigin, 1.1497 + const nsRect& aRange) { 1.1498 + mRange = aRange; 1.1499 + TimeDuration duration = CalcDurationForEventTime(aTime, aOrigin); 1.1500 + nsSize currentVelocity(0, 0); 1.1501 + if (!mIsFirstIteration) { 1.1502 + // If an additional event has not changed the destination, then do not let 1.1503 + // another minimum duration reset slow things down. If it would then 1.1504 + // instead continue with the existing timing function. 1.1505 + if (aDestination == mDestination && 1.1506 + aTime + duration > mStartTime + mDuration) 1.1507 + return; 1.1508 + 1.1509 + currentVelocity = VelocityAt(aTime); 1.1510 + mStartPos = PositionAt(aTime); 1.1511 + } 1.1512 + mStartTime = aTime; 1.1513 + mDuration = duration; 1.1514 + mDestination = aDestination; 1.1515 + InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width, 1.1516 + aDestination.x); 1.1517 + InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height, 1.1518 + aDestination.y); 1.1519 + mIsFirstIteration = false; 1.1520 +} 1.1521 + 1.1522 + 1.1523 +nscoord 1.1524 +ScrollFrameHelper::AsyncScroll::VelocityComponent(double aTimeProgress, 1.1525 + nsSMILKeySpline& aTimingFunction, 1.1526 + nscoord aStart, 1.1527 + nscoord aDestination) 1.1528 +{ 1.1529 + double dt, dxy; 1.1530 + aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy); 1.1531 + if (dt == 0) 1.1532 + return dxy >= 0 ? nscoord_MAX : nscoord_MIN; 1.1533 + 1.1534 + const TimeDuration oneSecond = TimeDuration::FromSeconds(1); 1.1535 + double slope = dxy / dt; 1.1536 + return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond)); 1.1537 +} 1.1538 + 1.1539 +void 1.1540 +ScrollFrameHelper::AsyncScroll::InitTimingFunction(nsSMILKeySpline& aTimingFunction, 1.1541 + nscoord aCurrentPos, 1.1542 + nscoord aCurrentVelocity, 1.1543 + nscoord aDestination) 1.1544 +{ 1.1545 + if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) { 1.1546 + aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1); 1.1547 + return; 1.1548 + } 1.1549 + 1.1550 + const TimeDuration oneSecond = TimeDuration::FromSeconds(1); 1.1551 + double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos); 1.1552 + double normalization = sqrt(1.0 + slope * slope); 1.1553 + double dt = 1.0 / normalization * kCurrentVelocityWeighting; 1.1554 + double dxy = slope / normalization * kCurrentVelocityWeighting; 1.1555 + aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1); 1.1556 +} 1.1557 + 1.1558 +static bool 1.1559 +IsSmoothScrollingEnabled() 1.1560 +{ 1.1561 + return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false); 1.1562 +} 1.1563 + 1.1564 +class ScrollFrameActivityTracker MOZ_FINAL : public nsExpirationTracker<ScrollFrameHelper,4> { 1.1565 +public: 1.1566 + // Wait for 3-4s between scrolls before we remove our layers. 1.1567 + // That's 4 generations of 1s each. 1.1568 + enum { TIMEOUT_MS = 1000 }; 1.1569 + ScrollFrameActivityTracker() 1.1570 + : nsExpirationTracker<ScrollFrameHelper,4>(TIMEOUT_MS) {} 1.1571 + ~ScrollFrameActivityTracker() { 1.1572 + AgeAllGenerations(); 1.1573 + } 1.1574 + 1.1575 + virtual void NotifyExpired(ScrollFrameHelper *aObject) { 1.1576 + RemoveObject(aObject); 1.1577 + aObject->MarkInactive(); 1.1578 + } 1.1579 +}; 1.1580 + 1.1581 +static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nullptr; 1.1582 + 1.1583 +// There are situations when a scroll frame is destroyed and then re-created 1.1584 +// for the same content element. In this case we want to increment the scroll 1.1585 +// generation between the old and new scrollframes. If the new one knew about 1.1586 +// the old one then it could steal the old generation counter and increment it 1.1587 +// but it doesn't have that reference so instead we use a static global to 1.1588 +// ensure the new one gets a fresh value. 1.1589 +static uint32_t sScrollGenerationCounter = 0; 1.1590 + 1.1591 +ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter, 1.1592 + bool aIsRoot) 1.1593 + : mHScrollbarBox(nullptr) 1.1594 + , mVScrollbarBox(nullptr) 1.1595 + , mScrolledFrame(nullptr) 1.1596 + , mScrollCornerBox(nullptr) 1.1597 + , mResizerBox(nullptr) 1.1598 + , mOuter(aOuter) 1.1599 + , mAsyncScroll(nullptr) 1.1600 + , mOriginOfLastScroll(nsGkAtoms::other) 1.1601 + , mScrollGeneration(++sScrollGenerationCounter) 1.1602 + , mDestination(0, 0) 1.1603 + , mScrollPosAtLastPaint(0, 0) 1.1604 + , mRestorePos(-1, -1) 1.1605 + , mLastPos(-1, -1) 1.1606 + , mResolution(1.0, 1.0) 1.1607 + , mScrollPosForLayerPixelAlignment(-1, -1) 1.1608 + , mLastUpdateImagesPos(-1, -1) 1.1609 + , mNeverHasVerticalScrollbar(false) 1.1610 + , mNeverHasHorizontalScrollbar(false) 1.1611 + , mHasVerticalScrollbar(false) 1.1612 + , mHasHorizontalScrollbar(false) 1.1613 + , mFrameIsUpdatingScrollbar(false) 1.1614 + , mDidHistoryRestore(false) 1.1615 + , mIsRoot(aIsRoot) 1.1616 + , mClipAllDescendants(aIsRoot) 1.1617 + , mSupppressScrollbarUpdate(false) 1.1618 + , mSkippedScrollbarLayout(false) 1.1619 + , mHadNonInitialReflow(false) 1.1620 + , mHorizontalOverflow(false) 1.1621 + , mVerticalOverflow(false) 1.1622 + , mPostedReflowCallback(false) 1.1623 + , mMayHaveDirtyFixedChildren(false) 1.1624 + , mUpdateScrollbarAttributes(false) 1.1625 + , mCollapsedResizer(false) 1.1626 + , mShouldBuildScrollableLayer(false) 1.1627 + , mHasBeenScrolled(false) 1.1628 + , mIsResolutionSet(false) 1.1629 +{ 1.1630 + mScrollingActive = IsAlwaysActive(); 1.1631 + 1.1632 + if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { 1.1633 + mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter)); 1.1634 + } 1.1635 + 1.1636 + EnsureImageVisPrefsCached(); 1.1637 +} 1.1638 + 1.1639 +ScrollFrameHelper::~ScrollFrameHelper() 1.1640 +{ 1.1641 + if (mActivityExpirationState.IsTracked()) { 1.1642 + gScrollFrameActivityTracker->RemoveObject(this); 1.1643 + } 1.1644 + if (gScrollFrameActivityTracker && 1.1645 + gScrollFrameActivityTracker->IsEmpty()) { 1.1646 + delete gScrollFrameActivityTracker; 1.1647 + gScrollFrameActivityTracker = nullptr; 1.1648 + } 1.1649 + 1.1650 + if (mScrollActivityTimer) { 1.1651 + mScrollActivityTimer->Cancel(); 1.1652 + mScrollActivityTimer = nullptr; 1.1653 + } 1.1654 +} 1.1655 + 1.1656 +/* 1.1657 + * Callback function from AsyncScroll, used in ScrollFrameHelper::ScrollTo 1.1658 + */ 1.1659 +void 1.1660 +ScrollFrameHelper::AsyncScrollCallback(void* anInstance, mozilla::TimeStamp aTime) 1.1661 +{ 1.1662 + ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance); 1.1663 + if (!self || !self->mAsyncScroll) 1.1664 + return; 1.1665 + 1.1666 + nsRect range = self->mAsyncScroll->mRange; 1.1667 + if (self->mAsyncScroll->mIsSmoothScroll) { 1.1668 + if (!self->mAsyncScroll->IsFinished(aTime)) { 1.1669 + nsPoint destination = self->mAsyncScroll->PositionAt(aTime); 1.1670 + // Allow this scroll operation to land on any pixel boundary between the 1.1671 + // current position and the final allowed range. (We don't want 1.1672 + // intermediate steps to be more constrained than the final step!) 1.1673 + nsRect intermediateRange = 1.1674 + nsRect(self->GetScrollPosition(), nsSize()).UnionEdges(range); 1.1675 + self->ScrollToImpl(destination, intermediateRange); 1.1676 + // 'self' might be destroyed here 1.1677 + return; 1.1678 + } 1.1679 + } 1.1680 + 1.1681 + // Apply desired destination range since this is the last step of scrolling. 1.1682 + self->mAsyncScroll = nullptr; 1.1683 + nsWeakFrame weakFrame(self->mOuter); 1.1684 + self->ScrollToImpl(self->mDestination, range); 1.1685 + if (!weakFrame.IsAlive()) { 1.1686 + return; 1.1687 + } 1.1688 + // We are done scrolling, set our destination to wherever we actually ended 1.1689 + // up scrolling to. 1.1690 + self->mDestination = self->GetScrollPosition(); 1.1691 +} 1.1692 + 1.1693 +void 1.1694 +ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition) 1.1695 +{ 1.1696 + nsPoint current = GetScrollPosition(); 1.1697 + CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels(); 1.1698 + nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition); 1.1699 + nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); 1.1700 + nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1); 1.1701 + // XXX I don't think the following blocks are needed anymore, now that 1.1702 + // ScrollToImpl simply tries to scroll an integer number of layer 1.1703 + // pixels from the current position 1.1704 + if (currentCSSPixels.x == aScrollPosition.x) { 1.1705 + pt.x = current.x; 1.1706 + range.x = pt.x; 1.1707 + range.width = 0; 1.1708 + } 1.1709 + if (currentCSSPixels.y == aScrollPosition.y) { 1.1710 + pt.y = current.y; 1.1711 + range.y = pt.y; 1.1712 + range.height = 0; 1.1713 + } 1.1714 + ScrollTo(pt, nsIScrollableFrame::INSTANT, &range); 1.1715 + // 'this' might be destroyed here 1.1716 +} 1.1717 + 1.1718 +void 1.1719 +ScrollFrameHelper::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition, 1.1720 + nsIAtom *aOrigin) 1.1721 +{ 1.1722 + nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition); 1.1723 + nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000); 1.1724 + nsRect range(pt.x - halfRange, pt.y - halfRange, 2*halfRange - 1, 2*halfRange - 1); 1.1725 + ScrollToWithOrigin(pt, nsIScrollableFrame::INSTANT, aOrigin, &range); 1.1726 + // 'this' might be destroyed here 1.1727 +} 1.1728 + 1.1729 +CSSIntPoint 1.1730 +ScrollFrameHelper::GetScrollPositionCSSPixels() 1.1731 +{ 1.1732 + return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition()); 1.1733 +} 1.1734 + 1.1735 +/* 1.1736 + * this method wraps calls to ScrollToImpl(), either in one shot or incrementally, 1.1737 + * based on the setting of the smoothness scroll pref 1.1738 + */ 1.1739 +void 1.1740 +ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition, 1.1741 + nsIScrollableFrame::ScrollMode aMode, 1.1742 + nsIAtom *aOrigin, 1.1743 + const nsRect* aRange) 1.1744 +{ 1.1745 + nsRect scrollRange = GetScrollRangeForClamping(); 1.1746 + mDestination = scrollRange.ClampPoint(aScrollPosition); 1.1747 + 1.1748 + nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0)); 1.1749 + 1.1750 + if (aMode == nsIScrollableFrame::INSTANT) { 1.1751 + // Asynchronous scrolling is not allowed, so we'll kill any existing 1.1752 + // async-scrolling process and do an instant scroll. 1.1753 + mAsyncScroll = nullptr; 1.1754 + nsWeakFrame weakFrame(mOuter); 1.1755 + ScrollToImpl(mDestination, range, aOrigin); 1.1756 + if (!weakFrame.IsAlive()) { 1.1757 + return; 1.1758 + } 1.1759 + // We are done scrolling, set our destination to wherever we actually ended 1.1760 + // up scrolling to. 1.1761 + mDestination = GetScrollPosition(); 1.1762 + return; 1.1763 + } 1.1764 + 1.1765 + TimeStamp now = TimeStamp::Now(); 1.1766 + bool isSmoothScroll = (aMode == nsIScrollableFrame::SMOOTH) && 1.1767 + IsSmoothScrollingEnabled(); 1.1768 + 1.1769 + if (!mAsyncScroll) { 1.1770 + mAsyncScroll = new AsyncScroll(GetScrollPosition()); 1.1771 + if (!mAsyncScroll->SetRefreshObserver(this)) { 1.1772 + mAsyncScroll = nullptr; 1.1773 + // Observer setup failed. Scroll the normal way. 1.1774 + nsWeakFrame weakFrame(mOuter); 1.1775 + ScrollToImpl(mDestination, range, aOrigin); 1.1776 + if (!weakFrame.IsAlive()) { 1.1777 + return; 1.1778 + } 1.1779 + // We are done scrolling, set our destination to wherever we actually 1.1780 + // ended up scrolling to. 1.1781 + mDestination = GetScrollPosition(); 1.1782 + return; 1.1783 + } 1.1784 + } 1.1785 + 1.1786 + mAsyncScroll->mIsSmoothScroll = isSmoothScroll; 1.1787 + 1.1788 + if (isSmoothScroll) { 1.1789 + mAsyncScroll->InitSmoothScroll(now, mDestination, aOrigin, range); 1.1790 + } else { 1.1791 + mAsyncScroll->Init(range); 1.1792 + } 1.1793 +} 1.1794 + 1.1795 +// We can't use nsContainerFrame::PositionChildViews here because 1.1796 +// we don't want to invalidate views that have moved. 1.1797 +static void AdjustViews(nsIFrame* aFrame) 1.1798 +{ 1.1799 + nsView* view = aFrame->GetView(); 1.1800 + if (view) { 1.1801 + nsPoint pt; 1.1802 + aFrame->GetParent()->GetClosestView(&pt); 1.1803 + pt += aFrame->GetPosition(); 1.1804 + view->SetPosition(pt.x, pt.y); 1.1805 + 1.1806 + return; 1.1807 + } 1.1808 + 1.1809 + if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) { 1.1810 + return; 1.1811 + } 1.1812 + 1.1813 + // Call AdjustViews recursively for all child frames except the popup list as 1.1814 + // the views for popups are not scrolled. 1.1815 + nsIFrame::ChildListIterator lists(aFrame); 1.1816 + for (; !lists.IsDone(); lists.Next()) { 1.1817 + if (lists.CurrentID() == nsIFrame::kPopupList) { 1.1818 + continue; 1.1819 + } 1.1820 + nsFrameList::Enumerator childFrames(lists.CurrentList()); 1.1821 + for (; !childFrames.AtEnd(); childFrames.Next()) { 1.1822 + AdjustViews(childFrames.get()); 1.1823 + } 1.1824 + } 1.1825 +} 1.1826 + 1.1827 +static bool 1.1828 +CanScrollWithBlitting(nsIFrame* aFrame) 1.1829 +{ 1.1830 + if (aFrame->GetStateBits() & NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL) 1.1831 + return false; 1.1832 + 1.1833 + for (nsIFrame* f = aFrame; f; 1.1834 + f = nsLayoutUtils::GetCrossDocParentFrame(f)) { 1.1835 + if (nsSVGIntegrationUtils::UsingEffectsForFrame(f) || 1.1836 + f->IsFrameOfType(nsIFrame::eSVG) || 1.1837 + f->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) { 1.1838 + return false; 1.1839 + } 1.1840 + if (nsLayoutUtils::IsPopup(f)) 1.1841 + break; 1.1842 + } 1.1843 + return true; 1.1844 +} 1.1845 + 1.1846 +bool ScrollFrameHelper::IsIgnoringViewportClipping() const 1.1847 +{ 1.1848 + if (!mIsRoot) 1.1849 + return false; 1.1850 + nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*> 1.1851 + (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame())); 1.1852 + return subdocFrame && !subdocFrame->ShouldClipSubdocument(); 1.1853 +} 1.1854 + 1.1855 +bool ScrollFrameHelper::ShouldClampScrollPosition() const 1.1856 +{ 1.1857 + if (!mIsRoot) 1.1858 + return true; 1.1859 + nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*> 1.1860 + (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame())); 1.1861 + return !subdocFrame || subdocFrame->ShouldClampScrollPosition(); 1.1862 +} 1.1863 + 1.1864 +bool ScrollFrameHelper::IsAlwaysActive() const 1.1865 +{ 1.1866 + if (nsDisplayItem::ForceActiveLayers()) { 1.1867 + return true; 1.1868 + } 1.1869 + 1.1870 + const nsStyleDisplay* disp = mOuter->StyleDisplay(); 1.1871 + if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) { 1.1872 + return true; 1.1873 + } 1.1874 + 1.1875 + // Unless this is the root scrollframe for a non-chrome document 1.1876 + // which is the direct child of a chrome document, we default to not 1.1877 + // being "active". 1.1878 + if (!(mIsRoot && mOuter->PresContext()->IsRootContentDocument())) { 1.1879 + return false; 1.1880 + } 1.1881 + 1.1882 + // If we have scrolled before, then we should stay active. 1.1883 + if (mHasBeenScrolled) { 1.1884 + return true; 1.1885 + } 1.1886 + 1.1887 + // If we're overflow:hidden, then start as inactive until 1.1888 + // we get scrolled manually. 1.1889 + ScrollbarStyles styles = GetScrollbarStylesFromFrame(); 1.1890 + return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN && 1.1891 + styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN); 1.1892 +} 1.1893 + 1.1894 +void ScrollFrameHelper::MarkInactive() 1.1895 +{ 1.1896 + if (IsAlwaysActive() || !mScrollingActive) 1.1897 + return; 1.1898 + 1.1899 + mScrollingActive = false; 1.1900 + mOuter->InvalidateFrameSubtree(); 1.1901 +} 1.1902 + 1.1903 +void ScrollFrameHelper::MarkActive() 1.1904 +{ 1.1905 + mScrollingActive = true; 1.1906 + if (IsAlwaysActive()) 1.1907 + return; 1.1908 + 1.1909 + if (mActivityExpirationState.IsTracked()) { 1.1910 + gScrollFrameActivityTracker->MarkUsed(this); 1.1911 + } else { 1.1912 + if (!gScrollFrameActivityTracker) { 1.1913 + gScrollFrameActivityTracker = new ScrollFrameActivityTracker(); 1.1914 + } 1.1915 + gScrollFrameActivityTracker->AddObject(this); 1.1916 + } 1.1917 +} 1.1918 + 1.1919 +void ScrollFrameHelper::ScrollVisual(nsPoint aOldScrolledFramePos) 1.1920 +{ 1.1921 + // Mark this frame as having been scrolled. If this is the root 1.1922 + // scroll frame of a content document, then IsAlwaysActive() 1.1923 + // will return true from now on and MarkInactive() won't 1.1924 + // have any effect. 1.1925 + mHasBeenScrolled = true; 1.1926 + 1.1927 + AdjustViews(mScrolledFrame); 1.1928 + // We need to call this after fixing up the view positions 1.1929 + // to be consistent with the frame hierarchy. 1.1930 + bool canScrollWithBlitting = CanScrollWithBlitting(mOuter); 1.1931 + mOuter->RemoveStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL); 1.1932 + if (IsScrollingActive()) { 1.1933 + if (!canScrollWithBlitting) { 1.1934 + MarkInactive(); 1.1935 + } 1.1936 + } 1.1937 + if (canScrollWithBlitting) { 1.1938 + MarkActive(); 1.1939 + } 1.1940 + 1.1941 + mOuter->SchedulePaint(); 1.1942 +} 1.1943 + 1.1944 +/** 1.1945 + * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper] 1.1946 + * to [aBoundLower, aBoundUpper] and then select the appunit value from among 1.1947 + * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) * 1.1948 + * aRes/aAppUnitsPerPixel is an integer (or as close as we can get 1.1949 + * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and 1.1950 + * closest to aDesired. If no such value exists, return the nearest in 1.1951 + * [aDestLower, aDestUpper]. 1.1952 + */ 1.1953 +static nscoord 1.1954 +ClampAndAlignWithPixels(nscoord aDesired, 1.1955 + nscoord aBoundLower, nscoord aBoundUpper, 1.1956 + nscoord aDestLower, nscoord aDestUpper, 1.1957 + nscoord aAppUnitsPerPixel, double aRes, 1.1958 + nscoord aCurrent) 1.1959 +{ 1.1960 + // Intersect scroll range with allowed range, by clamping the ends 1.1961 + // of aRange to be within bounds 1.1962 + nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper); 1.1963 + nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper); 1.1964 + 1.1965 + nscoord desired = clamped(aDesired, destLower, destUpper); 1.1966 + 1.1967 + double currentLayerVal = (aRes*aCurrent)/aAppUnitsPerPixel; 1.1968 + double desiredLayerVal = (aRes*desired)/aAppUnitsPerPixel; 1.1969 + double delta = desiredLayerVal - currentLayerVal; 1.1970 + double nearestLayerVal = NS_round(delta) + currentLayerVal; 1.1971 + 1.1972 + // Convert back from ThebesLayer space to appunits relative to the top-left 1.1973 + // of the scrolled frame. 1.1974 + nscoord aligned = 1.1975 + NSToCoordRoundWithClamp(nearestLayerVal*aAppUnitsPerPixel/aRes); 1.1976 + 1.1977 + // Use a bound if it is within the allowed range and closer to desired than 1.1978 + // the nearest pixel-aligned value. 1.1979 + if (aBoundUpper == destUpper && 1.1980 + static_cast<decltype(Abs(desired))>(aBoundUpper - desired) < 1.1981 + Abs(desired - aligned)) 1.1982 + return aBoundUpper; 1.1983 + 1.1984 + if (aBoundLower == destLower && 1.1985 + static_cast<decltype(Abs(desired))>(desired - aBoundLower) < 1.1986 + Abs(aligned - desired)) 1.1987 + return aBoundLower; 1.1988 + 1.1989 + // Accept the nearest pixel-aligned value if it is within the allowed range. 1.1990 + if (aligned >= destLower && aligned <= destUpper) 1.1991 + return aligned; 1.1992 + 1.1993 + // Check if opposite pixel boundary fits into allowed range. 1.1994 + double oppositeLayerVal = 1.1995 + nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0); 1.1996 + nscoord opposite = 1.1997 + NSToCoordRoundWithClamp(oppositeLayerVal*aAppUnitsPerPixel/aRes); 1.1998 + if (opposite >= destLower && opposite <= destUpper) { 1.1999 + return opposite; 1.2000 + } 1.2001 + 1.2002 + // No alignment available. 1.2003 + return desired; 1.2004 +} 1.2005 + 1.2006 +/** 1.2007 + * Clamp desired scroll position aPt to aBounds and then snap 1.2008 + * it to the same layer pixel edges as aCurrent, keeping it within aRange 1.2009 + * during snapping. aCurrent is the current scroll position. 1.2010 + */ 1.2011 +static nsPoint 1.2012 +ClampAndAlignWithLayerPixels(const nsPoint& aPt, 1.2013 + const nsRect& aBounds, 1.2014 + const nsRect& aRange, 1.2015 + const nsPoint& aCurrent, 1.2016 + nscoord aAppUnitsPerPixel, 1.2017 + const gfxSize& aScale) 1.2018 +{ 1.2019 + return nsPoint(ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(), 1.2020 + aRange.x, aRange.XMost(), 1.2021 + aAppUnitsPerPixel, aScale.width, 1.2022 + aCurrent.x), 1.2023 + ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(), 1.2024 + aRange.y, aRange.YMost(), 1.2025 + aAppUnitsPerPixel, aScale.height, 1.2026 + aCurrent.y)); 1.2027 +} 1.2028 + 1.2029 +/* static */ void 1.2030 +ScrollFrameHelper::ScrollActivityCallback(nsITimer *aTimer, void* anInstance) 1.2031 +{ 1.2032 + ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance); 1.2033 + 1.2034 + // Fire the synth mouse move. 1.2035 + self->mScrollActivityTimer->Cancel(); 1.2036 + self->mScrollActivityTimer = nullptr; 1.2037 + self->mOuter->PresContext()->PresShell()->SynthesizeMouseMove(true); 1.2038 +} 1.2039 + 1.2040 + 1.2041 +void 1.2042 +ScrollFrameHelper::ScheduleSyntheticMouseMove() 1.2043 +{ 1.2044 + if (!mScrollActivityTimer) { 1.2045 + mScrollActivityTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.2046 + if (!mScrollActivityTimer) 1.2047 + return; 1.2048 + } 1.2049 + 1.2050 + mScrollActivityTimer->InitWithFuncCallback( 1.2051 + ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT); 1.2052 +} 1.2053 + 1.2054 +void 1.2055 +ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOrigin) 1.2056 +{ 1.2057 + if (aOrigin == nullptr) { 1.2058 + // If no origin was specified, we still want to set it to something that's 1.2059 + // non-null, so that we can use nullness to distinguish if the frame was scrolled 1.2060 + // at all. Default it to some generic placeholder. 1.2061 + aOrigin = nsGkAtoms::other; 1.2062 + } 1.2063 + 1.2064 + nsPresContext* presContext = mOuter->PresContext(); 1.2065 + nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); 1.2066 + // 'scale' is our estimate of the scale factor that will be applied 1.2067 + // when rendering the scrolled content to its own ThebesLayer. 1.2068 + gfxSize scale = FrameLayerBuilder::GetThebesLayerScaleForFrame(mScrolledFrame); 1.2069 + nsPoint curPos = GetScrollPosition(); 1.2070 + nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1,-1) 1.2071 + ? curPos : mScrollPosForLayerPixelAlignment; 1.2072 + // Try to align aPt with curPos so they have an integer number of layer 1.2073 + // pixels between them. This gives us the best chance of scrolling without 1.2074 + // having to invalidate due to changes in subpixel rendering. 1.2075 + // Note that when we actually draw into a ThebesLayer, the coordinates 1.2076 + // that get mapped onto the layer buffer pixels are from the display list, 1.2077 + // which are relative to the display root frame's top-left increasing down, 1.2078 + // whereas here our coordinates are scroll positions which increase upward 1.2079 + // and are relative to the scrollport top-left. This difference doesn't actually 1.2080 + // matter since all we are about is that there be an integer number of 1.2081 + // layer pixels between pt and curPos. 1.2082 + nsPoint pt = 1.2083 + ClampAndAlignWithLayerPixels(aPt, 1.2084 + GetScrollRangeForClamping(), 1.2085 + aRange, 1.2086 + alignWithPos, 1.2087 + appUnitsPerDevPixel, 1.2088 + scale); 1.2089 + if (pt == curPos) { 1.2090 + return; 1.2091 + } 1.2092 + 1.2093 + bool needImageVisibilityUpdate = (mLastUpdateImagesPos == nsPoint(-1,-1)); 1.2094 + 1.2095 + nsPoint dist(std::abs(pt.x - mLastUpdateImagesPos.x), 1.2096 + std::abs(pt.y - mLastUpdateImagesPos.y)); 1.2097 + nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize(); 1.2098 + nscoord horzAllowance = std::max(scrollPortSize.width / std::max(sHorzScrollFraction, 1), 1.2099 + nsPresContext::AppUnitsPerCSSPixel()); 1.2100 + nscoord vertAllowance = std::max(scrollPortSize.height / std::max(sVertScrollFraction, 1), 1.2101 + nsPresContext::AppUnitsPerCSSPixel()); 1.2102 + if (dist.x >= horzAllowance || dist.y >= vertAllowance) { 1.2103 + needImageVisibilityUpdate = true; 1.2104 + } 1.2105 + 1.2106 + if (needImageVisibilityUpdate) { 1.2107 + presContext->PresShell()->ScheduleImageVisibilityUpdate(); 1.2108 + } 1.2109 + 1.2110 + // notify the listeners. 1.2111 + for (uint32_t i = 0; i < mListeners.Length(); i++) { 1.2112 + mListeners[i]->ScrollPositionWillChange(pt.x, pt.y); 1.2113 + } 1.2114 + 1.2115 + nsPoint oldScrollFramePos = mScrolledFrame->GetPosition(); 1.2116 + // Update frame position for scrolling 1.2117 + mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt); 1.2118 + mOriginOfLastScroll = aOrigin; 1.2119 + mScrollGeneration = ++sScrollGenerationCounter; 1.2120 + 1.2121 + // We pass in the amount to move visually 1.2122 + ScrollVisual(oldScrollFramePos); 1.2123 + 1.2124 + ScheduleSyntheticMouseMove(); 1.2125 + nsWeakFrame weakFrame(mOuter); 1.2126 + UpdateScrollbarPosition(); 1.2127 + if (!weakFrame.IsAlive()) { 1.2128 + return; 1.2129 + } 1.2130 + PostScrollEvent(); 1.2131 + 1.2132 + // notify the listeners. 1.2133 + for (uint32_t i = 0; i < mListeners.Length(); i++) { 1.2134 + mListeners[i]->ScrollPositionDidChange(pt.x, pt.y); 1.2135 + } 1.2136 + 1.2137 + nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell(); 1.2138 + if (docShell) { 1.2139 + docShell->NotifyScrollObservers(); 1.2140 + } 1.2141 +} 1.2142 + 1.2143 +static int32_t 1.2144 +MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder) 1.2145 +{ 1.2146 + int32_t maxZIndex = 0; 1.2147 + for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) { 1.2148 + maxZIndex = std::max(maxZIndex, item->ZIndex()); 1.2149 + } 1.2150 + return maxZIndex; 1.2151 +} 1.2152 + 1.2153 +static void 1.2154 +AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists, 1.2155 + nsDisplayList* aSource, nsIFrame* aSourceFrame, bool aOwnLayer, 1.2156 + uint32_t aFlags, mozilla::layers::FrameMetrics::ViewID aScrollTargetId, 1.2157 + bool aPositioned) 1.2158 +{ 1.2159 + if (aSource->IsEmpty()) 1.2160 + return; 1.2161 + 1.2162 + nsDisplayWrapList* newItem = aOwnLayer? 1.2163 + new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource, 1.2164 + aFlags, aScrollTargetId) : 1.2165 + new (aBuilder) nsDisplayWrapList(aBuilder, aSourceFrame, aSource); 1.2166 + 1.2167 + if (aPositioned) { 1.2168 + // We want overlay scrollbars to always be on top of the scrolled content, 1.2169 + // but we don't want them to unnecessarily cover overlapping elements from 1.2170 + // outside our scroll frame. 1.2171 + nsDisplayList* positionedDescendants = aLists.PositionedDescendants(); 1.2172 + if (!positionedDescendants->IsEmpty()) { 1.2173 + newItem->SetOverrideZIndex(MaxZIndexInList(positionedDescendants, aBuilder)); 1.2174 + positionedDescendants->AppendNewToTop(newItem); 1.2175 + } else { 1.2176 + aLists.Outlines()->AppendNewToTop(newItem); 1.2177 + } 1.2178 + } else { 1.2179 + aLists.BorderBackground()->AppendNewToTop(newItem); 1.2180 + } 1.2181 +} 1.2182 + 1.2183 +struct HoveredStateComparator 1.2184 +{ 1.2185 + bool Equals(nsIFrame* A, nsIFrame* B) const { 1.2186 + bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None, 1.2187 + nsGkAtoms::hover); 1.2188 + bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None, 1.2189 + nsGkAtoms::hover); 1.2190 + return aHovered == bHovered; 1.2191 + } 1.2192 + bool LessThan(nsIFrame* A, nsIFrame* B) const { 1.2193 + bool aHovered = A->GetContent()->HasAttr(kNameSpaceID_None, 1.2194 + nsGkAtoms::hover); 1.2195 + bool bHovered = B->GetContent()->HasAttr(kNameSpaceID_None, 1.2196 + nsGkAtoms::hover); 1.2197 + return !aHovered && bHovered; 1.2198 + } 1.2199 +}; 1.2200 + 1.2201 +void 1.2202 +ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder, 1.2203 + const nsRect& aDirtyRect, 1.2204 + const nsDisplayListSet& aLists, 1.2205 + bool& aCreateLayer, 1.2206 + bool aPositioned) 1.2207 +{ 1.2208 + nsITheme* theme = mOuter->PresContext()->GetTheme(); 1.2209 + if (theme && 1.2210 + theme->ShouldHideScrollbars()) { 1.2211 + return; 1.2212 + } 1.2213 + 1.2214 + bool overlayScrollbars = 1.2215 + LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0; 1.2216 + 1.2217 + nsAutoTArray<nsIFrame*, 3> scrollParts; 1.2218 + for (nsIFrame* kid = mOuter->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { 1.2219 + if (kid == mScrolledFrame || 1.2220 + (kid->IsPositioned() || overlayScrollbars) != aPositioned) 1.2221 + continue; 1.2222 + 1.2223 + scrollParts.AppendElement(kid); 1.2224 + } 1.2225 + 1.2226 + mozilla::layers::FrameMetrics::ViewID scrollTargetId = aCreateLayer 1.2227 + ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()) 1.2228 + : mozilla::layers::FrameMetrics::NULL_SCROLL_ID; 1.2229 + 1.2230 + scrollParts.Sort(HoveredStateComparator()); 1.2231 + 1.2232 + for (uint32_t i = 0; i < scrollParts.Length(); ++i) { 1.2233 + nsDisplayListCollection partList; 1.2234 + mOuter->BuildDisplayListForChild( 1.2235 + aBuilder, scrollParts[i], aDirtyRect, partList, 1.2236 + nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT); 1.2237 + 1.2238 + uint32_t flags = 0; 1.2239 + if (scrollParts[i] == mVScrollbarBox) { 1.2240 + flags |= nsDisplayOwnLayer::VERTICAL_SCROLLBAR; 1.2241 + } 1.2242 + if (scrollParts[i] == mHScrollbarBox) { 1.2243 + flags |= nsDisplayOwnLayer::HORIZONTAL_SCROLLBAR; 1.2244 + } 1.2245 + 1.2246 + // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into 1.2247 + // partList.PositionedDescendants(). 1.2248 + ::AppendToTop(aBuilder, aLists, 1.2249 + partList.PositionedDescendants(), scrollParts[i], 1.2250 + aCreateLayer, flags, scrollTargetId, aPositioned); 1.2251 + } 1.2252 +} 1.2253 + 1.2254 +class ScrollLayerWrapper : public nsDisplayWrapper 1.2255 +{ 1.2256 +public: 1.2257 + ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame) 1.2258 + : mCount(0) 1.2259 + , mProps(aScrolledFrame->Properties()) 1.2260 + , mScrollFrame(aScrollFrame) 1.2261 + , mScrolledFrame(aScrolledFrame) 1.2262 + { 1.2263 + SetCount(0); 1.2264 + } 1.2265 + 1.2266 + virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, 1.2267 + nsIFrame* aFrame, 1.2268 + nsDisplayList* aList) MOZ_OVERRIDE { 1.2269 + SetCount(++mCount); 1.2270 + return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, mScrolledFrame, mScrolledFrame, mScrollFrame); 1.2271 + } 1.2272 + 1.2273 + virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, 1.2274 + nsDisplayItem* aItem) MOZ_OVERRIDE { 1.2275 + 1.2276 + SetCount(++mCount); 1.2277 + return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame); 1.2278 + } 1.2279 + 1.2280 +protected: 1.2281 + void SetCount(intptr_t aCount) { 1.2282 + mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount)); 1.2283 + } 1.2284 + 1.2285 + intptr_t mCount; 1.2286 + FrameProperties mProps; 1.2287 + nsIFrame* mScrollFrame; 1.2288 + nsIFrame* mScrolledFrame; 1.2289 +}; 1.2290 + 1.2291 +/* static */ bool ScrollFrameHelper::sImageVisPrefsCached = false; 1.2292 +/* static */ uint32_t ScrollFrameHelper::sHorzExpandScrollPort = 0; 1.2293 +/* static */ uint32_t ScrollFrameHelper::sVertExpandScrollPort = 1; 1.2294 +/* static */ int32_t ScrollFrameHelper::sHorzScrollFraction = 2; 1.2295 +/* static */ int32_t ScrollFrameHelper::sVertScrollFraction = 2; 1.2296 + 1.2297 +/* static */ void 1.2298 +ScrollFrameHelper::EnsureImageVisPrefsCached() 1.2299 +{ 1.2300 + if (!sImageVisPrefsCached) { 1.2301 + Preferences::AddUintVarCache(&sHorzExpandScrollPort, 1.2302 + "layout.imagevisibility.numscrollportwidths", (uint32_t)0); 1.2303 + Preferences::AddUintVarCache(&sVertExpandScrollPort, 1.2304 + "layout.imagevisibility.numscrollportheights", 1); 1.2305 + 1.2306 + Preferences::AddIntVarCache(&sHorzScrollFraction, 1.2307 + "layout.imagevisibility.amountscrollbeforeupdatehorizontal", 2); 1.2308 + Preferences::AddIntVarCache(&sVertScrollFraction, 1.2309 + "layout.imagevisibility.amountscrollbeforeupdatevertical", 2); 1.2310 + 1.2311 + sImageVisPrefsCached = true; 1.2312 + } 1.2313 +} 1.2314 + 1.2315 +nsRect 1.2316 +ScrollFrameHelper::ExpandRect(const nsRect& aRect) const 1.2317 +{ 1.2318 + // We don't want to expand a rect in a direction that we can't scroll, so we 1.2319 + // check the scroll range. 1.2320 + nsRect scrollRange = GetScrollRangeForClamping(); 1.2321 + nsPoint scrollPos = GetScrollPosition(); 1.2322 + nsMargin expand(0, 0, 0, 0); 1.2323 + 1.2324 + nscoord vertShift = sVertExpandScrollPort * aRect.height; 1.2325 + if (scrollRange.y < scrollPos.y) { 1.2326 + expand.top = vertShift; 1.2327 + } 1.2328 + if (scrollPos.y < scrollRange.YMost()) { 1.2329 + expand.bottom = vertShift; 1.2330 + } 1.2331 + 1.2332 + nscoord horzShift = sHorzExpandScrollPort * aRect.width; 1.2333 + if (scrollRange.x < scrollPos.x) { 1.2334 + expand.left = horzShift; 1.2335 + } 1.2336 + if (scrollPos.x < scrollRange.XMost()) { 1.2337 + expand.right = horzShift; 1.2338 + } 1.2339 + 1.2340 + nsRect rect = aRect; 1.2341 + rect.Inflate(expand); 1.2342 + return rect; 1.2343 +} 1.2344 + 1.2345 +static bool 1.2346 +ShouldBeClippedByFrame(nsIFrame* aClipFrame, nsIFrame* aClippedFrame) 1.2347 +{ 1.2348 + return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame); 1.2349 +} 1.2350 + 1.2351 +static void 1.2352 +ClipItemsExceptCaret(nsDisplayList* aList, nsDisplayListBuilder* aBuilder, 1.2353 + nsIFrame* aClipFrame, const DisplayItemClip& aClip) 1.2354 +{ 1.2355 + nsDisplayItem* i = aList->GetBottom(); 1.2356 + for (; i; i = i->GetAbove()) { 1.2357 + if (!::ShouldBeClippedByFrame(aClipFrame, i->Frame())) { 1.2358 + continue; 1.2359 + } 1.2360 + 1.2361 + bool unused; 1.2362 + nsRect bounds = i->GetBounds(aBuilder, &unused); 1.2363 + bool isAffectedByClip = aClip.IsRectAffectedByClip(bounds); 1.2364 + if (isAffectedByClip && nsDisplayItem::TYPE_CARET == i->GetType()) { 1.2365 + // Don't clip the caret if it overflows vertically only, and by half 1.2366 + // its height at most. This is to avoid clipping it when the line-height 1.2367 + // is small. 1.2368 + auto half = bounds.height / 2; 1.2369 + bounds.y += half; 1.2370 + bounds.height -= half; 1.2371 + isAffectedByClip = aClip.IsRectAffectedByClip(bounds); 1.2372 + if (isAffectedByClip) { 1.2373 + // Don't clip the caret if it's just outside on the right side. 1.2374 + nsRect rightSide(bounds.x - 1, bounds.y, 1, bounds.height); 1.2375 + isAffectedByClip = aClip.IsRectAffectedByClip(rightSide); 1.2376 + // Also, avoid clipping it in a zero-height line box (heuristic only). 1.2377 + if (isAffectedByClip) { 1.2378 + isAffectedByClip = i->Frame()->GetRect().height != 0; 1.2379 + } 1.2380 + } 1.2381 + } 1.2382 + if (isAffectedByClip) { 1.2383 + DisplayItemClip newClip; 1.2384 + newClip.IntersectWith(i->GetClip()); 1.2385 + newClip.IntersectWith(aClip); 1.2386 + i->SetClip(aBuilder, newClip); 1.2387 + } 1.2388 + nsDisplayList* children = i->GetSameCoordinateSystemChildren(); 1.2389 + if (children) { 1.2390 + ClipItemsExceptCaret(children, aBuilder, aClipFrame, aClip); 1.2391 + } 1.2392 + } 1.2393 +} 1.2394 + 1.2395 +static void 1.2396 +ClipListsExceptCaret(nsDisplayListCollection* aLists, 1.2397 + nsDisplayListBuilder* aBuilder, 1.2398 + nsIFrame* aClipFrame, 1.2399 + const DisplayItemClip& aClip) 1.2400 +{ 1.2401 + ::ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame, aClip); 1.2402 + ::ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame, aClip); 1.2403 + ::ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aClip); 1.2404 + ::ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame, aClip); 1.2405 + ::ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aClip); 1.2406 + ::ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aClip); 1.2407 +} 1.2408 + 1.2409 +static bool 1.2410 +DisplayportExceedsMaxTextureSize(nsPresContext* aPresContext, const nsRect& aDisplayPort) 1.2411 +{ 1.2412 +#ifdef MOZ_WIDGET_GONK 1.2413 + // On B2G we actively run into max texture size limits because the displayport-sizing code 1.2414 + // in the AsyncPanZoomController code is slightly busted (bug 957668 will fix it properly). 1.2415 + // We sometimes end up requesting displayports for which the corresponding layer will be 1.2416 + // larger than the max GL texture size (which we assume to be 4096 here). 1.2417 + // If we run into this case, we should just not use the displayport at all and fall back to 1.2418 + // just making a ScrollInfoLayer so that we use the APZC's synchronous scrolling fallback 1.2419 + // mechanism. 1.2420 + // Note also that if we don't do this here, it is quite likely that the parent B2G process 1.2421 + // will kill this child process to prevent OOMs (see the patch that landed as part of bug 1.2422 + // 965945 for details). 1.2423 + gfxSize resolution = aPresContext->PresShell()->GetCumulativeResolution(); 1.2424 + return (aPresContext->AppUnitsToDevPixels(aDisplayPort.width) * resolution.width > 4096) || 1.2425 + (aPresContext->AppUnitsToDevPixels(aDisplayPort.height) * resolution.height > 4096); 1.2426 +#else 1.2427 + return false; 1.2428 +#endif 1.2429 +} 1.2430 + 1.2431 +void 1.2432 +ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1.2433 + const nsRect& aDirtyRect, 1.2434 + const nsDisplayListSet& aLists) 1.2435 +{ 1.2436 + if (aBuilder->IsForImageVisibility()) { 1.2437 + mLastUpdateImagesPos = GetScrollPosition(); 1.2438 + } 1.2439 + 1.2440 + mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists); 1.2441 + 1.2442 + if (aBuilder->IsPaintingToWindow()) { 1.2443 + mScrollPosAtLastPaint = GetScrollPosition(); 1.2444 + if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) { 1.2445 + MarkInactive(); 1.2446 + } 1.2447 + if (IsScrollingActive()) { 1.2448 + if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) { 1.2449 + mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint; 1.2450 + } 1.2451 + } else { 1.2452 + mScrollPosForLayerPixelAlignment = nsPoint(-1,-1); 1.2453 + } 1.2454 + } 1.2455 + 1.2456 + // We put scrollbars in their own layers when this is the root scroll 1.2457 + // frame and we are a toplevel content document. In this situation, the 1.2458 + // scrollbar(s) would normally be assigned their own layer anyway, since 1.2459 + // they're not scrolled with the rest of the document. But when both 1.2460 + // scrollbars are visible, the layer's visible rectangle would be the size 1.2461 + // of the viewport, so most layer implementations would create a layer buffer 1.2462 + // that's much larger than necessary. Creating independent layers for each 1.2463 + // scrollbar works around the problem. 1.2464 + bool createLayersForScrollbars = mIsRoot && 1.2465 + mOuter->PresContext()->IsRootContentDocument(); 1.2466 + 1.2467 + if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) { 1.2468 + 1.2469 + // If we are a root scroll frame that has a display port we want to add 1.2470 + // scrollbars, they will be children of the scrollable layer, but they get 1.2471 + // adjusted by the APZC automatically. 1.2472 + bool addScrollBars = mIsRoot && 1.2473 + nsLayoutUtils::GetDisplayPort(mOuter->GetContent()) && 1.2474 + !aBuilder->IsForEventDelivery(); 1.2475 + // For now, don't add them for display root documents, cause we've never 1.2476 + // had them there. 1.2477 + if (aBuilder->RootReferenceFrame()->PresContext() == mOuter->PresContext()) { 1.2478 + addScrollBars = false; 1.2479 + } 1.2480 + 1.2481 + if (addScrollBars) { 1.2482 + // Add classic scrollbars. 1.2483 + AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars, 1.2484 + false); 1.2485 + } 1.2486 + 1.2487 + // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner. 1.2488 + // The scrolled frame shouldn't have its own background/border, so we 1.2489 + // can just pass aLists directly. 1.2490 + mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, 1.2491 + aDirtyRect, aLists); 1.2492 + 1.2493 +#ifdef MOZ_WIDGET_GONK 1.2494 + // TODO: only layerize the overlay scrollbars if this scrollframe can be 1.2495 + // panned asynchronously. For now just always layerize on B2G because. 1.2496 + // that's where we want the layerized scrollbars 1.2497 + createLayersForScrollbars = true; 1.2498 +#endif 1.2499 + if (addScrollBars) { 1.2500 + // Add overlay scrollbars. 1.2501 + AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars, 1.2502 + true); 1.2503 + } 1.2504 + 1.2505 + return; 1.2506 + } 1.2507 + 1.2508 + // Now display the scrollbars and scrollcorner. These parts are drawn 1.2509 + // in the border-background layer, on top of our own background and 1.2510 + // borders and underneath borders and backgrounds of later elements 1.2511 + // in the tree. 1.2512 + // Note that this does not apply for overlay scrollbars; those are drawn 1.2513 + // in the positioned-elements layer on top of everything else by the call 1.2514 + // to AppendScrollPartsTo(..., true) further down. 1.2515 + AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, createLayersForScrollbars, 1.2516 + false); 1.2517 + 1.2518 + // Overflow clipping can never clip frames outside our subtree, so there 1.2519 + // is no need to worry about whether we are a moving frame that might clip 1.2520 + // non-moving frames. 1.2521 + // Not all our descendants will be clipped by overflow clipping, but all 1.2522 + // the ones that aren't clipped will be out of flow frames that have already 1.2523 + // had dirty rects saved for them by their parent frames calling 1.2524 + // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our 1.2525 + // dirty rect here. 1.2526 + nsRect dirtyRect = aDirtyRect.Intersect(mScrollPort); 1.2527 + 1.2528 + nsRect displayPort; 1.2529 + bool usingDisplayport = false; 1.2530 + if (!aBuilder->IsForEventDelivery()) { 1.2531 + if (!mIsRoot) { 1.2532 + // For a non-root scroll frame, override the value of the display port 1.2533 + // base rect, and possibly create a display port if there isn't one 1.2534 + // already. For root scroll frame, nsLayoutUtils::PaintFrame or 1.2535 + // nsSubDocumentFrame::BuildDisplayList takes care of this. 1.2536 + nsRect displayportBase = dirtyRect; 1.2537 + usingDisplayport = nsLayoutUtils::GetOrMaybeCreateDisplayPort( 1.2538 + *aBuilder, mOuter, displayportBase, &displayPort); 1.2539 + } else { 1.2540 + // For a root frmae, just get the value of the existing of the display 1.2541 + // port, if any. 1.2542 + usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort); 1.2543 + } 1.2544 + 1.2545 + if (usingDisplayport && DisplayportExceedsMaxTextureSize(mOuter->PresContext(), displayPort)) { 1.2546 + usingDisplayport = false; 1.2547 + } 1.2548 + 1.2549 + // Override the dirty rectangle if the displayport has been set. 1.2550 + if (usingDisplayport) { 1.2551 + dirtyRect = displayPort; 1.2552 + } 1.2553 + } 1.2554 + 1.2555 + if (aBuilder->IsForImageVisibility()) { 1.2556 + // We expand the dirty rect to catch images just outside of the scroll port. 1.2557 + // We use the dirty rect instead of the whole scroll port to prevent 1.2558 + // too much expansion in the presence of very large (bigger than the 1.2559 + // viewport) scroll ports. 1.2560 + dirtyRect = ExpandRect(dirtyRect); 1.2561 + } 1.2562 + 1.2563 + // Since making new layers is expensive, only use nsDisplayScrollLayer 1.2564 + // if the area is scrollable and we're the content process (unless we're on 1.2565 + // B2G, where we support async scrolling for scrollable elements in the 1.2566 + // parent process as well). 1.2567 + // When a displayport is being used, force building of a layer so that 1.2568 + // CompositorParent can always find the scrollable layer for the root content 1.2569 + // document. 1.2570 + // If the element is marked 'scrollgrab', also force building of a layer 1.2571 + // so that APZ can implement scroll grabbing. 1.2572 + mShouldBuildScrollableLayer = usingDisplayport || nsContentUtils::HasScrollgrab(mOuter->GetContent()); 1.2573 + bool shouldBuildLayer = false; 1.2574 + if (mShouldBuildScrollableLayer) { 1.2575 + shouldBuildLayer = true; 1.2576 + } else { 1.2577 + shouldBuildLayer = 1.2578 + nsLayoutUtils::WantSubAPZC() && 1.2579 + WantAsyncScroll() && 1.2580 + // If we are the root scroll frame for the display root then we don't need a scroll 1.2581 + // info layer to make a RecordFrameMetrics call for us as 1.2582 + // nsDisplayList::PaintForFrame already calls RecordFrameMetrics for us. 1.2583 + (!mIsRoot || aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext()); 1.2584 + } 1.2585 + 1.2586 + nsDisplayListCollection scrolledContent; 1.2587 + { 1.2588 + // Note that setting the current scroll parent id here means that positioned children 1.2589 + // of this scroll info layer will pick up the scroll info layer as their scroll handoff 1.2590 + // parent. This is intentional because that is what happens for positioned children 1.2591 + // of scroll layers, and we want to maintain consistent behaviour between scroll layers 1.2592 + // and scroll info layers. 1.2593 + nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter( 1.2594 + aBuilder, 1.2595 + shouldBuildLayer && mScrolledFrame->GetContent() 1.2596 + ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()) 1.2597 + : aBuilder->GetCurrentScrollParentId()); 1.2598 + DisplayListClipState::AutoSaveRestore clipState(aBuilder); 1.2599 + 1.2600 + if (usingDisplayport) { 1.2601 + nsRect clip = displayPort + aBuilder->ToReferenceFrame(mOuter); 1.2602 + 1.2603 + // If we are using a display port, then ignore any pre-existing clip 1.2604 + // passed down from our parents, and use only the clip computed here 1.2605 + // based on the display port. The pre-existing clip would just defeat 1.2606 + // the purpose of a display port which is to paint regions that are not 1.2607 + // currently visible so that they can be brought into view asynchronously. 1.2608 + // Notes: 1.2609 + // - The pre-existing clip state will be restored when the 1.2610 + // AutoSaveRestore goes out of scope, so there is no permanent change 1.2611 + // to this clip state. 1.2612 + // - We still set a clip to the scroll port further below where we 1.2613 + // build the scroll wrapper. This doesn't prevent us from painting 1.2614 + // the entire displayport, but it lets the compositor know to 1.2615 + // clip to the scroll port after compositing. 1.2616 + clipState.Clear(); 1.2617 + 1.2618 + if (mClipAllDescendants) { 1.2619 + clipState.ClipContentDescendants(clip); 1.2620 + } else { 1.2621 + clipState.ClipContainingBlockDescendants(clip, nullptr); 1.2622 + } 1.2623 + } else { 1.2624 + nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter); 1.2625 + nscoord radii[8]; 1.2626 + bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii); 1.2627 + // Our override of GetBorderRadii ensures we never have a radius at 1.2628 + // the corners where we have a scrollbar. 1.2629 + if (mClipAllDescendants) { 1.2630 + clipState.ClipContentDescendants(clip, haveRadii ? radii : nullptr); 1.2631 + } else { 1.2632 + clipState.ClipContainingBlockDescendants(clip, haveRadii ? radii : nullptr); 1.2633 + } 1.2634 + } 1.2635 + 1.2636 + mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, scrolledContent); 1.2637 + } 1.2638 + 1.2639 + if (MOZ_UNLIKELY(mOuter->StyleDisplay()->mOverflowClipBox == 1.2640 + NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) { 1.2641 + // We only clip if there is *scrollable* overflow, to avoid clipping 1.2642 + // *visual* overflow unnecessarily. 1.2643 + nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter); 1.2644 + nsRect so = mScrolledFrame->GetScrollableOverflowRect(); 1.2645 + if (clipRect.width != so.width || clipRect.height != so.height || 1.2646 + so.x < 0 || so.y < 0) { 1.2647 + // The 'scrolledContent' items are clipped to the padding-box at this point. 1.2648 + // Now clip them again to the content-box, except the nsDisplayCaret item 1.2649 + // which we allow to overflow the content-box in various situations -- 1.2650 + // see ::ClipItemsExceptCaret. 1.2651 + clipRect.Deflate(mOuter->GetUsedPadding()); 1.2652 + DisplayItemClip clip; 1.2653 + clip.SetTo(clipRect); 1.2654 + ::ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame, clip); 1.2655 + } 1.2656 + } 1.2657 + 1.2658 + if (shouldBuildLayer) { 1.2659 + // ScrollLayerWrapper must always be created because it initializes the 1.2660 + // scroll layer count. The display lists depend on this. 1.2661 + ScrollLayerWrapper wrapper(mOuter, mScrolledFrame); 1.2662 + 1.2663 + if (mShouldBuildScrollableLayer) { 1.2664 + DisplayListClipState::AutoSaveRestore clipState(aBuilder); 1.2665 + 1.2666 + // For root scrollframes in documents where the CSS viewport has been 1.2667 + // modified, the CSS viewport no longer corresponds to what is visible, 1.2668 + // so we don't want to clip the content to it. For root scrollframes 1.2669 + // in documents where the CSS viewport is NOT modified, the mScrollPort 1.2670 + // is the same as the CSS viewport, modulo scrollbars. 1.2671 + if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) { 1.2672 + nsRect clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter); 1.2673 + if (mClipAllDescendants) { 1.2674 + clipState.ClipContentDescendants(clip); 1.2675 + } else { 1.2676 + clipState.ClipContainingBlockDescendants(clip); 1.2677 + } 1.2678 + } 1.2679 + 1.2680 + // Once a displayport is set, assume that scrolling needs to be fast 1.2681 + // so create a layer with all the content inside. The compositor 1.2682 + // process will be able to scroll the content asynchronously. 1.2683 + wrapper.WrapListsInPlace(aBuilder, mOuter, scrolledContent); 1.2684 + } 1.2685 + 1.2686 + // In case we are not using displayport or the nsDisplayScrollLayers are 1.2687 + // flattened during visibility computation, we still need to export the 1.2688 + // metadata about this scroll box to the compositor process. 1.2689 + nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer( 1.2690 + aBuilder, mScrolledFrame, mOuter); 1.2691 + scrolledContent.BorderBackground()->AppendNewToBottom(layerItem); 1.2692 + } 1.2693 + // Now display overlay scrollbars and the resizer, if we have one. 1.2694 +#ifdef MOZ_WIDGET_GONK 1.2695 + // TODO: only layerize the overlay scrollbars if this scrollframe can be 1.2696 + // panned asynchronously. For now just always layerize on B2G because. 1.2697 + // that's where we want the layerized scrollbars 1.2698 + createLayersForScrollbars = true; 1.2699 +#endif 1.2700 + AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent, 1.2701 + createLayersForScrollbars, true); 1.2702 + scrolledContent.MoveTo(aLists); 1.2703 +} 1.2704 + 1.2705 +bool 1.2706 +ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const 1.2707 +{ 1.2708 + // Use the right rect depending on if a display port is set. 1.2709 + nsRect displayPort; 1.2710 + bool usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort); 1.2711 + return aRect.Intersects(ExpandRect(usingDisplayport ? displayPort : mScrollPort)); 1.2712 +} 1.2713 + 1.2714 +static void HandleScrollPref(nsIScrollable *aScrollable, int32_t aOrientation, 1.2715 + uint8_t& aValue) 1.2716 +{ 1.2717 + int32_t pref; 1.2718 + aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref); 1.2719 + switch (pref) { 1.2720 + case nsIScrollable::Scrollbar_Auto: 1.2721 + // leave |aValue| untouched 1.2722 + break; 1.2723 + case nsIScrollable::Scrollbar_Never: 1.2724 + aValue = NS_STYLE_OVERFLOW_HIDDEN; 1.2725 + break; 1.2726 + case nsIScrollable::Scrollbar_Always: 1.2727 + aValue = NS_STYLE_OVERFLOW_SCROLL; 1.2728 + break; 1.2729 + } 1.2730 +} 1.2731 + 1.2732 +ScrollbarStyles 1.2733 +ScrollFrameHelper::GetScrollbarStylesFromFrame() const 1.2734 +{ 1.2735 + nsPresContext* presContext = mOuter->PresContext(); 1.2736 + if (!presContext->IsDynamic() && 1.2737 + !(mIsRoot && presContext->HasPaginatedScrolling())) { 1.2738 + return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN); 1.2739 + } 1.2740 + 1.2741 + if (!mIsRoot) { 1.2742 + const nsStyleDisplay* disp = mOuter->StyleDisplay(); 1.2743 + return ScrollbarStyles(disp->mOverflowX, disp->mOverflowY); 1.2744 + } 1.2745 + 1.2746 + ScrollbarStyles result = presContext->GetViewportOverflowOverride(); 1.2747 + nsCOMPtr<nsISupports> container = presContext->GetContainerWeak(); 1.2748 + nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container); 1.2749 + if (scrollable) { 1.2750 + HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X, 1.2751 + result.mHorizontal); 1.2752 + HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y, 1.2753 + result.mVertical); 1.2754 + } 1.2755 + return result; 1.2756 +} 1.2757 + 1.2758 +nsRect 1.2759 +ScrollFrameHelper::GetScrollRange() const 1.2760 +{ 1.2761 + return GetScrollRange(mScrollPort.width, mScrollPort.height); 1.2762 +} 1.2763 + 1.2764 +nsRect 1.2765 +ScrollFrameHelper::GetScrollRange(nscoord aWidth, nscoord aHeight) const 1.2766 +{ 1.2767 + nsRect range = GetScrolledRect(); 1.2768 + range.width = std::max(range.width - aWidth, 0); 1.2769 + range.height = std::max(range.height - aHeight, 0); 1.2770 + return range; 1.2771 +} 1.2772 + 1.2773 +nsRect 1.2774 +ScrollFrameHelper::GetScrollRangeForClamping() const 1.2775 +{ 1.2776 + if (!ShouldClampScrollPosition()) { 1.2777 + return nsRect(nscoord_MIN/2, nscoord_MIN/2, 1.2778 + nscoord_MAX - nscoord_MIN/2, nscoord_MAX - nscoord_MIN/2); 1.2779 + } 1.2780 + nsSize scrollPortSize = GetScrollPositionClampingScrollPortSize(); 1.2781 + return GetScrollRange(scrollPortSize.width, scrollPortSize.height); 1.2782 +} 1.2783 + 1.2784 +nsSize 1.2785 +ScrollFrameHelper::GetScrollPositionClampingScrollPortSize() const 1.2786 +{ 1.2787 + nsIPresShell* presShell = mOuter->PresContext()->PresShell(); 1.2788 + if (mIsRoot && presShell->IsScrollPositionClampingScrollPortSizeSet()) { 1.2789 + return presShell->GetScrollPositionClampingScrollPortSize(); 1.2790 + } 1.2791 + return mScrollPort.Size(); 1.2792 +} 1.2793 + 1.2794 +gfxSize 1.2795 +ScrollFrameHelper::GetResolution() const 1.2796 +{ 1.2797 + return mResolution; 1.2798 +} 1.2799 + 1.2800 +void 1.2801 +ScrollFrameHelper::SetResolution(const gfxSize& aResolution) 1.2802 +{ 1.2803 + mResolution = aResolution; 1.2804 + mIsResolutionSet = true; 1.2805 +} 1.2806 + 1.2807 +static void 1.2808 +AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord) 1.2809 +{ 1.2810 + if (aDelta < 0) { 1.2811 + *aCoord = nscoord_MIN; 1.2812 + } else if (aDelta > 0) { 1.2813 + *aCoord = nscoord_MAX; 1.2814 + } 1.2815 +} 1.2816 + 1.2817 +/** 1.2818 + * Calculate lower/upper scrollBy range in given direction. 1.2819 + * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size 1.2820 + * @param aPos desired destination in AppUnits 1.2821 + * @param aNeg/PosTolerance defines relative range distance 1.2822 + * below and above of aPos point 1.2823 + * @param aMultiplier used for conversion of tolerance into appUnis 1.2824 + */ 1.2825 +static void 1.2826 +CalcRangeForScrollBy(int32_t aDelta, nscoord aPos, 1.2827 + float aNegTolerance, 1.2828 + float aPosTolerance, 1.2829 + nscoord aMultiplier, 1.2830 + nscoord* aLower, nscoord* aUpper) 1.2831 +{ 1.2832 + if (!aDelta) { 1.2833 + *aLower = *aUpper = aPos; 1.2834 + return; 1.2835 + } 1.2836 + *aLower = aPos - NSToCoordRound(aMultiplier * (aDelta > 0 ? aNegTolerance : aPosTolerance)); 1.2837 + *aUpper = aPos + NSToCoordRound(aMultiplier * (aDelta > 0 ? aPosTolerance : aNegTolerance)); 1.2838 +} 1.2839 + 1.2840 +void 1.2841 +ScrollFrameHelper::ScrollBy(nsIntPoint aDelta, 1.2842 + nsIScrollableFrame::ScrollUnit aUnit, 1.2843 + nsIScrollableFrame::ScrollMode aMode, 1.2844 + nsIntPoint* aOverflow, 1.2845 + nsIAtom *aOrigin) 1.2846 +{ 1.2847 + nsSize deltaMultiplier; 1.2848 + float negativeTolerance; 1.2849 + float positiveTolerance; 1.2850 + if (!aOrigin){ 1.2851 + aOrigin = nsGkAtoms::other; 1.2852 + } 1.2853 + bool isGenericOrigin = (aOrigin == nsGkAtoms::other); 1.2854 + switch (aUnit) { 1.2855 + case nsIScrollableFrame::DEVICE_PIXELS: { 1.2856 + nscoord appUnitsPerDevPixel = 1.2857 + mOuter->PresContext()->AppUnitsPerDevPixel(); 1.2858 + deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel); 1.2859 + if (isGenericOrigin){ 1.2860 + aOrigin = nsGkAtoms::pixels; 1.2861 + } 1.2862 + negativeTolerance = positiveTolerance = 0.5f; 1.2863 + break; 1.2864 + } 1.2865 + case nsIScrollableFrame::LINES: { 1.2866 + deltaMultiplier = GetLineScrollAmount(); 1.2867 + if (isGenericOrigin){ 1.2868 + aOrigin = nsGkAtoms::lines; 1.2869 + } 1.2870 + negativeTolerance = positiveTolerance = 0.1f; 1.2871 + break; 1.2872 + } 1.2873 + case nsIScrollableFrame::PAGES: { 1.2874 + deltaMultiplier = GetPageScrollAmount(); 1.2875 + if (isGenericOrigin){ 1.2876 + aOrigin = nsGkAtoms::pages; 1.2877 + } 1.2878 + negativeTolerance = 0.05f; 1.2879 + positiveTolerance = 0; 1.2880 + break; 1.2881 + } 1.2882 + case nsIScrollableFrame::WHOLE: { 1.2883 + nsPoint pos = GetScrollPosition(); 1.2884 + AdjustForWholeDelta(aDelta.x, &pos.x); 1.2885 + AdjustForWholeDelta(aDelta.y, &pos.y); 1.2886 + ScrollTo(pos, aMode); 1.2887 + // 'this' might be destroyed here 1.2888 + if (aOverflow) { 1.2889 + *aOverflow = nsIntPoint(0, 0); 1.2890 + } 1.2891 + return; 1.2892 + } 1.2893 + default: 1.2894 + NS_ERROR("Invalid scroll mode"); 1.2895 + return; 1.2896 + } 1.2897 + 1.2898 + nsPoint newPos = mDestination + 1.2899 + nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height); 1.2900 + // Calculate desired range values. 1.2901 + nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY; 1.2902 + CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance, 1.2903 + deltaMultiplier.width, &rangeLowerX, &rangeUpperX); 1.2904 + CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance, 1.2905 + deltaMultiplier.height, &rangeLowerY, &rangeUpperY); 1.2906 + nsRect range(rangeLowerX, 1.2907 + rangeLowerY, 1.2908 + rangeUpperX - rangeLowerX, 1.2909 + rangeUpperY - rangeLowerY); 1.2910 + nsWeakFrame weakFrame(mOuter); 1.2911 + ScrollToWithOrigin(newPos, aMode, aOrigin, &range); 1.2912 + if (!weakFrame.IsAlive()) { 1.2913 + return; 1.2914 + } 1.2915 + 1.2916 + if (aOverflow) { 1.2917 + nsPoint clampAmount = newPos - mDestination; 1.2918 + float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel(); 1.2919 + *aOverflow = nsIntPoint( 1.2920 + NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel), 1.2921 + NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel)); 1.2922 + } 1.2923 +} 1.2924 + 1.2925 +nsSize 1.2926 +ScrollFrameHelper::GetLineScrollAmount() const 1.2927 +{ 1.2928 + nsRefPtr<nsFontMetrics> fm; 1.2929 + nsLayoutUtils::GetFontMetricsForFrame(mOuter, getter_AddRefs(fm), 1.2930 + nsLayoutUtils::FontSizeInflationFor(mOuter)); 1.2931 + NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit"); 1.2932 + static nscoord sMinLineScrollAmountInPixels = -1; 1.2933 + if (sMinLineScrollAmountInPixels < 0) { 1.2934 + Preferences::AddIntVarCache(&sMinLineScrollAmountInPixels, 1.2935 + "mousewheel.min_line_scroll_amount", 1); 1.2936 + } 1.2937 + int32_t appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel(); 1.2938 + nscoord minScrollAmountInAppUnits = 1.2939 + std::max(1, sMinLineScrollAmountInPixels) * appUnitsPerDevPixel; 1.2940 + nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0; 1.2941 + nscoord verticalAmount = fm ? fm->MaxHeight() : 0; 1.2942 + return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits), 1.2943 + std::max(verticalAmount, minScrollAmountInAppUnits)); 1.2944 +} 1.2945 + 1.2946 +/** 1.2947 + * Compute the scrollport size excluding any fixed-pos headers and 1.2948 + * footers. A header or footer is an box that spans that entire width 1.2949 + * of the viewport and touches the top (or bottom, respectively) of the 1.2950 + * viewport. We also want to consider fixed elements that stack or overlap 1.2951 + * to effectively create a larger header or footer. Headers and footers that 1.2952 + * cover more than a third of the the viewport are ignored since they 1.2953 + * probably aren't true headers and footers and we don't want to restrict 1.2954 + * scrolling too much in such cases. This is a bit conservative --- some 1.2955 + * pages use elements as headers or footers that don't span the entire width 1.2956 + * of the viewport --- but it should be a good start. 1.2957 + */ 1.2958 +struct TopAndBottom 1.2959 +{ 1.2960 + TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {} 1.2961 + 1.2962 + nscoord top, bottom; 1.2963 +}; 1.2964 +struct TopComparator 1.2965 +{ 1.2966 + bool Equals(const TopAndBottom& A, const TopAndBottom& B) const { 1.2967 + return A.top == B.top; 1.2968 + } 1.2969 + bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const { 1.2970 + return A.top < B.top; 1.2971 + } 1.2972 +}; 1.2973 +struct ReverseBottomComparator 1.2974 +{ 1.2975 + bool Equals(const TopAndBottom& A, const TopAndBottom& B) const { 1.2976 + return A.bottom == B.bottom; 1.2977 + } 1.2978 + bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const { 1.2979 + return A.bottom > B.bottom; 1.2980 + } 1.2981 +}; 1.2982 +static nsSize 1.2983 +GetScrollPortSizeExcludingHeadersAndFooters(nsIFrame* aViewportFrame, 1.2984 + const nsRect& aScrollPort) 1.2985 +{ 1.2986 + nsTArray<TopAndBottom> list; 1.2987 + nsFrameList fixedFrames = aViewportFrame->GetChildList(nsIFrame::kFixedList); 1.2988 + for (nsFrameList::Enumerator iterator(fixedFrames); !iterator.AtEnd(); 1.2989 + iterator.Next()) { 1.2990 + nsIFrame* f = iterator.get(); 1.2991 + nsRect r = f->GetRect().Intersect(aScrollPort); 1.2992 + if (r.x == 0 && r.width == aScrollPort.width && 1.2993 + r.height <= aScrollPort.height/3) { 1.2994 + list.AppendElement(TopAndBottom(r.y, r.YMost())); 1.2995 + } 1.2996 + } 1.2997 + 1.2998 + list.Sort(TopComparator()); 1.2999 + nscoord headerBottom = 0; 1.3000 + for (uint32_t i = 0; i < list.Length(); ++i) { 1.3001 + if (list[i].top <= headerBottom) { 1.3002 + headerBottom = std::max(headerBottom, list[i].bottom); 1.3003 + } 1.3004 + } 1.3005 + 1.3006 + list.Sort(ReverseBottomComparator()); 1.3007 + nscoord footerTop = aScrollPort.height; 1.3008 + for (uint32_t i = 0; i < list.Length(); ++i) { 1.3009 + if (list[i].bottom >= footerTop) { 1.3010 + footerTop = std::min(footerTop, list[i].top); 1.3011 + } 1.3012 + } 1.3013 + 1.3014 + headerBottom = std::min(aScrollPort.height/3, headerBottom); 1.3015 + footerTop = std::max(aScrollPort.height - aScrollPort.height/3, footerTop); 1.3016 + 1.3017 + return nsSize(aScrollPort.width, footerTop - headerBottom); 1.3018 +} 1.3019 + 1.3020 +nsSize 1.3021 +ScrollFrameHelper::GetPageScrollAmount() const 1.3022 +{ 1.3023 + nsSize lineScrollAmount = GetLineScrollAmount(); 1.3024 + nsSize effectiveScrollPortSize; 1.3025 + if (mIsRoot) { 1.3026 + // Reduce effective scrollport height by the height of any fixed-pos 1.3027 + // headers or footers 1.3028 + nsIFrame* root = mOuter->PresContext()->PresShell()->GetRootFrame(); 1.3029 + effectiveScrollPortSize = 1.3030 + GetScrollPortSizeExcludingHeadersAndFooters(root, mScrollPort); 1.3031 + } else { 1.3032 + effectiveScrollPortSize = mScrollPort.Size(); 1.3033 + } 1.3034 + // The page increment is the size of the page, minus the smaller of 1.3035 + // 10% of the size or 2 lines. 1.3036 + return nsSize( 1.3037 + effectiveScrollPortSize.width - 1.3038 + std::min(effectiveScrollPortSize.width/10, 2*lineScrollAmount.width), 1.3039 + effectiveScrollPortSize.height - 1.3040 + std::min(effectiveScrollPortSize.height/10, 2*lineScrollAmount.height)); 1.3041 +} 1.3042 + 1.3043 + /** 1.3044 + * this code is resposible for restoring the scroll position back to some 1.3045 + * saved position. if the user has not moved the scroll position manually 1.3046 + * we keep scrolling down until we get to our original position. keep in 1.3047 + * mind that content could incrementally be coming in. we only want to stop 1.3048 + * when we reach our new position. 1.3049 + */ 1.3050 +void 1.3051 +ScrollFrameHelper::ScrollToRestoredPosition() 1.3052 +{ 1.3053 + if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) { 1.3054 + return; 1.3055 + } 1.3056 + // make sure our scroll position did not change for where we last put 1.3057 + // it. if it does then the user must have moved it, and we no longer 1.3058 + // need to restore. 1.3059 + // 1.3060 + // In the RTL case, we check whether the scroll position changed using the 1.3061 + // logical scroll position, but we scroll to the physical scroll position in 1.3062 + // all cases 1.3063 + 1.3064 + // if we didn't move, we still need to restore 1.3065 + if (GetLogicalScrollPosition() == mLastPos) { 1.3066 + // if our desired position is different to the scroll position, scroll. 1.3067 + // remember that we could be incrementally loading so we may enter 1.3068 + // and scroll many times. 1.3069 + if (mRestorePos != mLastPos /* GetLogicalScrollPosition() */) { 1.3070 + nsPoint scrollToPos = mRestorePos; 1.3071 + if (!IsLTR()) 1.3072 + // convert from logical to physical scroll position 1.3073 + scrollToPos.x = mScrollPort.x - 1.3074 + (mScrollPort.XMost() - scrollToPos.x - mScrolledFrame->GetRect().width); 1.3075 + nsWeakFrame weakFrame(mOuter); 1.3076 + ScrollTo(scrollToPos, nsIScrollableFrame::INSTANT); 1.3077 + if (!weakFrame.IsAlive()) { 1.3078 + return; 1.3079 + } 1.3080 + // Re-get the scroll position, it might not be exactly equal to 1.3081 + // mRestorePos due to rounding and clamping. 1.3082 + mLastPos = GetLogicalScrollPosition(); 1.3083 + } else { 1.3084 + // if we reached the position then stop 1.3085 + mRestorePos.y = -1; 1.3086 + mLastPos.x = -1; 1.3087 + mLastPos.y = -1; 1.3088 + } 1.3089 + } else { 1.3090 + // user moved the position, so we won't need to restore 1.3091 + mLastPos.x = -1; 1.3092 + mLastPos.y = -1; 1.3093 + } 1.3094 +} 1.3095 + 1.3096 +nsresult 1.3097 +ScrollFrameHelper::FireScrollPortEvent() 1.3098 +{ 1.3099 + mAsyncScrollPortEvent.Forget(); 1.3100 + 1.3101 + // Keep this in sync with PostOverflowEvent(). 1.3102 + nsSize scrollportSize = mScrollPort.Size(); 1.3103 + nsSize childSize = GetScrolledRect().Size(); 1.3104 + 1.3105 + bool newVerticalOverflow = childSize.height > scrollportSize.height; 1.3106 + bool vertChanged = mVerticalOverflow != newVerticalOverflow; 1.3107 + 1.3108 + bool newHorizontalOverflow = childSize.width > scrollportSize.width; 1.3109 + bool horizChanged = mHorizontalOverflow != newHorizontalOverflow; 1.3110 + 1.3111 + if (!vertChanged && !horizChanged) { 1.3112 + return NS_OK; 1.3113 + } 1.3114 + 1.3115 + // If both either overflowed or underflowed then we dispatch only one 1.3116 + // DOM event. 1.3117 + bool both = vertChanged && horizChanged && 1.3118 + newVerticalOverflow == newHorizontalOverflow; 1.3119 + InternalScrollPortEvent::orientType orient; 1.3120 + if (both) { 1.3121 + orient = InternalScrollPortEvent::both; 1.3122 + mHorizontalOverflow = newHorizontalOverflow; 1.3123 + mVerticalOverflow = newVerticalOverflow; 1.3124 + } 1.3125 + else if (vertChanged) { 1.3126 + orient = InternalScrollPortEvent::vertical; 1.3127 + mVerticalOverflow = newVerticalOverflow; 1.3128 + if (horizChanged) { 1.3129 + // We need to dispatch a separate horizontal DOM event. Do that the next 1.3130 + // time around since dispatching the vertical DOM event might destroy 1.3131 + // the frame. 1.3132 + PostOverflowEvent(); 1.3133 + } 1.3134 + } 1.3135 + else { 1.3136 + orient = InternalScrollPortEvent::horizontal; 1.3137 + mHorizontalOverflow = newHorizontalOverflow; 1.3138 + } 1.3139 + 1.3140 + InternalScrollPortEvent event(true, 1.3141 + (orient == InternalScrollPortEvent::horizontal ? mHorizontalOverflow : 1.3142 + mVerticalOverflow) ? 1.3143 + NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW, nullptr); 1.3144 + event.orient = orient; 1.3145 + return EventDispatcher::Dispatch(mOuter->GetContent(), 1.3146 + mOuter->PresContext(), &event); 1.3147 +} 1.3148 + 1.3149 +void 1.3150 +ScrollFrameHelper::ReloadChildFrames() 1.3151 +{ 1.3152 + mScrolledFrame = nullptr; 1.3153 + mHScrollbarBox = nullptr; 1.3154 + mVScrollbarBox = nullptr; 1.3155 + mScrollCornerBox = nullptr; 1.3156 + mResizerBox = nullptr; 1.3157 + 1.3158 + nsIFrame* frame = mOuter->GetFirstPrincipalChild(); 1.3159 + while (frame) { 1.3160 + nsIContent* content = frame->GetContent(); 1.3161 + if (content == mOuter->GetContent()) { 1.3162 + NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame"); 1.3163 + mScrolledFrame = frame; 1.3164 + } else { 1.3165 + nsAutoString value; 1.3166 + content->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, value); 1.3167 + if (!value.IsEmpty()) { 1.3168 + // probably a scrollbar then 1.3169 + if (value.LowerCaseEqualsLiteral("horizontal")) { 1.3170 + NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?"); 1.3171 + mHScrollbarBox = frame; 1.3172 + } else { 1.3173 + NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?"); 1.3174 + mVScrollbarBox = frame; 1.3175 + } 1.3176 + } else if (content->Tag() == nsGkAtoms::resizer) { 1.3177 + NS_ASSERTION(!mResizerBox, "Found multiple resizers"); 1.3178 + mResizerBox = frame; 1.3179 + } else if (content->Tag() == nsGkAtoms::scrollcorner) { 1.3180 + // probably a scrollcorner 1.3181 + NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners"); 1.3182 + mScrollCornerBox = frame; 1.3183 + } 1.3184 + } 1.3185 + 1.3186 + frame = frame->GetNextSibling(); 1.3187 + } 1.3188 +} 1.3189 + 1.3190 +nsresult 1.3191 +ScrollFrameHelper::CreateAnonymousContent( 1.3192 + nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements) 1.3193 +{ 1.3194 + nsPresContext* presContext = mOuter->PresContext(); 1.3195 + nsIFrame* parent = mOuter->GetParent(); 1.3196 + 1.3197 + // Don't create scrollbars if we're an SVG document being used as an image, 1.3198 + // or if we're printing/print previewing. 1.3199 + // (In the printing case, we allow scrollbars if this is the child of the 1.3200 + // viewport & paginated scrolling is enabled, because then we must be the 1.3201 + // scroll frame for the print preview window, & that does need scrollbars.) 1.3202 + if (presContext->Document()->IsBeingUsedAsImage() || 1.3203 + (!presContext->IsDynamic() && 1.3204 + !(mIsRoot && presContext->HasPaginatedScrolling()))) { 1.3205 + mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true; 1.3206 + return NS_OK; 1.3207 + } 1.3208 + 1.3209 + // Check if the frame is resizable. 1.3210 + int8_t resizeStyle = mOuter->StyleDisplay()->mResize; 1.3211 + bool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE; 1.3212 + 1.3213 + nsIScrollableFrame *scrollable = do_QueryFrame(mOuter); 1.3214 + 1.3215 + // If we're the scrollframe for the root, then we want to construct 1.3216 + // our scrollbar frames no matter what. That way later dynamic 1.3217 + // changes to propagated overflow styles will show or hide 1.3218 + // scrollbars on the viewport without requiring frame reconstruction 1.3219 + // of the viewport (good!). 1.3220 + bool canHaveHorizontal; 1.3221 + bool canHaveVertical; 1.3222 + if (!mIsRoot) { 1.3223 + ScrollbarStyles styles = scrollable->GetScrollbarStyles(); 1.3224 + canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN; 1.3225 + canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN; 1.3226 + if (!canHaveHorizontal && !canHaveVertical && !isResizable) { 1.3227 + // Nothing to do. 1.3228 + return NS_OK; 1.3229 + } 1.3230 + } else { 1.3231 + canHaveHorizontal = true; 1.3232 + canHaveVertical = true; 1.3233 + } 1.3234 + 1.3235 + // The anonymous <div> used by <inputs> never gets scrollbars. 1.3236 + nsITextControlFrame* textFrame = do_QueryFrame(parent); 1.3237 + if (textFrame) { 1.3238 + // Make sure we are not a text area. 1.3239 + nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent())); 1.3240 + if (!textAreaElement) { 1.3241 + mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true; 1.3242 + return NS_OK; 1.3243 + } 1.3244 + } 1.3245 + 1.3246 + nsNodeInfoManager *nodeInfoManager = 1.3247 + presContext->Document()->NodeInfoManager(); 1.3248 + nsCOMPtr<nsINodeInfo> nodeInfo; 1.3249 + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nullptr, 1.3250 + kNameSpaceID_XUL, 1.3251 + nsIDOMNode::ELEMENT_NODE); 1.3252 + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); 1.3253 + 1.3254 + if (canHaveHorizontal) { 1.3255 + nsCOMPtr<nsINodeInfo> ni = nodeInfo; 1.3256 + NS_TrustedNewXULElement(getter_AddRefs(mHScrollbarContent), ni.forget()); 1.3257 + mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, 1.3258 + NS_LITERAL_STRING("horizontal"), false); 1.3259 + mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough, 1.3260 + NS_LITERAL_STRING("always"), false); 1.3261 + if (mIsRoot) { 1.3262 + mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, 1.3263 + NS_LITERAL_STRING("true"), false); 1.3264 + } 1.3265 + if (!aElements.AppendElement(mHScrollbarContent)) 1.3266 + return NS_ERROR_OUT_OF_MEMORY; 1.3267 + } 1.3268 + 1.3269 + if (canHaveVertical) { 1.3270 + nsCOMPtr<nsINodeInfo> ni = nodeInfo; 1.3271 + NS_TrustedNewXULElement(getter_AddRefs(mVScrollbarContent), ni.forget()); 1.3272 + mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, 1.3273 + NS_LITERAL_STRING("vertical"), false); 1.3274 + mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough, 1.3275 + NS_LITERAL_STRING("always"), false); 1.3276 + if (mIsRoot) { 1.3277 + mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, 1.3278 + NS_LITERAL_STRING("true"), false); 1.3279 + } 1.3280 + if (!aElements.AppendElement(mVScrollbarContent)) 1.3281 + return NS_ERROR_OUT_OF_MEMORY; 1.3282 + } 1.3283 + 1.3284 + if (isResizable) { 1.3285 + nsCOMPtr<nsINodeInfo> nodeInfo; 1.3286 + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nullptr, 1.3287 + kNameSpaceID_XUL, 1.3288 + nsIDOMNode::ELEMENT_NODE); 1.3289 + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); 1.3290 + 1.3291 + NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget()); 1.3292 + 1.3293 + nsAutoString dir; 1.3294 + switch (resizeStyle) { 1.3295 + case NS_STYLE_RESIZE_HORIZONTAL: 1.3296 + if (IsScrollbarOnRight()) { 1.3297 + dir.AssignLiteral("right"); 1.3298 + } 1.3299 + else { 1.3300 + dir.AssignLiteral("left"); 1.3301 + } 1.3302 + break; 1.3303 + case NS_STYLE_RESIZE_VERTICAL: 1.3304 + dir.AssignLiteral("bottom"); 1.3305 + break; 1.3306 + case NS_STYLE_RESIZE_BOTH: 1.3307 + dir.AssignLiteral("bottomend"); 1.3308 + break; 1.3309 + default: 1.3310 + NS_WARNING("only resizable types should have resizers"); 1.3311 + } 1.3312 + mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false); 1.3313 + 1.3314 + if (mIsRoot) { 1.3315 + nsIContent* browserRoot = GetBrowserRoot(mOuter->GetContent()); 1.3316 + mCollapsedResizer = !(browserRoot && 1.3317 + browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer)); 1.3318 + } 1.3319 + else { 1.3320 + mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element, 1.3321 + NS_LITERAL_STRING("_parent"), false); 1.3322 + } 1.3323 + 1.3324 + mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough, 1.3325 + NS_LITERAL_STRING("always"), false); 1.3326 + 1.3327 + if (!aElements.AppendElement(mResizerContent)) 1.3328 + return NS_ERROR_OUT_OF_MEMORY; 1.3329 + } 1.3330 + 1.3331 + if (canHaveHorizontal && canHaveVertical) { 1.3332 + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr, 1.3333 + kNameSpaceID_XUL, 1.3334 + nsIDOMNode::ELEMENT_NODE); 1.3335 + NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo.forget()); 1.3336 + if (!aElements.AppendElement(mScrollCornerContent)) 1.3337 + return NS_ERROR_OUT_OF_MEMORY; 1.3338 + } 1.3339 + 1.3340 + return NS_OK; 1.3341 +} 1.3342 + 1.3343 +void 1.3344 +ScrollFrameHelper::AppendAnonymousContentTo(nsBaseContentList& aElements, 1.3345 + uint32_t aFilter) 1.3346 +{ 1.3347 + aElements.MaybeAppendElement(mHScrollbarContent); 1.3348 + aElements.MaybeAppendElement(mVScrollbarContent); 1.3349 + aElements.MaybeAppendElement(mScrollCornerContent); 1.3350 + aElements.MaybeAppendElement(mResizerContent); 1.3351 +} 1.3352 + 1.3353 +void 1.3354 +ScrollFrameHelper::Destroy() 1.3355 +{ 1.3356 + if (mScrollbarActivity) { 1.3357 + mScrollbarActivity->Destroy(); 1.3358 + mScrollbarActivity = nullptr; 1.3359 + } 1.3360 + 1.3361 + // Unbind any content created in CreateAnonymousContent from the tree 1.3362 + nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent); 1.3363 + nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent); 1.3364 + nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent); 1.3365 + nsContentUtils::DestroyAnonymousContent(&mResizerContent); 1.3366 + 1.3367 + if (mPostedReflowCallback) { 1.3368 + mOuter->PresContext()->PresShell()->CancelReflowCallback(this); 1.3369 + mPostedReflowCallback = false; 1.3370 + } 1.3371 +} 1.3372 + 1.3373 +/** 1.3374 + * Called when we want to update the scrollbar position, either because scrolling happened 1.3375 + * or the user moved the scrollbar position and we need to undo that (e.g., when the user 1.3376 + * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back 1.3377 + * to its initial position for the start of the smooth sequence). 1.3378 + */ 1.3379 +void 1.3380 +ScrollFrameHelper::UpdateScrollbarPosition() 1.3381 +{ 1.3382 + nsWeakFrame weakFrame(mOuter); 1.3383 + mFrameIsUpdatingScrollbar = true; 1.3384 + 1.3385 + nsPoint pt = GetScrollPosition(); 1.3386 + if (mVScrollbarBox) { 1.3387 + SetCoordAttribute(mVScrollbarBox->GetContent(), nsGkAtoms::curpos, 1.3388 + pt.y - GetScrolledRect().y); 1.3389 + if (!weakFrame.IsAlive()) { 1.3390 + return; 1.3391 + } 1.3392 + } 1.3393 + if (mHScrollbarBox) { 1.3394 + SetCoordAttribute(mHScrollbarBox->GetContent(), nsGkAtoms::curpos, 1.3395 + pt.x - GetScrolledRect().x); 1.3396 + if (!weakFrame.IsAlive()) { 1.3397 + return; 1.3398 + } 1.3399 + } 1.3400 + 1.3401 + mFrameIsUpdatingScrollbar = false; 1.3402 +} 1.3403 + 1.3404 +void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent) 1.3405 +{ 1.3406 + NS_ASSERTION(aContent, "aContent must not be null"); 1.3407 + NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) || 1.3408 + (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent), 1.3409 + "unexpected child"); 1.3410 + 1.3411 + // Attribute changes on the scrollbars happen in one of three ways: 1.3412 + // 1) The scrollbar changed the attribute in response to some user event 1.3413 + // 2) We changed the attribute in response to a ScrollPositionDidChange 1.3414 + // callback from the scrolling view 1.3415 + // 3) We changed the attribute to adjust the scrollbars for the start 1.3416 + // of a smooth scroll operation 1.3417 + // 1.3418 + // In cases 2 and 3 we do not need to scroll because we're just 1.3419 + // updating our scrollbar. 1.3420 + if (mFrameIsUpdatingScrollbar) 1.3421 + return; 1.3422 + 1.3423 + nsRect scrolledRect = GetScrolledRect(); 1.3424 + 1.3425 + nsPoint current = GetScrollPosition() - scrolledRect.TopLeft(); 1.3426 + nsPoint dest; 1.3427 + nsRect allowedRange; 1.3428 + dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x, 1.3429 + &allowedRange.x, &allowedRange.width); 1.3430 + dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y, 1.3431 + &allowedRange.y, &allowedRange.height); 1.3432 + current += scrolledRect.TopLeft(); 1.3433 + dest += scrolledRect.TopLeft(); 1.3434 + allowedRange += scrolledRect.TopLeft(); 1.3435 + 1.3436 + // Don't try to scroll if we're already at an acceptable place. 1.3437 + // Don't call Contains here since Contains returns false when the point is 1.3438 + // on the bottom or right edge of the rectangle. 1.3439 + if (allowedRange.ClampPoint(current) == current) { 1.3440 + return; 1.3441 + } 1.3442 + 1.3443 + if (mScrollbarActivity) { 1.3444 + nsRefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity); 1.3445 + scrollbarActivity->ActivityOccurred(); 1.3446 + } 1.3447 + 1.3448 + bool isSmooth = aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth); 1.3449 + if (isSmooth) { 1.3450 + // Make sure an attribute-setting callback occurs even if the view 1.3451 + // didn't actually move yet. We need to make sure other listeners 1.3452 + // see that the scroll position is not (yet) what they thought it 1.3453 + // was. 1.3454 + nsWeakFrame weakFrame(mOuter); 1.3455 + UpdateScrollbarPosition(); 1.3456 + if (!weakFrame.IsAlive()) { 1.3457 + return; 1.3458 + } 1.3459 + } 1.3460 + ScrollToWithOrigin(dest, 1.3461 + isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT, 1.3462 + nsGkAtoms::scrollbars, &allowedRange); 1.3463 + // 'this' might be destroyed here 1.3464 +} 1.3465 + 1.3466 +/* ============= Scroll events ========== */ 1.3467 + 1.3468 +NS_IMETHODIMP 1.3469 +ScrollFrameHelper::ScrollEvent::Run() 1.3470 +{ 1.3471 + if (mHelper) 1.3472 + mHelper->FireScrollEvent(); 1.3473 + return NS_OK; 1.3474 +} 1.3475 + 1.3476 +void 1.3477 +ScrollFrameHelper::FireScrollEvent() 1.3478 +{ 1.3479 + mScrollEvent.Forget(); 1.3480 + 1.3481 + WidgetGUIEvent event(true, NS_SCROLL_EVENT, nullptr); 1.3482 + nsEventStatus status = nsEventStatus_eIgnore; 1.3483 + nsIContent* content = mOuter->GetContent(); 1.3484 + nsPresContext* prescontext = mOuter->PresContext(); 1.3485 + // Fire viewport scroll events at the document (where they 1.3486 + // will bubble to the window) 1.3487 + if (mIsRoot) { 1.3488 + nsIDocument* doc = content->GetCurrentDoc(); 1.3489 + if (doc) { 1.3490 + EventDispatcher::Dispatch(doc, prescontext, &event, nullptr, &status); 1.3491 + } 1.3492 + } else { 1.3493 + // scroll events fired at elements don't bubble (although scroll events 1.3494 + // fired at documents do, to the window) 1.3495 + event.mFlags.mBubbles = false; 1.3496 + EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status); 1.3497 + } 1.3498 +} 1.3499 + 1.3500 +void 1.3501 +ScrollFrameHelper::PostScrollEvent() 1.3502 +{ 1.3503 + if (mScrollEvent.IsPending()) 1.3504 + return; 1.3505 + 1.3506 + nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext(); 1.3507 + if (!rpc) 1.3508 + return; 1.3509 + mScrollEvent = new ScrollEvent(this); 1.3510 + rpc->AddWillPaintObserver(mScrollEvent.get()); 1.3511 +} 1.3512 + 1.3513 +NS_IMETHODIMP 1.3514 +ScrollFrameHelper::AsyncScrollPortEvent::Run() 1.3515 +{ 1.3516 + if (mHelper) { 1.3517 + mHelper->mOuter->PresContext()->GetPresShell()-> 1.3518 + FlushPendingNotifications(Flush_InterruptibleLayout); 1.3519 + } 1.3520 + return mHelper ? mHelper->FireScrollPortEvent() : NS_OK; 1.3521 +} 1.3522 + 1.3523 +bool 1.3524 +nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom) 1.3525 +{ 1.3526 + if (!mHelper.mHScrollbarBox) 1.3527 + return true; 1.3528 + 1.3529 + return AddRemoveScrollbar(aState, aOnBottom, true, true); 1.3530 +} 1.3531 + 1.3532 +bool 1.3533 +nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight) 1.3534 +{ 1.3535 + if (!mHelper.mVScrollbarBox) 1.3536 + return true; 1.3537 + 1.3538 + return AddRemoveScrollbar(aState, aOnRight, false, true); 1.3539 +} 1.3540 + 1.3541 +void 1.3542 +nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom) 1.3543 +{ 1.3544 + // removing a scrollbar should always fit 1.3545 + DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnBottom, true, false); 1.3546 + NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??"); 1.3547 +} 1.3548 + 1.3549 +void 1.3550 +nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight) 1.3551 +{ 1.3552 + // removing a scrollbar should always fit 1.3553 + DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnRight, false, false); 1.3554 + NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??"); 1.3555 +} 1.3556 + 1.3557 +bool 1.3558 +nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState, 1.3559 + bool aOnRightOrBottom, bool aHorizontal, bool aAdd) 1.3560 +{ 1.3561 + if (aHorizontal) { 1.3562 + if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox) 1.3563 + return false; 1.3564 + 1.3565 + nsSize hSize = mHelper.mHScrollbarBox->GetPrefSize(aState); 1.3566 + nsBox::AddMargin(mHelper.mHScrollbarBox, hSize); 1.3567 + 1.3568 + mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd); 1.3569 + 1.3570 + bool hasHorizontalScrollbar; 1.3571 + bool fit = AddRemoveScrollbar(hasHorizontalScrollbar, 1.3572 + mHelper.mScrollPort.y, 1.3573 + mHelper.mScrollPort.height, 1.3574 + hSize.height, aOnRightOrBottom, aAdd); 1.3575 + mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar; // because mHasHorizontalScrollbar is a bool 1.3576 + if (!fit) 1.3577 + mHelper.SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd); 1.3578 + 1.3579 + return fit; 1.3580 + } else { 1.3581 + if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox) 1.3582 + return false; 1.3583 + 1.3584 + nsSize vSize = mHelper.mVScrollbarBox->GetPrefSize(aState); 1.3585 + nsBox::AddMargin(mHelper.mVScrollbarBox, vSize); 1.3586 + 1.3587 + mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd); 1.3588 + 1.3589 + bool hasVerticalScrollbar; 1.3590 + bool fit = AddRemoveScrollbar(hasVerticalScrollbar, 1.3591 + mHelper.mScrollPort.x, 1.3592 + mHelper.mScrollPort.width, 1.3593 + vSize.width, aOnRightOrBottom, aAdd); 1.3594 + mHelper.mHasVerticalScrollbar = hasVerticalScrollbar; // because mHasVerticalScrollbar is a bool 1.3595 + if (!fit) 1.3596 + mHelper.SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd); 1.3597 + 1.3598 + return fit; 1.3599 + } 1.3600 +} 1.3601 + 1.3602 +bool 1.3603 +nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY, 1.3604 + nscoord& aSize, nscoord aSbSize, 1.3605 + bool aOnRightOrBottom, bool aAdd) 1.3606 +{ 1.3607 + nscoord size = aSize; 1.3608 + nscoord xy = aXY; 1.3609 + 1.3610 + if (size != NS_INTRINSICSIZE) { 1.3611 + if (aAdd) { 1.3612 + size -= aSbSize; 1.3613 + if (!aOnRightOrBottom && size >= 0) 1.3614 + xy += aSbSize; 1.3615 + } else { 1.3616 + size += aSbSize; 1.3617 + if (!aOnRightOrBottom) 1.3618 + xy -= aSbSize; 1.3619 + } 1.3620 + } 1.3621 + 1.3622 + // not enough room? Yes? Return true. 1.3623 + if (size >= 0) { 1.3624 + aHasScrollbar = aAdd; 1.3625 + aSize = size; 1.3626 + aXY = xy; 1.3627 + return true; 1.3628 + } 1.3629 + 1.3630 + aHasScrollbar = false; 1.3631 + return false; 1.3632 +} 1.3633 + 1.3634 +void 1.3635 +nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState, 1.3636 + const nsPoint& aScrollPosition) 1.3637 +{ 1.3638 + uint32_t oldflags = aState.LayoutFlags(); 1.3639 + nsRect childRect = nsRect(mHelper.mScrollPort.TopLeft() - aScrollPosition, 1.3640 + mHelper.mScrollPort.Size()); 1.3641 + int32_t flags = NS_FRAME_NO_MOVE_VIEW; 1.3642 + 1.3643 + nsSize minSize = mHelper.mScrolledFrame->GetMinSize(aState); 1.3644 + 1.3645 + if (minSize.height > childRect.height) 1.3646 + childRect.height = minSize.height; 1.3647 + 1.3648 + if (minSize.width > childRect.width) 1.3649 + childRect.width = minSize.width; 1.3650 + 1.3651 + aState.SetLayoutFlags(flags); 1.3652 + ClampAndSetBounds(aState, childRect, aScrollPosition); 1.3653 + mHelper.mScrolledFrame->Layout(aState); 1.3654 + 1.3655 + childRect = mHelper.mScrolledFrame->GetRect(); 1.3656 + 1.3657 + if (childRect.width < mHelper.mScrollPort.width || 1.3658 + childRect.height < mHelper.mScrollPort.height) 1.3659 + { 1.3660 + childRect.width = std::max(childRect.width, mHelper.mScrollPort.width); 1.3661 + childRect.height = std::max(childRect.height, mHelper.mScrollPort.height); 1.3662 + 1.3663 + // remove overflow areas when we update the bounds, 1.3664 + // because we've already accounted for it 1.3665 + // REVIEW: Have we accounted for both? 1.3666 + ClampAndSetBounds(aState, childRect, aScrollPosition, true); 1.3667 + } 1.3668 + 1.3669 + aState.SetLayoutFlags(oldflags); 1.3670 + 1.3671 +} 1.3672 + 1.3673 +void ScrollFrameHelper::PostOverflowEvent() 1.3674 +{ 1.3675 + if (mAsyncScrollPortEvent.IsPending()) 1.3676 + return; 1.3677 + 1.3678 + // Keep this in sync with FireScrollPortEvent(). 1.3679 + nsSize scrollportSize = mScrollPort.Size(); 1.3680 + nsSize childSize = GetScrolledRect().Size(); 1.3681 + 1.3682 + bool newVerticalOverflow = childSize.height > scrollportSize.height; 1.3683 + bool vertChanged = mVerticalOverflow != newVerticalOverflow; 1.3684 + 1.3685 + bool newHorizontalOverflow = childSize.width > scrollportSize.width; 1.3686 + bool horizChanged = mHorizontalOverflow != newHorizontalOverflow; 1.3687 + 1.3688 + if (!vertChanged && !horizChanged) { 1.3689 + return; 1.3690 + } 1.3691 + 1.3692 + nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext(); 1.3693 + if (!rpc) 1.3694 + return; 1.3695 + 1.3696 + mAsyncScrollPortEvent = new AsyncScrollPortEvent(this); 1.3697 + rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get()); 1.3698 +} 1.3699 + 1.3700 +bool 1.3701 +ScrollFrameHelper::IsLTR() const 1.3702 +{ 1.3703 + //TODO make bidi code set these from preferences 1.3704 + 1.3705 + nsIFrame *frame = mOuter; 1.3706 + // XXX This is a bit on the slow side. 1.3707 + if (mIsRoot) { 1.3708 + // If we're the root scrollframe, we need the root element's style data. 1.3709 + nsPresContext *presContext = mOuter->PresContext(); 1.3710 + nsIDocument *document = presContext->Document(); 1.3711 + Element *root = document->GetRootElement(); 1.3712 + 1.3713 + // But for HTML and XHTML we want the body element. 1.3714 + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document); 1.3715 + if (htmlDoc) { 1.3716 + Element *bodyElement = document->GetBodyElement(); 1.3717 + if (bodyElement) 1.3718 + root = bodyElement; // we can trust the document to hold on to it 1.3719 + } 1.3720 + 1.3721 + if (root) { 1.3722 + nsIFrame *rootsFrame = root->GetPrimaryFrame(); 1.3723 + if (rootsFrame) 1.3724 + frame = rootsFrame; 1.3725 + } 1.3726 + } 1.3727 + 1.3728 + return frame->StyleVisibility()->mDirection != NS_STYLE_DIRECTION_RTL; 1.3729 +} 1.3730 + 1.3731 +bool 1.3732 +ScrollFrameHelper::IsScrollbarOnRight() const 1.3733 +{ 1.3734 + nsPresContext *presContext = mOuter->PresContext(); 1.3735 + 1.3736 + // The position of the scrollbar in top-level windows depends on the pref 1.3737 + // layout.scrollbar.side. For non-top-level elements, it depends only on the 1.3738 + // directionaliy of the element (equivalent to a value of "1" for the pref). 1.3739 + if (!mIsRoot) 1.3740 + return IsLTR(); 1.3741 + switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) { 1.3742 + default: 1.3743 + case 0: // UI directionality 1.3744 + return presContext->GetCachedIntPref(kPresContext_BidiDirection) 1.3745 + == IBMBIDI_TEXTDIRECTION_LTR; 1.3746 + case 1: // Document / content directionality 1.3747 + return IsLTR(); 1.3748 + case 2: // Always right 1.3749 + return true; 1.3750 + case 3: // Always left 1.3751 + return false; 1.3752 + } 1.3753 +} 1.3754 + 1.3755 +/** 1.3756 + * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will 1.3757 + * cause any of the scrollbars to need to be reflowed. 1.3758 + */ 1.3759 +nsresult 1.3760 +nsXULScrollFrame::Layout(nsBoxLayoutState& aState) 1.3761 +{ 1.3762 + bool scrollbarRight = mHelper.IsScrollbarOnRight(); 1.3763 + bool scrollbarBottom = true; 1.3764 + 1.3765 + // get the content rect 1.3766 + nsRect clientRect(0,0,0,0); 1.3767 + GetClientRect(clientRect); 1.3768 + 1.3769 + nsRect oldScrollAreaBounds = mHelper.mScrollPort; 1.3770 + nsPoint oldScrollPosition = mHelper.GetLogicalScrollPosition(); 1.3771 + 1.3772 + // the scroll area size starts off as big as our content area 1.3773 + mHelper.mScrollPort = clientRect; 1.3774 + 1.3775 + /************** 1.3776 + Our basic strategy here is to first try laying out the content with 1.3777 + the scrollbars in their current state. We're hoping that that will 1.3778 + just "work"; the content will overflow wherever there's a scrollbar 1.3779 + already visible. If that does work, then there's no need to lay out 1.3780 + the scrollarea. Otherwise we fix up the scrollbars; first we add a 1.3781 + vertical one to scroll the content if necessary, or remove it if 1.3782 + it's not needed. Then we reflow the content if the scrollbar 1.3783 + changed. Then we add a horizontal scrollbar if necessary (or 1.3784 + remove if not needed), and if that changed, we reflow the content 1.3785 + again. At this point, any scrollbars that are needed to scroll the 1.3786 + content have been added. 1.3787 + 1.3788 + In the second phase we check to see if any scrollbars are too small 1.3789 + to display, and if so, we remove them. We check the horizontal 1.3790 + scrollbar first; removing it might make room for the vertical 1.3791 + scrollbar, and if we have room for just one scrollbar we'll save 1.3792 + the vertical one. 1.3793 + 1.3794 + Finally we position and size the scrollbars and scrollcorner (the 1.3795 + square that is needed in the corner of the window when two 1.3796 + scrollbars are visible), and reflow any fixed position views 1.3797 + (if we're the viewport and we added or removed a scrollbar). 1.3798 + **************/ 1.3799 + 1.3800 + ScrollbarStyles styles = GetScrollbarStyles(); 1.3801 + 1.3802 + // Look at our style do we always have vertical or horizontal scrollbars? 1.3803 + if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) 1.3804 + mHelper.mHasHorizontalScrollbar = true; 1.3805 + if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) 1.3806 + mHelper.mHasVerticalScrollbar = true; 1.3807 + 1.3808 + if (mHelper.mHasHorizontalScrollbar) 1.3809 + AddHorizontalScrollbar(aState, scrollbarBottom); 1.3810 + 1.3811 + if (mHelper.mHasVerticalScrollbar) 1.3812 + AddVerticalScrollbar(aState, scrollbarRight); 1.3813 + 1.3814 + // layout our the scroll area 1.3815 + LayoutScrollArea(aState, oldScrollPosition); 1.3816 + 1.3817 + // now look at the content area and see if we need scrollbars or not 1.3818 + bool needsLayout = false; 1.3819 + 1.3820 + // if we have 'auto' scrollbars look at the vertical case 1.3821 + if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) { 1.3822 + // These are only good until the call to LayoutScrollArea. 1.3823 + nsRect scrolledRect = mHelper.GetScrolledRect(); 1.3824 + 1.3825 + // There are two cases to consider 1.3826 + if (scrolledRect.height <= mHelper.mScrollPort.height 1.3827 + || styles.mVertical != NS_STYLE_OVERFLOW_AUTO) { 1.3828 + if (mHelper.mHasVerticalScrollbar) { 1.3829 + // We left room for the vertical scrollbar, but it's not needed; 1.3830 + // remove it. 1.3831 + RemoveVerticalScrollbar(aState, scrollbarRight); 1.3832 + needsLayout = true; 1.3833 + } 1.3834 + } else { 1.3835 + if (!mHelper.mHasVerticalScrollbar) { 1.3836 + // We didn't leave room for the vertical scrollbar, but it turns 1.3837 + // out we needed it 1.3838 + if (AddVerticalScrollbar(aState, scrollbarRight)) 1.3839 + needsLayout = true; 1.3840 + } 1.3841 + } 1.3842 + 1.3843 + // ok layout at the right size 1.3844 + if (needsLayout) { 1.3845 + nsBoxLayoutState resizeState(aState); 1.3846 + LayoutScrollArea(resizeState, oldScrollPosition); 1.3847 + needsLayout = false; 1.3848 + } 1.3849 + } 1.3850 + 1.3851 + 1.3852 + // if scrollbars are auto look at the horizontal case 1.3853 + if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) 1.3854 + { 1.3855 + // These are only good until the call to LayoutScrollArea. 1.3856 + nsRect scrolledRect = mHelper.GetScrolledRect(); 1.3857 + 1.3858 + // if the child is wider that the scroll area 1.3859 + // and we don't have a scrollbar add one. 1.3860 + if ((scrolledRect.width > mHelper.mScrollPort.width) 1.3861 + && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) { 1.3862 + 1.3863 + if (!mHelper.mHasHorizontalScrollbar) { 1.3864 + // no scrollbar? 1.3865 + if (AddHorizontalScrollbar(aState, scrollbarBottom)) 1.3866 + needsLayout = true; 1.3867 + 1.3868 + // if we added a horizontal scrollbar and we did not have a vertical 1.3869 + // there is a chance that by adding the horizontal scrollbar we will 1.3870 + // suddenly need a vertical scrollbar. Is a special case but its 1.3871 + // important. 1.3872 + //if (!mHasVerticalScrollbar && scrolledRect.height > scrollAreaRect.height - sbSize.height) 1.3873 + // printf("****Gfx Scrollbar Special case hit!!*****\n"); 1.3874 + 1.3875 + } 1.3876 + } else { 1.3877 + // if the area is smaller or equal to and we have a scrollbar then 1.3878 + // remove it. 1.3879 + if (mHelper.mHasHorizontalScrollbar) { 1.3880 + RemoveHorizontalScrollbar(aState, scrollbarBottom); 1.3881 + needsLayout = true; 1.3882 + } 1.3883 + } 1.3884 + } 1.3885 + 1.3886 + // we only need to set the rect. The inner child stays the same size. 1.3887 + if (needsLayout) { 1.3888 + nsBoxLayoutState resizeState(aState); 1.3889 + LayoutScrollArea(resizeState, oldScrollPosition); 1.3890 + needsLayout = false; 1.3891 + } 1.3892 + 1.3893 + // get the preferred size of the scrollbars 1.3894 + nsSize hMinSize(0, 0); 1.3895 + if (mHelper.mHScrollbarBox && mHelper.mHasHorizontalScrollbar) { 1.3896 + GetScrollbarMetrics(aState, mHelper.mHScrollbarBox, &hMinSize, nullptr, false); 1.3897 + } 1.3898 + nsSize vMinSize(0, 0); 1.3899 + if (mHelper.mVScrollbarBox && mHelper.mHasVerticalScrollbar) { 1.3900 + GetScrollbarMetrics(aState, mHelper.mVScrollbarBox, &vMinSize, nullptr, true); 1.3901 + } 1.3902 + 1.3903 + // Disable scrollbars that are too small 1.3904 + // Disable horizontal scrollbar first. If we have to disable only one 1.3905 + // scrollbar, we'd rather keep the vertical scrollbar. 1.3906 + // Note that we always give horizontal scrollbars their preferred height, 1.3907 + // never their min-height. So check that there's room for the preferred height. 1.3908 + if (mHelper.mHasHorizontalScrollbar && 1.3909 + (hMinSize.width > clientRect.width - vMinSize.width 1.3910 + || hMinSize.height > clientRect.height)) { 1.3911 + RemoveHorizontalScrollbar(aState, scrollbarBottom); 1.3912 + needsLayout = true; 1.3913 + } 1.3914 + // Now disable vertical scrollbar if necessary 1.3915 + if (mHelper.mHasVerticalScrollbar && 1.3916 + (vMinSize.height > clientRect.height - hMinSize.height 1.3917 + || vMinSize.width > clientRect.width)) { 1.3918 + RemoveVerticalScrollbar(aState, scrollbarRight); 1.3919 + needsLayout = true; 1.3920 + } 1.3921 + 1.3922 + // we only need to set the rect. The inner child stays the same size. 1.3923 + if (needsLayout) { 1.3924 + nsBoxLayoutState resizeState(aState); 1.3925 + LayoutScrollArea(resizeState, oldScrollPosition); 1.3926 + } 1.3927 + 1.3928 + if (!mHelper.mSupppressScrollbarUpdate) { 1.3929 + mHelper.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds); 1.3930 + } 1.3931 + if (!mHelper.mPostedReflowCallback) { 1.3932 + // Make sure we'll try scrolling to restored position 1.3933 + PresContext()->PresShell()->PostReflowCallback(&mHelper); 1.3934 + mHelper.mPostedReflowCallback = true; 1.3935 + } 1.3936 + if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { 1.3937 + mHelper.mHadNonInitialReflow = true; 1.3938 + } 1.3939 + 1.3940 + mHelper.UpdateSticky(); 1.3941 + 1.3942 + // Set up overflow areas for block frames for the benefit of 1.3943 + // text-overflow. 1.3944 + nsIFrame* f = mHelper.mScrolledFrame->GetContentInsertionFrame(); 1.3945 + if (nsLayoutUtils::GetAsBlock(f)) { 1.3946 + nsRect origRect = f->GetRect(); 1.3947 + nsRect clippedRect = origRect; 1.3948 + clippedRect.MoveBy(mHelper.mScrollPort.TopLeft()); 1.3949 + clippedRect.IntersectRect(clippedRect, mHelper.mScrollPort); 1.3950 + nsOverflowAreas overflow = f->GetOverflowAreas(); 1.3951 + f->FinishAndStoreOverflow(overflow, clippedRect.Size()); 1.3952 + clippedRect.MoveTo(origRect.TopLeft()); 1.3953 + f->SetRect(clippedRect); 1.3954 + } 1.3955 + 1.3956 + mHelper.PostOverflowEvent(); 1.3957 + return NS_OK; 1.3958 +} 1.3959 + 1.3960 +void 1.3961 +ScrollFrameHelper::FinishReflowForScrollbar(nsIContent* aContent, 1.3962 + nscoord aMinXY, nscoord aMaxXY, 1.3963 + nscoord aCurPosXY, 1.3964 + nscoord aPageIncrement, 1.3965 + nscoord aIncrement) 1.3966 +{ 1.3967 + // Scrollbars assume zero is the minimum position, so translate for them. 1.3968 + SetCoordAttribute(aContent, nsGkAtoms::curpos, aCurPosXY - aMinXY); 1.3969 + SetScrollbarEnabled(aContent, aMaxXY - aMinXY); 1.3970 + SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY); 1.3971 + SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement); 1.3972 + SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement); 1.3973 +} 1.3974 + 1.3975 +bool 1.3976 +ScrollFrameHelper::ReflowFinished() 1.3977 +{ 1.3978 + nsAutoScriptBlocker scriptBlocker; 1.3979 + mPostedReflowCallback = false; 1.3980 + 1.3981 + ScrollToRestoredPosition(); 1.3982 + 1.3983 + // Clamp current scroll position to new bounds. Normally this won't 1.3984 + // do anything. 1.3985 + nsPoint currentScrollPos = GetScrollPosition(); 1.3986 + ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0))); 1.3987 + if (!mAsyncScroll) { 1.3988 + // We need to have mDestination track the current scroll position, 1.3989 + // in case it falls outside the new reflow area. mDestination is used 1.3990 + // by ScrollBy as its starting position. 1.3991 + mDestination = GetScrollPosition(); 1.3992 + } 1.3993 + 1.3994 + if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes) 1.3995 + return false; 1.3996 + 1.3997 + mUpdateScrollbarAttributes = false; 1.3998 + 1.3999 + // Update scrollbar attributes. 1.4000 + nsPresContext* presContext = mOuter->PresContext(); 1.4001 + 1.4002 + if (mMayHaveDirtyFixedChildren) { 1.4003 + mMayHaveDirtyFixedChildren = false; 1.4004 + nsIFrame* parentFrame = mOuter->GetParent(); 1.4005 + for (nsIFrame* fixedChild = 1.4006 + parentFrame->GetFirstChild(nsIFrame::kFixedList); 1.4007 + fixedChild; fixedChild = fixedChild->GetNextSibling()) { 1.4008 + // force a reflow of the fixed child 1.4009 + presContext->PresShell()-> 1.4010 + FrameNeedsReflow(fixedChild, nsIPresShell::eResize, 1.4011 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.4012 + } 1.4013 + } 1.4014 + 1.4015 + nsRect scrolledContentRect = GetScrolledRect(); 1.4016 + nscoord minX = scrolledContentRect.x; 1.4017 + nscoord maxX = scrolledContentRect.XMost() - mScrollPort.width; 1.4018 + nscoord minY = scrolledContentRect.y; 1.4019 + nscoord maxY = scrolledContentRect.YMost() - mScrollPort.height; 1.4020 + 1.4021 + // Suppress handling of the curpos attribute changes we make here. 1.4022 + NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here"); 1.4023 + mFrameIsUpdatingScrollbar = true; 1.4024 + 1.4025 + nsCOMPtr<nsIContent> vScroll = 1.4026 + mVScrollbarBox ? mVScrollbarBox->GetContent() : nullptr; 1.4027 + nsCOMPtr<nsIContent> hScroll = 1.4028 + mHScrollbarBox ? mHScrollbarBox->GetContent() : nullptr; 1.4029 + 1.4030 + // Note, in some cases mOuter may get deleted while finishing reflow 1.4031 + // for scrollbars. XXXmats is this still true now that we have a script 1.4032 + // blocker in this scope? (if not, remove the weak frame checks below). 1.4033 + if (vScroll || hScroll) { 1.4034 + nsWeakFrame weakFrame(mOuter); 1.4035 + nsPoint scrollPos = GetScrollPosition(); 1.4036 + nsSize lineScrollAmount = GetLineScrollAmount(); 1.4037 + if (vScroll) { 1.4038 + const double kScrollMultiplier = 1.4039 + Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance", 1.4040 + NS_DEFAULT_VERTICAL_SCROLL_DISTANCE); 1.4041 + nscoord increment = lineScrollAmount.height * kScrollMultiplier; 1.4042 + // We normally use (scrollArea.height - increment) for height 1.4043 + // of page scrolling. However, it is too small when 1.4044 + // increment is very large. (If increment is larger than 1.4045 + // scrollArea.height, direction of scrolling will be opposite). 1.4046 + // To avoid it, we use (float(scrollArea.height) * 0.8) as 1.4047 + // lower bound value of height of page scrolling. (bug 383267) 1.4048 + // XXX shouldn't we use GetPageScrollAmount here? 1.4049 + nscoord pageincrement = nscoord(mScrollPort.height - increment); 1.4050 + nscoord pageincrementMin = nscoord(float(mScrollPort.height) * 0.8); 1.4051 + FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y, 1.4052 + std::max(pageincrement, pageincrementMin), 1.4053 + increment); 1.4054 + } 1.4055 + if (hScroll) { 1.4056 + const double kScrollMultiplier = 1.4057 + Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance", 1.4058 + NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE); 1.4059 + nscoord increment = lineScrollAmount.width * kScrollMultiplier; 1.4060 + FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x, 1.4061 + nscoord(float(mScrollPort.width) * 0.8), 1.4062 + increment); 1.4063 + } 1.4064 + NS_ENSURE_TRUE(weakFrame.IsAlive(), false); 1.4065 + } 1.4066 + 1.4067 + mFrameIsUpdatingScrollbar = false; 1.4068 + // We used to rely on the curpos attribute changes above to scroll the 1.4069 + // view. However, for scrolling to the left of the viewport, we 1.4070 + // rescale the curpos attribute, which means that operations like 1.4071 + // resizing the window while it is scrolled all the way to the left 1.4072 + // hold the curpos attribute constant at 0 while still requiring 1.4073 + // scrolling. So we suppress the effect of the changes above with 1.4074 + // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here. 1.4075 + // (It actually even works some of the time without this, thanks to 1.4076 + // nsSliderFrame::AttributeChanged's handling of maxpos, but not when 1.4077 + // we hide the scrollbar on a large size change, such as 1.4078 + // maximization.) 1.4079 + if (!mHScrollbarBox && !mVScrollbarBox) 1.4080 + return false; 1.4081 + CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent() 1.4082 + : mHScrollbarBox->GetContent()); 1.4083 + return true; 1.4084 +} 1.4085 + 1.4086 +void 1.4087 +ScrollFrameHelper::ReflowCallbackCanceled() 1.4088 +{ 1.4089 + mPostedReflowCallback = false; 1.4090 +} 1.4091 + 1.4092 +bool 1.4093 +ScrollFrameHelper::UpdateOverflow() 1.4094 +{ 1.4095 + nsIScrollableFrame* sf = do_QueryFrame(mOuter); 1.4096 + ScrollbarStyles ss = sf->GetScrollbarStyles(); 1.4097 + 1.4098 + if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN || 1.4099 + ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN || 1.4100 + GetScrollPosition() != nsPoint()) { 1.4101 + // If there are scrollbars, or we're not at the beginning of the pane, 1.4102 + // the scroll position may change. In this case, mark the frame as 1.4103 + // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means 1.4104 + // we have to reflow the frame and all its descendants, and we don't 1.4105 + // have to do that here. Only this frame needs to be reflowed. 1.4106 + mOuter->PresContext()->PresShell()->FrameNeedsReflow( 1.4107 + mOuter, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN); 1.4108 + // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip 1.4109 + // updating the scrollbars. (Because the overflow area of the scrolled 1.4110 + // frame has probably just been updated, Reflow won't see it change.) 1.4111 + mSkippedScrollbarLayout = true; 1.4112 + return false; // reflowing will update overflow 1.4113 + } 1.4114 + PostOverflowEvent(); 1.4115 + return mOuter->nsContainerFrame::UpdateOverflow(); 1.4116 +} 1.4117 + 1.4118 +void 1.4119 +ScrollFrameHelper::UpdateSticky() 1.4120 +{ 1.4121 + StickyScrollContainer* ssc = StickyScrollContainer:: 1.4122 + GetStickyScrollContainerForScrollFrame(mOuter); 1.4123 + if (ssc) { 1.4124 + nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter); 1.4125 + ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter); 1.4126 + } 1.4127 +} 1.4128 + 1.4129 +void 1.4130 +ScrollFrameHelper::AdjustScrollbarRectForResizer( 1.4131 + nsIFrame* aFrame, nsPresContext* aPresContext, 1.4132 + nsRect& aRect, bool aHasResizer, bool aVertical) 1.4133 +{ 1.4134 + if ((aVertical ? aRect.width : aRect.height) == 0) 1.4135 + return; 1.4136 + 1.4137 + // if a content resizer is present, use its size. Otherwise, check if the 1.4138 + // widget has a resizer. 1.4139 + nsRect resizerRect; 1.4140 + if (aHasResizer) { 1.4141 + resizerRect = mResizerBox->GetRect(); 1.4142 + } 1.4143 + else { 1.4144 + nsPoint offset; 1.4145 + nsIWidget* widget = aFrame->GetNearestWidget(offset); 1.4146 + nsIntRect widgetRect; 1.4147 + if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) 1.4148 + return; 1.4149 + 1.4150 + resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x, 1.4151 + aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y, 1.4152 + aPresContext->DevPixelsToAppUnits(widgetRect.width), 1.4153 + aPresContext->DevPixelsToAppUnits(widgetRect.height)); 1.4154 + } 1.4155 + 1.4156 + if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) 1.4157 + return; 1.4158 + 1.4159 + if (aVertical) 1.4160 + aRect.height = std::max(0, resizerRect.y - aRect.y); 1.4161 + else 1.4162 + aRect.width = std::max(0, resizerRect.x - aRect.x); 1.4163 +} 1.4164 + 1.4165 +static void 1.4166 +AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect) 1.4167 +{ 1.4168 + if (aVRect.IsEmpty() || aHRect.IsEmpty()) 1.4169 + return; 1.4170 + 1.4171 + const nsRect oldVRect = aVRect; 1.4172 + const nsRect oldHRect = aHRect; 1.4173 + if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) { 1.4174 + aHRect.width = std::max(0, oldVRect.x - oldHRect.x); 1.4175 + } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) { 1.4176 + nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x); 1.4177 + aHRect.x += overlap; 1.4178 + aHRect.width -= overlap; 1.4179 + } 1.4180 + if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) { 1.4181 + aVRect.height = std::max(0, oldHRect.y - oldVRect.y); 1.4182 + } 1.4183 +} 1.4184 + 1.4185 +void 1.4186 +ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState, 1.4187 + const nsRect& aContentArea, 1.4188 + const nsRect& aOldScrollArea) 1.4189 +{ 1.4190 + NS_ASSERTION(!mSupppressScrollbarUpdate, 1.4191 + "This should have been suppressed"); 1.4192 + 1.4193 + bool hasResizer = HasResizer(); 1.4194 + bool scrollbarOnLeft = !IsScrollbarOnRight(); 1.4195 + 1.4196 + // place the scrollcorner 1.4197 + if (mScrollCornerBox || mResizerBox) { 1.4198 + NS_PRECONDITION(!mScrollCornerBox || mScrollCornerBox->IsBoxFrame(), "Must be a box frame!"); 1.4199 + 1.4200 + nsRect r(0, 0, 0, 0); 1.4201 + if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) { 1.4202 + // scrollbar (if any) on left 1.4203 + r.x = aContentArea.x; 1.4204 + r.width = mScrollPort.x - aContentArea.x; 1.4205 + NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect"); 1.4206 + } else { 1.4207 + // scrollbar (if any) on right 1.4208 + r.width = aContentArea.XMost() - mScrollPort.XMost(); 1.4209 + r.x = aContentArea.XMost() - r.width; 1.4210 + NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect"); 1.4211 + } 1.4212 + if (aContentArea.y != mScrollPort.y) { 1.4213 + NS_ERROR("top scrollbars not supported"); 1.4214 + } else { 1.4215 + // scrollbar (if any) on bottom 1.4216 + r.height = aContentArea.YMost() - mScrollPort.YMost(); 1.4217 + r.y = aContentArea.YMost() - r.height; 1.4218 + NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect"); 1.4219 + } 1.4220 + 1.4221 + if (mScrollCornerBox) { 1.4222 + nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r); 1.4223 + } 1.4224 + 1.4225 + if (hasResizer) { 1.4226 + // if a resizer is present, get its size. Assume a default size of 15 pixels. 1.4227 + nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15); 1.4228 + nsSize resizerMinSize = mResizerBox->GetMinSize(aState); 1.4229 + 1.4230 + nscoord vScrollbarWidth = mVScrollbarBox ? 1.4231 + mVScrollbarBox->GetPrefSize(aState).width : defaultSize; 1.4232 + r.width = std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width); 1.4233 + if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) { 1.4234 + r.x = aContentArea.XMost() - r.width; 1.4235 + } 1.4236 + 1.4237 + nscoord hScrollbarHeight = mHScrollbarBox ? 1.4238 + mHScrollbarBox->GetPrefSize(aState).height : defaultSize; 1.4239 + r.height = std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height); 1.4240 + if (aContentArea.y == mScrollPort.y) { 1.4241 + r.y = aContentArea.YMost() - r.height; 1.4242 + } 1.4243 + 1.4244 + nsBoxFrame::LayoutChildAt(aState, mResizerBox, r); 1.4245 + } 1.4246 + else if (mResizerBox) { 1.4247 + // otherwise lay out the resizer with an empty rectangle 1.4248 + nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect()); 1.4249 + } 1.4250 + } 1.4251 + 1.4252 + nsPresContext* presContext = mScrolledFrame->PresContext(); 1.4253 + nsRect vRect; 1.4254 + if (mVScrollbarBox) { 1.4255 + NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!"); 1.4256 + vRect = mScrollPort; 1.4257 + vRect.width = aContentArea.width - mScrollPort.width; 1.4258 + vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost(); 1.4259 + if (mHasVerticalScrollbar) { 1.4260 + nsMargin margin; 1.4261 + mVScrollbarBox->GetMargin(margin); 1.4262 + vRect.Deflate(margin); 1.4263 + } 1.4264 + AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true); 1.4265 + } 1.4266 + 1.4267 + nsRect hRect; 1.4268 + if (mHScrollbarBox) { 1.4269 + NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!"); 1.4270 + hRect = mScrollPort; 1.4271 + hRect.height = aContentArea.height - mScrollPort.height; 1.4272 + hRect.y = true ? mScrollPort.YMost() : aContentArea.y; 1.4273 + if (mHasHorizontalScrollbar) { 1.4274 + nsMargin margin; 1.4275 + mHScrollbarBox->GetMargin(margin); 1.4276 + hRect.Deflate(margin); 1.4277 + } 1.4278 + AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false); 1.4279 + } 1.4280 + 1.4281 + if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) { 1.4282 + AdjustOverlappingScrollbars(vRect, hRect); 1.4283 + } 1.4284 + if (mVScrollbarBox) { 1.4285 + nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect); 1.4286 + } 1.4287 + if (mHScrollbarBox) { 1.4288 + nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect); 1.4289 + } 1.4290 + 1.4291 + // may need to update fixed position children of the viewport, 1.4292 + // if the client area changed size because of an incremental 1.4293 + // reflow of a descendant. (If the outer frame is dirty, the fixed 1.4294 + // children will be re-laid out anyway) 1.4295 + if (aOldScrollArea.Size() != mScrollPort.Size() && 1.4296 + !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) && 1.4297 + mIsRoot) { 1.4298 + mMayHaveDirtyFixedChildren = true; 1.4299 + } 1.4300 + 1.4301 + // post reflow callback to modify scrollbar attributes 1.4302 + mUpdateScrollbarAttributes = true; 1.4303 + if (!mPostedReflowCallback) { 1.4304 + aState.PresContext()->PresShell()->PostReflowCallback(this); 1.4305 + mPostedReflowCallback = true; 1.4306 + } 1.4307 +} 1.4308 + 1.4309 +#if DEBUG 1.4310 +static bool ShellIsAlive(nsWeakPtr& aWeakPtr) 1.4311 +{ 1.4312 + nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aWeakPtr)); 1.4313 + return !!shell; 1.4314 +} 1.4315 +#endif 1.4316 + 1.4317 +void 1.4318 +ScrollFrameHelper::SetScrollbarEnabled(nsIContent* aContent, nscoord aMaxPos) 1.4319 +{ 1.4320 + DebugOnly<nsWeakPtr> weakShell( 1.4321 + do_GetWeakReference(mOuter->PresContext()->PresShell())); 1.4322 + if (aMaxPos) { 1.4323 + aContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); 1.4324 + } else { 1.4325 + aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, 1.4326 + NS_LITERAL_STRING("true"), true); 1.4327 + } 1.4328 + MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling"); 1.4329 +} 1.4330 + 1.4331 +void 1.4332 +ScrollFrameHelper::SetCoordAttribute(nsIContent* aContent, nsIAtom* aAtom, 1.4333 + nscoord aSize) 1.4334 +{ 1.4335 + DebugOnly<nsWeakPtr> weakShell( 1.4336 + do_GetWeakReference(mOuter->PresContext()->PresShell())); 1.4337 + // convert to pixels 1.4338 + aSize = nsPresContext::AppUnitsToIntCSSPixels(aSize); 1.4339 + 1.4340 + // only set the attribute if it changed. 1.4341 + 1.4342 + nsAutoString newValue; 1.4343 + newValue.AppendInt(aSize); 1.4344 + 1.4345 + if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters)) 1.4346 + return; 1.4347 + 1.4348 + nsWeakFrame weakFrame(mOuter); 1.4349 + nsCOMPtr<nsIContent> kungFuDeathGrip = aContent; 1.4350 + aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, true); 1.4351 + MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling"); 1.4352 + if (!weakFrame.IsAlive()) { 1.4353 + return; 1.4354 + } 1.4355 + 1.4356 + if (mScrollbarActivity) { 1.4357 + nsRefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity); 1.4358 + scrollbarActivity->ActivityOccurred(); 1.4359 + } 1.4360 +} 1.4361 + 1.4362 +static void 1.4363 +ReduceRadii(nscoord aXBorder, nscoord aYBorder, 1.4364 + nscoord& aXRadius, nscoord& aYRadius) 1.4365 +{ 1.4366 + // In order to ensure that the inside edge of the border has no 1.4367 + // curvature, we need at least one of its radii to be zero. 1.4368 + if (aXRadius <= aXBorder || aYRadius <= aYBorder) 1.4369 + return; 1.4370 + 1.4371 + // For any corner where we reduce the radii, preserve the corner's shape. 1.4372 + double ratio = std::max(double(aXBorder) / aXRadius, 1.4373 + double(aYBorder) / aYRadius); 1.4374 + aXRadius *= ratio; 1.4375 + aYRadius *= ratio; 1.4376 +} 1.4377 + 1.4378 +/** 1.4379 + * Implement an override for nsIFrame::GetBorderRadii to ensure that 1.4380 + * the clipping region for the border radius does not clip the scrollbars. 1.4381 + * 1.4382 + * In other words, we require that the border radius be reduced until the 1.4383 + * inner border radius at the inner edge of the border is 0 wherever we 1.4384 + * have scrollbars. 1.4385 + */ 1.4386 +bool 1.4387 +ScrollFrameHelper::GetBorderRadii(nscoord aRadii[8]) const 1.4388 +{ 1.4389 + if (!mOuter->nsContainerFrame::GetBorderRadii(aRadii)) 1.4390 + return false; 1.4391 + 1.4392 + // Since we can use GetActualScrollbarSizes (rather than 1.4393 + // GetDesiredScrollbarSizes) since this doesn't affect reflow, we 1.4394 + // probably should. 1.4395 + nsMargin sb = GetActualScrollbarSizes(); 1.4396 + nsMargin border = mOuter->GetUsedBorder(); 1.4397 + 1.4398 + if (sb.left > 0 || sb.top > 0) { 1.4399 + ReduceRadii(border.left, border.top, 1.4400 + aRadii[NS_CORNER_TOP_LEFT_X], 1.4401 + aRadii[NS_CORNER_TOP_LEFT_Y]); 1.4402 + } 1.4403 + 1.4404 + if (sb.top > 0 || sb.right > 0) { 1.4405 + ReduceRadii(border.right, border.top, 1.4406 + aRadii[NS_CORNER_TOP_RIGHT_X], 1.4407 + aRadii[NS_CORNER_TOP_RIGHT_Y]); 1.4408 + } 1.4409 + 1.4410 + if (sb.right > 0 || sb.bottom > 0) { 1.4411 + ReduceRadii(border.right, border.bottom, 1.4412 + aRadii[NS_CORNER_BOTTOM_RIGHT_X], 1.4413 + aRadii[NS_CORNER_BOTTOM_RIGHT_Y]); 1.4414 + } 1.4415 + 1.4416 + if (sb.bottom > 0 || sb.left > 0) { 1.4417 + ReduceRadii(border.left, border.bottom, 1.4418 + aRadii[NS_CORNER_BOTTOM_LEFT_X], 1.4419 + aRadii[NS_CORNER_BOTTOM_LEFT_Y]); 1.4420 + } 1.4421 + 1.4422 + return true; 1.4423 +} 1.4424 + 1.4425 +nsRect 1.4426 +ScrollFrameHelper::GetScrolledRect() const 1.4427 +{ 1.4428 + nsRect result = 1.4429 + GetScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(), 1.4430 + mScrollPort.Size()); 1.4431 + 1.4432 + if (result.width < mScrollPort.width) { 1.4433 + NS_WARNING("Scrolled rect smaller than scrollport?"); 1.4434 + } 1.4435 + if (result.height < mScrollPort.height) { 1.4436 + NS_WARNING("Scrolled rect smaller than scrollport?"); 1.4437 + } 1.4438 + return result; 1.4439 +} 1.4440 + 1.4441 +nsRect 1.4442 +ScrollFrameHelper::GetScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea, 1.4443 + const nsSize& aScrollPortSize) const 1.4444 +{ 1.4445 + return nsLayoutUtils::GetScrolledRect(mScrolledFrame, 1.4446 + aScrolledFrameOverflowArea, aScrollPortSize, 1.4447 + IsLTR() ? NS_STYLE_DIRECTION_LTR : NS_STYLE_DIRECTION_RTL); 1.4448 +} 1.4449 + 1.4450 +nsMargin 1.4451 +ScrollFrameHelper::GetActualScrollbarSizes() const 1.4452 +{ 1.4453 + nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition(); 1.4454 + 1.4455 + return nsMargin(mScrollPort.y - r.y, 1.4456 + r.XMost() - mScrollPort.XMost(), 1.4457 + r.YMost() - mScrollPort.YMost(), 1.4458 + mScrollPort.x - r.x); 1.4459 +} 1.4460 + 1.4461 +void 1.4462 +ScrollFrameHelper::SetScrollbarVisibility(nsIFrame* aScrollbar, bool aVisible) 1.4463 +{ 1.4464 + nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar); 1.4465 + if (scrollbar) { 1.4466 + // See if we have a mediator. 1.4467 + nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator(); 1.4468 + if (mediator) { 1.4469 + // Inform the mediator of the visibility change. 1.4470 + mediator->VisibilityChanged(aVisible); 1.4471 + } 1.4472 + } 1.4473 +} 1.4474 + 1.4475 +nscoord 1.4476 +ScrollFrameHelper::GetCoordAttribute(nsIFrame* aBox, nsIAtom* aAtom, 1.4477 + nscoord aDefaultValue, 1.4478 + nscoord* aRangeStart, 1.4479 + nscoord* aRangeLength) 1.4480 +{ 1.4481 + if (aBox) { 1.4482 + nsIContent* content = aBox->GetContent(); 1.4483 + 1.4484 + nsAutoString value; 1.4485 + content->GetAttr(kNameSpaceID_None, aAtom, value); 1.4486 + if (!value.IsEmpty()) 1.4487 + { 1.4488 + nsresult error; 1.4489 + // convert it to appunits 1.4490 + nscoord result = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); 1.4491 + nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); 1.4492 + // Any nscoord value that would round to the attribute value when converted 1.4493 + // to CSS pixels is allowed. 1.4494 + *aRangeStart = result - halfPixel; 1.4495 + *aRangeLength = halfPixel*2 - 1; 1.4496 + return result; 1.4497 + } 1.4498 + } 1.4499 + 1.4500 + // Only this exact default value is allowed. 1.4501 + *aRangeStart = aDefaultValue; 1.4502 + *aRangeLength = 0; 1.4503 + return aDefaultValue; 1.4504 +} 1.4505 + 1.4506 +nsPresState* 1.4507 +ScrollFrameHelper::SaveState() const 1.4508 +{ 1.4509 + nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame()); 1.4510 + if (mediator) { 1.4511 + // child handles its own scroll state, so don't bother saving state here 1.4512 + return nullptr; 1.4513 + } 1.4514 + 1.4515 + // Don't store a scroll state if we never have been scrolled or restored 1.4516 + // a previous scroll state. 1.4517 + if (!mHasBeenScrolled && !mDidHistoryRestore) { 1.4518 + return nullptr; 1.4519 + } 1.4520 + 1.4521 + nsPresState* state = new nsPresState(); 1.4522 + // Save mRestorePos instead of our actual current scroll position, if it's 1.4523 + // valid and we haven't moved since the last update of mLastPos (same check 1.4524 + // that ScrollToRestoredPosition uses). This ensures if a reframe occurs 1.4525 + // while we're in the process of loading content to scroll to a restored 1.4526 + // position, we'll keep trying after the reframe. 1.4527 + nsPoint pt = GetLogicalScrollPosition(); 1.4528 + if (mRestorePos.y != -1 && pt == mLastPos) { 1.4529 + pt = mRestorePos; 1.4530 + } 1.4531 + state->SetScrollState(pt); 1.4532 + state->SetResolution(mResolution); 1.4533 + return state; 1.4534 +} 1.4535 + 1.4536 +void 1.4537 +ScrollFrameHelper::RestoreState(nsPresState* aState) 1.4538 +{ 1.4539 + mRestorePos = aState->GetScrollState(); 1.4540 + mDidHistoryRestore = true; 1.4541 + mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0); 1.4542 + mResolution = aState->GetResolution(); 1.4543 + mIsResolutionSet = true; 1.4544 + 1.4545 + if (mIsRoot) { 1.4546 + mOuter->PresContext()->PresShell()->SetResolution(mResolution.width, mResolution.height); 1.4547 + } 1.4548 +} 1.4549 + 1.4550 +void 1.4551 +ScrollFrameHelper::PostScrolledAreaEvent() 1.4552 +{ 1.4553 + if (mScrolledAreaEvent.IsPending()) { 1.4554 + return; 1.4555 + } 1.4556 + mScrolledAreaEvent = new ScrolledAreaEvent(this); 1.4557 + nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get()); 1.4558 +} 1.4559 + 1.4560 +//////////////////////////////////////////////////////////////////////////////// 1.4561 +// ScrolledArea change event dispatch 1.4562 + 1.4563 +NS_IMETHODIMP 1.4564 +ScrollFrameHelper::ScrolledAreaEvent::Run() 1.4565 +{ 1.4566 + if (mHelper) { 1.4567 + mHelper->FireScrolledAreaEvent(); 1.4568 + } 1.4569 + return NS_OK; 1.4570 +} 1.4571 + 1.4572 +void 1.4573 +ScrollFrameHelper::FireScrolledAreaEvent() 1.4574 +{ 1.4575 + mScrolledAreaEvent.Forget(); 1.4576 + 1.4577 + InternalScrollAreaEvent event(true, NS_SCROLLEDAREACHANGED, nullptr); 1.4578 + nsPresContext *prescontext = mOuter->PresContext(); 1.4579 + nsIContent* content = mOuter->GetContent(); 1.4580 + 1.4581 + event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent(); 1.4582 + 1.4583 + nsIDocument *doc = content->GetCurrentDoc(); 1.4584 + if (doc) { 1.4585 + EventDispatcher::Dispatch(doc, prescontext, &event, nullptr); 1.4586 + } 1.4587 +} 1.4588 + 1.4589 +uint32_t 1.4590 +nsIScrollableFrame::GetPerceivedScrollingDirections() const 1.4591 +{ 1.4592 + nscoord oneDevPixel = GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel(); 1.4593 + uint32_t directions = GetScrollbarVisibility(); 1.4594 + nsRect scrollRange = GetScrollRange(); 1.4595 + if (scrollRange.width >= oneDevPixel) { 1.4596 + directions |= HORIZONTAL; 1.4597 + } 1.4598 + if (scrollRange.height >= oneDevPixel) { 1.4599 + directions |= VERTICAL; 1.4600 + } 1.4601 + return directions; 1.4602 +}