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