layout/generic/nsGfxScrollFrame.cpp

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

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

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

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 }

mercurial