Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/generic/nsGfxScrollFrame.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/* rendering object to wrap rendering objects that should be scrollable */
8
9
#include "nsGfxScrollFrame.h"
10
11
#include "ActiveLayerTracker.h"
12
#include "base/compiler_specific.h"
13
#include "DisplayItemClip.h"
14
#include "nsCOMPtr.h"
15
#include "nsIContentViewer.h"
16
#include "nsPresContext.h"
17
#include "nsView.h"
18
#include "nsIScrollable.h"
19
#include "nsContainerFrame.h"
20
#include "nsGkAtoms.h"
21
#include "nsNameSpaceManager.h"
22
#include "nsIDocumentInlines.h"
23
#include "nsFontMetrics.h"
24
#include "nsBoxLayoutState.h"
25
#include "mozilla/dom/NodeInfo.h"
26
#include "nsScrollbarFrame.h"
27
#include "nsINode.h"
28
#include "nsIScrollbarMediator.h"
29
#include "nsITextControlFrame.h"
30
#include "nsNodeInfoManager.h"
31
#include "nsContentCreatorFunctions.h"
32
#include "mozilla/PresState.h"
33
#include "nsIHTMLDocument.h"
34
#include "nsContentUtils.h"
35
#include "nsLayoutUtils.h"
36
#include "nsBidiPresUtils.h"
37
#include "nsBidiUtils.h"
38
#include "mozilla/ContentEvents.h"
39
#include "mozilla/EventDispatcher.h"
40
#include "mozilla/Preferences.h"
41
#include "mozilla/LookAndFeel.h"
42
#include "mozilla/dom/Element.h"
43
#include "mozilla/dom/Event.h"
44
#include "mozilla/dom/HTMLTextAreaElement.h"
45
#include <stdint.h>
46
#include "mozilla/MathAlgorithms.h"
47
#include "mozilla/Telemetry.h"
48
#include "FrameLayerBuilder.h"
49
#include "nsSMILKeySpline.h"
50
#include "nsSubDocumentFrame.h"
51
#include "nsSVGOuterSVGFrame.h"
52
#include "nsIObjectLoadingContent.h"
53
#include "mozilla/Attributes.h"
54
#include "ScrollbarActivity.h"
55
#include "nsRefreshDriver.h"
56
#include "nsStyleConsts.h"
57
#include "nsSVGIntegrationUtils.h"
58
#include "nsIScrollPositionListener.h"
59
#include "StickyScrollContainer.h"
60
#include "nsIFrameInlines.h"
61
#include "nsILayoutHistoryState.h"
62
#include "gfxPlatform.h"
63
#include "gfxPrefs.h"
64
#include "ScrollAnimationPhysics.h"
65
#include "ScrollAnimationBezierPhysics.h"
66
#include "ScrollAnimationMSDPhysics.h"
67
#include "ScrollSnap.h"
68
#include "UnitTransforms.h"
69
#include "nsPluginFrame.h"
70
#include "nsSliderFrame.h"
71
#include "mozilla/layers/APZCCallbackHelper.h"
72
#include <mozilla/layers/AxisPhysicsModel.h>
73
#include <mozilla/layers/AxisPhysicsMSDModel.h>
74
#include "mozilla/layers/LayerTransactionChild.h"
75
#include "mozilla/layers/ScrollLinkedEffectDetector.h"
76
#include "mozilla/Unused.h"
77
#include "LayersLogging.h"  // for Stringify
78
#include <algorithm>
79
#include <cstdlib> // for std::abs(int/long)
80
#include <cmath> // for std::abs(float/double)
81
82
#define PAINT_SKIP_LOG(...)
83
// #define PAINT_SKIP_LOG(...) printf_stderr("PSKIP: " __VA_ARGS__)
84
85
using namespace mozilla;
86
using namespace mozilla::dom;
87
using namespace mozilla::gfx;
88
using namespace mozilla::layers;
89
using namespace mozilla::layout;
90
91
static uint32_t
92
GetOverflowChange(const nsRect& aCurScrolledRect, const nsRect& aPrevScrolledRect)
93
0
{
94
0
  uint32_t result = 0;
95
0
  if (aPrevScrolledRect.x != aCurScrolledRect.x ||
96
0
      aPrevScrolledRect.width != aCurScrolledRect.width) {
97
0
    result |= nsIScrollableFrame::HORIZONTAL;
98
0
  }
99
0
  if (aPrevScrolledRect.y != aCurScrolledRect.y ||
100
0
      aPrevScrolledRect.height != aCurScrolledRect.height) {
101
0
    result |= nsIScrollableFrame::VERTICAL;
102
0
  }
103
0
  return result;
104
0
}
105
106
//----------------------------------------------------------------------
107
108
//----------nsHTMLScrollFrame-------------------------------------------
109
110
nsHTMLScrollFrame*
111
NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle, bool aIsRoot)
112
0
{
113
0
  return new (aPresShell) nsHTMLScrollFrame(aStyle, aIsRoot);
114
0
}
115
116
NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
117
118
nsHTMLScrollFrame::nsHTMLScrollFrame(ComputedStyle* aStyle,
119
                                     nsIFrame::ClassID aID,
120
                                     bool aIsRoot)
121
  : nsContainerFrame(aStyle, aID)
122
  , mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot)
123
0
{
124
0
}
125
126
void
127
nsHTMLScrollFrame::ScrollbarActivityStarted() const
128
0
{
129
0
  if (mHelper.mScrollbarActivity) {
130
0
    mHelper.mScrollbarActivity->ActivityStarted();
131
0
  }
132
0
}
133
134
void
135
nsHTMLScrollFrame::ScrollbarActivityStopped() const
136
0
{
137
0
  if (mHelper.mScrollbarActivity) {
138
0
    mHelper.mScrollbarActivity->ActivityStopped();
139
0
  }
140
0
}
141
142
nsresult
143
nsHTMLScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
144
0
{
145
0
  return mHelper.CreateAnonymousContent(aElements);
146
0
}
147
148
void
149
nsHTMLScrollFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
150
                                            uint32_t aFilter)
151
0
{
152
0
  mHelper.AppendAnonymousContentTo(aElements, aFilter);
153
0
}
154
155
void
156
nsHTMLScrollFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
157
0
{
158
0
  DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData);
159
0
  mHelper.Destroy(aPostDestroyData);
160
0
  nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
161
0
}
162
163
void
164
nsHTMLScrollFrame::SetInitialChildList(ChildListID  aListID,
165
                                       nsFrameList& aChildList)
166
0
{
167
0
  nsContainerFrame::SetInitialChildList(aListID, aChildList);
168
0
  mHelper.ReloadChildFrames();
169
0
}
170
171
172
void
173
nsHTMLScrollFrame::AppendFrames(ChildListID  aListID,
174
                                nsFrameList& aFrameList)
175
0
{
176
0
  NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
177
0
  mFrames.AppendFrames(nullptr, aFrameList);
178
0
  mHelper.ReloadChildFrames();
179
0
}
180
181
void
182
nsHTMLScrollFrame::InsertFrames(ChildListID aListID,
183
                                nsIFrame* aPrevFrame,
184
                                nsFrameList& aFrameList)
185
0
{
186
0
  NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
187
0
  NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
188
0
               "inserting after sibling frame with different parent");
189
0
  mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
190
0
  mHelper.ReloadChildFrames();
191
0
}
192
193
void
194
nsHTMLScrollFrame::RemoveFrame(ChildListID aListID,
195
                               nsIFrame* aOldFrame)
196
0
{
197
0
  NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
198
0
  mFrames.DestroyFrame(aOldFrame);
199
0
  mHelper.ReloadChildFrames();
200
0
}
201
202
nsSplittableType
203
nsHTMLScrollFrame::GetSplittableType() const
204
0
{
205
0
  return NS_FRAME_NOT_SPLITTABLE;
206
0
}
207
208
/**
209
 HTML scrolling implementation
210
211
 All other things being equal, we prefer layouts with fewer scrollbars showing.
212
*/
213
214
namespace mozilla {
215
216
enum class ShowScrollbar : uint8_t
217
{
218
  Auto,
219
  Always,
220
  Never,
221
};
222
223
static ShowScrollbar
224
ShouldShowScrollbar(uint8_t aOverflow)
225
0
{
226
0
  switch (aOverflow) {
227
0
    case NS_STYLE_OVERFLOW_SCROLL:
228
0
      return ShowScrollbar::Always;
229
0
    case NS_STYLE_OVERFLOW_HIDDEN:
230
0
      return ShowScrollbar::Never;
231
0
    default:
232
0
    case NS_STYLE_OVERFLOW_AUTO:
233
0
      return ShowScrollbar::Auto;
234
0
  }
235
0
}
236
237
struct MOZ_STACK_CLASS ScrollReflowInput {
238
  const ReflowInput& mReflowInput;
239
  nsBoxLayoutState mBoxState;
240
  ShowScrollbar mHScrollbar;
241
  ShowScrollbar mVScrollbar;
242
  nsMargin mComputedBorder;
243
244
  // === Filled in by ReflowScrolledFrame ===
245
  nsOverflowAreas mContentsOverflowAreas;
246
  MOZ_INIT_OUTSIDE_CTOR
247
  bool mReflowedContentsWithHScrollbar;
248
  MOZ_INIT_OUTSIDE_CTOR
249
  bool mReflowedContentsWithVScrollbar;
250
251
  // === Filled in when TryLayout succeeds ===
252
  // The size of the inside-border area
253
  nsSize mInsideBorderSize;
254
  // Whether we decided to show the horizontal scrollbar
255
  MOZ_INIT_OUTSIDE_CTOR
256
  bool mShowHScrollbar;
257
  // Whether we decided to show the vertical scrollbar
258
  MOZ_INIT_OUTSIDE_CTOR
259
  bool mShowVScrollbar;
260
261
  ScrollReflowInput(nsIScrollableFrame* aFrame,
262
                    const ReflowInput& aReflowInput)
263
  : mReflowInput(aReflowInput)
264
    // mBoxState is just used for scrollbars so we don't need to
265
    // worry about the reflow depth here
266
  , mBoxState(aReflowInput.mFrame->PresContext(),
267
              aReflowInput.mRenderingContext)
268
0
  {
269
0
    ScrollStyles styles = aFrame->GetScrollStyles();
270
0
    mHScrollbar = ShouldShowScrollbar(styles.mHorizontal);
271
0
    mVScrollbar = ShouldShowScrollbar(styles.mVertical);
272
0
  }
273
};
274
275
} // namespace mozilla
276
277
// XXXldb Can this go away?
278
static nsSize ComputeInsideBorderSize(ScrollReflowInput* aState,
279
                                      const nsSize& aDesiredInsideBorderSize)
280
0
{
281
0
  // aDesiredInsideBorderSize is the frame size; i.e., it includes
282
0
  // borders and padding (but the scrolled child doesn't have
283
0
  // borders). The scrolled child has the same padding as us.
284
0
  nscoord contentWidth = aState->mReflowInput.ComputedWidth();
285
0
  if (contentWidth == NS_UNCONSTRAINEDSIZE) {
286
0
    contentWidth = aDesiredInsideBorderSize.width -
287
0
      aState->mReflowInput.ComputedPhysicalPadding().LeftRight();
288
0
  }
289
0
  nscoord contentHeight = aState->mReflowInput.ComputedHeight();
290
0
  if (contentHeight == NS_UNCONSTRAINEDSIZE) {
291
0
    contentHeight = aDesiredInsideBorderSize.height -
292
0
      aState->mReflowInput.ComputedPhysicalPadding().TopBottom();
293
0
  }
294
0
295
0
  contentWidth  = aState->mReflowInput.ApplyMinMaxWidth(contentWidth);
296
0
  contentHeight = aState->mReflowInput.ApplyMinMaxHeight(contentHeight);
297
0
  return nsSize(contentWidth + aState->mReflowInput.ComputedPhysicalPadding().LeftRight(),
298
0
                contentHeight + aState->mReflowInput.ComputedPhysicalPadding().TopBottom());
299
0
}
300
301
static void
302
GetScrollbarMetrics(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize* aMin,
303
                    nsSize* aPref, bool aVertical)
304
0
{
305
0
  NS_ASSERTION(aState.GetRenderingContext(),
306
0
               "Must have rendering context in layout state for size "
307
0
               "computations");
308
0
309
0
  if (aMin) {
310
0
    *aMin = aBox->GetXULMinSize(aState);
311
0
    nsBox::AddMargin(aBox, *aMin);
312
0
    if (aMin->width < 0) {
313
0
      aMin->width = 0;
314
0
    }
315
0
    if (aMin->height < 0) {
316
0
      aMin->height = 0;
317
0
    }
318
0
  }
319
0
320
0
  if (aPref) {
321
0
    *aPref = aBox->GetXULPrefSize(aState);
322
0
    nsBox::AddMargin(aBox, *aPref);
323
0
    if (aPref->width < 0) {
324
0
      aPref->width = 0;
325
0
    }
326
0
    if (aPref->height < 0) {
327
0
      aPref->height = 0;
328
0
    }
329
0
  }
330
0
}
331
332
/**
333
 * Assuming that we know the metrics for our wrapped frame and
334
 * whether the horizontal and/or vertical scrollbars are present,
335
 * compute the resulting layout and return true if the layout is
336
 * consistent. If the layout is consistent then we fill in the
337
 * computed fields of the ScrollReflowInput.
338
 *
339
 * The layout is consistent when both scrollbars are showing if and only
340
 * if they should be showing. A horizontal scrollbar should be showing if all
341
 * following conditions are met:
342
 * 1) the style is not HIDDEN
343
 * 2) our inside-border height is at least the scrollbar height (i.e., the
344
 * scrollbar fits vertically)
345
 * 3) our scrollport width (the inside-border width minus the width allocated for a
346
 * vertical scrollbar, if showing) is at least the scrollbar's min-width
347
 * (i.e., the scrollbar fits horizontally)
348
 * 4) the style is SCROLL, or the kid's overflow-area XMost is
349
 * greater than the scrollport width
350
 *
351
 * @param aForce if true, then we just assume the layout is consistent.
352
 */
353
bool
354
nsHTMLScrollFrame::TryLayout(ScrollReflowInput* aState,
355
                             ReflowOutput* aKidMetrics,
356
                             bool aAssumeHScroll, bool aAssumeVScroll,
357
                             bool aForce)
358
0
{
359
0
  if ((aState->mVScrollbar == ShowScrollbar::Never && aAssumeVScroll) ||
360
0
      (aState->mHScrollbar == ShowScrollbar::Never && aAssumeHScroll)) {
361
0
    NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
362
0
    return false;
363
0
  }
364
0
365
0
  if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
366
0
      (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar &&
367
0
       ScrolledContentDependsOnHeight(aState))) {
368
0
    if (aAssumeHScroll != aState->mReflowedContentsWithHScrollbar) {
369
0
      nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(
370
0
          mHelper.mScrolledFrame);
371
0
    }
372
0
    aKidMetrics->mOverflowAreas.Clear();
373
0
    ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics,
374
0
                        false);
375
0
  }
376
0
377
0
  nsSize vScrollbarMinSize(0, 0);
378
0
  nsSize vScrollbarPrefSize(0, 0);
379
0
  if (mHelper.mVScrollbarBox) {
380
0
    GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
381
0
                        &vScrollbarMinSize,
382
0
                        aAssumeVScroll ? &vScrollbarPrefSize : nullptr, true);
383
0
    nsScrollbarFrame* scrollbar = do_QueryFrame(mHelper.mVScrollbarBox);
384
0
    scrollbar->SetScrollbarMediatorContent(mContent);
385
0
  }
386
0
  nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
387
0
  nscoord vScrollbarMinHeight = aAssumeVScroll ? vScrollbarMinSize.height : 0;
388
0
389
0
  nsSize hScrollbarMinSize(0, 0);
390
0
  nsSize hScrollbarPrefSize(0, 0);
391
0
  if (mHelper.mHScrollbarBox) {
392
0
    GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
393
0
                        &hScrollbarMinSize,
394
0
                        aAssumeHScroll ? &hScrollbarPrefSize : nullptr, false);
395
0
    nsScrollbarFrame* scrollbar = do_QueryFrame(mHelper.mHScrollbarBox);
396
0
    scrollbar->SetScrollbarMediatorContent(mContent);
397
0
  }
398
0
  nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
399
0
  nscoord hScrollbarMinWidth = aAssumeHScroll ? hScrollbarMinSize.width : 0;
400
0
401
0
  // First, compute our inside-border size and scrollport size
402
0
  // XXXldb Can we depend more on ComputeSize here?
403
0
  nsSize desiredInsideBorderSize;
404
0
  desiredInsideBorderSize.width = vScrollbarDesiredWidth +
405
0
    std::max(aKidMetrics->Width(), hScrollbarMinWidth);
406
0
  desiredInsideBorderSize.height = hScrollbarDesiredHeight +
407
0
    std::max(aKidMetrics->Height(), vScrollbarMinHeight);
408
0
  aState->mInsideBorderSize =
409
0
    ComputeInsideBorderSize(aState, desiredInsideBorderSize);
410
0
  nsSize scrollPortSize = nsSize(std::max(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
411
0
                                 std::max(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
412
0
413
0
  nsSize visualViewportSize = scrollPortSize;
414
0
  nsIPresShell* presShell = PresShell();
415
0
  if (mHelper.mIsRoot && presShell->IsVisualViewportSizeSet()) {
416
0
    nsSize compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(this, false);
417
0
    float resolution = presShell->GetResolution();
418
0
    compositionSize.width /= resolution;
419
0
    compositionSize.height /= resolution;
420
0
    visualViewportSize = nsSize(std::max(0, compositionSize.width - vScrollbarDesiredWidth),
421
0
                                  std::max(0, compositionSize.height - hScrollbarDesiredHeight));
422
0
  }
423
0
424
0
  nsRect scrolledRect =
425
0
    mHelper.GetUnsnappedScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
426
0
                                             scrollPortSize);
427
0
  nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
428
0
429
0
  if (!aForce) {
430
0
    // If the style is HIDDEN then we already know that aAssumeHScroll is false
431
0
    if (aState->mHScrollbar != ShowScrollbar::Never) {
432
0
      bool wantHScrollbar =
433
0
        aState->mHScrollbar == ShowScrollbar::Always ||
434
0
        scrolledRect.XMost() >= visualViewportSize.width + oneDevPixel ||
435
0
        scrolledRect.x <= -oneDevPixel;
436
0
      if (scrollPortSize.width < hScrollbarMinSize.width)
437
0
        wantHScrollbar = false;
438
0
      if (wantHScrollbar != aAssumeHScroll)
439
0
        return false;
440
0
    }
441
0
442
0
    // If the style is HIDDEN then we already know that aAssumeVScroll is false
443
0
    if (aState->mVScrollbar != ShowScrollbar::Never) {
444
0
      bool wantVScrollbar =
445
0
        aState->mVScrollbar == ShowScrollbar::Always ||
446
0
        scrolledRect.YMost() >= visualViewportSize.height + oneDevPixel ||
447
0
        scrolledRect.y <= -oneDevPixel;
448
0
      if (scrollPortSize.height < vScrollbarMinSize.height)
449
0
        wantVScrollbar = false;
450
0
      if (wantVScrollbar != aAssumeVScroll)
451
0
        return false;
452
0
    }
453
0
  }
454
0
455
0
  do {
456
0
    if (!mHelper.mIsRoot) {
457
0
      break;
458
0
    }
459
0
    // Check whether there is actually any overflow.
460
0
    nscoord scrolledWidth = scrolledRect.width + oneDevPixel;
461
0
    if (scrolledWidth <= scrollPortSize.width) {
462
0
      break;
463
0
    }
464
0
    // Viewport scrollbar style is used below instead of aState because
465
0
    // the latter can be affected by various factors, while we only
466
0
    // care about what the page itself specifies.
467
0
    nsPresContext* pc = PresContext();
468
0
    ScrollStyles styles = pc->GetViewportScrollStylesOverride();
469
0
    if (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
470
0
      break;
471
0
    }
472
0
    // Only top level content document is considered.
473
0
    nsIDocument* doc = pc->Document();
474
0
    if (!doc->IsTopLevelContentDocument()) {
475
0
      break;
476
0
    }
477
0
    doc->UpdateViewportOverflowType(scrolledWidth, scrollPortSize.width);
478
0
  } while (false);
479
0
480
0
  nscoord vScrollbarActualWidth = aState->mInsideBorderSize.width - scrollPortSize.width;
481
0
482
0
  aState->mShowHScrollbar = aAssumeHScroll;
483
0
  aState->mShowVScrollbar = aAssumeVScroll;
484
0
  nsPoint scrollPortOrigin(aState->mComputedBorder.left,
485
0
                           aState->mComputedBorder.top);
486
0
  if (!IsScrollbarOnRight()) {
487
0
    scrollPortOrigin.x += vScrollbarActualWidth;
488
0
  }
489
0
  mHelper.mScrollPort = nsRect(scrollPortOrigin, scrollPortSize);
490
0
  return true;
491
0
}
492
493
// XXX Height/BSize mismatch needs to be addressed here; check the caller!
494
// Currently this will only behave as expected for horizontal writing modes.
495
// (See bug 1175509.)
496
bool
497
nsHTMLScrollFrame::ScrolledContentDependsOnHeight(ScrollReflowInput* aState)
498
0
{
499
0
  // Return true if ReflowScrolledFrame is going to do something different
500
0
  // based on the presence of a horizontal scrollbar.
501
0
  return mHelper.mScrolledFrame->HasAnyStateBits(
502
0
      NS_FRAME_CONTAINS_RELATIVE_BSIZE | NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE) ||
503
0
    aState->mReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
504
0
    aState->mReflowInput.ComputedMinBSize() > 0 ||
505
0
    aState->mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE;
506
0
}
507
508
void
509
nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput* aState,
510
                                       bool aAssumeHScroll,
511
                                       bool aAssumeVScroll,
512
                                       ReflowOutput* aMetrics,
513
                                       bool aFirstPass)
514
0
{
515
0
  WritingMode wm = mHelper.mScrolledFrame->GetWritingMode();
516
0
517
0
  // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
518
0
  // be OK
519
0
  LogicalMargin padding = aState->mReflowInput.ComputedLogicalPadding();
520
0
  nscoord availISize =
521
0
    aState->mReflowInput.ComputedISize() + padding.IStartEnd(wm);
522
0
523
0
  nscoord computedBSize = aState->mReflowInput.ComputedBSize();
524
0
  nscoord computedMinBSize = aState->mReflowInput.ComputedMinBSize();
525
0
  nscoord computedMaxBSize = aState->mReflowInput.ComputedMaxBSize();
526
0
  if (!ShouldPropagateComputedBSizeToScrolledContent()) {
527
0
    computedBSize = NS_UNCONSTRAINEDSIZE;
528
0
    computedMinBSize = 0;
529
0
    computedMaxBSize = NS_UNCONSTRAINEDSIZE;
530
0
  }
531
0
532
0
  if (wm.IsVertical()) {
533
0
    if (aAssumeVScroll) {
534
0
      nsSize vScrollbarPrefSize;
535
0
      GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
536
0
                          nullptr, &vScrollbarPrefSize, false);
537
0
      if (computedBSize != NS_UNCONSTRAINEDSIZE) {
538
0
        computedBSize = std::max(0, computedBSize - vScrollbarPrefSize.width);
539
0
      }
540
0
      computedMinBSize = std::max(0, computedMinBSize - vScrollbarPrefSize.width);
541
0
      if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) {
542
0
        computedMaxBSize = std::max(0, computedMaxBSize - vScrollbarPrefSize.width);
543
0
      }
544
0
    }
545
0
546
0
    if (aAssumeHScroll) {
547
0
      nsSize hScrollbarPrefSize;
548
0
      GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
549
0
                          nullptr, &hScrollbarPrefSize, true);
550
0
      availISize = std::max(0, availISize - hScrollbarPrefSize.height);
551
0
    }
552
0
  } else {
553
0
    if (aAssumeHScroll) {
554
0
      nsSize hScrollbarPrefSize;
555
0
      GetScrollbarMetrics(aState->mBoxState, mHelper.mHScrollbarBox,
556
0
                          nullptr, &hScrollbarPrefSize, false);
557
0
      if (computedBSize != NS_UNCONSTRAINEDSIZE) {
558
0
        computedBSize = std::max(0, computedBSize - hScrollbarPrefSize.height);
559
0
      }
560
0
      computedMinBSize = std::max(0, computedMinBSize - hScrollbarPrefSize.height);
561
0
      if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) {
562
0
        computedMaxBSize = std::max(0, computedMaxBSize - hScrollbarPrefSize.height);
563
0
      }
564
0
    }
565
0
566
0
    if (aAssumeVScroll) {
567
0
      nsSize vScrollbarPrefSize;
568
0
      GetScrollbarMetrics(aState->mBoxState, mHelper.mVScrollbarBox,
569
0
                          nullptr, &vScrollbarPrefSize, true);
570
0
      availISize = std::max(0, availISize - vScrollbarPrefSize.width);
571
0
    }
572
0
  }
573
0
574
0
  nsPresContext* presContext = PresContext();
575
0
576
0
  // Pass false for aInit so we can pass in the correct padding.
577
0
  ReflowInput
578
0
    kidReflowInput(presContext, aState->mReflowInput,
579
0
                   mHelper.mScrolledFrame,
580
0
                   LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE),
581
0
                   nullptr, ReflowInput::CALLER_WILL_INIT);
582
0
  const nsMargin physicalPadding = padding.GetPhysicalMargin(wm);
583
0
  kidReflowInput.Init(presContext, nullptr, nullptr,
584
0
                      &physicalPadding);
585
0
  kidReflowInput.mFlags.mAssumingHScrollbar = aAssumeHScroll;
586
0
  kidReflowInput.mFlags.mAssumingVScrollbar = aAssumeVScroll;
587
0
  kidReflowInput.SetComputedBSize(computedBSize);
588
0
  kidReflowInput.ComputedMinBSize() = computedMinBSize;
589
0
  kidReflowInput.ComputedMaxBSize() = computedMaxBSize;
590
0
  if (aState->mReflowInput.IsBResizeForWM(kidReflowInput.GetWritingMode())) {
591
0
    kidReflowInput.SetBResize(true);
592
0
  }
593
0
594
0
  // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
595
0
  // reflect our assumptions while we reflow the child.
596
0
  bool didHaveHorizontalScrollbar = mHelper.mHasHorizontalScrollbar;
597
0
  bool didHaveVerticalScrollbar = mHelper.mHasVerticalScrollbar;
598
0
  mHelper.mHasHorizontalScrollbar = aAssumeHScroll;
599
0
  mHelper.mHasVerticalScrollbar = aAssumeVScroll;
600
0
601
0
  nsReflowStatus status;
602
0
  // No need to pass a true container-size to ReflowChild or
603
0
  // FinishReflowChild, because it's only used there when positioning
604
0
  // the frame (i.e. if NS_FRAME_NO_MOVE_FRAME isn't set)
605
0
  const nsSize dummyContainerSize;
606
0
  ReflowChild(mHelper.mScrolledFrame, presContext, *aMetrics,
607
0
              kidReflowInput, wm, LogicalPoint(wm), dummyContainerSize,
608
0
              NS_FRAME_NO_MOVE_FRAME, status);
609
0
610
0
  mHelper.mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
611
0
  mHelper.mHasVerticalScrollbar = didHaveVerticalScrollbar;
612
0
613
0
  // Don't resize or position the view (if any) because we're going to resize
614
0
  // it to the correct size anyway in PlaceScrollArea. Allowing it to
615
0
  // resize here would size it to the natural height of the frame,
616
0
  // which will usually be different from the scrollport height;
617
0
  // invalidating the difference will cause unnecessary repainting.
618
0
  FinishReflowChild(mHelper.mScrolledFrame, presContext,
619
0
                    *aMetrics, &kidReflowInput, wm, LogicalPoint(wm),
620
0
                    dummyContainerSize,
621
0
                    NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW);
622
0
623
0
  // XXX Some frames (e.g., nsPluginFrame, nsFrameFrame, nsTextFrame) don't bother
624
0
  // setting their mOverflowArea. This is wrong because every frame should
625
0
  // always set mOverflowArea. In fact nsPluginFrame and nsFrameFrame don't
626
0
  // support the 'outline' property because of this. Rather than fix the world
627
0
  // right now, just fix up the overflow area if necessary. Note that we don't
628
0
  // check HasOverflowRect() because it could be set even though the
629
0
  // overflow area doesn't include the frame bounds.
630
0
  aMetrics->UnionOverflowAreasWithDesiredBounds();
631
0
632
0
  auto* disp = StyleDisplay();
633
0
  if (MOZ_UNLIKELY(disp->mOverflowClipBoxBlock ==
634
0
                     NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX ||
635
0
                   disp->mOverflowClipBoxInline ==
636
0
                     NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) {
637
0
    nsOverflowAreas childOverflow;
638
0
    nsLayoutUtils::UnionChildOverflow(mHelper.mScrolledFrame, childOverflow);
639
0
    nsRect childScrollableOverflow = childOverflow.ScrollableOverflow();
640
0
    if (disp->mOverflowClipBoxBlock == NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX) {
641
0
      padding.BStart(wm) = nscoord(0);
642
0
      padding.BEnd(wm) = nscoord(0);
643
0
    }
644
0
    if (disp->mOverflowClipBoxInline == NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX) {
645
0
      padding.IStart(wm) = nscoord(0);
646
0
      padding.IEnd(wm) = nscoord(0);
647
0
    }
648
0
    childScrollableOverflow.Inflate(padding.GetPhysicalMargin(wm));
649
0
    nsRect contentArea =
650
0
      wm.IsVertical() ? nsRect(0, 0, computedBSize, availISize)
651
0
                      : nsRect(0, 0, availISize, computedBSize);
652
0
    if (!contentArea.Contains(childScrollableOverflow)) {
653
0
      aMetrics->mOverflowAreas.ScrollableOverflow() = childScrollableOverflow;
654
0
    }
655
0
  }
656
0
657
0
  aState->mContentsOverflowAreas = aMetrics->mOverflowAreas;
658
0
  aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
659
0
  aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
660
0
}
661
662
bool
663
nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowInput& aState)
664
0
{
665
0
  if (aState.mHScrollbar != ShowScrollbar::Auto) {
666
0
    // no guessing required
667
0
    return aState.mHScrollbar == ShowScrollbar::Always;
668
0
  }
669
0
  return mHelper.mHasHorizontalScrollbar;
670
0
}
671
672
bool
673
nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowInput& aState)
674
0
{
675
0
  if (aState.mVScrollbar != ShowScrollbar::Auto) {
676
0
    // no guessing required
677
0
    return aState.mVScrollbar == ShowScrollbar::Always;
678
0
  }
679
0
680
0
  // If we've had at least one non-initial reflow, then just assume
681
0
  // the state of the vertical scrollbar will be what we determined
682
0
  // last time.
683
0
  if (mHelper.mHadNonInitialReflow) {
684
0
    return mHelper.mHasVerticalScrollbar;
685
0
  }
686
0
687
0
  // If this is the initial reflow, guess false because usually
688
0
  // we have very little content by then.
689
0
  if (InInitialReflow())
690
0
    return false;
691
0
692
0
  if (mHelper.mIsRoot) {
693
0
    nsIFrame *f = mHelper.mScrolledFrame->PrincipalChildList().FirstChild();
694
0
    if (f && f->IsSVGOuterSVGFrame() &&
695
0
        static_cast<nsSVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) {
696
0
      // Common SVG case - avoid a bad guess.
697
0
      return false;
698
0
    }
699
0
    // Assume that there will be a scrollbar; it seems to me
700
0
    // that 'most pages' do have a scrollbar, and anyway, it's cheaper
701
0
    // to do an extra reflow for the pages that *don't* need a
702
0
    // scrollbar (because on average they will have less content).
703
0
    return true;
704
0
  }
705
0
706
0
  // For non-viewports, just guess that we don't need a scrollbar.
707
0
  // XXX I wonder if statistically this is the right idea; I'm
708
0
  // basically guessing that there are a lot of overflow:auto DIVs
709
0
  // that get their intrinsic size and don't overflow
710
0
  return false;
711
0
}
712
713
bool
714
nsHTMLScrollFrame::InInitialReflow() const
715
0
{
716
0
  // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
717
0
  // root scrollframe.  In that case we want to skip this clause altogether.
718
0
  // The guess here is that there are lots of overflow:auto divs out there that
719
0
  // end up auto-sizing so they don't overflow, and that the root basically
720
0
  // always needs a scrollbar if it did last time we loaded this page (good
721
0
  // assumption, because our initial reflow is no longer synchronous).
722
0
  return !mHelper.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
723
0
}
724
725
void
726
nsHTMLScrollFrame::ReflowContents(ScrollReflowInput* aState,
727
                                  const ReflowOutput& aDesiredSize)
728
0
{
729
0
  ReflowOutput kidDesiredSize(aDesiredSize.GetWritingMode());
730
0
  ReflowScrolledFrame(aState, GuessHScrollbarNeeded(*aState),
731
0
                      GuessVScrollbarNeeded(*aState), &kidDesiredSize, true);
732
0
733
0
  // There's an important special case ... if the child appears to fit
734
0
  // in the inside-border rect (but overflows the scrollport), we
735
0
  // should try laying it out without a vertical scrollbar. It will
736
0
  // usually fit because making the available-width wider will not
737
0
  // normally make the child taller. (The only situation I can think
738
0
  // of is when you have a line containing %-width inline replaced
739
0
  // elements whose percentages sum to more than 100%, so increasing
740
0
  // the available width makes the line break where it was fitting
741
0
  // before.) If we don't treat this case specially, then we will
742
0
  // decide that showing scrollbars is OK because the content
743
0
  // overflows when we're showing scrollbars and we won't try to
744
0
  // remove the vertical scrollbar.
745
0
746
0
  // Detecting when we enter this special case is important for when
747
0
  // people design layouts that exactly fit the container "most of the
748
0
  // time".
749
0
750
0
  // XXX Is this check really sufficient to catch all the incremental cases
751
0
  // where the ideal case doesn't have a scrollbar?
752
0
  if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
753
0
      aState->mVScrollbar != ShowScrollbar::Always &&
754
0
      aState->mHScrollbar != ShowScrollbar::Always) {
755
0
    nsSize insideBorderSize =
756
0
      ComputeInsideBorderSize(aState,
757
0
                              nsSize(kidDesiredSize.Width(), kidDesiredSize.Height()));
758
0
    nsRect scrolledRect =
759
0
      mHelper.GetUnsnappedScrolledRectInternal(kidDesiredSize.ScrollableOverflow(),
760
0
                                               insideBorderSize);
761
0
    if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
762
0
      // Let's pretend we had no scrollbars coming in here
763
0
      kidDesiredSize.mOverflowAreas.Clear();
764
0
      ReflowScrolledFrame(aState, false, false, &kidDesiredSize, false);
765
0
    }
766
0
  }
767
0
768
0
  // Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
769
0
  // Do this first because changing the vertical scrollbar setting is expensive,
770
0
  // forcing a reflow always.
771
0
772
0
  // Try leaving the horizontal scrollbar unchanged first. This will be more
773
0
  // efficient.
774
0
  if (TryLayout(aState, &kidDesiredSize, aState->mReflowedContentsWithHScrollbar,
775
0
                aState->mReflowedContentsWithVScrollbar, false))
776
0
    return;
777
0
  if (TryLayout(aState, &kidDesiredSize, !aState->mReflowedContentsWithHScrollbar,
778
0
                aState->mReflowedContentsWithVScrollbar, false))
779
0
    return;
780
0
781
0
  // OK, now try toggling the vertical scrollbar. The performance advantage
782
0
  // of trying the status-quo horizontal scrollbar state
783
0
  // does not exist here (we'll have to reflow due to the vertical scrollbar
784
0
  // change), so always try no horizontal scrollbar first.
785
0
  bool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
786
0
  if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false))
787
0
    return;
788
0
  if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false))
789
0
    return;
790
0
791
0
  // OK, we're out of ideas. Try again enabling whatever scrollbars we can
792
0
  // enable and force the layout to stick even if it's inconsistent.
793
0
  // This just happens sometimes.
794
0
  TryLayout(aState, &kidDesiredSize,
795
0
            aState->mHScrollbar != ShowScrollbar::Never,
796
0
            aState->mVScrollbar != ShowScrollbar::Never,
797
0
            true);
798
0
}
799
800
void
801
nsHTMLScrollFrame::PlaceScrollArea(ScrollReflowInput& aState,
802
                                   const nsPoint& aScrollPosition)
803
0
{
804
0
  nsIFrame *scrolledFrame = mHelper.mScrolledFrame;
805
0
  // Set the x,y of the scrolled frame to the correct value
806
0
  scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition);
807
0
808
0
  // Recompute our scrollable overflow, taking perspective children into
809
0
  // account. Note that this only recomputes the overflow areas stored on the
810
0
  // helper (which are used to compute scrollable length and scrollbar thumb
811
0
  // sizes) but not the overflow areas stored on the frame. This seems to work
812
0
  // for now, but it's possible that we may need to update both in the future.
813
0
  AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow());
814
0
815
0
  nsRect scrolledArea;
816
0
  // Preserve the width or height of empty rects
817
0
  nsSize portSize = mHelper.mScrollPort.Size();
818
0
  nsRect scrolledRect =
819
0
    mHelper.GetUnsnappedScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(),
820
0
                                             portSize);
821
0
  scrolledArea.UnionRectEdges(scrolledRect,
822
0
                              nsRect(nsPoint(0,0), portSize));
823
0
824
0
  // Store the new overflow area. Note that this changes where an outline
825
0
  // of the scrolled frame would be painted, but scrolled frames can't have
826
0
  // outlines (the outline would go on this scrollframe instead).
827
0
  // Using FinishAndStoreOverflow is needed so the overflow rect
828
0
  // gets set correctly.  It also messes with the overflow rect in the
829
0
  // -moz-hidden-unscrollable case, but scrolled frames can't have
830
0
  // 'overflow' either.
831
0
  // This needs to happen before SyncFrameViewAfterReflow so
832
0
  // HasOverflowRect() will return the correct value.
833
0
  nsOverflowAreas overflow(scrolledArea, scrolledArea);
834
0
  scrolledFrame->FinishAndStoreOverflow(overflow,
835
0
                                        scrolledFrame->GetSize());
836
0
837
0
  // Note that making the view *exactly* the size of the scrolled area
838
0
  // is critical, since the view scrolling code uses the size of the
839
0
  // scrolled view to clamp scroll requests.
840
0
  // Normally the scrolledFrame won't have a view but in some cases it
841
0
  // might create its own.
842
0
  nsContainerFrame::SyncFrameViewAfterReflow(scrolledFrame->PresContext(),
843
0
                                             scrolledFrame,
844
0
                                             scrolledFrame->GetView(),
845
0
                                             scrolledArea,
846
0
                                             0);
847
0
}
848
849
nscoord
850
nsHTMLScrollFrame::GetIntrinsicVScrollbarWidth(gfxContext *aRenderingContext)
851
0
{
852
0
  ScrollStyles ss = GetScrollStyles();
853
0
  if (ss.mVertical != NS_STYLE_OVERFLOW_SCROLL || !mHelper.mVScrollbarBox)
854
0
    return 0;
855
0
856
0
  // Don't need to worry about reflow depth here since it's
857
0
  // just for scrollbars
858
0
  nsBoxLayoutState bls(PresContext(), aRenderingContext, 0);
859
0
  nsSize vScrollbarPrefSize(0, 0);
860
0
  GetScrollbarMetrics(bls, mHelper.mVScrollbarBox,
861
0
                      nullptr, &vScrollbarPrefSize, true);
862
0
  return vScrollbarPrefSize.width;
863
0
}
864
865
/* virtual */ nscoord
866
nsHTMLScrollFrame::GetMinISize(gfxContext *aRenderingContext)
867
0
{
868
0
  nscoord result = mHelper.mScrolledFrame->GetMinISize(aRenderingContext);
869
0
  DISPLAY_MIN_INLINE_SIZE(this, result);
870
0
  return result + GetIntrinsicVScrollbarWidth(aRenderingContext);
871
0
}
872
873
/* virtual */ nscoord
874
nsHTMLScrollFrame::GetPrefISize(gfxContext *aRenderingContext)
875
0
{
876
0
  nscoord result = mHelper.mScrolledFrame->GetPrefISize(aRenderingContext);
877
0
  DISPLAY_PREF_INLINE_SIZE(this, result);
878
0
  return NSCoordSaturatingAdd(result, GetIntrinsicVScrollbarWidth(aRenderingContext));
879
0
}
880
881
nsresult
882
nsHTMLScrollFrame::GetXULPadding(nsMargin& aMargin)
883
0
{
884
0
  // Our padding hangs out on the inside of the scrollframe, but XUL doesn't
885
0
  // reaize that.  If we're stuck inside a XUL box, we need to claim no
886
0
  // padding.
887
0
  // @see also nsXULScrollFrame::GetXULPadding.
888
0
  aMargin.SizeTo(0,0,0,0);
889
0
  return NS_OK;
890
0
}
891
892
bool
893
nsHTMLScrollFrame::IsXULCollapsed()
894
0
{
895
0
  // We're never collapsed in the box sense.
896
0
  return false;
897
0
}
898
899
// Return the <browser> if the scrollframe is for the root frame directly
900
// inside a <browser>.
901
static Element*
902
GetBrowserRoot(nsIContent* aContent)
903
0
{
904
0
  if (aContent) {
905
0
    nsIDocument* doc = aContent->GetUncomposedDoc();
906
0
    if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
907
0
      Element* frameElement = win->GetFrameElementInternal();
908
0
      if (frameElement &&
909
0
          frameElement->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL))
910
0
        return frameElement;
911
0
    }
912
0
  }
913
0
914
0
  return nullptr;
915
0
}
916
917
// When we have perspective set on the outer scroll frame, and transformed
918
// children (possibly with preserve-3d) then the effective transform on the
919
// child depends on the offset to the scroll frame, which changes as we scroll.
920
// This perspective transform can cause the element to move relative to the
921
// scrolled inner frame, which would cause the scrollable length changes during
922
// scrolling if we didn't account for it. Since we don't want scrollHeight/Width
923
// and the size of scrollbar thumbs to change during scrolling, we compute the
924
// scrollable overflow by determining the scroll position at which the child
925
// becomes completely visible within the scrollport rather than using the union
926
// of the overflow areas at their current position.
927
static void
928
GetScrollableOverflowForPerspective(nsIFrame* aScrolledFrame,
929
                                    nsIFrame* aCurrentFrame,
930
                                    const nsRect aScrollPort,
931
                                    nsPoint aOffset,
932
                                    nsRect& aScrolledFrameOverflowArea)
933
0
{
934
0
  // Iterate over all children except pop-ups.
935
0
  FrameChildListIDs skip = nsIFrame::kSelectPopupList | nsIFrame::kPopupList;
936
0
  for (nsIFrame::ChildListIterator childLists(aCurrentFrame);
937
0
       !childLists.IsDone(); childLists.Next()) {
938
0
    if (skip.Contains(childLists.CurrentID())) {
939
0
      continue;
940
0
    }
941
0
942
0
    for (nsIFrame* child : childLists.CurrentList()) {
943
0
      nsPoint offset = aOffset;
944
0
945
0
      // When we reach a direct child of the scroll, then we record the offset
946
0
      // to convert from that frame's coordinate into the scroll frame's
947
0
      // coordinates. Preserve-3d descendant frames use the same offset as their
948
0
      // ancestors, since TransformRect already converts us into the coordinate
949
0
      // space of the preserve-3d root.
950
0
      if (aScrolledFrame == aCurrentFrame) {
951
0
        offset = child->GetPosition();
952
0
      }
953
0
954
0
      if (child->Extend3DContext()) {
955
0
        // If we're a preserve-3d frame, then recurse and include our
956
0
        // descendants since overflow of preserve-3d frames is only included
957
0
        // in the post-transform overflow area of the preserve-3d root frame.
958
0
        GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort,
959
0
                                            offset, aScrolledFrameOverflowArea);
960
0
      }
961
0
962
0
      // If we're transformed, then we want to consider the possibility that
963
0
      // this frame might move relative to the scrolled frame when scrolling.
964
0
      // For preserve-3d, leaf frames have correct overflow rects relative to
965
0
      // themselves. preserve-3d 'nodes' (intermediate frames and the root) have
966
0
      // only their untransformed children included in their overflow relative
967
0
      // to self, which is what we want to include here.
968
0
      if (child->IsTransformed()) {
969
0
        // Compute the overflow rect for this leaf transform frame in the
970
0
        // coordinate space of the scrolled frame.
971
0
        nsPoint scrollPos = aScrolledFrame->GetPosition();
972
0
        nsRect preScroll = nsDisplayTransform::TransformRect(
973
0
          child->GetScrollableOverflowRectRelativeToSelf(), child);
974
0
975
0
        // Temporarily override the scroll position of the scrolled frame by
976
0
        // 10 CSS pixels, and then recompute what the overflow rect would be.
977
0
        // This scroll position may not be valid, but that shouldn't matter
978
0
        // for our calculations.
979
0
        aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600));
980
0
        nsRect postScroll = nsDisplayTransform::TransformRect(
981
0
          child->GetScrollableOverflowRectRelativeToSelf(), child);
982
0
        aScrolledFrame->SetPosition(scrollPos);
983
0
984
0
        // Compute how many app units the overflow rects moves by when we adjust
985
0
        // the scroll position by 1 app unit.
986
0
        double rightDelta =
987
0
          (postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0;
988
0
        double bottomDelta =
989
0
          (postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0;
990
0
991
0
        // We can't ever have negative scrolling.
992
0
        NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f,
993
0
                     "Scrolling can't be reversed!");
994
0
995
0
        // Move preScroll into the coordinate space of the scrollport.
996
0
        preScroll += offset + scrollPos;
997
0
998
0
        // For each of the four edges of preScroll, figure out how far they
999
0
        // extend beyond the scrollport. Ignore negative values since that means
1000
0
        // that side is already scrolled in to view and we don't need to add
1001
0
        // overflow to account for it.
1002
0
        nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()),
1003
0
                          std::max(0, preScroll.XMost() - aScrollPort.XMost()),
1004
0
                          std::max(0, preScroll.YMost() - aScrollPort.YMost()),
1005
0
                          std::max(0, aScrollPort.X() - preScroll.X()));
1006
0
1007
0
        // Scale according to rightDelta/bottomDelta to adjust for the different
1008
0
        // scroll rates.
1009
0
        overhang.top /= bottomDelta;
1010
0
        overhang.right /= rightDelta;
1011
0
        overhang.bottom /= bottomDelta;
1012
0
        overhang.left /= rightDelta;
1013
0
1014
0
        // Take the minimum overflow rect that would allow the current scroll
1015
0
        // position, using the size of the scroll port and offset by the
1016
0
        // inverse of the scroll position.
1017
0
        nsRect overflow = aScrollPort - scrollPos;
1018
0
1019
0
        // Expand it by our margins to get an overflow rect that would allow all
1020
0
        // edges of our transformed content to be scrolled into view.
1021
0
        overflow.Inflate(overhang);
1022
0
1023
0
        // Merge it with the combined overflow
1024
0
        aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea,
1025
0
                                             overflow);
1026
0
      } else if (aCurrentFrame == aScrolledFrame) {
1027
0
        aScrolledFrameOverflowArea.UnionRect(
1028
0
          aScrolledFrameOverflowArea,
1029
0
          child->GetScrollableOverflowRectRelativeToParent());
1030
0
      }
1031
0
    }
1032
0
  }
1033
0
}
1034
1035
void
1036
nsHTMLScrollFrame::AdjustForPerspective(nsRect& aScrollableOverflow)
1037
0
{
1038
0
  // If we have perspective that is being applied to our children, then
1039
0
  // the effective transform on the child depends on the relative position
1040
0
  // of the child to us and changes during scrolling.
1041
0
  if (!ChildrenHavePerspective()) {
1042
0
    return;
1043
0
  }
1044
0
  aScrollableOverflow.SetEmpty();
1045
0
  GetScrollableOverflowForPerspective(mHelper.mScrolledFrame,
1046
0
                                      mHelper.mScrolledFrame,
1047
0
                                      mHelper.mScrollPort,
1048
0
                                      nsPoint(), aScrollableOverflow);
1049
0
}
1050
1051
void
1052
nsHTMLScrollFrame::Reflow(nsPresContext*           aPresContext,
1053
                          ReflowOutput&     aDesiredSize,
1054
                          const ReflowInput& aReflowInput,
1055
                          nsReflowStatus&          aStatus)
1056
0
{
1057
0
  MarkInReflow();
1058
0
  DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
1059
0
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1060
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1061
0
1062
0
  mHelper.HandleScrollbarStyleSwitching();
1063
0
1064
0
  ScrollReflowInput state(this, aReflowInput);
1065
0
  // sanity check: ensure that if we have no scrollbar, we treat it
1066
0
  // as hidden.
1067
0
  if (!mHelper.mVScrollbarBox || mHelper.mNeverHasVerticalScrollbar) {
1068
0
    state.mVScrollbar = ShowScrollbar::Never;
1069
0
  }
1070
0
  if (!mHelper.mHScrollbarBox || mHelper.mNeverHasHorizontalScrollbar) {
1071
0
    state.mHScrollbar = ShowScrollbar::Never;
1072
0
  }
1073
0
1074
0
  //------------ Handle Incremental Reflow -----------------
1075
0
  bool reflowHScrollbar = true;
1076
0
  bool reflowVScrollbar = true;
1077
0
  bool reflowScrollCorner = true;
1078
0
  if (!aReflowInput.ShouldReflowAllKids()) {
1079
0
    #define NEEDS_REFLOW(frame_) \
1080
0
      ((frame_) && NS_SUBTREE_DIRTY(frame_))
1081
0
1082
0
    reflowHScrollbar = NEEDS_REFLOW(mHelper.mHScrollbarBox);
1083
0
    reflowVScrollbar = NEEDS_REFLOW(mHelper.mVScrollbarBox);
1084
0
    reflowScrollCorner = NEEDS_REFLOW(mHelper.mScrollCornerBox) ||
1085
0
                         NEEDS_REFLOW(mHelper.mResizerBox);
1086
0
1087
0
    #undef NEEDS_REFLOW
1088
0
  }
1089
0
1090
0
  if (mHelper.mIsRoot) {
1091
0
    mHelper.mCollapsedResizer = true;
1092
0
1093
0
    Element* browserRoot = GetBrowserRoot(mContent);
1094
0
    if (browserRoot) {
1095
0
      bool showResizer = browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer);
1096
0
      reflowScrollCorner = showResizer == mHelper.mCollapsedResizer;
1097
0
      mHelper.mCollapsedResizer = !showResizer;
1098
0
    }
1099
0
1100
0
    // Hide the scrollbar when the scrollbar-width is set to none.
1101
0
    // This is only needed for root element because scrollbars of non-
1102
0
    // root elements with "scrollbar-width: none" is already suppressed
1103
0
    // in ScrollFrameHelper::CreateAnonymousContent.
1104
0
    ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(this);
1105
0
    auto scrollbarWidth = scrollbarStyle->StyleUIReset()->mScrollbarWidth;
1106
0
    if (scrollbarWidth == StyleScrollbarWidth::None) {
1107
0
      state.mVScrollbar = ShowScrollbar::Never;
1108
0
      state.mHScrollbar = ShowScrollbar::Never;
1109
0
    }
1110
0
  }
1111
0
1112
0
  nsRect oldScrollAreaBounds = mHelper.mScrollPort;
1113
0
  nsRect oldScrolledAreaBounds =
1114
0
    mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
1115
0
  nsPoint oldScrollPosition = mHelper.GetScrollPosition();
1116
0
1117
0
  state.mComputedBorder = aReflowInput.ComputedPhysicalBorderPadding() -
1118
0
    aReflowInput.ComputedPhysicalPadding();
1119
0
1120
0
  ReflowContents(&state, aDesiredSize);
1121
0
1122
0
  aDesiredSize.Width() = state.mInsideBorderSize.width +
1123
0
    state.mComputedBorder.LeftRight();
1124
0
  aDesiredSize.Height() = state.mInsideBorderSize.height +
1125
0
    state.mComputedBorder.TopBottom();
1126
0
1127
0
  // Set the size of the frame now since computing the perspective-correct
1128
0
  // overflow (within PlaceScrollArea) can rely on it.
1129
0
  SetSize(aDesiredSize.GetWritingMode(),
1130
0
          aDesiredSize.Size(aDesiredSize.GetWritingMode()));
1131
0
1132
0
  // Restore the old scroll position, for now, even if that's not valid anymore
1133
0
  // because we changed size. We'll fix it up in a post-reflow callback, because
1134
0
  // our current size may only be temporary (e.g. we're compute XUL desired sizes).
1135
0
  PlaceScrollArea(state, oldScrollPosition);
1136
0
  if (!mHelper.mPostedReflowCallback) {
1137
0
    // Make sure we'll try scrolling to restored position
1138
0
    PresShell()->PostReflowCallback(&mHelper);
1139
0
    mHelper.mPostedReflowCallback = true;
1140
0
  }
1141
0
1142
0
  bool didHaveHScrollbar = mHelper.mHasHorizontalScrollbar;
1143
0
  bool didHaveVScrollbar = mHelper.mHasVerticalScrollbar;
1144
0
  mHelper.mHasHorizontalScrollbar = state.mShowHScrollbar;
1145
0
  mHelper.mHasVerticalScrollbar = state.mShowVScrollbar;
1146
0
  nsRect newScrollAreaBounds = mHelper.mScrollPort;
1147
0
  nsRect newScrolledAreaBounds =
1148
0
    mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
1149
0
  if (mHelper.mSkippedScrollbarLayout ||
1150
0
      reflowHScrollbar || reflowVScrollbar || reflowScrollCorner ||
1151
0
      (GetStateBits() & NS_FRAME_IS_DIRTY) ||
1152
0
      didHaveHScrollbar != state.mShowHScrollbar ||
1153
0
      didHaveVScrollbar != state.mShowVScrollbar ||
1154
0
      !oldScrollAreaBounds.IsEqualEdges(newScrollAreaBounds) ||
1155
0
      !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
1156
0
    if (!mHelper.mSuppressScrollbarUpdate) {
1157
0
      mHelper.mSkippedScrollbarLayout = false;
1158
0
      ScrollFrameHelper::SetScrollbarVisibility(mHelper.mHScrollbarBox, state.mShowHScrollbar);
1159
0
      ScrollFrameHelper::SetScrollbarVisibility(mHelper.mVScrollbarBox, state.mShowVScrollbar);
1160
0
      // place and reflow scrollbars
1161
0
      nsRect insideBorderArea =
1162
0
        nsRect(nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
1163
0
               state.mInsideBorderSize);
1164
0
      mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea,
1165
0
                              oldScrollAreaBounds);
1166
0
    } else {
1167
0
      mHelper.mSkippedScrollbarLayout = true;
1168
0
    }
1169
0
  }
1170
0
1171
0
  aDesiredSize.SetOverflowAreasToDesiredBounds();
1172
0
  if (mHelper.IsIgnoringViewportClipping()) {
1173
0
    aDesiredSize.mOverflowAreas.UnionWith(
1174
0
      state.mContentsOverflowAreas + mHelper.mScrolledFrame->GetPosition());
1175
0
  }
1176
0
1177
0
  mHelper.UpdateSticky();
1178
0
  FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
1179
0
1180
0
  if (!InInitialReflow() && !mHelper.mHadNonInitialReflow) {
1181
0
    mHelper.mHadNonInitialReflow = true;
1182
0
  }
1183
0
1184
0
  if (mHelper.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
1185
0
    mHelper.PostScrolledAreaEvent();
1186
0
  }
1187
0
1188
0
  mHelper.UpdatePrevScrolledRect();
1189
0
1190
0
  aStatus.Reset(); // This type of frame can't be split.
1191
0
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
1192
0
  mHelper.PostOverflowEvent();
1193
0
}
1194
1195
1196
////////////////////////////////////////////////////////////////////////////////
1197
1198
#ifdef DEBUG_FRAME_DUMP
1199
nsresult
1200
nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const
1201
{
1202
  return MakeFrameName(NS_LITERAL_STRING("HTMLScroll"), aResult);
1203
}
1204
#endif
1205
1206
#ifdef ACCESSIBILITY
1207
a11y::AccType
1208
nsHTMLScrollFrame::AccessibleType()
1209
0
{
1210
0
  if (IsTableCaption()) {
1211
0
    return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
1212
0
  }
1213
0
1214
0
  // Create an accessible regardless of focusable state because the state can be
1215
0
  // changed during frame life cycle without any notifications to accessibility.
1216
0
  if (mContent->IsRootOfNativeAnonymousSubtree() ||
1217
0
      GetScrollStyles().IsHiddenInBothDirections()) {
1218
0
    return a11y::eNoType;
1219
0
  }
1220
0
1221
0
  return a11y::eHyperTextType;
1222
0
}
1223
#endif
1224
1225
0
NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
1226
0
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1227
0
  NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1228
0
  NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1229
0
  NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
1230
0
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
1231
1232
//----------nsXULScrollFrame-------------------------------------------
1233
1234
nsXULScrollFrame*
1235
NS_NewXULScrollFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle,
1236
                     bool aIsRoot, bool aClipAllDescendants)
1237
0
{
1238
0
  return new (aPresShell) nsXULScrollFrame(aStyle, aIsRoot,
1239
0
                                           aClipAllDescendants);
1240
0
}
1241
1242
NS_IMPL_FRAMEARENA_HELPERS(nsXULScrollFrame)
1243
1244
nsXULScrollFrame::nsXULScrollFrame(ComputedStyle* aStyle,
1245
                                   bool aIsRoot,
1246
                                   bool aClipAllDescendants)
1247
  : nsBoxFrame(aStyle, kClassID, aIsRoot)
1248
  , mHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this), aIsRoot)
1249
0
{
1250
0
  SetXULLayoutManager(nullptr);
1251
0
  mHelper.mClipAllDescendants = aClipAllDescendants;
1252
0
}
1253
1254
void
1255
nsXULScrollFrame::ScrollbarActivityStarted() const
1256
0
{
1257
0
  if (mHelper.mScrollbarActivity) {
1258
0
    mHelper.mScrollbarActivity->ActivityStarted();
1259
0
  }
1260
0
}
1261
1262
void
1263
nsXULScrollFrame::ScrollbarActivityStopped() const
1264
0
{
1265
0
  if (mHelper.mScrollbarActivity) {
1266
0
    mHelper.mScrollbarActivity->ActivityStopped();
1267
0
  }
1268
0
}
1269
1270
nsMargin
1271
ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState)
1272
0
{
1273
0
  NS_ASSERTION(aState && aState->GetRenderingContext(),
1274
0
               "Must have rendering context in layout state for size "
1275
0
               "computations");
1276
0
1277
0
  nsMargin result(0, 0, 0, 0);
1278
0
1279
0
  if (mVScrollbarBox) {
1280
0
    nsSize size = mVScrollbarBox->GetXULPrefSize(*aState);
1281
0
    nsBox::AddMargin(mVScrollbarBox, size);
1282
0
    if (IsScrollbarOnRight())
1283
0
      result.left = size.width;
1284
0
    else
1285
0
      result.right = size.width;
1286
0
  }
1287
0
1288
0
  if (mHScrollbarBox) {
1289
0
    nsSize size = mHScrollbarBox->GetXULPrefSize(*aState);
1290
0
    nsBox::AddMargin(mHScrollbarBox, size);
1291
0
    // We don't currently support any scripts that would require a scrollbar
1292
0
    // at the top. (Are there any?)
1293
0
    result.bottom = size.height;
1294
0
  }
1295
0
1296
0
  return result;
1297
0
}
1298
1299
nscoord
1300
ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState,
1301
                                                    WritingMode aWM)
1302
0
{
1303
0
  NS_ASSERTION(aState && aState->GetRenderingContext(),
1304
0
               "Must have rendering context in layout state for size "
1305
0
               "computations");
1306
0
1307
0
  bool verticalWM = aWM.IsVertical();
1308
0
  if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
1309
0
    // We're using overlay scrollbars, so we need to get the width that
1310
0
    // non-disappearing scrollbars would have.
1311
0
    nsITheme* theme = aState->PresContext()->GetTheme();
1312
0
    if (theme &&
1313
0
        theme->ThemeSupportsWidget(aState->PresContext(),
1314
0
                                   verticalWM ? mHScrollbarBox
1315
0
                                              : mVScrollbarBox,
1316
0
                                   StyleAppearance::ScrollbarNonDisappearing)) {
1317
0
      LayoutDeviceIntSize size;
1318
0
      bool canOverride = true;
1319
0
      theme->GetMinimumWidgetSize(aState->PresContext(),
1320
0
                                  verticalWM ? mHScrollbarBox
1321
0
                                             : mVScrollbarBox,
1322
0
                                  StyleAppearance::ScrollbarNonDisappearing,
1323
0
                                  &size,
1324
0
                                  &canOverride);
1325
0
      return aState->PresContext()->
1326
0
             DevPixelsToAppUnits(verticalWM ? size.height : size.width);
1327
0
    }
1328
0
  }
1329
0
1330
0
  nsMargin sizes(GetDesiredScrollbarSizes(aState));
1331
0
  return verticalWM ? sizes.TopBottom() : sizes.LeftRight();
1332
0
}
1333
1334
void
1335
ScrollFrameHelper::HandleScrollbarStyleSwitching()
1336
0
{
1337
0
  // Check if we switched between scrollbar styles.
1338
0
  if (mScrollbarActivity &&
1339
0
      LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) {
1340
0
    mScrollbarActivity->Destroy();
1341
0
    mScrollbarActivity = nullptr;
1342
0
    mOuter->PresContext()->ThemeChanged();
1343
0
  }
1344
0
  else if (!mScrollbarActivity &&
1345
0
           LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
1346
0
    mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(mOuter));
1347
0
    mOuter->PresContext()->ThemeChanged();
1348
0
  }
1349
0
}
1350
1351
#if defined(MOZ_WIDGET_ANDROID)
1352
static bool IsFocused(nsIContent* aContent)
1353
{
1354
  // Some content elements, like the GetContent() of a scroll frame
1355
  // for a text input field, are inside anonymous subtrees, but the focus
1356
  // manager always reports a non-anonymous element as the focused one, so
1357
  // walk up the tree until we reach a non-anonymous element.
1358
  while (aContent && aContent->IsInAnonymousSubtree()) {
1359
    aContent = aContent->GetParent();
1360
  }
1361
1362
  return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
1363
}
1364
#endif
1365
1366
void
1367
ScrollFrameHelper::SetScrollableByAPZ(bool aScrollable)
1368
0
{
1369
0
  mScrollableByAPZ = aScrollable;
1370
0
}
1371
1372
void
1373
ScrollFrameHelper::SetZoomableByAPZ(bool aZoomable)
1374
0
{
1375
0
  if (mZoomableByAPZ != aZoomable) {
1376
0
    // We might be changing the result of WantAsyncScroll() so schedule a
1377
0
    // paint to make sure we pick up the result of that change.
1378
0
    mZoomableByAPZ = aZoomable;
1379
0
    mOuter->SchedulePaint();
1380
0
  }
1381
0
}
1382
1383
void
1384
ScrollFrameHelper::SetHasOutOfFlowContentInsideFilter()
1385
0
{
1386
0
  mHasOutOfFlowContentInsideFilter = true;
1387
0
}
1388
1389
bool
1390
ScrollFrameHelper::WantAsyncScroll() const
1391
0
{
1392
0
  // If zooming is allowed, and this is a frame that's allowed to zoom, then
1393
0
  // we want it to be async-scrollable or zooming will not be permitted.
1394
0
  if (mZoomableByAPZ) {
1395
0
    return true;
1396
0
  }
1397
0
1398
0
  ScrollStyles styles = GetScrollStylesFromFrame();
1399
0
  nscoord oneDevPixel = GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
1400
0
  nsRect scrollRange = GetScrollRange();
1401
0
1402
0
  // If the page has a visual viewport size that's different from
1403
0
  // the layout viewport size at the current zoom level, we need to be
1404
0
  // able to scroll the visual viewport inside the layout viewport
1405
0
  // even if the page is not zoomable.
1406
0
  if (!GetScrollRangeForClamping().IsEqualInterior(scrollRange)) {
1407
0
    return true;
1408
0
  }
1409
0
1410
0
  bool isVScrollable = (scrollRange.height >= oneDevPixel) &&
1411
0
                       (styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
1412
0
  bool isHScrollable = (scrollRange.width >= oneDevPixel) &&
1413
0
                       (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN);
1414
0
1415
#if defined(MOZ_WIDGET_ANDROID)
1416
  // Mobile platforms need focus to scroll text inputs.
1417
  bool canScrollWithoutScrollbars =
1418
    !IsForTextControlWithNoScrollbars() || IsFocused(mOuter->GetContent());
1419
#else
1420
  bool canScrollWithoutScrollbars = true;
1421
0
#endif
1422
0
1423
0
  // The check for scroll bars was added in bug 825692 to prevent layerization
1424
0
  // of text inputs for performance reasons.
1425
0
  bool isVAsyncScrollable = isVScrollable && (mVScrollbarBox || canScrollWithoutScrollbars);
1426
0
  bool isHAsyncScrollable = isHScrollable && (mHScrollbarBox || canScrollWithoutScrollbars);
1427
0
  return isVAsyncScrollable || isHAsyncScrollable;
1428
0
}
1429
1430
static nsRect
1431
GetOnePixelRangeAroundPoint(const nsPoint& aPoint, bool aIsHorizontal)
1432
0
{
1433
0
  nsRect allowedRange(aPoint, nsSize());
1434
0
  nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
1435
0
  if (aIsHorizontal) {
1436
0
    allowedRange.x = aPoint.x - halfPixel;
1437
0
    allowedRange.width = halfPixel*2 - 1;
1438
0
  } else {
1439
0
    allowedRange.y = aPoint.y - halfPixel;
1440
0
    allowedRange.height = halfPixel*2 - 1;
1441
0
  }
1442
0
  return allowedRange;
1443
0
}
1444
1445
void
1446
ScrollFrameHelper::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
1447
                                nsIScrollbarMediator::ScrollSnapMode aSnap)
1448
0
{
1449
0
  ScrollByUnit(aScrollbar, nsIScrollableFrame::SMOOTH, aDirection,
1450
0
               nsIScrollableFrame::PAGES, aSnap);
1451
0
}
1452
1453
void
1454
ScrollFrameHelper::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
1455
                                 nsIScrollbarMediator::ScrollSnapMode aSnap)
1456
0
{
1457
0
  ScrollByUnit(aScrollbar, nsIScrollableFrame::INSTANT, aDirection,
1458
0
               nsIScrollableFrame::WHOLE, aSnap);
1459
0
}
1460
1461
void
1462
ScrollFrameHelper::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
1463
                                nsIScrollbarMediator::ScrollSnapMode aSnap)
1464
0
{
1465
0
  bool isHorizontal = aScrollbar->IsXULHorizontal();
1466
0
  nsIntPoint delta;
1467
0
  if (isHorizontal) {
1468
0
    const double kScrollMultiplier =
1469
0
      Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
1470
0
                          NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
1471
0
    delta.x = aDirection * kScrollMultiplier;
1472
0
    if (GetLineScrollAmount().width * delta.x > GetPageScrollAmount().width) {
1473
0
      // The scroll frame is so small that the delta would be more
1474
0
      // than an entire page.  Scroll by one page instead to maintain
1475
0
      // context.
1476
0
      ScrollByPage(aScrollbar, aDirection);
1477
0
      return;
1478
0
    }
1479
0
  } else {
1480
0
    const double kScrollMultiplier =
1481
0
      Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
1482
0
                          NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
1483
0
    delta.y = aDirection * kScrollMultiplier;
1484
0
    if (GetLineScrollAmount().height * delta.y > GetPageScrollAmount().height) {
1485
0
      // The scroll frame is so small that the delta would be more
1486
0
      // than an entire page.  Scroll by one page instead to maintain
1487
0
      // context.
1488
0
      ScrollByPage(aScrollbar, aDirection);
1489
0
      return;
1490
0
    }
1491
0
  }
1492
0
1493
0
  nsIntPoint overflow;
1494
0
  ScrollBy(delta, nsIScrollableFrame::LINES, nsIScrollableFrame::SMOOTH,
1495
0
           &overflow, nsGkAtoms::other, nsIScrollableFrame::NOT_MOMENTUM,
1496
0
           aSnap);
1497
0
}
1498
1499
void
1500
ScrollFrameHelper::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
1501
0
{
1502
0
  aScrollbar->MoveToNewPosition();
1503
0
}
1504
1505
void
1506
ScrollFrameHelper::ThumbMoved(nsScrollbarFrame* aScrollbar,
1507
                              nscoord aOldPos,
1508
                              nscoord aNewPos)
1509
0
{
1510
0
  MOZ_ASSERT(aScrollbar != nullptr);
1511
0
  bool isHorizontal = aScrollbar->IsXULHorizontal();
1512
0
  nsPoint current = GetScrollPosition();
1513
0
  nsPoint dest = current;
1514
0
  if (isHorizontal) {
1515
0
    dest.x = IsPhysicalLTR() ? aNewPos : aNewPos - GetScrollRange().width;
1516
0
  } else {
1517
0
    dest.y = aNewPos;
1518
0
  }
1519
0
  nsRect allowedRange = GetOnePixelRangeAroundPoint(dest, isHorizontal);
1520
0
1521
0
  // Don't try to scroll if we're already at an acceptable place.
1522
0
  // Don't call Contains here since Contains returns false when the point is
1523
0
  // on the bottom or right edge of the rectangle.
1524
0
  if (allowedRange.ClampPoint(current) == current) {
1525
0
    return;
1526
0
  }
1527
0
1528
0
  ScrollTo(dest, nsIScrollableFrame::INSTANT, &allowedRange);
1529
0
}
1530
1531
void
1532
ScrollFrameHelper::ScrollbarReleased(nsScrollbarFrame* aScrollbar)
1533
0
{
1534
0
  // Scrollbar scrolling does not result in fling gestures, clear any
1535
0
  // accumulated velocity
1536
0
  mVelocityQueue.Reset();
1537
0
1538
0
  // Perform scroll snapping, if needed.  Scrollbar movement uses the same
1539
0
  // smooth scrolling animation as keyboard scrolling.
1540
0
  ScrollSnap(mDestination, nsIScrollableFrame::SMOOTH);
1541
0
}
1542
1543
void
1544
ScrollFrameHelper::ScrollByUnit(nsScrollbarFrame* aScrollbar,
1545
                                nsIScrollableFrame::ScrollMode aMode,
1546
                                int32_t aDirection,
1547
                                nsIScrollableFrame::ScrollUnit aUnit,
1548
                                nsIScrollbarMediator::ScrollSnapMode aSnap)
1549
0
{
1550
0
  MOZ_ASSERT(aScrollbar != nullptr);
1551
0
  bool isHorizontal = aScrollbar->IsXULHorizontal();
1552
0
  nsIntPoint delta;
1553
0
  if (isHorizontal) {
1554
0
    delta.x = aDirection;
1555
0
  } else {
1556
0
    delta.y = aDirection;
1557
0
  }
1558
0
  nsIntPoint overflow;
1559
0
  ScrollBy(delta, aUnit, aMode, &overflow, nsGkAtoms::other,
1560
0
           nsIScrollableFrame::NOT_MOMENTUM, aSnap);
1561
0
}
1562
1563
nsresult
1564
nsXULScrollFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1565
0
{
1566
0
  return mHelper.CreateAnonymousContent(aElements);
1567
0
}
1568
1569
void
1570
nsXULScrollFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
1571
                                           uint32_t aFilter)
1572
0
{
1573
0
  mHelper.AppendAnonymousContentTo(aElements, aFilter);
1574
0
}
1575
1576
void
1577
nsXULScrollFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
1578
0
{
1579
0
  mHelper.Destroy(aPostDestroyData);
1580
0
  nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
1581
0
}
1582
1583
void
1584
nsXULScrollFrame::SetInitialChildList(ChildListID     aListID,
1585
                                      nsFrameList&    aChildList)
1586
0
{
1587
0
  nsBoxFrame::SetInitialChildList(aListID, aChildList);
1588
0
  if (aListID == kPrincipalList) {
1589
0
    mHelper.ReloadChildFrames();
1590
0
  }
1591
0
}
1592
1593
1594
void
1595
nsXULScrollFrame::AppendFrames(ChildListID     aListID,
1596
                               nsFrameList&    aFrameList)
1597
0
{
1598
0
  nsBoxFrame::AppendFrames(aListID, aFrameList);
1599
0
  mHelper.ReloadChildFrames();
1600
0
}
1601
1602
void
1603
nsXULScrollFrame::InsertFrames(ChildListID     aListID,
1604
                               nsIFrame*       aPrevFrame,
1605
                               nsFrameList&    aFrameList)
1606
0
{
1607
0
  nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1608
0
  mHelper.ReloadChildFrames();
1609
0
}
1610
1611
void
1612
nsXULScrollFrame::RemoveFrame(ChildListID     aListID,
1613
                              nsIFrame*       aOldFrame)
1614
0
{
1615
0
  nsBoxFrame::RemoveFrame(aListID, aOldFrame);
1616
0
  mHelper.ReloadChildFrames();
1617
0
}
1618
1619
nsSplittableType
1620
nsXULScrollFrame::GetSplittableType() const
1621
0
{
1622
0
  return NS_FRAME_NOT_SPLITTABLE;
1623
0
}
1624
1625
nsresult
1626
nsXULScrollFrame::GetXULPadding(nsMargin& aMargin)
1627
0
{
1628
0
  aMargin.SizeTo(0,0,0,0);
1629
0
  return NS_OK;
1630
0
}
1631
1632
nscoord
1633
nsXULScrollFrame::GetXULBoxAscent(nsBoxLayoutState& aState)
1634
0
{
1635
0
  if (!mHelper.mScrolledFrame)
1636
0
    return 0;
1637
0
1638
0
  nscoord ascent = mHelper.mScrolledFrame->GetXULBoxAscent(aState);
1639
0
  nsMargin m(0,0,0,0);
1640
0
  GetXULBorderAndPadding(m);
1641
0
  ascent += m.top;
1642
0
  GetXULMargin(m);
1643
0
  ascent += m.top;
1644
0
1645
0
  return ascent;
1646
0
}
1647
1648
nsSize
1649
nsXULScrollFrame::GetXULPrefSize(nsBoxLayoutState& aState)
1650
0
{
1651
0
  nsSize pref = mHelper.mScrolledFrame->GetXULPrefSize(aState);
1652
0
1653
0
  ScrollStyles styles = GetScrollStyles();
1654
0
1655
0
  // scrolled frames don't have their own margins
1656
0
1657
0
  if (mHelper.mVScrollbarBox &&
1658
0
      styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1659
0
    nsSize vSize = mHelper.mVScrollbarBox->GetXULPrefSize(aState);
1660
0
    nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
1661
0
    pref.width += vSize.width;
1662
0
  }
1663
0
1664
0
  if (mHelper.mHScrollbarBox &&
1665
0
      styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1666
0
    nsSize hSize = mHelper.mHScrollbarBox->GetXULPrefSize(aState);
1667
0
    nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
1668
0
    pref.height += hSize.height;
1669
0
  }
1670
0
1671
0
  AddBorderAndPadding(pref);
1672
0
  bool widthSet, heightSet;
1673
0
  nsIFrame::AddXULPrefSize(this, pref, widthSet, heightSet);
1674
0
  return pref;
1675
0
}
1676
1677
nsSize
1678
nsXULScrollFrame::GetXULMinSize(nsBoxLayoutState& aState)
1679
0
{
1680
0
  nsSize min = mHelper.mScrolledFrame->GetXULMinSizeForScrollArea(aState);
1681
0
1682
0
  ScrollStyles styles = GetScrollStyles();
1683
0
1684
0
  if (mHelper.mVScrollbarBox &&
1685
0
      styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
1686
0
     nsSize vSize = mHelper.mVScrollbarBox->GetXULMinSize(aState);
1687
0
     AddMargin(mHelper.mVScrollbarBox, vSize);
1688
0
     min.width += vSize.width;
1689
0
     if (min.height < vSize.height)
1690
0
        min.height = vSize.height;
1691
0
  }
1692
0
1693
0
  if (mHelper.mHScrollbarBox &&
1694
0
      styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
1695
0
     nsSize hSize = mHelper.mHScrollbarBox->GetXULMinSize(aState);
1696
0
     AddMargin(mHelper.mHScrollbarBox, hSize);
1697
0
     min.height += hSize.height;
1698
0
     if (min.width < hSize.width)
1699
0
        min.width = hSize.width;
1700
0
  }
1701
0
1702
0
  AddBorderAndPadding(min);
1703
0
  bool widthSet, heightSet;
1704
0
  nsIFrame::AddXULMinSize(aState, this, min, widthSet, heightSet);
1705
0
  return min;
1706
0
}
1707
1708
nsSize
1709
nsXULScrollFrame::GetXULMaxSize(nsBoxLayoutState& aState)
1710
0
{
1711
0
  nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
1712
0
1713
0
  AddBorderAndPadding(maxSize);
1714
0
  bool widthSet, heightSet;
1715
0
  nsIFrame::AddXULMaxSize(this, maxSize, widthSet, heightSet);
1716
0
  return maxSize;
1717
0
}
1718
1719
#ifdef DEBUG_FRAME_DUMP
1720
nsresult
1721
nsXULScrollFrame::GetFrameName(nsAString& aResult) const
1722
{
1723
  return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
1724
}
1725
#endif
1726
1727
NS_IMETHODIMP
1728
nsXULScrollFrame::DoXULLayout(nsBoxLayoutState& aState)
1729
0
{
1730
0
  uint32_t flags = aState.LayoutFlags();
1731
0
  nsresult rv = XULLayout(aState);
1732
0
  aState.SetLayoutFlags(flags);
1733
0
1734
0
  nsBox::DoXULLayout(aState);
1735
0
  return rv;
1736
0
}
1737
1738
0
NS_QUERYFRAME_HEAD(nsXULScrollFrame)
1739
0
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
1740
0
  NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
1741
0
  NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
1742
0
  NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
1743
0
NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
1744
1745
//-------------------- Helper ----------------------
1746
1747
0
#define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
1748
1749
// AsyncSmoothMSDScroll has ref counting.
1750
class ScrollFrameHelper::AsyncSmoothMSDScroll final : public nsARefreshObserver {
1751
public:
1752
  AsyncSmoothMSDScroll(const nsPoint &aInitialPosition,
1753
                       const nsPoint &aInitialDestination,
1754
                       const nsSize &aInitialVelocity,
1755
                       const nsRect &aRange,
1756
                       const mozilla::TimeStamp &aStartTime,
1757
                       nsPresContext* aPresContext)
1758
    : mXAxisModel(aInitialPosition.x, aInitialDestination.x,
1759
                  aInitialVelocity.width,
1760
                  gfxPrefs::ScrollBehaviorSpringConstant(),
1761
                  gfxPrefs::ScrollBehaviorDampingRatio())
1762
    , mYAxisModel(aInitialPosition.y, aInitialDestination.y,
1763
                  aInitialVelocity.height,
1764
                  gfxPrefs::ScrollBehaviorSpringConstant(),
1765
                  gfxPrefs::ScrollBehaviorDampingRatio())
1766
    , mRange(aRange)
1767
    , mLastRefreshTime(aStartTime)
1768
    , mCallee(nullptr)
1769
    , mOneDevicePixelInAppUnits(aPresContext->DevPixelsToAppUnits(1))
1770
0
  {
1771
0
    Telemetry::SetHistogramRecordingEnabled(
1772
0
      Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
1773
0
  }
1774
1775
  NS_INLINE_DECL_REFCOUNTING(AsyncSmoothMSDScroll, override)
1776
1777
0
  nsSize GetVelocity() {
1778
0
    // In nscoords per second
1779
0
    return nsSize(mXAxisModel.GetVelocity(), mYAxisModel.GetVelocity());
1780
0
  }
1781
1782
0
  nsPoint GetPosition() {
1783
0
    // In nscoords
1784
0
    return nsPoint(NSToCoordRound(mXAxisModel.GetPosition()), NSToCoordRound(mYAxisModel.GetPosition()));
1785
0
  }
1786
1787
0
  void SetDestination(const nsPoint &aDestination) {
1788
0
    mXAxisModel.SetDestination(static_cast<int32_t>(aDestination.x));
1789
0
    mYAxisModel.SetDestination(static_cast<int32_t>(aDestination.y));
1790
0
  }
1791
1792
  void SetRange(const nsRect &aRange)
1793
0
  {
1794
0
    mRange = aRange;
1795
0
  }
1796
1797
  nsRect GetRange()
1798
0
  {
1799
0
    return mRange;
1800
0
  }
1801
1802
  void Simulate(const TimeDuration& aDeltaTime)
1803
0
  {
1804
0
    mXAxisModel.Simulate(aDeltaTime);
1805
0
    mYAxisModel.Simulate(aDeltaTime);
1806
0
1807
0
    nsPoint desired = GetPosition();
1808
0
    nsPoint clamped = mRange.ClampPoint(desired);
1809
0
    if(desired.x != clamped.x) {
1810
0
      // The scroll has hit the "wall" at the left or right edge of the allowed
1811
0
      // scroll range.
1812
0
      // Absorb the impact to avoid bounceback effect.
1813
0
      mXAxisModel.SetVelocity(0.0);
1814
0
      mXAxisModel.SetPosition(clamped.x);
1815
0
    }
1816
0
1817
0
    if(desired.y != clamped.y) {
1818
0
      // The scroll has hit the "wall" at the left or right edge of the allowed
1819
0
      // scroll range.
1820
0
      // Absorb the impact to avoid bounceback effect.
1821
0
      mYAxisModel.SetVelocity(0.0);
1822
0
      mYAxisModel.SetPosition(clamped.y);
1823
0
    }
1824
0
  }
1825
1826
  bool IsFinished()
1827
0
  {
1828
0
    return mXAxisModel.IsFinished(mOneDevicePixelInAppUnits) &&
1829
0
           mYAxisModel.IsFinished(mOneDevicePixelInAppUnits);
1830
0
  }
1831
1832
0
  virtual void WillRefresh(mozilla::TimeStamp aTime) override {
1833
0
    mozilla::TimeDuration deltaTime = aTime - mLastRefreshTime;
1834
0
    mLastRefreshTime = aTime;
1835
0
1836
0
    // The callback may release "this".
1837
0
    // We don't access members after returning, so no need for KungFuDeathGrip.
1838
0
    ScrollFrameHelper::AsyncSmoothMSDScrollCallback(mCallee, deltaTime);
1839
0
  }
1840
1841
  /*
1842
   * Set a refresh observer for smooth scroll iterations (and start observing).
1843
   * Should be used at most once during the lifetime of this object.
1844
   * Return value: true on success, false otherwise.
1845
   */
1846
0
  bool SetRefreshObserver(ScrollFrameHelper *aCallee) {
1847
0
    NS_ASSERTION(aCallee && !mCallee, "AsyncSmoothMSDScroll::SetRefreshObserver - Invalid usage.");
1848
0
1849
0
    if (!RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style)) {
1850
0
      return false;
1851
0
    }
1852
0
1853
0
    mCallee = aCallee;
1854
0
    return true;
1855
0
  }
1856
1857
  /**
1858
   * The mCallee holds a strong ref to us since the refresh driver doesn't.
1859
   * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
1860
   * whichever comes first removes us from the refresh driver.
1861
   */
1862
0
  void RemoveObserver() {
1863
0
    if (mCallee) {
1864
0
      RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
1865
0
      mCallee = nullptr;
1866
0
    }
1867
0
  }
1868
1869
private:
1870
  // Private destructor, to discourage deletion outside of Release():
1871
0
  ~AsyncSmoothMSDScroll() {
1872
0
    RemoveObserver();
1873
0
    Telemetry::SetHistogramRecordingEnabled(
1874
0
      Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
1875
0
  }
1876
1877
0
  nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) {
1878
0
    return aCallee->mOuter->PresContext()->RefreshDriver();
1879
0
  }
1880
1881
  mozilla::layers::AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
1882
  nsRect mRange;
1883
  mozilla::TimeStamp mLastRefreshTime;
1884
  ScrollFrameHelper *mCallee;
1885
  nscoord mOneDevicePixelInAppUnits;
1886
};
1887
1888
// AsyncScroll has ref counting.
1889
class ScrollFrameHelper::AsyncScroll final
1890
  : public nsARefreshObserver
1891
{
1892
public:
1893
  typedef mozilla::TimeStamp TimeStamp;
1894
  typedef mozilla::TimeDuration TimeDuration;
1895
1896
  explicit AsyncScroll()
1897
    : mCallee(nullptr)
1898
0
  {
1899
0
    Telemetry::SetHistogramRecordingEnabled(
1900
0
      Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
1901
0
  }
1902
1903
private:
1904
  // Private destructor, to discourage deletion outside of Release():
1905
0
  ~AsyncScroll() {
1906
0
    RemoveObserver();
1907
0
    Telemetry::SetHistogramRecordingEnabled(
1908
0
      Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
1909
0
  }
1910
1911
public:
1912
  void InitSmoothScroll(TimeStamp aTime,
1913
                        nsPoint aInitialPosition, nsPoint aDestination,
1914
                        nsAtom *aOrigin, const nsRect& aRange,
1915
                        const nsSize& aCurrentVelocity);
1916
0
  void Init(const nsRect& aRange) {
1917
0
    mAnimationPhysics = nullptr;
1918
0
    mRange = aRange;
1919
0
  }
1920
1921
0
  bool IsSmoothScroll() { return mAnimationPhysics != nullptr; }
1922
1923
0
  bool IsFinished(const TimeStamp& aTime) const {
1924
0
    MOZ_RELEASE_ASSERT(mAnimationPhysics);
1925
0
    return mAnimationPhysics->IsFinished(aTime);
1926
0
  }
1927
1928
0
  nsPoint PositionAt(const TimeStamp& aTime) const {
1929
0
    MOZ_RELEASE_ASSERT(mAnimationPhysics);
1930
0
    return mAnimationPhysics->PositionAt(aTime);
1931
0
  }
1932
1933
0
  nsSize VelocityAt(const TimeStamp& aTime) const {
1934
0
    MOZ_RELEASE_ASSERT(mAnimationPhysics);
1935
0
    return mAnimationPhysics->VelocityAt(aTime);
1936
0
  }
1937
1938
  // Most recent scroll origin.
1939
  RefPtr<nsAtom> mOrigin;
1940
1941
  // Allowed destination positions around mDestination
1942
  nsRect mRange;
1943
1944
private:
1945
  void InitPreferences(TimeStamp aTime, nsAtom *aOrigin);
1946
1947
  UniquePtr<ScrollAnimationPhysics> mAnimationPhysics;
1948
1949
// The next section is observer/callback management
1950
// Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code.
1951
public:
1952
  NS_INLINE_DECL_REFCOUNTING(AsyncScroll, override)
1953
1954
  /*
1955
   * Set a refresh observer for smooth scroll iterations (and start observing).
1956
   * Should be used at most once during the lifetime of this object.
1957
   * Return value: true on success, false otherwise.
1958
   */
1959
0
  bool SetRefreshObserver(ScrollFrameHelper *aCallee) {
1960
0
    NS_ASSERTION(aCallee && !mCallee, "AsyncScroll::SetRefreshObserver - Invalid usage.");
1961
0
1962
0
    if (!RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style)) {
1963
0
      return false;
1964
0
    }
1965
0
1966
0
    mCallee = aCallee;
1967
0
    nsIPresShell* shell = mCallee->mOuter->PresShell();
1968
0
    MOZ_ASSERT(shell);
1969
0
    shell->SuppressDisplayport(true);
1970
0
    return true;
1971
0
  }
1972
1973
0
  virtual void WillRefresh(mozilla::TimeStamp aTime) override {
1974
0
    // The callback may release "this".
1975
0
    // We don't access members after returning, so no need for KungFuDeathGrip.
1976
0
    ScrollFrameHelper::AsyncScrollCallback(mCallee, aTime);
1977
0
  }
1978
1979
  /**
1980
   * The mCallee holds a strong ref to us since the refresh driver doesn't.
1981
   * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
1982
   * whichever comes first removes us from the refresh driver.
1983
   */
1984
0
  void RemoveObserver() {
1985
0
    if (mCallee) {
1986
0
      RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
1987
0
      nsIPresShell* shell = mCallee->mOuter->PresShell();
1988
0
      MOZ_ASSERT(shell);
1989
0
      shell->SuppressDisplayport(false);
1990
0
      mCallee = nullptr;
1991
0
    }
1992
0
  }
1993
private:
1994
  ScrollFrameHelper *mCallee;
1995
1996
0
  nsRefreshDriver* RefreshDriver(ScrollFrameHelper* aCallee) {
1997
0
    return aCallee->mOuter->PresContext()->RefreshDriver();
1998
0
  }
1999
};
2000
2001
/*
2002
 * Calculate duration, possibly dynamically according to events rate and event origin.
2003
 * (also maintain previous timestamps - which are only used here).
2004
 */
2005
static ScrollAnimationBezierPhysicsSettings
2006
ComputeBezierAnimationSettingsForOrigin(nsAtom *aOrigin)
2007
0
{
2008
0
  int32_t minMS = 0;
2009
0
  int32_t maxMS = 0;
2010
0
  bool isOriginSmoothnessEnabled = false;
2011
0
  double intervalRatio = 1;
2012
0
2013
0
  // Default values for all preferences are defined in all.js
2014
0
  static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
2015
0
  static const bool kDefaultIsSmoothEnabled = true;
2016
0
2017
0
  nsAutoCString originName;
2018
0
  aOrigin->ToUTF8String(originName);
2019
0
  nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
2020
0
2021
0
  isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
2022
0
  if (isOriginSmoothnessEnabled) {
2023
0
    nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
2024
0
    nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
2025
0
    minMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
2026
0
    maxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
2027
0
2028
0
    static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
2029
0
    maxMS = clamped(maxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
2030
0
    minMS = clamped(minMS, 0, maxMS);
2031
0
  }
2032
0
2033
0
  // Keep the animation duration longer than the average event intervals
2034
0
  //   (to "connect" consecutive scroll animations before the scroll comes to a stop).
2035
0
  static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
2036
0
  intervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
2037
0
                                                      kDefaultDurationToIntervalRatio * 100) / 100.0;
2038
0
2039
0
  // Duration should be at least as long as the intervals -> ratio is at least 1
2040
0
  intervalRatio = std::max(1.0, intervalRatio);
2041
0
2042
0
  return ScrollAnimationBezierPhysicsSettings { minMS, maxMS, intervalRatio };
2043
0
}
2044
2045
void
2046
ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
2047
                                                 nsPoint aInitialPosition,
2048
                                                 nsPoint aDestination,
2049
                                                 nsAtom *aOrigin,
2050
                                                 const nsRect& aRange,
2051
                                                 const nsSize& aCurrentVelocity)
2052
0
{
2053
0
  if (!aOrigin || aOrigin == nsGkAtoms::restore) {
2054
0
    // We don't have special prefs for "restore", just treat it as "other".
2055
0
    // "restore" scrolls are (for now) always instant anyway so unless something
2056
0
    // changes we should never have aOrigin == nsGkAtoms::restore here.
2057
0
    aOrigin = nsGkAtoms::other;
2058
0
  }
2059
0
  // Likewise we should never get APZ-triggered scrolls here, and if that changes
2060
0
  // something is likely broken somewhere.
2061
0
  MOZ_ASSERT(aOrigin != nsGkAtoms::apz);
2062
0
2063
0
  // Read preferences only on first iteration or for a different event origin.
2064
0
  if (!mAnimationPhysics || aOrigin != mOrigin) {
2065
0
    mOrigin = aOrigin;
2066
0
    if (gfxPrefs::SmoothScrollMSDPhysicsEnabled()) {
2067
0
      mAnimationPhysics = MakeUnique<ScrollAnimationMSDPhysics>(aInitialPosition);
2068
0
    } else {
2069
0
      ScrollAnimationBezierPhysicsSettings settings =
2070
0
        ComputeBezierAnimationSettingsForOrigin(mOrigin);
2071
0
      mAnimationPhysics =
2072
0
        MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings);
2073
0
    }
2074
0
  }
2075
0
2076
0
  mRange = aRange;
2077
0
2078
0
  mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity);
2079
0
}
2080
2081
bool
2082
ScrollFrameHelper::IsSmoothScrollingEnabled()
2083
0
{
2084
0
  return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false);
2085
0
}
2086
2087
class ScrollFrameActivityTracker final : public nsExpirationTracker<ScrollFrameHelper,4> {
2088
public:
2089
  // Wait for 3-4s between scrolls before we remove our layers.
2090
  // That's 4 generations of 1s each.
2091
  enum { TIMEOUT_MS = 1000 };
2092
  explicit ScrollFrameActivityTracker(nsIEventTarget* aEventTarget)
2093
    : nsExpirationTracker<ScrollFrameHelper,4>(TIMEOUT_MS,
2094
                                               "ScrollFrameActivityTracker",
2095
                                               aEventTarget)
2096
0
  {}
2097
0
  ~ScrollFrameActivityTracker() {
2098
0
    AgeAllGenerations();
2099
0
  }
2100
2101
0
  virtual void NotifyExpired(ScrollFrameHelper *aObject) override {
2102
0
    RemoveObject(aObject);
2103
0
    aObject->MarkNotRecentlyScrolled();
2104
0
  }
2105
};
2106
2107
static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nullptr;
2108
2109
// There are situations when a scroll frame is destroyed and then re-created
2110
// for the same content element. In this case we want to increment the scroll
2111
// generation between the old and new scrollframes. If the new one knew about
2112
// the old one then it could steal the old generation counter and increment it
2113
// but it doesn't have that reference so instead we use a static global to
2114
// ensure the new one gets a fresh value.
2115
static uint32_t sScrollGenerationCounter = 0;
2116
2117
ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
2118
                                             bool aIsRoot)
2119
  : mHScrollbarBox(nullptr)
2120
  , mVScrollbarBox(nullptr)
2121
  , mScrolledFrame(nullptr)
2122
  , mScrollCornerBox(nullptr)
2123
  , mResizerBox(nullptr)
2124
  , mOuter(aOuter)
2125
  , mReferenceFrameDuringPainting(nullptr)
2126
  , mAsyncScroll(nullptr)
2127
  , mAsyncSmoothMSDScroll(nullptr)
2128
  , mLastScrollOrigin(nsGkAtoms::other)
2129
  , mAllowScrollOriginDowngrade(false)
2130
  , mLastSmoothScrollOrigin(nullptr)
2131
  , mScrollGeneration(++sScrollGenerationCounter)
2132
  , mDestination(0, 0)
2133
  , mScrollPosAtLastPaint(0, 0)
2134
  , mRestorePos(-1, -1)
2135
  , mLastPos(-1, -1)
2136
  , mScrollPosForLayerPixelAlignment(-1, -1)
2137
  , mLastUpdateFramesPos(-1, -1)
2138
  , mHadDisplayPortAtLastFrameUpdate(false)
2139
  , mDisplayPortAtLastFrameUpdate()
2140
  , mScrollParentID(mozilla::layers::FrameMetrics::NULL_SCROLL_ID)
2141
  , mNeverHasVerticalScrollbar(false)
2142
  , mNeverHasHorizontalScrollbar(false)
2143
  , mHasVerticalScrollbar(false)
2144
  , mHasHorizontalScrollbar(false)
2145
  , mFrameIsUpdatingScrollbar(false)
2146
  , mDidHistoryRestore(false)
2147
  , mIsRoot(aIsRoot)
2148
  , mClipAllDescendants(aIsRoot)
2149
  , mSuppressScrollbarUpdate(false)
2150
  , mSkippedScrollbarLayout(false)
2151
  , mHadNonInitialReflow(false)
2152
  , mHorizontalOverflow(false)
2153
  , mVerticalOverflow(false)
2154
  , mPostedReflowCallback(false)
2155
  , mMayHaveDirtyFixedChildren(false)
2156
  , mUpdateScrollbarAttributes(false)
2157
  , mHasBeenScrolledRecently(false)
2158
  , mCollapsedResizer(false)
2159
  , mWillBuildScrollableLayer(false)
2160
  , mIsScrollParent(false)
2161
  , mIsScrollableLayerInRootContainer(false)
2162
  , mAddClipRectToLayer(false)
2163
  , mHasBeenScrolled(false)
2164
  , mIgnoreMomentumScroll(false)
2165
  , mTransformingByAPZ(false)
2166
  , mScrollableByAPZ(false)
2167
  , mZoomableByAPZ(false)
2168
  , mHasOutOfFlowContentInsideFilter(false)
2169
  , mSuppressScrollbarRepaints(false)
2170
  , mVelocityQueue(aOuter->PresContext())
2171
0
{
2172
0
  if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
2173
0
    mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
2174
0
  }
2175
0
2176
0
  EnsureFrameVisPrefsCached();
2177
0
2178
0
  if (IsAlwaysActive() &&
2179
0
      gfxPrefs::LayersTilesEnabled() &&
2180
0
      !nsLayoutUtils::UsesAsyncScrolling(mOuter) &&
2181
0
      mOuter->GetContent()) {
2182
0
    // If we have tiling but no APZ, then set a 0-margin display port on
2183
0
    // active scroll containers so that we paint by whole tile increments
2184
0
    // when scrolling.
2185
0
    nsLayoutUtils::SetDisplayPortMargins(mOuter->GetContent(),
2186
0
                                         mOuter->PresShell(),
2187
0
                                         ScreenMargin(),
2188
0
                                         0,
2189
0
                                         nsLayoutUtils::RepaintMode::DoNotRepaint);
2190
0
    nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
2191
0
        mOuter, nsLayoutUtils::RepaintMode::DoNotRepaint);
2192
0
  }
2193
0
2194
0
}
2195
2196
ScrollFrameHelper::~ScrollFrameHelper()
2197
0
{
2198
0
  if (mScrollEvent) {
2199
0
    mScrollEvent->Revoke();
2200
0
  }
2201
0
  if (mScrollEndEvent) {
2202
0
    mScrollEndEvent->Revoke();
2203
0
  }
2204
0
}
2205
2206
/*
2207
 * Callback function from AsyncSmoothMSDScroll, used in ScrollFrameHelper::ScrollTo
2208
 */
2209
void
2210
ScrollFrameHelper::AsyncSmoothMSDScrollCallback(ScrollFrameHelper* aInstance,
2211
                                                mozilla::TimeDuration aDeltaTime)
2212
0
{
2213
0
  NS_ASSERTION(aInstance != nullptr, "aInstance must not be null");
2214
0
  NS_ASSERTION(aInstance->mAsyncSmoothMSDScroll,
2215
0
    "Did not expect AsyncSmoothMSDScrollCallback without an active MSD scroll.");
2216
0
2217
0
  nsRect range = aInstance->mAsyncSmoothMSDScroll->GetRange();
2218
0
  aInstance->mAsyncSmoothMSDScroll->Simulate(aDeltaTime);
2219
0
2220
0
  if (!aInstance->mAsyncSmoothMSDScroll->IsFinished()) {
2221
0
    nsPoint destination = aInstance->mAsyncSmoothMSDScroll->GetPosition();
2222
0
    // Allow this scroll operation to land on any pixel boundary within the
2223
0
    // allowed scroll range for this frame.
2224
0
    // If the MSD is under-dampened or the destination is changed rapidly,
2225
0
    // it is expected (and desired) that the scrolling may overshoot.
2226
0
    nsRect intermediateRange =
2227
0
      nsRect(destination, nsSize()).UnionEdges(range);
2228
0
    aInstance->ScrollToImpl(destination, intermediateRange);
2229
0
    // 'aInstance' might be destroyed here
2230
0
    return;
2231
0
  }
2232
0
2233
0
  aInstance->CompleteAsyncScroll(range);
2234
0
}
2235
2236
/*
2237
 * Callback function from AsyncScroll, used in ScrollFrameHelper::ScrollTo
2238
 */
2239
void
2240
ScrollFrameHelper::AsyncScrollCallback(ScrollFrameHelper* aInstance,
2241
                                       mozilla::TimeStamp aTime)
2242
0
{
2243
0
  MOZ_ASSERT(aInstance != nullptr, "aInstance must not be null");
2244
0
  MOZ_ASSERT(aInstance->mAsyncScroll,
2245
0
    "Did not expect AsyncScrollCallback without an active async scroll.");
2246
0
2247
0
  if (!aInstance || !aInstance->mAsyncScroll) {
2248
0
    return;  // XXX wallpaper bug 1107353 for now.
2249
0
  }
2250
0
2251
0
  nsRect range = aInstance->mAsyncScroll->mRange;
2252
0
  if (aInstance->mAsyncScroll->IsSmoothScroll()) {
2253
0
    if (!aInstance->mAsyncScroll->IsFinished(aTime)) {
2254
0
      nsPoint destination = aInstance->mAsyncScroll->PositionAt(aTime);
2255
0
      // Allow this scroll operation to land on any pixel boundary between the
2256
0
      // current position and the final allowed range.  (We don't want
2257
0
      // intermediate steps to be more constrained than the final step!)
2258
0
      nsRect intermediateRange =
2259
0
        nsRect(aInstance->GetScrollPosition(), nsSize()).UnionEdges(range);
2260
0
      aInstance->ScrollToImpl(destination, intermediateRange);
2261
0
      // 'aInstance' might be destroyed here
2262
0
      return;
2263
0
    }
2264
0
  }
2265
0
2266
0
  aInstance->CompleteAsyncScroll(range);
2267
0
}
2268
2269
void
2270
ScrollFrameHelper::CompleteAsyncScroll(const nsRect &aRange, nsAtom* aOrigin)
2271
0
{
2272
0
  // Apply desired destination range since this is the last step of scrolling.
2273
0
  mAsyncSmoothMSDScroll = nullptr;
2274
0
  mAsyncScroll = nullptr;
2275
0
  AutoWeakFrame weakFrame(mOuter);
2276
0
  ScrollToImpl(mDestination, aRange, aOrigin);
2277
0
  if (!weakFrame.IsAlive()) {
2278
0
    return;
2279
0
  }
2280
0
  // We are done scrolling, set our destination to wherever we actually ended
2281
0
  // up scrolling to.
2282
0
  mDestination = GetScrollPosition();
2283
0
  PostScrollEndEvent();
2284
0
}
2285
2286
bool
2287
ScrollFrameHelper::HasPluginFrames()
2288
0
{
2289
0
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
2290
0
  if (XRE_IsContentProcess()) {
2291
0
    nsPresContext* presContext = mOuter->PresContext();
2292
0
    nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
2293
0
    if (!rootPresContext || rootPresContext->NeedToComputePluginGeometryUpdates()) {
2294
0
      return true;
2295
0
    }
2296
0
  }
2297
0
#endif
2298
0
  return false;
2299
0
}
2300
2301
bool
2302
ScrollFrameHelper::HasBgAttachmentLocal() const
2303
0
{
2304
0
  const nsStyleBackground* bg = mOuter->StyleBackground();
2305
0
  return bg->HasLocalBackground();
2306
0
}
2307
2308
void
2309
ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition,
2310
                                     nsIScrollableFrame::ScrollMode aMode)
2311
0
{
2312
0
  nsPoint current = GetScrollPosition();
2313
0
  CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
2314
0
  nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
2315
0
  nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
2316
0
  nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1);
2317
0
  // XXX I don't think the following blocks are needed anymore, now that
2318
0
  // ScrollToImpl simply tries to scroll an integer number of layer
2319
0
  // pixels from the current position
2320
0
  if (currentCSSPixels.x == aScrollPosition.x) {
2321
0
    pt.x = current.x;
2322
0
    range.x = pt.x;
2323
0
    range.width = 0;
2324
0
  }
2325
0
  if (currentCSSPixels.y == aScrollPosition.y) {
2326
0
    pt.y = current.y;
2327
0
    range.y = pt.y;
2328
0
    range.height = 0;
2329
0
  }
2330
0
  ScrollTo(pt, aMode, &range);
2331
0
  // 'this' might be destroyed here
2332
0
}
2333
2334
void
2335
ScrollFrameHelper::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition,
2336
                                                nsAtom *aOrigin)
2337
0
{
2338
0
  nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
2339
0
  nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
2340
0
  nsRect range(pt.x - halfRange, pt.y - halfRange, 2*halfRange - 1, 2*halfRange - 1);
2341
0
  ScrollToWithOrigin(pt, nsIScrollableFrame::INSTANT, aOrigin, &range);
2342
0
  // 'this' might be destroyed here
2343
0
}
2344
2345
CSSIntPoint
2346
ScrollFrameHelper::GetScrollPositionCSSPixels()
2347
0
{
2348
0
  return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
2349
0
}
2350
2351
/*
2352
 * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
2353
 *  based on the setting of the smoothness scroll pref
2354
 */
2355
void
2356
ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
2357
                                          nsIScrollableFrame::ScrollMode aMode,
2358
                                          nsAtom *aOrigin,
2359
                                          const nsRect* aRange,
2360
                                          nsIScrollbarMediator::ScrollSnapMode aSnap)
2361
0
{
2362
0
  if (aOrigin != nsGkAtoms::restore) {
2363
0
    // If we're doing a non-restore scroll, we don't want to later
2364
0
    // override it by restoring our saved scroll position.
2365
0
    mRestorePos.x = mRestorePos.y = -1;
2366
0
  }
2367
0
2368
0
  if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
2369
0
    GetSnapPointForDestination(nsIScrollableFrame::DEVICE_PIXELS,
2370
0
                               mDestination,
2371
0
                               aScrollPosition);
2372
0
  }
2373
0
2374
0
  nsRect scrollRange = GetScrollRangeForClamping();
2375
0
  mDestination = scrollRange.ClampPoint(aScrollPosition);
2376
0
  if (mDestination != aScrollPosition && aOrigin == nsGkAtoms::restore &&
2377
0
      GetPageLoadingState() != LoadingState::Loading) {
2378
0
    // If we're doing a restore but the scroll position is clamped, promote
2379
0
    // the origin from one that APZ can clobber to one that it can't clobber.
2380
0
    aOrigin = nsGkAtoms::other;
2381
0
  }
2382
0
2383
0
  nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0));
2384
0
2385
0
  if (aMode != nsIScrollableFrame::SMOOTH_MSD) {
2386
0
    // If we get a non-smooth-scroll, reset the cached APZ scroll destination,
2387
0
    // so that we know to process the next smooth-scroll destined for APZ.
2388
0
    mApzSmoothScrollDestination = Nothing();
2389
0
  }
2390
0
2391
0
  if (aMode == nsIScrollableFrame::INSTANT) {
2392
0
    // Asynchronous scrolling is not allowed, so we'll kill any existing
2393
0
    // async-scrolling process and do an instant scroll.
2394
0
    CompleteAsyncScroll(range, aOrigin);
2395
0
    return;
2396
0
  }
2397
0
2398
0
  nsPresContext* presContext = mOuter->PresContext();
2399
0
  TimeStamp now = presContext->RefreshDriver()->IsTestControllingRefreshesEnabled()
2400
0
                ? presContext->RefreshDriver()->MostRecentRefresh()
2401
0
                : TimeStamp::Now();
2402
0
  bool isSmoothScroll = (aMode == nsIScrollableFrame::SMOOTH) &&
2403
0
                          IsSmoothScrollingEnabled();
2404
0
2405
0
  nsSize currentVelocity(0, 0);
2406
0
2407
0
  if (gfxPrefs::ScrollBehaviorEnabled()) {
2408
0
    if (aMode == nsIScrollableFrame::SMOOTH_MSD) {
2409
0
      mIgnoreMomentumScroll = true;
2410
0
      if (!mAsyncSmoothMSDScroll) {
2411
0
        nsPoint sv = mVelocityQueue.GetVelocity();
2412
0
        currentVelocity.width = sv.x;
2413
0
        currentVelocity.height = sv.y;
2414
0
        if (mAsyncScroll) {
2415
0
          if (mAsyncScroll->IsSmoothScroll()) {
2416
0
            currentVelocity = mAsyncScroll->VelocityAt(now);
2417
0
          }
2418
0
          mAsyncScroll = nullptr;
2419
0
        }
2420
0
2421
0
        if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll()) {
2422
0
          if (mApzSmoothScrollDestination == Some(mDestination) &&
2423
0
              mScrollGeneration == sScrollGenerationCounter) {
2424
0
            // If we already sent APZ a smooth-scroll request to this
2425
0
            // destination with this generation (i.e. it was the last request
2426
0
            // we sent), then don't send another one because it is redundant.
2427
0
            // This is to avoid a scenario where pages do repeated scrollBy
2428
0
            // calls, incrementing the generation counter, and blocking APZ from
2429
0
            // syncing the scroll offset back to the main thread.
2430
0
            // Note that if we get two smooth-scroll requests to the same
2431
0
            // destination with some other scroll in between,
2432
0
            // mApzSmoothScrollDestination will get reset to Nothing() and so
2433
0
            // we shouldn't have the problem where this check discards a
2434
0
            // legitimate smooth-scroll.
2435
0
            // Note: if there are two separate scrollframes both getting smooth
2436
0
            // scrolled at the same time, sScrollGenerationCounter can get
2437
0
            // incremented and this early-exit won't get taken. Bug 1231177 is
2438
0
            // on file for this.
2439
0
            return;
2440
0
          }
2441
0
2442
0
          // The animation will be handled in the compositor, pass the
2443
0
          // information needed to start the animation and skip the main-thread
2444
0
          // animation for this scroll.
2445
0
          mLastSmoothScrollOrigin = aOrigin;
2446
0
          mApzSmoothScrollDestination = Some(mDestination);
2447
0
          mScrollGeneration = ++sScrollGenerationCounter;
2448
0
2449
0
          if (!nsLayoutUtils::HasDisplayPort(mOuter->GetContent())) {
2450
0
            // If this frame doesn't have a displayport then there won't be an
2451
0
            // APZC instance for it and so there won't be anything to process
2452
0
            // this smooth scroll request. We should set a displayport on this
2453
0
            // frame to force an APZC which can handle the request.
2454
0
            nsLayoutUtils::CalculateAndSetDisplayPortMargins(
2455
0
              mOuter->GetScrollTargetFrame(),
2456
0
              nsLayoutUtils::RepaintMode::DoNotRepaint);
2457
0
            nsIFrame* frame = do_QueryFrame(mOuter->GetScrollTargetFrame());
2458
0
            nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
2459
0
              frame,
2460
0
              nsLayoutUtils::RepaintMode::DoNotRepaint);
2461
0
          }
2462
0
2463
0
          // Schedule a paint to ensure that the frame metrics get updated on
2464
0
          // the compositor thread.
2465
0
          mOuter->SchedulePaint();
2466
0
          return;
2467
0
        }
2468
0
2469
0
        mAsyncSmoothMSDScroll =
2470
0
          new AsyncSmoothMSDScroll(GetScrollPosition(), mDestination,
2471
0
                                   currentVelocity, GetScrollRangeForClamping(),
2472
0
                                   now, presContext);
2473
0
2474
0
        if (!mAsyncSmoothMSDScroll->SetRefreshObserver(this)) {
2475
0
          // Observer setup failed. Scroll the normal way.
2476
0
          CompleteAsyncScroll(range, aOrigin);
2477
0
          return;
2478
0
        }
2479
0
      } else {
2480
0
        // A previous smooth MSD scroll is still in progress, so we just need to
2481
0
        // update its range and destination.
2482
0
        mAsyncSmoothMSDScroll->SetRange(GetScrollRangeForClamping());
2483
0
        mAsyncSmoothMSDScroll->SetDestination(mDestination);
2484
0
      }
2485
0
2486
0
      return;
2487
0
    } else {
2488
0
      if (mAsyncSmoothMSDScroll) {
2489
0
        currentVelocity = mAsyncSmoothMSDScroll->GetVelocity();
2490
0
        mAsyncSmoothMSDScroll = nullptr;
2491
0
      }
2492
0
    }
2493
0
  }
2494
0
2495
0
  if (!mAsyncScroll) {
2496
0
    mAsyncScroll = new AsyncScroll();
2497
0
    if (!mAsyncScroll->SetRefreshObserver(this)) {
2498
0
      // Observer setup failed. Scroll the normal way.
2499
0
      CompleteAsyncScroll(range, aOrigin);
2500
0
      return;
2501
0
    }
2502
0
  }
2503
0
2504
0
  if (isSmoothScroll) {
2505
0
    mAsyncScroll->InitSmoothScroll(now, GetScrollPosition(), mDestination,
2506
0
                                   aOrigin, range, currentVelocity);
2507
0
  } else {
2508
0
    mAsyncScroll->Init(range);
2509
0
  }
2510
0
}
2511
2512
// We can't use nsContainerFrame::PositionChildViews here because
2513
// we don't want to invalidate views that have moved.
2514
static void AdjustViews(nsIFrame* aFrame)
2515
0
{
2516
0
  nsView* view = aFrame->GetView();
2517
0
  if (view) {
2518
0
    nsPoint pt;
2519
0
    aFrame->GetParent()->GetClosestView(&pt);
2520
0
    pt += aFrame->GetPosition();
2521
0
    view->SetPosition(pt.x, pt.y);
2522
0
2523
0
    return;
2524
0
  }
2525
0
2526
0
  if (!(aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
2527
0
    return;
2528
0
  }
2529
0
2530
0
  // Call AdjustViews recursively for all child frames except the popup list as
2531
0
  // the views for popups are not scrolled.
2532
0
  nsIFrame::ChildListIterator lists(aFrame);
2533
0
  for (; !lists.IsDone(); lists.Next()) {
2534
0
    if (lists.CurrentID() == nsIFrame::kPopupList) {
2535
0
      continue;
2536
0
    }
2537
0
    nsFrameList::Enumerator childFrames(lists.CurrentList());
2538
0
    for (; !childFrames.AtEnd(); childFrames.Next()) {
2539
0
      AdjustViews(childFrames.get());
2540
0
    }
2541
0
  }
2542
0
}
2543
2544
bool ScrollFrameHelper::IsIgnoringViewportClipping() const
2545
0
{
2546
0
  if (!mIsRoot)
2547
0
    return false;
2548
0
  nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
2549
0
    (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresShell()->GetRootFrame()));
2550
0
  return subdocFrame && !subdocFrame->ShouldClipSubdocument();
2551
0
}
2552
2553
void ScrollFrameHelper::MarkScrollbarsDirtyForReflow() const
2554
0
{
2555
0
  nsIPresShell* presShell = mOuter->PresShell();
2556
0
  if (mVScrollbarBox) {
2557
0
    presShell->FrameNeedsReflow(mVScrollbarBox, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
2558
0
  }
2559
0
  if (mHScrollbarBox) {
2560
0
    presShell->FrameNeedsReflow(mHScrollbarBox, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
2561
0
  }
2562
0
}
2563
2564
bool ScrollFrameHelper::ShouldClampScrollPosition() const
2565
0
{
2566
0
  if (!mIsRoot)
2567
0
    return true;
2568
0
  nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
2569
0
    (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresShell()->GetRootFrame()));
2570
0
  return !subdocFrame || subdocFrame->ShouldClampScrollPosition();
2571
0
}
2572
2573
bool ScrollFrameHelper::IsAlwaysActive() const
2574
0
{
2575
0
  if (nsDisplayItem::ForceActiveLayers()) {
2576
0
    return true;
2577
0
  }
2578
0
2579
0
  // Unless this is the root scrollframe for a non-chrome document
2580
0
  // which is the direct child of a chrome document, we default to not
2581
0
  // being "active".
2582
0
  if (!(mIsRoot && mOuter->PresContext()->IsRootContentDocument())) {
2583
0
     return false;
2584
0
  }
2585
0
2586
0
  // If we have scrolled before, then we should stay active.
2587
0
  if (mHasBeenScrolled) {
2588
0
    return true;
2589
0
  }
2590
0
2591
0
  // If we're overflow:hidden, then start as inactive until
2592
0
  // we get scrolled manually.
2593
0
  ScrollStyles styles = GetScrollStylesFromFrame();
2594
0
  return (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
2595
0
          styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
2596
0
}
2597
2598
static void
2599
RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure)
2600
0
{
2601
0
  ScrollFrameHelper* helper = static_cast<ScrollFrameHelper*>(aClosure);
2602
0
2603
0
  // This function only ever gets called from the expiry timer, so it must
2604
0
  // be non-null here. Set it to null here so that we don't keep resetting
2605
0
  // it unnecessarily in MarkRecentlyScrolled().
2606
0
  MOZ_ASSERT(helper->mDisplayPortExpiryTimer);
2607
0
  helper->mDisplayPortExpiryTimer = nullptr;
2608
0
2609
0
  if (!helper->AllowDisplayPortExpiration() || helper->mIsScrollParent) {
2610
0
    // If this is a scroll parent for some other scrollable frame, don't
2611
0
    // expire the displayport because it would break scroll handoff. Once the
2612
0
    // descendant scrollframes have their displayports expired, they will
2613
0
    // trigger the displayport expiration on this scrollframe as well, and
2614
0
    // mIsScrollParent will presumably be false when that kicks in.
2615
0
    return;
2616
0
  }
2617
0
2618
0
  // Remove the displayport from this scrollframe if it's been a while
2619
0
  // since it's scrolled, except if it needs to be always active. Note that
2620
0
  // there is one scrollframe that doesn't fall under this general rule, and
2621
0
  // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put
2622
0
  // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s).
2623
0
  // If that scrollframe is this one, we remove the displayport anyway, and
2624
0
  // as part of the next paint MaybeCreateDisplayPort will put another
2625
0
  // displayport back on it. Although the displayport will "flicker" off and
2626
0
  // back on, the layer itself should never disappear, because this all
2627
0
  // happens between actual painting. If the displayport is reset to a
2628
0
  // different position that's ok; this scrollframe hasn't been scrolled
2629
0
  // recently and so the reset should be correct.
2630
0
  nsLayoutUtils::RemoveDisplayPort(helper->mOuter->GetContent());
2631
0
  nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(helper->mOuter);
2632
0
  helper->mOuter->SchedulePaint();
2633
0
  // Be conservative and unflag this this scrollframe as being scrollable by
2634
0
  // APZ. If it is still scrollable this will get flipped back soon enough.
2635
0
  helper->mScrollableByAPZ = false;
2636
0
}
2637
2638
void ScrollFrameHelper::MarkNotRecentlyScrolled()
2639
0
{
2640
0
  if (!mHasBeenScrolledRecently)
2641
0
    return;
2642
0
2643
0
  mHasBeenScrolledRecently = false;
2644
0
  mOuter->SchedulePaint();
2645
0
}
2646
2647
void ScrollFrameHelper::MarkRecentlyScrolled()
2648
0
{
2649
0
  mHasBeenScrolledRecently = true;
2650
0
  if (IsAlwaysActive()) {
2651
0
    return;
2652
0
  }
2653
0
2654
0
  if (mActivityExpirationState.IsTracked()) {
2655
0
    gScrollFrameActivityTracker->MarkUsed(this);
2656
0
  } else {
2657
0
    if (!gScrollFrameActivityTracker) {
2658
0
      gScrollFrameActivityTracker = new ScrollFrameActivityTracker(
2659
0
        SystemGroup::EventTargetFor(TaskCategory::Other));
2660
0
    }
2661
0
    gScrollFrameActivityTracker->AddObject(this);
2662
0
  }
2663
0
2664
0
  // If we just scrolled and there's a displayport expiry timer in place,
2665
0
  // reset the timer.
2666
0
  ResetDisplayPortExpiryTimer();
2667
0
}
2668
2669
void ScrollFrameHelper::ResetDisplayPortExpiryTimer()
2670
0
{
2671
0
  if (mDisplayPortExpiryTimer) {
2672
0
    mDisplayPortExpiryTimer->InitWithNamedFuncCallback(
2673
0
      RemoveDisplayPortCallback,
2674
0
      this,
2675
0
      gfxPrefs::APZDisplayPortExpiryTime(),
2676
0
      nsITimer::TYPE_ONE_SHOT,
2677
0
      "ScrollFrameHelper::ResetDisplayPortExpiryTimer");
2678
0
  }
2679
0
}
2680
2681
bool ScrollFrameHelper::AllowDisplayPortExpiration()
2682
0
{
2683
0
  if (IsAlwaysActive()) {
2684
0
    return false;
2685
0
  }
2686
0
  if (mIsRoot && mOuter->PresContext()->IsRoot()) {
2687
0
    return false;
2688
0
  }
2689
0
  return true;
2690
0
}
2691
2692
void ScrollFrameHelper::TriggerDisplayPortExpiration()
2693
0
{
2694
0
  if (!AllowDisplayPortExpiration()) {
2695
0
    return;
2696
0
  }
2697
0
2698
0
  if (!gfxPrefs::APZDisplayPortExpiryTime()) {
2699
0
    // a zero time disables the expiry
2700
0
    return;
2701
0
  }
2702
0
2703
0
  if (!mDisplayPortExpiryTimer) {
2704
0
    mDisplayPortExpiryTimer = NS_NewTimer();
2705
0
  }
2706
0
  ResetDisplayPortExpiryTimer();
2707
0
}
2708
2709
void ScrollFrameHelper::ScrollVisual()
2710
0
{
2711
0
  // Mark this frame as having been scrolled. If this is the root
2712
0
  // scroll frame of a content document, then IsAlwaysActive()
2713
0
  // will return true from now on and MarkNotRecentlyScrolled() won't
2714
0
  // have any effect.
2715
0
  mHasBeenScrolled = true;
2716
0
2717
0
  AdjustViews(mScrolledFrame);
2718
0
  // We need to call this after fixing up the view positions
2719
0
  // to be consistent with the frame hierarchy.
2720
0
  MarkRecentlyScrolled();
2721
0
}
2722
2723
/**
2724
 * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper]
2725
 * to [aBoundLower, aBoundUpper] and then select the appunit value from among
2726
 * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) *
2727
 * aRes/aAppUnitsPerPixel is an integer (or as close as we can get
2728
 * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and
2729
 * closest to aDesired.  If no such value exists, return the nearest in
2730
 * [aDestLower, aDestUpper].
2731
 */
2732
static nscoord
2733
ClampAndAlignWithPixels(nscoord aDesired,
2734
                        nscoord aBoundLower, nscoord aBoundUpper,
2735
                        nscoord aDestLower, nscoord aDestUpper,
2736
                        nscoord aAppUnitsPerPixel, double aRes,
2737
                        nscoord aCurrent)
2738
0
{
2739
0
  // Intersect scroll range with allowed range, by clamping the ends
2740
0
  // of aRange to be within bounds
2741
0
  nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper);
2742
0
  nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper);
2743
0
2744
0
  nscoord desired = clamped(aDesired, destLower, destUpper);
2745
0
2746
0
  double currentLayerVal = (aRes*aCurrent)/aAppUnitsPerPixel;
2747
0
  double desiredLayerVal = (aRes*desired)/aAppUnitsPerPixel;
2748
0
  double delta = desiredLayerVal - currentLayerVal;
2749
0
  double nearestLayerVal = NS_round(delta) + currentLayerVal;
2750
0
2751
0
  // Convert back from PaintedLayer space to appunits relative to the top-left
2752
0
  // of the scrolled frame.
2753
0
  nscoord aligned =
2754
0
    NSToCoordRoundWithClamp(nearestLayerVal*aAppUnitsPerPixel/aRes);
2755
0
2756
0
  // Use a bound if it is within the allowed range and closer to desired than
2757
0
  // the nearest pixel-aligned value.
2758
0
  if (aBoundUpper == destUpper &&
2759
0
      static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <
2760
0
      Abs(desired - aligned))
2761
0
    return aBoundUpper;
2762
0
2763
0
  if (aBoundLower == destLower &&
2764
0
      static_cast<decltype(Abs(desired))>(desired - aBoundLower) <
2765
0
      Abs(aligned - desired))
2766
0
    return aBoundLower;
2767
0
2768
0
  // Accept the nearest pixel-aligned value if it is within the allowed range.
2769
0
  if (aligned >= destLower && aligned <= destUpper)
2770
0
    return aligned;
2771
0
2772
0
  // Check if opposite pixel boundary fits into allowed range.
2773
0
  double oppositeLayerVal =
2774
0
    nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0);
2775
0
  nscoord opposite =
2776
0
    NSToCoordRoundWithClamp(oppositeLayerVal*aAppUnitsPerPixel/aRes);
2777
0
  if (opposite >= destLower && opposite <= destUpper) {
2778
0
    return opposite;
2779
0
  }
2780
0
2781
0
  // No alignment available.
2782
0
  return desired;
2783
0
}
2784
2785
/**
2786
 * Clamp desired scroll position aPt to aBounds and then snap
2787
 * it to the same layer pixel edges as aCurrent, keeping it within aRange
2788
 * during snapping. aCurrent is the current scroll position.
2789
 */
2790
static nsPoint
2791
ClampAndAlignWithLayerPixels(const nsPoint& aPt,
2792
                             const nsRect& aBounds,
2793
                             const nsRect& aRange,
2794
                             const nsPoint& aCurrent,
2795
                             nscoord aAppUnitsPerPixel,
2796
                             const gfxSize& aScale)
2797
0
{
2798
0
  return nsPoint(ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(),
2799
0
                                         aRange.x, aRange.XMost(),
2800
0
                                         aAppUnitsPerPixel, aScale.width,
2801
0
                                         aCurrent.x),
2802
0
                 ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(),
2803
0
                                         aRange.y, aRange.YMost(),
2804
0
                                         aAppUnitsPerPixel, aScale.height,
2805
0
                                         aCurrent.y));
2806
0
}
2807
2808
/* static */ void
2809
ScrollFrameHelper::ScrollActivityCallback(nsITimer *aTimer, void* anInstance)
2810
0
{
2811
0
  ScrollFrameHelper* self = static_cast<ScrollFrameHelper*>(anInstance);
2812
0
2813
0
  // Fire the synth mouse move.
2814
0
  self->mScrollActivityTimer->Cancel();
2815
0
  self->mScrollActivityTimer = nullptr;
2816
0
  self->mOuter->PresShell()->SynthesizeMouseMove(true);
2817
0
}
2818
2819
2820
void
2821
ScrollFrameHelper::ScheduleSyntheticMouseMove()
2822
0
{
2823
0
  if (!mScrollActivityTimer) {
2824
0
    mScrollActivityTimer = NS_NewTimer(
2825
0
      mOuter->PresContext()->Document()->EventTargetFor(TaskCategory::Other));
2826
0
    if (!mScrollActivityTimer) {
2827
0
      return;
2828
0
    }
2829
0
  }
2830
0
2831
0
  mScrollActivityTimer->InitWithNamedFuncCallback(
2832
0
    ScrollActivityCallback,
2833
0
    this,
2834
0
    100,
2835
0
    nsITimer::TYPE_ONE_SHOT,
2836
0
    "ScrollFrameHelper::ScheduleSyntheticMouseMove");
2837
0
}
2838
2839
void
2840
ScrollFrameHelper::NotifyApproximateFrameVisibilityUpdate(bool aIgnoreDisplayPort)
2841
0
{
2842
0
  mLastUpdateFramesPos = GetScrollPosition();
2843
0
  if (aIgnoreDisplayPort) {
2844
0
    mHadDisplayPortAtLastFrameUpdate = false;
2845
0
    mDisplayPortAtLastFrameUpdate = nsRect();
2846
0
  } else {
2847
0
    mHadDisplayPortAtLastFrameUpdate =
2848
0
      nsLayoutUtils::GetDisplayPort(mOuter->GetContent(),
2849
0
                                    &mDisplayPortAtLastFrameUpdate);
2850
0
  }
2851
0
}
2852
2853
bool
2854
ScrollFrameHelper::GetDisplayPortAtLastApproximateFrameVisibilityUpdate(nsRect* aDisplayPort)
2855
0
{
2856
0
  if (mHadDisplayPortAtLastFrameUpdate) {
2857
0
    *aDisplayPort = mDisplayPortAtLastFrameUpdate;
2858
0
  }
2859
0
  return mHadDisplayPortAtLastFrameUpdate;
2860
0
}
2861
2862
void
2863
ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsAtom* aOrigin)
2864
0
{
2865
0
  if (aOrigin == nullptr) {
2866
0
    // If no origin was specified, we still want to set it to something that's
2867
0
    // non-null, so that we can use nullness to distinguish if the frame was scrolled
2868
0
    // at all. Default it to some generic placeholder.
2869
0
    aOrigin = nsGkAtoms::other;
2870
0
  }
2871
0
2872
0
  nsPresContext* presContext = mOuter->PresContext();
2873
0
  nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
2874
0
  // 'scale' is our estimate of the scale factor that will be applied
2875
0
  // when rendering the scrolled content to its own PaintedLayer.
2876
0
  gfxSize scale = FrameLayerBuilder::GetPaintedLayerScaleForFrame(mScrolledFrame);
2877
0
  nsPoint curPos = GetScrollPosition();
2878
0
  nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)
2879
0
                         ? curPos : mScrollPosForLayerPixelAlignment;
2880
0
  // Try to align aPt with curPos so they have an integer number of layer
2881
0
  // pixels between them. This gives us the best chance of scrolling without
2882
0
  // having to invalidate due to changes in subpixel rendering.
2883
0
  // Note that when we actually draw into a PaintedLayer, the coordinates
2884
0
  // that get mapped onto the layer buffer pixels are from the display list,
2885
0
  // which are relative to the display root frame's top-left increasing down,
2886
0
  // whereas here our coordinates are scroll positions which increase upward
2887
0
  // and are relative to the scrollport top-left. This difference doesn't actually
2888
0
  // matter since all we are about is that there be an integer number of
2889
0
  // layer pixels between pt and curPos.
2890
0
  nsPoint pt =
2891
0
    ClampAndAlignWithLayerPixels(aPt,
2892
0
                                 GetScrollRangeForClamping(),
2893
0
                                 aRange,
2894
0
                                 alignWithPos,
2895
0
                                 appUnitsPerDevPixel,
2896
0
                                 scale);
2897
0
  if (pt == curPos) {
2898
0
    return;
2899
0
  }
2900
0
2901
0
  bool needFrameVisibilityUpdate = mLastUpdateFramesPos == nsPoint(-1,-1);
2902
0
2903
0
  nsPoint dist(std::abs(pt.x - mLastUpdateFramesPos.x),
2904
0
               std::abs(pt.y - mLastUpdateFramesPos.y));
2905
0
  nsSize visualViewportSize = GetVisualViewportSize();
2906
0
  nscoord horzAllowance = std::max(visualViewportSize.width / std::max(sHorzScrollFraction, 1),
2907
0
                                   AppUnitsPerCSSPixel());
2908
0
  nscoord vertAllowance = std::max(visualViewportSize.height / std::max(sVertScrollFraction, 1),
2909
0
                                   AppUnitsPerCSSPixel());
2910
0
  if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
2911
0
    needFrameVisibilityUpdate = true;
2912
0
  }
2913
0
2914
0
  // notify the listeners.
2915
0
  for (uint32_t i = 0; i < mListeners.Length(); i++) {
2916
0
    mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
2917
0
  }
2918
0
2919
0
  nsRect oldDisplayPort;
2920
0
  nsIContent* content = mOuter->GetContent();
2921
0
  nsLayoutUtils::GetHighResolutionDisplayPort(content, &oldDisplayPort);
2922
0
  oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition());
2923
0
2924
0
  // Update frame position for scrolling
2925
0
  mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
2926
0
2927
0
  // If |mLastScrollOrigin| is already set to something that can clobber APZ's
2928
0
  // scroll offset, then we don't want to change it to something that can't.
2929
0
  // If we allowed this, then we could end up in a state where APZ ignores
2930
0
  // legitimate scroll offset updates because the origin has been masked by
2931
0
  // a later change within the same refresh driver tick.
2932
0
  bool isScrollOriginDowngrade =
2933
0
    nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) &&
2934
0
    !nsLayoutUtils::CanScrollOriginClobberApz(aOrigin);
2935
0
  bool allowScrollOriginChange = mAllowScrollOriginDowngrade ||
2936
0
    !isScrollOriginDowngrade;
2937
0
  if (allowScrollOriginChange) {
2938
0
    mLastScrollOrigin = aOrigin;
2939
0
    mAllowScrollOriginDowngrade = false;
2940
0
  }
2941
0
  mLastSmoothScrollOrigin = nullptr;
2942
0
  mScrollGeneration = ++sScrollGenerationCounter;
2943
0
2944
0
  ScrollVisual();
2945
0
2946
0
  bool schedulePaint = true;
2947
0
  if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) &&
2948
0
      !nsLayoutUtils::ShouldDisableApzForElement(content) &&
2949
0
      gfxPrefs::APZPaintSkipping()) {
2950
0
    // If APZ is enabled with paint-skipping, there are certain conditions in
2951
0
    // which we can skip paints:
2952
0
    // 1) If APZ triggered this scroll, and the tile-aligned displayport is
2953
0
    //    unchanged.
2954
0
    // 2) If non-APZ triggered this scroll, but we can handle it by just asking
2955
0
    //    APZ to update the scroll position. Again we make this conditional on
2956
0
    //    the tile-aligned displayport being unchanged.
2957
0
    // We do the displayport check first since it's common to all scenarios,
2958
0
    // and then if the displayport is unchanged, we check if APZ triggered,
2959
0
    // or can handle, this scroll. If so, we set schedulePaint to false and
2960
0
    // skip the paint.
2961
0
    // Because of bug 1264297, we also don't do paint-skipping for elements with
2962
0
    // perspective, because the displayport may not have captured everything
2963
0
    // that needs to be painted. So even if the final tile-aligned displayport
2964
0
    // is the same, we force a repaint for these elements. Bug 1254260 tracks
2965
0
    // fixing this properly.
2966
0
    nsRect displayPort;
2967
0
    bool usingDisplayPort =
2968
0
      nsLayoutUtils::GetHighResolutionDisplayPort(content, &displayPort);
2969
0
    displayPort.MoveBy(-mScrolledFrame->GetPosition());
2970
0
2971
0
    PAINT_SKIP_LOG("New scrollpos %s usingDP %d dpEqual %d scrollableByApz %d plugins"
2972
0
        "%d perspective %d bglocal %d filter %d\n",
2973
0
        Stringify(CSSPoint::FromAppUnits(GetScrollPosition())).c_str(),
2974
0
        usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort),
2975
0
        mScrollableByAPZ, HasPluginFrames(), HasPerspective(),
2976
0
        HasBgAttachmentLocal(), mHasOutOfFlowContentInsideFilter);
2977
0
    if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort) &&
2978
0
        !HasPerspective() && !HasBgAttachmentLocal() &&
2979
0
        !mHasOutOfFlowContentInsideFilter) {
2980
0
      bool haveScrollLinkedEffects = content->GetComposedDoc()->HasScrollLinkedEffect();
2981
0
      bool apzDisabled = haveScrollLinkedEffects && gfxPrefs::APZDisableForScrollLinkedEffects();
2982
0
      if (!apzDisabled && !HasPluginFrames()) {
2983
0
        if (LastScrollOrigin() == nsGkAtoms::apz) {
2984
0
          schedulePaint = false;
2985
0
          PAINT_SKIP_LOG("Skipping due to APZ scroll\n");
2986
0
        } else if (mScrollableByAPZ) {
2987
0
          nsIWidget* widget = presContext->GetNearestWidget();
2988
0
          LayerManager* manager = widget ? widget->GetLayerManager() : nullptr;
2989
0
          if (manager) {
2990
0
            mozilla::layers::FrameMetrics::ViewID id;
2991
0
            bool success = nsLayoutUtils::FindIDFor(content, &id);
2992
0
            MOZ_ASSERT(success); // we have a displayport, we better have an ID
2993
0
2994
0
            // Schedule an empty transaction to carry over the scroll offset update,
2995
0
            // instead of a full transaction. This empty transaction might still get
2996
0
            // squashed into a full transaction if something happens to trigger one.
2997
0
            success = manager->SetPendingScrollUpdateForNextTransaction(id,
2998
0
                { mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()) });
2999
0
            if (success) {
3000
0
              schedulePaint = false;
3001
0
              mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
3002
0
              PAINT_SKIP_LOG("Skipping due to APZ-forwarded main-thread scroll\n");
3003
0
            } else {
3004
0
              PAINT_SKIP_LOG("Failed to set pending scroll update on layer manager\n");
3005
0
            }
3006
0
          }
3007
0
        }
3008
0
      }
3009
0
    }
3010
0
  }
3011
0
3012
0
  if (schedulePaint) {
3013
0
    mOuter->SchedulePaint();
3014
0
3015
0
    if (needFrameVisibilityUpdate) {
3016
0
      presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
3017
0
    }
3018
0
  }
3019
0
3020
0
  if (mOuter->ChildrenHavePerspective()) {
3021
0
    // The overflow areas of descendants may depend on the scroll position,
3022
0
    // so ensure they get updated.
3023
0
3024
0
    // First we recompute the overflow areas of the transformed children
3025
0
    // that use the perspective. FinishAndStoreOverflow only calls this
3026
0
    // if the size changes, so we need to do it manually.
3027
0
    mOuter->RecomputePerspectiveChildrenOverflow(mOuter);
3028
0
3029
0
    // Update the overflow for the scrolled frame to take any changes from the
3030
0
    // children into account.
3031
0
    mScrolledFrame->UpdateOverflow();
3032
0
3033
0
    // Update the overflow for the outer so that we recompute scrollbars.
3034
0
    mOuter->UpdateOverflow();
3035
0
  }
3036
0
3037
0
  ScheduleSyntheticMouseMove();
3038
0
3039
0
  { // scope the AutoScrollbarRepaintSuppression
3040
0
    AutoScrollbarRepaintSuppression repaintSuppression(this, !schedulePaint);
3041
0
    AutoWeakFrame weakFrame(mOuter);
3042
0
    UpdateScrollbarPosition();
3043
0
    if (!weakFrame.IsAlive()) {
3044
0
      return;
3045
0
    }
3046
0
  }
3047
0
3048
0
  presContext->RecordInteractionTime(
3049
0
    nsPresContext::InteractionType::eScrollInteraction,
3050
0
    TimeStamp::Now());
3051
0
3052
0
  PostScrollEvent();
3053
0
3054
0
  // notify the listeners.
3055
0
  for (uint32_t i = 0; i < mListeners.Length(); i++) {
3056
0
    mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
3057
0
  }
3058
0
3059
0
  nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell();
3060
0
  if (docShell) {
3061
0
    docShell->NotifyScrollObservers();
3062
0
  }
3063
0
}
3064
3065
static Maybe<int32_t>
3066
MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder)
3067
0
{
3068
0
  Maybe<int32_t> maxZIndex = Nothing();
3069
0
  for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
3070
0
    if (!maxZIndex) {
3071
0
      maxZIndex = Some(item->ZIndex());
3072
0
    } else {
3073
0
      maxZIndex = Some(std::max(maxZIndex.value(), item->ZIndex()));
3074
0
    }
3075
0
  }
3076
0
  return maxZIndex;
3077
0
}
3078
3079
template<class T>
3080
static void
3081
AppendInternalItemToTop(const nsDisplayListSet& aLists,
3082
                        T* aItem,
3083
                        const Maybe<int32_t>& aZIndex)
3084
0
{
3085
0
  if (aZIndex) {
3086
0
    aItem->SetOverrideZIndex(aZIndex.value());
3087
0
    aLists.PositionedDescendants()->AppendToTop(aItem);
3088
0
  } else {
3089
0
    aLists.Content()->AppendToTop(aItem);
3090
0
  }
3091
0
}
Unexecuted instantiation: Unified_cpp_layout_generic2.cpp:void AppendInternalItemToTop<nsDisplayWrapList>(nsDisplayListSet const&, nsDisplayWrapList*, mozilla::Maybe<int> const&)
Unexecuted instantiation: Unified_cpp_layout_generic2.cpp:void AppendInternalItemToTop<nsDisplayCompositorHitTestInfo>(nsDisplayListSet const&, nsDisplayCompositorHitTestInfo*, mozilla::Maybe<int> const&)
3092
3093
static const uint32_t APPEND_OWN_LAYER = 0x1;
3094
static const uint32_t APPEND_POSITIONED = 0x2;
3095
static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4;
3096
static const uint32_t APPEND_OVERLAY = 0x8;
3097
static const uint32_t APPEND_TOP = 0x10;
3098
3099
static void
3100
AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists,
3101
            nsDisplayList* aSource, nsIFrame* aSourceFrame, uint32_t aFlags)
3102
0
{
3103
0
  if (aSource->IsEmpty())
3104
0
    return;
3105
0
3106
0
  nsDisplayWrapList* newItem;
3107
0
  const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
3108
0
  if (aFlags & APPEND_OWN_LAYER) {
3109
0
    ScrollbarData scrollbarData;
3110
0
    if (aFlags & APPEND_SCROLLBAR_CONTAINER) {
3111
0
      scrollbarData = ScrollbarData::CreateForScrollbarContainer(aBuilder->GetCurrentScrollbarDirection(),
3112
0
                                                                 aBuilder->GetCurrentScrollbarTarget());
3113
0
      // Direction should be set
3114
0
      MOZ_ASSERT(scrollbarData.mDirection.isSome());
3115
0
    }
3116
0
3117
0
    newItem = MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, aSourceFrame, aSource, asr, nsDisplayOwnLayerFlags::eNone, scrollbarData);
3118
0
  } else {
3119
0
    // Build the wrap list with an index of 1, since the scrollbar frame itself might have already
3120
0
    // built an nsDisplayWrapList.
3121
0
    newItem = MakeDisplayItem<nsDisplayWrapList>(aBuilder, aSourceFrame, aSource, asr, false, 1);
3122
0
  }
3123
0
3124
0
  if (aFlags & APPEND_POSITIONED) {
3125
0
    // We want overlay scrollbars to always be on top of the scrolled content,
3126
0
    // but we don't want them to unnecessarily cover overlapping elements from
3127
0
    // outside our scroll frame.
3128
0
    Maybe<int32_t> zIndex = Nothing();
3129
0
    if (aFlags & APPEND_TOP) {
3130
0
      zIndex = Some(INT32_MAX);
3131
0
    } else if (aFlags & APPEND_OVERLAY) {
3132
0
      zIndex = MaxZIndexInList(aLists.PositionedDescendants(), aBuilder);
3133
0
    } else if (aSourceFrame->StylePosition()->mZIndex.GetUnit() == eStyleUnit_Integer) {
3134
0
      zIndex = Some(aSourceFrame->StylePosition()->mZIndex.GetIntValue());
3135
0
3136
0
    }
3137
0
    AppendInternalItemToTop(aLists, newItem, zIndex);
3138
0
  } else {
3139
0
    aLists.BorderBackground()->AppendToTop(newItem);
3140
0
  }
3141
0
}
3142
3143
struct HoveredStateComparator
3144
{
3145
  static bool Hovered(const nsIFrame* aFrame)
3146
0
  {
3147
0
      return aFrame->GetContent()->IsElement() &&
3148
0
             aFrame->GetContent()->AsElement()->HasAttr(kNameSpaceID_None,
3149
0
                                                        nsGkAtoms::hover);
3150
0
  }
3151
3152
0
  bool Equals(nsIFrame* A, nsIFrame* B) const {
3153
0
    return Hovered(A) == Hovered(B);
3154
0
  }
3155
3156
0
  bool LessThan(nsIFrame* A, nsIFrame* B) const {
3157
0
    return !Hovered(A) && Hovered(B);
3158
0
  }
3159
};
3160
3161
void
3162
ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder*   aBuilder,
3163
                                       const nsDisplayListSet& aLists,
3164
                                       bool                    aCreateLayer,
3165
                                       bool                    aPositioned)
3166
0
{
3167
0
  const bool overlayScrollbars =
3168
0
    LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
3169
0
3170
0
  AutoTArray<nsIFrame*, 3> scrollParts;
3171
0
  for (nsIFrame* kid : mOuter->PrincipalChildList()) {
3172
0
    if (kid == mScrolledFrame ||
3173
0
        (kid->IsAbsPosContainingBlock() || overlayScrollbars) != aPositioned) {
3174
0
      continue;
3175
0
    }
3176
0
3177
0
    scrollParts.AppendElement(kid);
3178
0
  }
3179
0
  if (scrollParts.IsEmpty()) {
3180
0
    return;
3181
0
  }
3182
0
3183
0
  // We can't check will-change budget during display list building phase.
3184
0
  // This means that we will build scroll bar layers for out of budget
3185
0
  // will-change: scroll position.
3186
0
  const mozilla::layers::FrameMetrics::ViewID scrollTargetId = IsMaybeScrollingActive()
3187
0
    ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
3188
0
    : mozilla::layers::FrameMetrics::NULL_SCROLL_ID;
3189
0
3190
0
  scrollParts.Sort(HoveredStateComparator());
3191
0
3192
0
  DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3193
0
  // Don't let scrollparts extent outside our frame's border-box, if these are
3194
0
  // viewport scrollbars. They would create layerization problems. This wouldn't
3195
0
  // normally be an issue but themes can add overflow areas to scrollbar parts.
3196
0
  if (mIsRoot) {
3197
0
    clipState.ClipContentDescendants(
3198
0
        mOuter->GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(mOuter));
3199
0
  }
3200
0
3201
0
  for (uint32_t i = 0; i < scrollParts.Length(); ++i) {
3202
0
    Maybe<ScrollDirection> scrollDirection;
3203
0
    uint32_t appendToTopFlags = 0;
3204
0
    if (scrollParts[i] == mVScrollbarBox) {
3205
0
      scrollDirection.emplace(ScrollDirection::eVertical);
3206
0
      appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
3207
0
    }
3208
0
    if (scrollParts[i] == mHScrollbarBox) {
3209
0
      MOZ_ASSERT(!scrollDirection.isSome());
3210
0
      scrollDirection.emplace(ScrollDirection::eHorizontal);
3211
0
      appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
3212
0
    }
3213
0
    if (scrollParts[i] == mResizerBox &&
3214
0
        !HasResizer()) {
3215
0
      continue;
3216
0
    }
3217
0
3218
0
    // The display port doesn't necessarily include the scrollbars, so just
3219
0
    // include all of the scrollbars if we are in a RCD-RSF. We only do
3220
0
    // this for the root scrollframe of the root content document, which is
3221
0
    // zoomable, and where the scrollbar sizes are bounded by the widget.
3222
0
    const nsRect visible = mIsRoot && mOuter->PresContext()->IsRootContentDocument()
3223
0
                     ? scrollParts[i]->GetVisualOverflowRectRelativeToParent()
3224
0
                     : aBuilder->GetVisibleRect();
3225
0
    if (visible.IsEmpty()) {
3226
0
      continue;
3227
0
    }
3228
0
    const nsRect dirty = mIsRoot && mOuter->PresContext()->IsRootContentDocument()
3229
0
                     ? scrollParts[i]->GetVisualOverflowRectRelativeToParent()
3230
0
                     : aBuilder->GetDirtyRect();
3231
0
3232
0
    // Always create layers for overlay scrollbars so that we don't create a
3233
0
    // giant layer covering the whole scrollport if both scrollbars are visible.
3234
0
    const bool isOverlayScrollbar = scrollDirection.isSome() && overlayScrollbars;
3235
0
    const bool createLayer = aCreateLayer || isOverlayScrollbar ||
3236
0
                       gfxPrefs::AlwaysLayerizeScrollbarTrackTestOnly();
3237
0
3238
0
    nsDisplayListCollection partList(aBuilder);
3239
0
    {
3240
0
      nsDisplayListBuilder::AutoBuildingDisplayList
3241
0
        buildingForChild(aBuilder, mOuter,
3242
0
                         visible, dirty, true);
3243
0
3244
0
      nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter
3245
0
        infoSetter(aBuilder, scrollTargetId, scrollDirection, createLayer);
3246
0
      mOuter->BuildDisplayListForChild(
3247
0
        aBuilder, scrollParts[i], partList,
3248
0
        nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
3249
0
    }
3250
0
3251
0
    // DISPLAY_CHILD_FORCE_STACKING_CONTEXT put everything into
3252
0
    // partList.PositionedDescendants().
3253
0
    if (partList.PositionedDescendants()->IsEmpty()) {
3254
0
      continue;
3255
0
    }
3256
0
3257
0
    if (createLayer) {
3258
0
      appendToTopFlags |= APPEND_OWN_LAYER;
3259
0
    }
3260
0
    if (aPositioned) {
3261
0
      appendToTopFlags |= APPEND_POSITIONED;
3262
0
    }
3263
0
3264
0
    if (isOverlayScrollbar ||
3265
0
        scrollParts[i] == mResizerBox) {
3266
0
      if (isOverlayScrollbar && mIsRoot) {
3267
0
        appendToTopFlags |= APPEND_TOP;
3268
0
      } else {
3269
0
        appendToTopFlags |= APPEND_OVERLAY;
3270
0
        aBuilder->SetDisablePartialUpdates(true);
3271
0
      }
3272
0
    }
3273
0
3274
0
    {
3275
0
      nsDisplayListBuilder::AutoBuildingDisplayList
3276
0
        buildingForChild(aBuilder, scrollParts[i],
3277
0
                         visible + mOuter->GetOffsetTo(scrollParts[i]),
3278
0
                         dirty + mOuter->GetOffsetTo(scrollParts[i]), true);
3279
0
      nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter
3280
0
        infoSetter(aBuilder, scrollTargetId, scrollDirection, createLayer);
3281
0
3282
0
      ::AppendToTop(aBuilder, aLists,
3283
0
                    partList.PositionedDescendants(), scrollParts[i],
3284
0
                    appendToTopFlags);
3285
0
    }
3286
0
  }
3287
0
}
3288
3289
/* static */ bool ScrollFrameHelper::sFrameVisPrefsCached = false;
3290
/* static */ uint32_t ScrollFrameHelper::sHorzExpandScrollPort = 0;
3291
/* static */ uint32_t ScrollFrameHelper::sVertExpandScrollPort = 1;
3292
/* static */ int32_t ScrollFrameHelper::sHorzScrollFraction = 2;
3293
/* static */ int32_t ScrollFrameHelper::sVertScrollFraction = 2;
3294
3295
/* static */ void
3296
ScrollFrameHelper::EnsureFrameVisPrefsCached()
3297
0
{
3298
0
  if (!sFrameVisPrefsCached) {
3299
0
    Preferences::AddUintVarCache(&sHorzExpandScrollPort,
3300
0
      "layout.framevisibility.numscrollportwidths", (uint32_t)0);
3301
0
    Preferences::AddUintVarCache(&sVertExpandScrollPort,
3302
0
      "layout.framevisibility.numscrollportheights", 1);
3303
0
3304
0
    Preferences::AddIntVarCache(&sHorzScrollFraction,
3305
0
      "layout.framevisibility.amountscrollbeforeupdatehorizontal", 2);
3306
0
    Preferences::AddIntVarCache(&sVertScrollFraction,
3307
0
      "layout.framevisibility.amountscrollbeforeupdatevertical", 2);
3308
0
3309
0
    sFrameVisPrefsCached = true;
3310
0
  }
3311
0
}
3312
3313
nsRect
3314
ScrollFrameHelper::ExpandRectToNearlyVisible(const nsRect& aRect) const
3315
0
{
3316
0
  // We don't want to expand a rect in a direction that we can't scroll, so we
3317
0
  // check the scroll range.
3318
0
  nsRect scrollRange = GetScrollRangeForClamping();
3319
0
  nsPoint scrollPos = GetScrollPosition();
3320
0
  nsMargin expand(0, 0, 0, 0);
3321
0
3322
0
  nscoord vertShift = sVertExpandScrollPort * aRect.height;
3323
0
  if (scrollRange.y < scrollPos.y) {
3324
0
    expand.top = vertShift;
3325
0
  }
3326
0
  if (scrollPos.y < scrollRange.YMost()) {
3327
0
    expand.bottom = vertShift;
3328
0
  }
3329
0
3330
0
  nscoord horzShift = sHorzExpandScrollPort * aRect.width;
3331
0
  if (scrollRange.x < scrollPos.x) {
3332
0
    expand.left = horzShift;
3333
0
  }
3334
0
  if (scrollPos.x < scrollRange.XMost()) {
3335
0
    expand.right = horzShift;
3336
0
  }
3337
0
3338
0
  nsRect rect = aRect;
3339
0
  rect.Inflate(expand);
3340
0
  return rect;
3341
0
}
3342
3343
static bool
3344
ShouldBeClippedByFrame(nsIFrame* aClipFrame, nsIFrame* aClippedFrame)
3345
0
{
3346
0
  return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame);
3347
0
}
3348
3349
static void
3350
ClipItemsExceptCaret(nsDisplayList* aList,
3351
                     nsDisplayListBuilder* aBuilder,
3352
                     nsIFrame* aClipFrame,
3353
                     const DisplayItemClipChain* aExtraClip,
3354
                     nsDataHashtable<nsPtrHashKey<const DisplayItemClipChain>, const DisplayItemClipChain*>& aCache)
3355
0
{
3356
0
  for (nsDisplayItem* i = aList->GetBottom(); i; i = i->GetAbove()) {
3357
0
    if (!ShouldBeClippedByFrame(aClipFrame, i->Frame())) {
3358
0
      continue;
3359
0
    }
3360
0
3361
0
    if (i->GetType() != DisplayItemType::TYPE_CARET) {
3362
0
      const DisplayItemClipChain* clip = i->GetClipChain();
3363
0
      const DisplayItemClipChain* intersection = nullptr;
3364
0
      if (aCache.Get(clip, &intersection)) {
3365
0
        i->SetClipChain(intersection, true);
3366
0
      } else {
3367
0
        i->IntersectClip(aBuilder, aExtraClip, true);
3368
0
        aCache.Put(clip, i->GetClipChain());
3369
0
      }
3370
0
    }
3371
0
    nsDisplayList* children = i->GetSameCoordinateSystemChildren();
3372
0
    if (children) {
3373
0
      ClipItemsExceptCaret(children, aBuilder, aClipFrame, aExtraClip, aCache);
3374
0
    }
3375
0
  }
3376
0
}
3377
3378
static void
3379
ClipListsExceptCaret(nsDisplayListCollection* aLists,
3380
                     nsDisplayListBuilder* aBuilder,
3381
                     nsIFrame* aClipFrame,
3382
                     const DisplayItemClipChain* aExtraClip)
3383
0
{
3384
0
  nsDataHashtable<nsPtrHashKey<const DisplayItemClipChain>, const DisplayItemClipChain*> cache;
3385
0
  ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame, aExtraClip, cache);
3386
0
  ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame, aExtraClip, cache);
3387
0
  ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aExtraClip, cache);
3388
0
  ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame, aExtraClip, cache);
3389
0
  ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aExtraClip, cache);
3390
0
  ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aExtraClip, cache);
3391
0
}
3392
3393
void
3394
ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
3395
                                    const nsDisplayListSet& aLists)
3396
0
{
3397
0
  SetAndNullOnExit<const nsIFrame> tmpBuilder(mReferenceFrameDuringPainting, aBuilder->GetCurrentReferenceFrame());
3398
0
  if (aBuilder->IsForFrameVisibility()) {
3399
0
    NotifyApproximateFrameVisibilityUpdate(false);
3400
0
  }
3401
0
3402
0
  mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
3403
0
3404
0
  if (aBuilder->IsPaintingToWindow()) {
3405
0
    mScrollPosAtLastPaint = GetScrollPosition();
3406
0
    if (IsMaybeScrollingActive()) {
3407
0
      if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) {
3408
0
        mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint;
3409
0
      }
3410
0
    } else {
3411
0
      mScrollPosForLayerPixelAlignment = nsPoint(-1,-1);
3412
0
    }
3413
0
  }
3414
0
3415
0
  // It's safe to get this value before the DecideScrollableLayer call below
3416
0
  // because that call cannot create a displayport for root scroll frames,
3417
0
  // and hence it cannot create an ignore scroll frame.
3418
0
  bool ignoringThisScrollFrame =
3419
0
    aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping();
3420
0
3421
0
  // Overflow clipping can never clip frames outside our subtree, so there
3422
0
  // is no need to worry about whether we are a moving frame that might clip
3423
0
  // non-moving frames.
3424
0
  // Not all our descendants will be clipped by overflow clipping, but all
3425
0
  // the ones that aren't clipped will be out of flow frames that have already
3426
0
  // had dirty rects saved for them by their parent frames calling
3427
0
  // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
3428
0
  // dirty rect here.
3429
0
  nsRect visibleRect = aBuilder->GetVisibleRect();
3430
0
  nsRect dirtyRect = aBuilder->GetDirtyRect();
3431
0
  if (!ignoringThisScrollFrame) {
3432
0
    visibleRect = visibleRect.Intersect(mScrollPort);
3433
0
    dirtyRect = dirtyRect.Intersect(mScrollPort);
3434
0
  }
3435
0
3436
0
  bool dirtyRectHasBeenOverriden = false;
3437
0
  Unused << DecideScrollableLayer(aBuilder, &visibleRect, &dirtyRect,
3438
0
              /* aSetBase = */ !mIsRoot, &dirtyRectHasBeenOverriden);
3439
0
3440
0
  if (aBuilder->IsForFrameVisibility()) {
3441
0
    // We expand the dirty rect to catch frames just outside of the scroll port.
3442
0
    // We use the dirty rect instead of the whole scroll port to prevent
3443
0
    // too much expansion in the presence of very large (bigger than the
3444
0
    // viewport) scroll ports.
3445
0
    dirtyRect = ExpandRectToNearlyVisible(dirtyRect);
3446
0
    visibleRect = dirtyRect;
3447
0
  }
3448
0
3449
0
  // We put non-overlay scrollbars in their own layers when this is the root
3450
0
  // scroll frame and we are a toplevel content document. In this situation,
3451
0
  // the scrollbar(s) would normally be assigned their own layer anyway, since
3452
0
  // they're not scrolled with the rest of the document. But when both
3453
0
  // scrollbars are visible, the layer's visible rectangle would be the size
3454
0
  // of the viewport, so most layer implementations would create a layer buffer
3455
0
  // that's much larger than necessary. Creating independent layers for each
3456
0
  // scrollbar works around the problem.
3457
0
  bool createLayersForScrollbars = mIsRoot &&
3458
0
    mOuter->PresContext()->IsRootContentDocument();
3459
0
3460
0
  nsIScrollableFrame* sf = do_QueryFrame(mOuter);
3461
0
  MOZ_ASSERT(sf);
3462
0
3463
0
  if (ignoringThisScrollFrame) {
3464
0
    // Root scrollframes have FrameMetrics and clipping on their container
3465
0
    // layers, so don't apply clipping again.
3466
0
    mAddClipRectToLayer = false;
3467
0
3468
0
    // If we are a root scroll frame that has a display port we want to add
3469
0
    // scrollbars, they will be children of the scrollable layer, but they get
3470
0
    // adjusted by the APZC automatically.
3471
0
    bool addScrollBars = mIsRoot && mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow();
3472
0
3473
0
    if (addScrollBars) {
3474
0
      // Add classic scrollbars.
3475
0
      AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false);
3476
0
    }
3477
0
3478
0
    {
3479
0
      nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
3480
0
      if (aBuilder->IsPaintingToWindow() &&
3481
0
          gfxPrefs::LayoutUseContainersForRootFrames() && mIsRoot) {
3482
0
        asrSetter.EnterScrollFrame(sf);
3483
0
        aBuilder->SetActiveScrolledRootForRootScrollframe(aBuilder->CurrentActiveScrolledRoot());
3484
0
      }
3485
0
3486
0
      nsDisplayListBuilder::AutoBuildingDisplayList
3487
0
        building(aBuilder, mOuter, visibleRect, dirtyRect, aBuilder->IsAtRootOfPseudoStackingContext());
3488
0
3489
0
      // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
3490
0
      // The scrolled frame shouldn't have its own background/border, so we
3491
0
      // can just pass aLists directly.
3492
0
      mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, aLists);
3493
0
    }
3494
0
3495
0
    if (addScrollBars) {
3496
0
      // Add overlay scrollbars.
3497
0
      AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, true);
3498
0
    }
3499
0
3500
0
    return;
3501
0
  }
3502
0
3503
0
  // Root scrollframes have FrameMetrics and clipping on their container
3504
0
  // layers, so don't apply clipping again.
3505
0
  mAddClipRectToLayer =
3506
0
    !(mIsRoot && mOuter->PresShell()->GetIsViewportOverridden());
3507
0
3508
0
  // Whether we might want to build a scrollable layer for this scroll frame
3509
0
  // at some point in the future. This controls whether we add the information
3510
0
  // to the layer tree (a scroll info layer if necessary, and add the right
3511
0
  // area to the dispatch to content layer event regions) necessary to activate
3512
0
  // a scroll frame so it creates a scrollable layer.
3513
0
  bool couldBuildLayer = false;
3514
0
  if (aBuilder->IsPaintingToWindow()) {
3515
0
    if (mWillBuildScrollableLayer) {
3516
0
      couldBuildLayer = true;
3517
0
    } else {
3518
0
      couldBuildLayer =
3519
0
        nsLayoutUtils::AsyncPanZoomEnabled(mOuter) &&
3520
0
        WantAsyncScroll() &&
3521
0
        // If we are using containers for root frames, and we are the root
3522
0
        // scroll frame for the display root, then we don't need a scroll
3523
0
        // info layer. nsDisplayList::PaintForFrame already calls
3524
0
        // ComputeFrameMetrics for us.
3525
0
        (!(gfxPrefs::LayoutUseContainersForRootFrames() && mIsRoot) ||
3526
0
         (aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext()));
3527
0
    }
3528
0
  }
3529
0
3530
0
  // Now display the scrollbars and scrollcorner. These parts are drawn
3531
0
  // in the border-background layer, on top of our own background and
3532
0
  // borders and underneath borders and backgrounds of later elements
3533
0
  // in the tree.
3534
0
  // Note that this does not apply for overlay scrollbars; those are drawn
3535
0
  // in the positioned-elements layer on top of everything else by the call
3536
0
  // to AppendScrollPartsTo(..., true) further down.
3537
0
  AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false);
3538
0
3539
0
  const nsStyleDisplay* disp = mOuter->StyleDisplay();
3540
0
  if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) {
3541
0
    aBuilder->AddToWillChangeBudget(mOuter, GetVisualViewportSize());
3542
0
  }
3543
0
3544
0
  mScrollParentID = aBuilder->GetCurrentScrollParentId();
3545
0
3546
0
  Maybe<nsRect> contentBoxClip;
3547
0
  Maybe<const DisplayItemClipChain*> extraContentBoxClipForNonCaretContent;
3548
0
  if (MOZ_UNLIKELY(disp->mOverflowClipBoxBlock ==
3549
0
                     NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX ||
3550
0
                   disp->mOverflowClipBoxInline ==
3551
0
                      NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX)) {
3552
0
    WritingMode wm = mScrolledFrame->GetWritingMode();
3553
0
    bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
3554
0
                                : disp->mOverflowClipBoxInline) ==
3555
0
               NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX;
3556
0
    bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
3557
0
                                : disp->mOverflowClipBoxBlock) ==
3558
0
               NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX;
3559
0
    // We only clip if there is *scrollable* overflow, to avoid clipping
3560
0
    // *visual* overflow unnecessarily.
3561
0
    nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
3562
0
    nsRect so = mScrolledFrame->GetScrollableOverflowRect();
3563
0
    if ((cbH && (clipRect.width != so.width || so.x < 0)) ||
3564
0
        (cbV && (clipRect.height != so.height || so.y < 0))) {
3565
0
      nsMargin padding = mOuter->GetUsedPadding();
3566
0
      if (!cbH) {
3567
0
        padding.left = padding.right = nscoord(0);
3568
0
      }
3569
0
      if (!cbV) {
3570
0
        padding.top = padding.bottom = nscoord(0);
3571
0
      }
3572
0
      clipRect.Deflate(padding);
3573
0
3574
0
      // The non-inflated clip needs to be set on all non-caret items.
3575
0
      // We prepare an extra DisplayItemClipChain here that will be intersected
3576
0
      // with those items after they've been created.
3577
0
      const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
3578
0
3579
0
      DisplayItemClip newClip;
3580
0
      newClip.SetTo(clipRect);
3581
0
3582
0
      const DisplayItemClipChain* extraClip =
3583
0
        aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
3584
0
3585
0
      extraContentBoxClipForNonCaretContent = Some(extraClip);
3586
0
3587
0
      nsIFrame* caretFrame = aBuilder->GetCaretFrame();
3588
0
      // Avoid clipping it in a zero-height line box (heuristic only).
3589
0
      if (caretFrame && caretFrame->GetRect().height != 0) {
3590
0
        nsRect caretRect = aBuilder->GetCaretRect();
3591
0
        // Allow the caret to stick out of the content box clip by half the
3592
0
        // caret height on the top, and its full width on the right.
3593
0
        nsRect inflatedClip = clipRect;
3594
0
        inflatedClip.Inflate(nsMargin(caretRect.height / 2, caretRect.width, 0, 0));
3595
0
        contentBoxClip = Some(inflatedClip);
3596
0
      }
3597
0
    }
3598
0
  }
3599
0
3600
0
  nsDisplayListCollection scrolledContent(aBuilder);
3601
0
  {
3602
0
    // Note that setting the current scroll parent id here means that positioned children
3603
0
    // of this scroll info layer will pick up the scroll info layer as their scroll handoff
3604
0
    // parent. This is intentional because that is what happens for positioned children
3605
0
    // of scroll layers, and we want to maintain consistent behaviour between scroll layers
3606
0
    // and scroll info layers.
3607
0
    nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
3608
0
        aBuilder,
3609
0
        couldBuildLayer && mScrolledFrame->GetContent()
3610
0
            ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
3611
0
            : aBuilder->GetCurrentScrollParentId());
3612
0
3613
0
    nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
3614
0
    // Our override of GetBorderRadii ensures we never have a radius at
3615
0
    // the corners where we have a scrollbar.
3616
0
    nscoord radii[8];
3617
0
    bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii);
3618
0
    if (mIsRoot) {
3619
0
      clipRect.SizeTo(nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter));
3620
0
      if (mOuter->PresContext()->IsRootContentDocument()) {
3621
0
        double res = mOuter->PresShell()->GetResolution();
3622
0
        clipRect.width = NSToCoordRound(clipRect.width / res);
3623
0
        clipRect.height = NSToCoordRound(clipRect.height / res);
3624
0
      }
3625
0
    }
3626
0
3627
0
    DisplayListClipState::AutoSaveRestore clipState(aBuilder);
3628
0
    if (mClipAllDescendants) {
3629
0
      clipState.ClipContentDescendants(clipRect, haveRadii ? radii : nullptr);
3630
0
    } else {
3631
0
      clipState.ClipContainingBlockDescendants(clipRect, haveRadii ? radii : nullptr);
3632
0
    }
3633
0
3634
0
    Maybe<DisplayListClipState::AutoSaveRestore> contentBoxClipState;;
3635
0
    if (contentBoxClip) {
3636
0
      contentBoxClipState.emplace(aBuilder);
3637
0
      if (mClipAllDescendants) {
3638
0
        contentBoxClipState->ClipContentDescendants(*contentBoxClip);
3639
0
      } else {
3640
0
        contentBoxClipState->ClipContainingBlockDescendants(*contentBoxClip);
3641
0
      }
3642
0
    }
3643
0
3644
0
    nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
3645
0
    if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
3646
0
      asrSetter.EnterScrollFrame(sf);
3647
0
    }
3648
0
3649
0
    if (mIsScrollableLayerInRootContainer) {
3650
0
      aBuilder->SetActiveScrolledRootForRootScrollframe(aBuilder->CurrentActiveScrolledRoot());
3651
0
    }
3652
0
3653
0
    if (mWillBuildScrollableLayer) {
3654
0
      // Create a hit test info item for the scrolled content that's not
3655
0
      // clipped to the displayport. This ensures that within the bounds
3656
0
      // of the scroll frame, the scrolled content is always hit, even
3657
0
      // if we are checkerboarding.
3658
0
      if (aBuilder->BuildCompositorHitTestInfo()) {
3659
0
        CompositorHitTestInfo info = mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
3660
0
        if (info != CompositorHitTestInfo::eInvisibleToHitTest) {
3661
0
          nsDisplayCompositorHitTestInfo* hitInfo =
3662
0
              MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1);
3663
0
          aBuilder->SetCompositorHitTestInfo(hitInfo);
3664
0
          scrolledContent.BorderBackground()->AppendToTop(hitInfo);
3665
0
        }
3666
0
      }
3667
0
    }
3668
0
3669
0
    {
3670
0
      // Clip our contents to the unsnapped scrolled rect. This makes sure that
3671
0
      // we don't have display items over the subpixel seam at the edge of the
3672
0
      // scrolled area.
3673
0
      DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
3674
0
      nsRect scrolledRectClip =
3675
0
        GetUnsnappedScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
3676
0
                                         mScrollPort.Size()) + mScrolledFrame->GetPosition();
3677
0
      if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
3678
0
        // Clip the contents to the display port.
3679
0
        // The dirty rect already acts kind of like a clip, in that
3680
0
        // FrameLayerBuilder intersects item bounds and opaque regions with
3681
0
        // it, but it doesn't have the consistent snapping behavior of a
3682
0
        // true clip.
3683
0
        // For a case where this makes a difference, imagine the following
3684
0
        // scenario: The display port has an edge that falls on a fractional
3685
0
        // layer pixel, and there's an opaque display item that covers the
3686
0
        // whole display port up until that fractional edge, and there is a
3687
0
        // transparent display item that overlaps the edge. We want to prevent
3688
0
        // this transparent item from enlarging the scrolled layer's visible
3689
0
        // region beyond its opaque region. The dirty rect doesn't do that -
3690
0
        // it gets rounded out, whereas a true clip gets rounded to nearest
3691
0
        // pixels.
3692
0
        // If there is no display port, we don't need this because the clip
3693
0
        // from the scroll port is still applied.
3694
0
        scrolledRectClip = scrolledRectClip.Intersect(visibleRect);
3695
0
      }
3696
0
      scrolledRectClipState.ClipContainingBlockDescendants(
3697
0
        scrolledRectClip + aBuilder->ToReferenceFrame(mOuter));
3698
0
3699
0
      nsDisplayListBuilder::AutoBuildingDisplayList
3700
0
        building(aBuilder, mOuter, visibleRect, dirtyRect, aBuilder->IsAtRootOfPseudoStackingContext());
3701
0
3702
0
      mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, scrolledContent);
3703
0
3704
0
      if (dirtyRectHasBeenOverriden && gfxPrefs::LayoutDisplayListShowArea()) {
3705
0
        nsDisplaySolidColor* color =
3706
0
          MakeDisplayItem<nsDisplaySolidColor>(aBuilder, mOuter,
3707
0
                                               dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
3708
0
                                               NS_RGBA(0, 0, 255, 64), false);
3709
0
        color->SetOverrideZIndex(INT32_MAX);
3710
0
        scrolledContent.PositionedDescendants()->AppendToTop(color);
3711
0
      }
3712
0
    }
3713
0
3714
0
    if (extraContentBoxClipForNonCaretContent) {
3715
0
      // The items were built while the inflated content box clip was in
3716
0
      // effect, so that the caret wasn't clipped unnecessarily. We apply
3717
0
      // the non-inflated clip to the non-caret items now, by intersecting
3718
0
      // it with their existing clip.
3719
0
      ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame,
3720
0
                           *extraContentBoxClipForNonCaretContent);
3721
0
    }
3722
0
3723
0
    if (aBuilder->IsPaintingToWindow()) {
3724
0
      mIsScrollParent = idSetter.ShouldForceLayerForScrollParent();
3725
0
    }
3726
0
    if (idSetter.ShouldForceLayerForScrollParent() &&
3727
0
        !gfxPrefs::LayoutUseContainersForRootFrames())
3728
0
    {
3729
0
      // Note that forcing layerization of scroll parents follows the scroll
3730
0
      // handoff chain which is subject to the out-of-flow-frames caveat noted
3731
0
      // above (where the idSetter variable is created).
3732
0
      //
3733
0
      // This is not compatible when using containes for root scrollframes.
3734
0
      MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() &&
3735
0
        aBuilder->IsPaintingToWindow());
3736
0
      if (!mWillBuildScrollableLayer) {
3737
0
        // Set a displayport so next paint we don't have to force layerization
3738
0
        // after the fact.
3739
0
        nsLayoutUtils::SetDisplayPortMargins(mOuter->GetContent(),
3740
0
                                             mOuter->PresShell(),
3741
0
                                             ScreenMargin(),
3742
0
                                             0,
3743
0
                                             nsLayoutUtils::RepaintMode::DoNotRepaint);
3744
0
        // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer and
3745
0
        // recompute the current animated geometry root if needed.
3746
0
        // It's too late to change the dirty rect so pass a copy.
3747
0
        nsRect copyOfDirtyRect = dirtyRect;
3748
0
        nsRect copyOfVisibleRect = visibleRect;
3749
0
        Unused << DecideScrollableLayer(aBuilder, &copyOfVisibleRect, &copyOfDirtyRect,
3750
0
                    /* aSetBase = */ false, nullptr);
3751
0
        if (mWillBuildScrollableLayer) {
3752
0
          asrSetter.InsertScrollFrame(sf);
3753
0
          aBuilder->SetDisablePartialUpdates(true);
3754
0
        }
3755
0
      }
3756
0
    }
3757
0
  }
3758
0
3759
0
  if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
3760
0
    aBuilder->ForceLayerForScrollParent();
3761
0
  }
3762
0
3763
0
  if (couldBuildLayer) {
3764
0
    // Make sure that APZ will dispatch events back to content so we can create
3765
0
    // a displayport for this frame. We'll add the item later on.
3766
0
    if (!mWillBuildScrollableLayer) {
3767
0
      if (aBuilder->BuildCompositorHitTestInfo()) {
3768
0
        CompositorHitTestInfo info = CompositorHitTestInfo::eVisibleToHitTest
3769
0
                                   | CompositorHitTestInfo::eDispatchToContent;
3770
0
        // If the scroll frame has non-default overscroll-behavior, instruct
3771
0
        // APZ to require a target confirmation before processing events that
3772
0
        // hit this scroll frame (that is, to drop the events if a confirmation
3773
0
        // does not arrive within the timeout period). Otherwise, APZ's
3774
0
        // fallback behaviour of scrolling the enclosing scroll frame would
3775
0
        // violate the specified overscroll-behavior.
3776
0
        ScrollStyles scrollStyles = GetScrollStylesFromFrame();
3777
0
        if (scrollStyles.mOverscrollBehaviorX != StyleOverscrollBehavior::Auto ||
3778
0
            scrollStyles.mOverscrollBehaviorY != StyleOverscrollBehavior::Auto) {
3779
0
          info |= CompositorHitTestInfo::eRequiresTargetConfirmation;
3780
0
        }
3781
0
        nsDisplayCompositorHitTestInfo* hitInfo =
3782
0
            MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1,
3783
0
                Some(mScrollPort + aBuilder->ToReferenceFrame(mOuter)));
3784
0
        AppendInternalItemToTop(scrolledContent, hitInfo, Some(INT32_MAX));
3785
0
      }
3786
0
    }
3787
0
3788
0
    if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
3789
0
      aBuilder->AppendNewScrollInfoItemForHoisting(
3790
0
        MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
3791
0
                                                mOuter));
3792
0
    }
3793
0
  }
3794
0
  // Now display overlay scrollbars and the resizer, if we have one.
3795
0
  AppendScrollPartsTo(aBuilder, scrolledContent, createLayersForScrollbars, true);
3796
0
3797
0
  scrolledContent.MoveTo(aLists);
3798
0
}
3799
3800
bool
3801
ScrollFrameHelper::DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
3802
                                         nsRect* aVisibleRect,
3803
                                         nsRect* aDirtyRect,
3804
                                         bool aSetBase,
3805
                                         bool* aDirtyRectHasBeenOverriden)
3806
0
{
3807
0
  // Save and check if this changes so we can recompute the current agr.
3808
0
  bool oldWillBuildScrollableLayer = mWillBuildScrollableLayer;
3809
0
3810
0
  nsIContent* content = mOuter->GetContent();
3811
0
  bool usingDisplayPort = nsLayoutUtils::HasDisplayPort(content);
3812
0
  if (aBuilder->IsPaintingToWindow()) {
3813
0
    if (aSetBase) {
3814
0
      nsRect displayportBase = *aVisibleRect;
3815
0
      nsPresContext* pc = mOuter->PresContext();
3816
0
      if (mIsRoot && (pc->IsRootContentDocument() || !pc->GetParentPresContext())) {
3817
0
        displayportBase =
3818
0
          nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter));
3819
0
      } else {
3820
0
        // Make the displayport base equal to the visible rect restricted to
3821
0
        // the scrollport and the root composition bounds, relative to the
3822
0
        // scrollport.
3823
0
        displayportBase = aVisibleRect->Intersect(mScrollPort);
3824
0
3825
0
        // Only restrict to the root composition bounds if necessary,
3826
0
        // as the required coordinate transformation is expensive.
3827
0
        if (usingDisplayPort) {
3828
0
          const nsPresContext* rootPresContext =
3829
0
            pc->GetToplevelContentDocumentPresContext();
3830
0
          if (!rootPresContext) {
3831
0
            rootPresContext = pc->GetRootPresContext();
3832
0
          }
3833
0
          if (rootPresContext) {
3834
0
            const nsIPresShell* const rootPresShell = rootPresContext->PresShell();
3835
0
            nsIFrame* rootFrame = rootPresShell->GetRootScrollFrame();
3836
0
            if (!rootFrame) {
3837
0
              rootFrame = rootPresShell->GetRootFrame();
3838
0
            }
3839
0
            if (rootFrame) {
3840
0
              nsRect rootCompBounds =
3841
0
                nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(rootFrame));
3842
0
3843
0
              // If rootFrame is the RCD-RSF then CalculateCompositionSizeForFrame
3844
0
              // did not take the document's resolution into account, so we must.
3845
0
              if (rootPresContext->IsRootContentDocument() &&
3846
0
                  rootFrame == rootPresShell->GetRootScrollFrame()) {
3847
0
                rootCompBounds = rootCompBounds.RemoveResolution(rootPresShell->GetResolution());
3848
0
              }
3849
0
3850
0
              // We want to convert the root composition bounds from the coordinate
3851
0
              // space of |rootFrame| to the coordinate space of |mOuter|. We do
3852
0
              // that with the TransformRect call below. However, since we care
3853
0
              // about the root composition bounds relative to what the user is
3854
0
              // actually seeing, we also need to incorporate the APZ callback
3855
0
              // transforms into this. Most of the time those transforms are
3856
0
              // negligible, but in some cases (e.g. when a zoom is applied on
3857
0
              // an overflow:hidden document) it is not (see bug 1280013).
3858
0
              // XXX: Eventually we may want to create a modified version of
3859
0
              // TransformRect that includes the APZ callback transforms
3860
0
              // directly.
3861
0
              nsLayoutUtils::TransformRect(rootFrame, mOuter, rootCompBounds);
3862
0
              rootCompBounds += CSSPoint::ToAppUnits(
3863
0
                  nsLayoutUtils::GetCumulativeApzCallbackTransform(mOuter));
3864
0
3865
0
              // We want to limit displayportBase to be no larger than rootCompBounds on
3866
0
              // either axis, but we don't want to just blindly intersect the two, because
3867
0
              // rootCompBounds might be offset from where displayportBase is (see bug
3868
0
              // 1327095 comment 8). Instead, we translate rootCompBounds so as to
3869
0
              // maximize the overlap with displayportBase, and *then* do the intersection.
3870
0
              if (rootCompBounds.x > displayportBase.x && rootCompBounds.XMost() > displayportBase.XMost()) {
3871
0
                // rootCompBounds is at a greater x-position for both left and right, so translate it such
3872
0
                // that the XMost() values are the same. This will line up the right edge of the two rects,
3873
0
                // and might mean that rootCompbounds.x is smaller than displayportBase.x. We can avoid that
3874
0
                // by taking the min of the x delta and XMost() delta, but it doesn't really matter because
3875
0
                // the intersection between the two rects below will end up the same.
3876
0
                rootCompBounds.x -= (rootCompBounds.XMost() - displayportBase.XMost());
3877
0
              } else if (rootCompBounds.x < displayportBase.x && rootCompBounds.XMost() < displayportBase.XMost()) {
3878
0
                // Analaogous code for when the rootCompBounds is at a smaller x-position.
3879
0
                rootCompBounds.x = displayportBase.x;
3880
0
              }
3881
0
              // Do the same for y-axis
3882
0
              if (rootCompBounds.y > displayportBase.y && rootCompBounds.YMost() > displayportBase.YMost()) {
3883
0
                rootCompBounds.y -= (rootCompBounds.YMost() - displayportBase.YMost());
3884
0
              } else if (rootCompBounds.y < displayportBase.y && rootCompBounds.YMost() < displayportBase.YMost()) {
3885
0
                rootCompBounds.y = displayportBase.y;
3886
0
              }
3887
0
3888
0
              // Now we can do the intersection
3889
0
              displayportBase = displayportBase.Intersect(rootCompBounds);
3890
0
            }
3891
0
          }
3892
0
        }
3893
0
3894
0
        displayportBase -= mScrollPort.TopLeft();
3895
0
      }
3896
0
3897
0
      nsLayoutUtils::SetDisplayPortBase(mOuter->GetContent(), displayportBase);
3898
0
    }
3899
0
3900
0
    // If we don't have aSetBase == true then should have already
3901
0
    // been called with aSetBase == true which should have set a
3902
0
    // displayport base.
3903
0
    MOZ_ASSERT(content->GetProperty(nsGkAtoms::DisplayPortBase));
3904
0
    nsRect displayPort;
3905
0
    usingDisplayPort =
3906
0
      nsLayoutUtils::GetDisplayPort(content, &displayPort, RelativeTo::ScrollFrame);
3907
0
3908
0
    if (usingDisplayPort) {
3909
0
      // Override the dirty rectangle if the displayport has been set.
3910
0
      *aVisibleRect = displayPort;
3911
0
      if (!aBuilder->IsPartialUpdate() || aBuilder->InInvalidSubtree()) {
3912
0
        *aDirtyRect = displayPort;
3913
0
        if (aDirtyRectHasBeenOverriden) {
3914
0
          *aDirtyRectHasBeenOverriden = true;
3915
0
        }
3916
0
      } else if (mOuter->HasOverrideDirtyRegion()) {
3917
0
        nsRect* rect =
3918
0
          mOuter->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
3919
0
        if (rect) {
3920
0
          *aDirtyRect = *rect;
3921
0
          if (aDirtyRectHasBeenOverriden) {
3922
0
            *aDirtyRectHasBeenOverriden = true;
3923
0
          }
3924
0
        }
3925
0
      }
3926
0
    } else if (mIsRoot) {
3927
0
      // The displayPort getter takes care of adjusting for resolution. So if
3928
0
      // we have resolution but no displayPort then we need to adjust for
3929
0
      // resolution here.
3930
0
      nsIPresShell* presShell = mOuter->PresShell();
3931
0
      *aVisibleRect = aVisibleRect->RemoveResolution(
3932
0
        presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
3933
0
      *aDirtyRect = aDirtyRect->RemoveResolution(
3934
0
        presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
3935
0
    }
3936
0
  }
3937
0
3938
0
  // Since making new layers is expensive, only create a scrollable layer
3939
0
  // for some scroll frames.
3940
0
  // When a displayport is being used, force building of a layer so that
3941
0
  // the compositor can find the scrollable layer for async scrolling.
3942
0
  // If the element is marked 'scrollgrab', also force building of a layer
3943
0
  // so that APZ can implement scroll grabbing.
3944
0
  mWillBuildScrollableLayer = usingDisplayPort || nsContentUtils::HasScrollgrab(content);
3945
0
3946
0
  // The cached animated geometry root for the display builder is out of
3947
0
  // date if we just introduced a new animated geometry root.
3948
0
  if (oldWillBuildScrollableLayer != mWillBuildScrollableLayer) {
3949
0
    aBuilder->RecomputeCurrentAnimatedGeometryRoot();
3950
0
  }
3951
0
3952
0
  if (gfxPrefs::LayoutUseContainersForRootFrames() && mWillBuildScrollableLayer && mIsRoot) {
3953
0
    mIsScrollableLayerInRootContainer = true;
3954
0
  }
3955
0
3956
0
  return mWillBuildScrollableLayer;
3957
0
}
3958
3959
3960
Maybe<ScrollMetadata>
3961
ScrollFrameHelper::ComputeScrollMetadata(LayerManager* aLayerManager,
3962
                                         const nsIFrame* aContainerReferenceFrame,
3963
                                         const ContainerLayerParameters& aParameters,
3964
                                         const DisplayItemClip* aClip) const
3965
0
{
3966
0
  if (!mWillBuildScrollableLayer || mIsScrollableLayerInRootContainer) {
3967
0
    return Nothing();
3968
0
  }
3969
0
3970
0
  if (!nsLayoutUtils::UsesAsyncScrolling(mOuter)) {
3971
0
    // Return early, since if we don't use APZ we don't need FrameMetrics.
3972
0
    return Nothing();
3973
0
  }
3974
0
3975
0
  nsPoint toReferenceFrame = mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame);
3976
0
3977
0
  Maybe<nsRect> parentLayerClip;
3978
0
  // For containerful frames, the clip is on the container layer.
3979
0
  if (aClip &&
3980
0
      (!gfxPrefs::LayoutUseContainersForRootFrames() || mAddClipRectToLayer)) {
3981
0
    parentLayerClip = Some(aClip->GetClipRect());
3982
0
  }
3983
0
3984
0
  bool isRootContent = mIsRoot && mOuter->PresContext()->IsRootContentDocument();
3985
0
3986
0
  MOZ_ASSERT(mScrolledFrame->GetContent());
3987
0
3988
0
  nsRect scrollport = mScrollPort + toReferenceFrame;
3989
0
3990
0
  return Some(nsLayoutUtils::ComputeScrollMetadata(
3991
0
    mScrolledFrame, mOuter, mOuter->GetContent(),
3992
0
    aContainerReferenceFrame, aLayerManager, mScrollParentID,
3993
0
    scrollport, parentLayerClip, isRootContent, aParameters));
3994
0
}
3995
3996
void
3997
ScrollFrameHelper::ClipLayerToDisplayPort(Layer* aLayer,
3998
                                          const DisplayItemClip* aClip,
3999
                                          const ContainerLayerParameters& aParameters) const
4000
0
{
4001
0
  // If APZ is not enabled, we still need the displayport to be clipped
4002
0
  // in the compositor.
4003
0
  if (!nsLayoutUtils::UsesAsyncScrolling(mOuter)) {
4004
0
    Maybe<nsRect> parentLayerClip;
4005
0
    // For containerful frames, the clip is on the container layer.
4006
0
    if (aClip &&
4007
0
        (!gfxPrefs::LayoutUseContainersForRootFrames() || mAddClipRectToLayer)) {
4008
0
      parentLayerClip = Some(aClip->GetClipRect());
4009
0
    }
4010
0
4011
0
    if (parentLayerClip) {
4012
0
      ParentLayerIntRect displayportClip =
4013
0
        ViewAs<ParentLayerPixel>(
4014
0
          parentLayerClip->ScaleToNearestPixels(
4015
0
            aParameters.mXScale,
4016
0
            aParameters.mYScale,
4017
0
            mScrolledFrame->PresContext()->AppUnitsPerDevPixel()));
4018
0
4019
0
      ParentLayerIntRect layerClip;
4020
0
      if (const ParentLayerIntRect* origClip = aLayer->GetClipRect().ptrOr(nullptr)) {
4021
0
        layerClip = displayportClip.Intersect(*origClip);
4022
0
      } else {
4023
0
        layerClip = displayportClip;
4024
0
      }
4025
0
      aLayer->SetClipRect(Some(layerClip));
4026
0
    }
4027
0
  }
4028
0
}
4029
4030
bool
4031
ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
4032
0
{
4033
0
  // Use the right rect depending on if a display port is set.
4034
0
  nsRect displayPort;
4035
0
  bool usingDisplayport =
4036
0
    nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort, RelativeTo::ScrollFrame);
4037
0
  return aRect.Intersects(ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort));
4038
0
}
4039
4040
static void HandleScrollPref(nsIScrollable *aScrollable, int32_t aOrientation,
4041
                             uint8_t& aValue)
4042
0
{
4043
0
  int32_t pref;
4044
0
  aScrollable->GetDefaultScrollbarPreferences(aOrientation, &pref);
4045
0
  switch (pref) {
4046
0
    case nsIScrollable::Scrollbar_Auto:
4047
0
      // leave |aValue| untouched
4048
0
      break;
4049
0
    case nsIScrollable::Scrollbar_Never:
4050
0
      aValue = NS_STYLE_OVERFLOW_HIDDEN;
4051
0
      break;
4052
0
    case nsIScrollable::Scrollbar_Always:
4053
0
      aValue = NS_STYLE_OVERFLOW_SCROLL;
4054
0
      break;
4055
0
  }
4056
0
}
4057
4058
ScrollStyles
4059
ScrollFrameHelper::GetScrollStylesFromFrame() const
4060
0
{
4061
0
  nsPresContext* presContext = mOuter->PresContext();
4062
0
  if (!presContext->IsDynamic() &&
4063
0
      !(mIsRoot && presContext->HasPaginatedScrolling())) {
4064
0
    return ScrollStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN);
4065
0
  }
4066
0
4067
0
  if (!mIsRoot) {
4068
0
    const nsStyleDisplay* disp = mOuter->StyleDisplay();
4069
0
    return ScrollStyles(disp);
4070
0
  }
4071
0
4072
0
  ScrollStyles result = presContext->GetViewportScrollStylesOverride();
4073
0
  nsCOMPtr<nsISupports> container = presContext->GetContainerWeak();
4074
0
  nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(container);
4075
0
  if (scrollable) {
4076
0
    HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_X,
4077
0
                     result.mHorizontal);
4078
0
    HandleScrollPref(scrollable, nsIScrollable::ScrollOrientation_Y,
4079
0
                     result.mVertical);
4080
0
  }
4081
0
  return result;
4082
0
}
4083
4084
nsRect
4085
ScrollFrameHelper::GetScrollRange() const
4086
0
{
4087
0
  return GetScrollRange(mScrollPort.width, mScrollPort.height);
4088
0
}
4089
4090
nsRect
4091
ScrollFrameHelper::GetScrollRange(nscoord aWidth, nscoord aHeight) const
4092
0
{
4093
0
  nsRect range = GetScrolledRect();
4094
0
  range.width = std::max(range.width - aWidth, 0);
4095
0
  range.height = std::max(range.height - aHeight, 0);
4096
0
  return range;
4097
0
}
4098
4099
nsRect
4100
ScrollFrameHelper::GetScrollRangeForClamping() const
4101
0
{
4102
0
  if (!ShouldClampScrollPosition()) {
4103
0
    return nsRect(nscoord_MIN/2, nscoord_MIN/2,
4104
0
                  nscoord_MAX - nscoord_MIN/2, nscoord_MAX - nscoord_MIN/2);
4105
0
  }
4106
0
  nsSize visualViewportSize = GetVisualViewportSize();
4107
0
  return GetScrollRange(visualViewportSize.width, visualViewportSize.height);
4108
0
}
4109
4110
nsSize
4111
ScrollFrameHelper::GetVisualViewportSize() const
4112
0
{
4113
0
  nsIPresShell* presShell = mOuter->PresShell();
4114
0
  if (mIsRoot && presShell->IsVisualViewportSizeSet()) {
4115
0
    return presShell->GetVisualViewportSize();
4116
0
  }
4117
0
  return mScrollPort.Size();
4118
0
}
4119
4120
static void
4121
AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord)
4122
0
{
4123
0
  if (aDelta < 0) {
4124
0
    *aCoord = nscoord_MIN;
4125
0
  } else if (aDelta > 0) {
4126
0
    *aCoord = nscoord_MAX;
4127
0
  }
4128
0
}
4129
4130
/**
4131
 * Calculate lower/upper scrollBy range in given direction.
4132
 * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size
4133
 * @param aPos desired destination in AppUnits
4134
 * @param aNeg/PosTolerance defines relative range distance
4135
 *   below and above of aPos point
4136
 * @param aMultiplier used for conversion of tolerance into appUnis
4137
 */
4138
static void
4139
CalcRangeForScrollBy(int32_t aDelta, nscoord aPos,
4140
                     float aNegTolerance,
4141
                     float aPosTolerance,
4142
                     nscoord aMultiplier,
4143
                     nscoord* aLower, nscoord* aUpper)
4144
0
{
4145
0
  if (!aDelta) {
4146
0
    *aLower = *aUpper = aPos;
4147
0
    return;
4148
0
  }
4149
0
  *aLower = aPos - NSToCoordRound(aMultiplier * (aDelta > 0 ? aNegTolerance : aPosTolerance));
4150
0
  *aUpper = aPos + NSToCoordRound(aMultiplier * (aDelta > 0 ? aPosTolerance : aNegTolerance));
4151
0
}
4152
4153
void
4154
ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
4155
                            nsIScrollableFrame::ScrollUnit aUnit,
4156
                            nsIScrollableFrame::ScrollMode aMode,
4157
                            nsIntPoint* aOverflow,
4158
                            nsAtom *aOrigin,
4159
                            nsIScrollableFrame::ScrollMomentum aMomentum,
4160
                            nsIScrollbarMediator::ScrollSnapMode aSnap)
4161
0
{
4162
0
  // When a smooth scroll is being processed on a frame, mouse wheel and trackpad
4163
0
  // momentum scroll event updates must notcancel the SMOOTH or SMOOTH_MSD
4164
0
  // scroll animations, enabling Javascript that depends on them to be responsive
4165
0
  // without forcing the user to wait for the fling animations to completely stop.
4166
0
  switch (aMomentum) {
4167
0
  case nsIScrollableFrame::NOT_MOMENTUM:
4168
0
    mIgnoreMomentumScroll = false;
4169
0
    break;
4170
0
  case nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT:
4171
0
    if (mIgnoreMomentumScroll) {
4172
0
      return;
4173
0
    }
4174
0
    break;
4175
0
  }
4176
0
4177
0
  if (mAsyncSmoothMSDScroll != nullptr) {
4178
0
    // When CSSOM-View scroll-behavior smooth scrolling is interrupted,
4179
0
    // the scroll is not completed to avoid non-smooth snapping to the
4180
0
    // prior smooth scroll's destination.
4181
0
    mDestination = GetScrollPosition();
4182
0
  }
4183
0
4184
0
  nsSize deltaMultiplier;
4185
0
  float negativeTolerance;
4186
0
  float positiveTolerance;
4187
0
  if (!aOrigin){
4188
0
    aOrigin = nsGkAtoms::other;
4189
0
  }
4190
0
  bool isGenericOrigin = (aOrigin == nsGkAtoms::other);
4191
0
  switch (aUnit) {
4192
0
  case nsIScrollableFrame::DEVICE_PIXELS: {
4193
0
    nscoord appUnitsPerDevPixel =
4194
0
      mOuter->PresContext()->AppUnitsPerDevPixel();
4195
0
    deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
4196
0
    if (isGenericOrigin){
4197
0
      aOrigin = nsGkAtoms::pixels;
4198
0
    }
4199
0
    negativeTolerance = positiveTolerance = 0.5f;
4200
0
    break;
4201
0
  }
4202
0
  case nsIScrollableFrame::LINES: {
4203
0
    deltaMultiplier = GetLineScrollAmount();
4204
0
    if (isGenericOrigin){
4205
0
      aOrigin = nsGkAtoms::lines;
4206
0
    }
4207
0
    negativeTolerance = positiveTolerance = 0.1f;
4208
0
    break;
4209
0
  }
4210
0
  case nsIScrollableFrame::PAGES: {
4211
0
    deltaMultiplier = GetPageScrollAmount();
4212
0
    if (isGenericOrigin){
4213
0
      aOrigin = nsGkAtoms::pages;
4214
0
    }
4215
0
    negativeTolerance = 0.05f;
4216
0
    positiveTolerance = 0;
4217
0
    break;
4218
0
  }
4219
0
  case nsIScrollableFrame::WHOLE: {
4220
0
    nsPoint pos = GetScrollPosition();
4221
0
    AdjustForWholeDelta(aDelta.x, &pos.x);
4222
0
    AdjustForWholeDelta(aDelta.y, &pos.y);
4223
0
    if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
4224
0
      GetSnapPointForDestination(aUnit, mDestination, pos);
4225
0
    }
4226
0
    ScrollTo(pos, aMode);
4227
0
    // 'this' might be destroyed here
4228
0
    if (aOverflow) {
4229
0
      *aOverflow = nsIntPoint(0, 0);
4230
0
    }
4231
0
    return;
4232
0
  }
4233
0
  default:
4234
0
    NS_ERROR("Invalid scroll mode");
4235
0
    return;
4236
0
  }
4237
0
4238
0
  nsPoint newPos = mDestination + nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
4239
0
4240
0
  if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
4241
0
    ScrollStyles styles = GetScrollStylesFromFrame();
4242
0
    if (styles.mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE ||
4243
0
        styles.mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
4244
0
      nscoord appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
4245
0
      deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
4246
0
      negativeTolerance = 0.1f;
4247
0
      positiveTolerance = 0;
4248
0
      nsIScrollableFrame::ScrollUnit snapUnit = aUnit;
4249
0
      if (aOrigin == nsGkAtoms::mouseWheel) {
4250
0
        // When using a clicky scroll wheel, snap point selection works the same
4251
0
        // as keyboard up/down/left/right navigation, but with varying amounts
4252
0
        // of scroll delta.
4253
0
        snapUnit = nsIScrollableFrame::LINES;
4254
0
      }
4255
0
      GetSnapPointForDestination(snapUnit, mDestination, newPos);
4256
0
    }
4257
0
  }
4258
0
4259
0
  // Calculate desired range values.
4260
0
  nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
4261
0
  CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
4262
0
                       deltaMultiplier.width, &rangeLowerX, &rangeUpperX);
4263
0
  CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance,
4264
0
                       deltaMultiplier.height, &rangeLowerY, &rangeUpperY);
4265
0
  nsRect range(rangeLowerX,
4266
0
               rangeLowerY,
4267
0
               rangeUpperX - rangeLowerX,
4268
0
               rangeUpperY - rangeLowerY);
4269
0
  AutoWeakFrame weakFrame(mOuter);
4270
0
  ScrollToWithOrigin(newPos, aMode, aOrigin, &range);
4271
0
  if (!weakFrame.IsAlive()) {
4272
0
    return;
4273
0
  }
4274
0
4275
0
  if (aOverflow) {
4276
0
    nsPoint clampAmount = newPos - mDestination;
4277
0
    float appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
4278
0
    *aOverflow = nsIntPoint(
4279
0
        NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
4280
0
        NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
4281
0
  }
4282
0
4283
0
  if (aUnit == nsIScrollableFrame::DEVICE_PIXELS &&
4284
0
      !nsLayoutUtils::AsyncPanZoomEnabled(mOuter)) {
4285
0
    // When APZ is disabled, we must track the velocity
4286
0
    // on the main thread; otherwise, the APZC will manage this.
4287
0
    mVelocityQueue.Sample(GetScrollPosition());
4288
0
  }
4289
0
}
4290
4291
void
4292
ScrollFrameHelper::ScrollSnap(nsIScrollableFrame::ScrollMode aMode)
4293
0
{
4294
0
  float flingSensitivity = gfxPrefs::ScrollSnapPredictionSensitivity();
4295
0
  int maxVelocity = gfxPrefs::ScrollSnapPredictionMaxVelocity();
4296
0
  maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
4297
0
  int maxOffset = maxVelocity * flingSensitivity;
4298
0
  nsPoint velocity = mVelocityQueue.GetVelocity();
4299
0
  // Multiply each component individually to avoid integer multiply
4300
0
  nsPoint predictedOffset = nsPoint(velocity.x * flingSensitivity,
4301
0
                                    velocity.y * flingSensitivity);
4302
0
  predictedOffset.Clamp(maxOffset);
4303
0
  nsPoint pos = GetScrollPosition();
4304
0
  nsPoint destinationPos = pos + predictedOffset;
4305
0
  ScrollSnap(destinationPos, aMode);
4306
0
}
4307
4308
void
4309
ScrollFrameHelper::ScrollSnap(const nsPoint &aDestination,
4310
                              nsIScrollableFrame::ScrollMode aMode)
4311
0
{
4312
0
  nsRect scrollRange = GetScrollRangeForClamping();
4313
0
  nsPoint pos = GetScrollPosition();
4314
0
  nsPoint snapDestination = scrollRange.ClampPoint(aDestination);
4315
0
  if (GetSnapPointForDestination(nsIScrollableFrame::DEVICE_PIXELS,
4316
0
                                                 pos,
4317
0
                                                 snapDestination)) {
4318
0
    ScrollTo(snapDestination, aMode);
4319
0
  }
4320
0
}
4321
4322
nsSize
4323
ScrollFrameHelper::GetLineScrollAmount() const
4324
0
{
4325
0
  RefPtr<nsFontMetrics> fm =
4326
0
    nsLayoutUtils::GetInflatedFontMetricsForFrame(mOuter);
4327
0
  NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
4328
0
  static nscoord sMinLineScrollAmountInPixels = -1;
4329
0
  if (sMinLineScrollAmountInPixels < 0) {
4330
0
    Preferences::AddIntVarCache(&sMinLineScrollAmountInPixels,
4331
0
                                "mousewheel.min_line_scroll_amount", 1);
4332
0
  }
4333
0
  int32_t appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
4334
0
  nscoord minScrollAmountInAppUnits =
4335
0
    std::max(1, sMinLineScrollAmountInPixels) * appUnitsPerDevPixel;
4336
0
  nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0;
4337
0
  nscoord verticalAmount = fm ? fm->MaxHeight() : 0;
4338
0
  return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits),
4339
0
                std::max(verticalAmount, minScrollAmountInAppUnits));
4340
0
}
4341
4342
/**
4343
 * Compute the scrollport size excluding any fixed-pos headers and
4344
 * footers. A header or footer is an box that spans that entire width
4345
 * of the viewport and touches the top (or bottom, respectively) of the
4346
 * viewport. We also want to consider fixed elements that stack or overlap
4347
 * to effectively create a larger header or footer. Headers and footers that
4348
 * cover more than a third of the the viewport are ignored since they
4349
 * probably aren't true headers and footers and we don't want to restrict
4350
 * scrolling too much in such cases. This is a bit conservative --- some
4351
 * pages use elements as headers or footers that don't span the entire width
4352
 * of the viewport --- but it should be a good start.
4353
 */
4354
struct TopAndBottom
4355
{
4356
0
  TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {}
4357
4358
  nscoord top, bottom;
4359
};
4360
struct TopComparator
4361
{
4362
0
  bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
4363
0
    return A.top == B.top;
4364
0
  }
4365
0
  bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
4366
0
    return A.top < B.top;
4367
0
  }
4368
};
4369
struct ReverseBottomComparator
4370
{
4371
0
  bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
4372
0
    return A.bottom == B.bottom;
4373
0
  }
4374
0
  bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
4375
0
    return A.bottom > B.bottom;
4376
0
  }
4377
};
4378
static nsSize
4379
GetScrollPortSizeExcludingHeadersAndFooters(nsIFrame* aViewportFrame,
4380
                                            const nsRect& aScrollPort)
4381
0
{
4382
0
  AutoTArray<TopAndBottom, 50> list;
4383
0
  nsFrameList fixedFrames = aViewportFrame->GetChildList(nsIFrame::kFixedList);
4384
0
  for (nsFrameList::Enumerator iterator(fixedFrames); !iterator.AtEnd();
4385
0
       iterator.Next()) {
4386
0
    nsIFrame* f = iterator.get();
4387
0
    nsRect r = f->GetRectRelativeToSelf();
4388
0
    r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, aViewportFrame);
4389
0
    r = r.Intersect(aScrollPort);
4390
0
    if ((r.width >= aScrollPort.width / 2 ||
4391
0
         r.width >= NSIntPixelsToAppUnits(800, AppUnitsPerCSSPixel())) &&
4392
0
        r.height <= aScrollPort.height/3) {
4393
0
      list.AppendElement(TopAndBottom(r.y, r.YMost()));
4394
0
    }
4395
0
  }
4396
0
4397
0
  list.Sort(TopComparator());
4398
0
  nscoord headerBottom = 0;
4399
0
  for (uint32_t i = 0; i < list.Length(); ++i) {
4400
0
    if (list[i].top <= headerBottom) {
4401
0
      headerBottom = std::max(headerBottom, list[i].bottom);
4402
0
    }
4403
0
  }
4404
0
4405
0
  list.Sort(ReverseBottomComparator());
4406
0
  nscoord footerTop = aScrollPort.height;
4407
0
  for (uint32_t i = 0; i < list.Length(); ++i) {
4408
0
    if (list[i].bottom >= footerTop) {
4409
0
      footerTop = std::min(footerTop, list[i].top);
4410
0
    }
4411
0
  }
4412
0
4413
0
  headerBottom = std::min(aScrollPort.height/3, headerBottom);
4414
0
  footerTop = std::max(aScrollPort.height - aScrollPort.height/3, footerTop);
4415
0
4416
0
  return nsSize(aScrollPort.width, footerTop - headerBottom);
4417
0
}
4418
4419
nsSize
4420
ScrollFrameHelper::GetPageScrollAmount() const
4421
0
{
4422
0
  nsSize lineScrollAmount = GetLineScrollAmount();
4423
0
  nsSize effectiveScrollPortSize;
4424
0
  if (mIsRoot) {
4425
0
    // Reduce effective scrollport height by the height of any fixed-pos
4426
0
    // headers or footers
4427
0
    nsIFrame* root = mOuter->PresShell()->GetRootFrame();
4428
0
    effectiveScrollPortSize =
4429
0
      GetScrollPortSizeExcludingHeadersAndFooters(root, mScrollPort);
4430
0
  } else {
4431
0
    effectiveScrollPortSize = mScrollPort.Size();
4432
0
  }
4433
0
  // The page increment is the size of the page, minus the smaller of
4434
0
  // 10% of the size or 2 lines.
4435
0
  return nsSize(
4436
0
    effectiveScrollPortSize.width -
4437
0
      std::min(effectiveScrollPortSize.width/10, 2*lineScrollAmount.width),
4438
0
    effectiveScrollPortSize.height -
4439
0
      std::min(effectiveScrollPortSize.height/10, 2*lineScrollAmount.height));
4440
0
}
4441
4442
  /**
4443
   * this code is resposible for restoring the scroll position back to some
4444
   * saved position. if the user has not moved the scroll position manually
4445
   * we keep scrolling down until we get to our original position. keep in
4446
   * mind that content could incrementally be coming in. we only want to stop
4447
   * when we reach our new position.
4448
   */
4449
void
4450
ScrollFrameHelper::ScrollToRestoredPosition()
4451
0
{
4452
0
  if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
4453
0
    return;
4454
0
  }
4455
0
  // make sure our scroll position did not change for where we last put
4456
0
  // it. if it does then the user must have moved it, and we no longer
4457
0
  // need to restore.
4458
0
  //
4459
0
  // In the RTL case, we check whether the scroll position changed using the
4460
0
  // logical scroll position, but we scroll to the physical scroll position in
4461
0
  // all cases
4462
0
4463
0
  // if we didn't move, we still need to restore
4464
0
  if (GetLogicalScrollPosition() == mLastPos) {
4465
0
    // if our desired position is different to the scroll position, scroll.
4466
0
    // remember that we could be incrementally loading so we may enter
4467
0
    // and scroll many times.
4468
0
    if (mRestorePos != mLastPos /* GetLogicalScrollPosition() */) {
4469
0
      LoadingState state = GetPageLoadingState();
4470
0
      if (state == LoadingState::Stopped && !NS_SUBTREE_DIRTY(mOuter)) {
4471
0
        return;
4472
0
      }
4473
0
      nsPoint scrollToPos = mRestorePos;
4474
0
      if (!IsPhysicalLTR()) {
4475
0
        // convert from logical to physical scroll position
4476
0
        scrollToPos.x = mScrollPort.x -
4477
0
          (mScrollPort.XMost() - scrollToPos.x - mScrolledFrame->GetRect().width);
4478
0
      }
4479
0
      AutoWeakFrame weakFrame(mOuter);
4480
0
      // It's very important to pass nsGkAtoms::restore here, so
4481
0
      // ScrollToWithOrigin won't clear out mRestorePos.
4482
0
      ScrollToWithOrigin(scrollToPos, nsIScrollableFrame::INSTANT,
4483
0
                         nsGkAtoms::restore, nullptr);
4484
0
      if (!weakFrame.IsAlive()) {
4485
0
        return;
4486
0
      }
4487
0
      if (state == LoadingState::Loading || NS_SUBTREE_DIRTY(mOuter)) {
4488
0
        // If we're trying to do a history scroll restore, then we want to
4489
0
        // keep trying this until we succeed, because the page can be loading
4490
0
        // incrementally. So re-get the scroll position for the next iteration,
4491
0
        // it might not be exactly equal to mRestorePos due to rounding and
4492
0
        // clamping.
4493
0
        mLastPos = GetLogicalScrollPosition();
4494
0
        return;
4495
0
      }
4496
0
    }
4497
0
    // If we get here, either we reached the desired position (mLastPos ==
4498
0
    // mRestorePos) or we're not trying to do a history scroll restore, so
4499
0
    // we can stop after the scroll attempt above.
4500
0
    mRestorePos.y = -1;
4501
0
    mLastPos.x = -1;
4502
0
    mLastPos.y = -1;
4503
0
  } else {
4504
0
    // user moved the position, so we won't need to restore
4505
0
    mLastPos.x = -1;
4506
0
    mLastPos.y = -1;
4507
0
  }
4508
0
}
4509
4510
auto
4511
ScrollFrameHelper::GetPageLoadingState() -> LoadingState
4512
0
{
4513
0
  bool loadCompleted = false, stopped = false;
4514
0
  nsCOMPtr<nsIDocShell> ds = mOuter->GetContent()->GetComposedDoc()->GetDocShell();
4515
0
  if (ds) {
4516
0
    nsCOMPtr<nsIContentViewer> cv;
4517
0
    ds->GetContentViewer(getter_AddRefs(cv));
4518
0
    cv->GetLoadCompleted(&loadCompleted);
4519
0
    cv->GetIsStopped(&stopped);
4520
0
  }
4521
0
  return loadCompleted ? (stopped ? LoadingState::Stopped : LoadingState::Loaded)
4522
0
                       : LoadingState::Loading;
4523
0
}
4524
4525
nsresult
4526
ScrollFrameHelper::FireScrollPortEvent()
4527
0
{
4528
0
  mAsyncScrollPortEvent.Forget();
4529
0
4530
0
  // Keep this in sync with PostOverflowEvent().
4531
0
  nsSize scrollportSize = mScrollPort.Size();
4532
0
  nsSize childSize = GetScrolledRect().Size();
4533
0
4534
0
  bool newVerticalOverflow = childSize.height > scrollportSize.height;
4535
0
  bool vertChanged = mVerticalOverflow != newVerticalOverflow;
4536
0
4537
0
  bool newHorizontalOverflow = childSize.width > scrollportSize.width;
4538
0
  bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
4539
0
4540
0
  if (!vertChanged && !horizChanged) {
4541
0
    return NS_OK;
4542
0
  }
4543
0
4544
0
  // If both either overflowed or underflowed then we dispatch only one
4545
0
  // DOM event.
4546
0
  bool both = vertChanged && horizChanged &&
4547
0
                newVerticalOverflow == newHorizontalOverflow;
4548
0
  InternalScrollPortEvent::OrientType orient;
4549
0
  if (both) {
4550
0
    orient = InternalScrollPortEvent::eBoth;
4551
0
    mHorizontalOverflow = newHorizontalOverflow;
4552
0
    mVerticalOverflow = newVerticalOverflow;
4553
0
  } else if (vertChanged) {
4554
0
    orient = InternalScrollPortEvent::eVertical;
4555
0
    mVerticalOverflow = newVerticalOverflow;
4556
0
    if (horizChanged) {
4557
0
      // We need to dispatch a separate horizontal DOM event. Do that the next
4558
0
      // time around since dispatching the vertical DOM event might destroy
4559
0
      // the frame.
4560
0
      PostOverflowEvent();
4561
0
    }
4562
0
  } else {
4563
0
    orient = InternalScrollPortEvent::eHorizontal;
4564
0
    mHorizontalOverflow = newHorizontalOverflow;
4565
0
  }
4566
0
4567
0
  InternalScrollPortEvent event(true,
4568
0
    (orient == InternalScrollPortEvent::eHorizontal ? mHorizontalOverflow :
4569
0
                                                      mVerticalOverflow) ?
4570
0
    eScrollPortOverflow : eScrollPortUnderflow, nullptr);
4571
0
  event.mOrient = orient;
4572
0
  return EventDispatcher::Dispatch(mOuter->GetContent(),
4573
0
                                   mOuter->PresContext(), &event);
4574
0
}
4575
4576
void
4577
ScrollFrameHelper::PostScrollEndEvent()
4578
0
{
4579
0
  if (mScrollEndEvent) {
4580
0
    return;
4581
0
  }
4582
0
4583
0
  // The ScrollEndEvent constructor registers itself with the refresh driver.
4584
0
  mScrollEndEvent = new ScrollEndEvent(this);
4585
0
}
4586
4587
void
4588
ScrollFrameHelper::FireScrollEndEvent()
4589
0
{
4590
0
  MOZ_ASSERT(mOuter->GetContent());
4591
0
  MOZ_ASSERT(mScrollEndEvent);
4592
0
  mScrollEndEvent->Revoke();
4593
0
  mScrollEndEvent = nullptr;
4594
0
4595
0
  nsContentUtils::DispatchEventOnlyToChrome(mOuter->GetContent()->OwnerDoc(),
4596
0
                                            mOuter->GetContent(),
4597
0
                                            NS_LITERAL_STRING("scrollend"),
4598
0
                                            CanBubble::eYes,
4599
0
                                            Cancelable::eNo);
4600
0
}
4601
4602
void
4603
ScrollFrameHelper::ReloadChildFrames()
4604
0
{
4605
0
  mScrolledFrame = nullptr;
4606
0
  mHScrollbarBox = nullptr;
4607
0
  mVScrollbarBox = nullptr;
4608
0
  mScrollCornerBox = nullptr;
4609
0
  mResizerBox = nullptr;
4610
0
4611
0
  for (nsIFrame* frame : mOuter->PrincipalChildList()) {
4612
0
    nsIContent* content = frame->GetContent();
4613
0
    if (content == mOuter->GetContent()) {
4614
0
      NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
4615
0
      mScrolledFrame = frame;
4616
0
    } else {
4617
0
      nsAutoString value;
4618
0
      if (content->IsElement()) {
4619
0
        content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::orient,
4620
0
                                      value);
4621
0
      }
4622
0
      if (!value.IsEmpty()) {
4623
0
        // probably a scrollbar then
4624
0
        if (value.LowerCaseEqualsLiteral("horizontal")) {
4625
0
          NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?");
4626
0
          mHScrollbarBox = frame;
4627
0
        } else {
4628
0
          NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
4629
0
          mVScrollbarBox = frame;
4630
0
        }
4631
0
      } else if (content->IsXULElement(nsGkAtoms::resizer)) {
4632
0
        NS_ASSERTION(!mResizerBox, "Found multiple resizers");
4633
0
        mResizerBox = frame;
4634
0
      } else if (content->IsXULElement(nsGkAtoms::scrollcorner)) {
4635
0
        // probably a scrollcorner
4636
0
        NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
4637
0
        mScrollCornerBox = frame;
4638
0
      }
4639
0
    }
4640
0
  }
4641
0
}
4642
4643
bool
4644
ScrollFrameHelper::IsForTextControlWithNoScrollbars() const
4645
0
{
4646
0
  nsIFrame* parent = mOuter->GetParent();
4647
0
  // The anonymous <div> used by <inputs> never gets scrollbars.
4648
0
  nsITextControlFrame* textFrame = do_QueryFrame(parent);
4649
0
  if (textFrame) {
4650
0
    // Make sure we are not a text area.
4651
0
    HTMLTextAreaElement* textAreaElement =
4652
0
      HTMLTextAreaElement::FromNode(parent->GetContent());
4653
0
    if (!textAreaElement) {
4654
0
      return true;
4655
0
    }
4656
0
  }
4657
0
  return false;
4658
0
}
4659
4660
nsresult
4661
ScrollFrameHelper::CreateAnonymousContent(
4662
  nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements)
4663
0
{
4664
0
  nsPresContext* presContext = mOuter->PresContext();
4665
0
4666
0
  // Don't create scrollbars if we're an SVG document being used as an image,
4667
0
  // or if we're printing/print previewing.
4668
0
  // (In the printing case, we allow scrollbars if this is the child of the
4669
0
  // viewport & paginated scrolling is enabled, because then we must be the
4670
0
  // scroll frame for the print preview window, & that does need scrollbars.)
4671
0
  if (presContext->Document()->IsBeingUsedAsImage() ||
4672
0
      (!presContext->IsDynamic() &&
4673
0
       !(mIsRoot && presContext->HasPaginatedScrolling()))) {
4674
0
    mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
4675
0
    return NS_OK;
4676
0
  }
4677
0
4678
0
  // Check if the frame is resizable. Note:
4679
0
  // "The effect of the resize property on generated content is undefined.
4680
0
  //  Implementations should not apply the resize property to generated
4681
0
  //  content." [1]
4682
0
  // For info on what is generated content, see [2].
4683
0
  // [1]: https://drafts.csswg.org/css-ui/#resize
4684
0
  // [2]: https://www.w3.org/TR/CSS2/generate.html#content
4685
0
  int8_t resizeStyle = mOuter->StyleDisplay()->mResize;
4686
0
  bool isResizable = resizeStyle != NS_STYLE_RESIZE_NONE &&
4687
0
                     !mOuter->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT);
4688
0
4689
0
  nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
4690
0
4691
0
  // If we're the scrollframe for the root, then we want to construct
4692
0
  // our scrollbar frames no matter what.  That way later dynamic
4693
0
  // changes to propagated overflow styles will show or hide
4694
0
  // scrollbars on the viewport without requiring frame reconstruction
4695
0
  // of the viewport (good!).
4696
0
  bool canHaveHorizontal;
4697
0
  bool canHaveVertical;
4698
0
  if (!mIsRoot) {
4699
0
    if (mOuter->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::None) {
4700
0
      // If scrollbar-width is none, don't generate scrollbars.
4701
0
      canHaveHorizontal = false;
4702
0
      canHaveVertical = false;
4703
0
    } else {
4704
0
      ScrollStyles styles = scrollable->GetScrollStyles();
4705
0
      canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
4706
0
      canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
4707
0
    }
4708
0
    if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
4709
0
      // Nothing to do.
4710
0
      return NS_OK;
4711
0
    }
4712
0
  } else {
4713
0
    canHaveHorizontal = true;
4714
0
    canHaveVertical = true;
4715
0
  }
4716
0
4717
0
  if (IsForTextControlWithNoScrollbars()) {
4718
0
    mNeverHasVerticalScrollbar = mNeverHasHorizontalScrollbar = true;
4719
0
    return NS_OK;
4720
0
  }
4721
0
4722
0
  nsNodeInfoManager* nodeInfoManager = presContext->Document()->NodeInfoManager();
4723
0
  RefPtr<NodeInfo> nodeInfo =
4724
0
    nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbar, nullptr,
4725
0
                                 kNameSpaceID_XUL,
4726
0
                                 nsINode::ELEMENT_NODE);
4727
0
  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
4728
0
4729
0
  if (canHaveHorizontal) {
4730
0
    RefPtr<NodeInfo> ni = nodeInfo;
4731
0
    NS_TrustedNewXULElement(getter_AddRefs(mHScrollbarContent), ni.forget());
4732
#ifdef DEBUG
4733
    // Scrollbars can get restyled by theme changes.  Whether such a restyle
4734
    // will actually reconstruct them correctly if it involves a frame
4735
    // reconstruct... I don't know.  :(
4736
    mHScrollbarContent->SetProperty(nsGkAtoms::restylableAnonymousNode,
4737
                                    reinterpret_cast<void*>(true));
4738
#endif // DEBUG
4739
4740
0
    mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
4741
0
                                NS_LITERAL_STRING("horizontal"), false);
4742
0
    mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
4743
0
                                NS_LITERAL_STRING("always"), false);
4744
0
    if (mIsRoot) {
4745
0
      mHScrollbarContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
4746
0
                                      reinterpret_cast<void*>(true));
4747
0
4748
0
      mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
4749
0
                                  NS_LITERAL_STRING("true"), false);
4750
0
    }
4751
0
    if (!aElements.AppendElement(mHScrollbarContent))
4752
0
      return NS_ERROR_OUT_OF_MEMORY;
4753
0
  }
4754
0
4755
0
  if (canHaveVertical) {
4756
0
    RefPtr<NodeInfo> ni = nodeInfo;
4757
0
    NS_TrustedNewXULElement(getter_AddRefs(mVScrollbarContent), ni.forget());
4758
#ifdef DEBUG
4759
    // Scrollbars can get restyled by theme changes.  Whether such a restyle
4760
    // will actually reconstruct them correctly if it involves a frame
4761
    // reconstruct... I don't know.  :(
4762
    mVScrollbarContent->SetProperty(nsGkAtoms::restylableAnonymousNode,
4763
                                    reinterpret_cast<void*>(true));
4764
#endif // DEBUG
4765
4766
0
    mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
4767
0
                                NS_LITERAL_STRING("vertical"), false);
4768
0
    mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
4769
0
                                NS_LITERAL_STRING("always"), false);
4770
0
    if (mIsRoot) {
4771
0
      mVScrollbarContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
4772
0
                                      reinterpret_cast<void*>(true));
4773
0
      mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
4774
0
                                  NS_LITERAL_STRING("true"), false);
4775
0
    }
4776
0
    if (!aElements.AppendElement(mVScrollbarContent))
4777
0
      return NS_ERROR_OUT_OF_MEMORY;
4778
0
  }
4779
0
4780
0
  if (isResizable) {
4781
0
    RefPtr<NodeInfo> nodeInfo;
4782
0
    nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nullptr,
4783
0
                                            kNameSpaceID_XUL,
4784
0
                                            nsINode::ELEMENT_NODE);
4785
0
    NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
4786
0
4787
0
    NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
4788
0
4789
0
    nsAutoString dir;
4790
0
    switch (resizeStyle) {
4791
0
      case NS_STYLE_RESIZE_HORIZONTAL:
4792
0
        if (IsScrollbarOnRight()) {
4793
0
          dir.AssignLiteral("right");
4794
0
        }
4795
0
        else {
4796
0
          dir.AssignLiteral("left");
4797
0
        }
4798
0
        break;
4799
0
      case NS_STYLE_RESIZE_VERTICAL:
4800
0
        dir.AssignLiteral("bottom");
4801
0
        if (!IsScrollbarOnRight()) {
4802
0
          mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::flip, EmptyString(), false);
4803
0
        }
4804
0
        break;
4805
0
      case NS_STYLE_RESIZE_BOTH:
4806
0
        if (IsScrollbarOnRight()) {
4807
0
          dir.AssignLiteral("bottomright");
4808
0
        }
4809
0
        else {
4810
0
          dir.AssignLiteral("bottomleft");
4811
0
        }
4812
0
        break;
4813
0
      default:
4814
0
        NS_WARNING("only resizable types should have resizers");
4815
0
    }
4816
0
    mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
4817
0
4818
0
    if (mIsRoot) {
4819
0
      mResizerContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
4820
0
                                   reinterpret_cast<void*>(true));
4821
0
4822
0
      Element* browserRoot = GetBrowserRoot(mOuter->GetContent());
4823
0
      mCollapsedResizer = !(browserRoot &&
4824
0
                            browserRoot->HasAttr(kNameSpaceID_None, nsGkAtoms::showresizer));
4825
0
    } else {
4826
0
      mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
4827
0
                                    NS_LITERAL_STRING("_parent"), false);
4828
0
    }
4829
0
4830
0
    mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::clickthrough,
4831
0
                                  NS_LITERAL_STRING("always"), false);
4832
0
4833
0
    if (!aElements.AppendElement(mResizerContent))
4834
0
      return NS_ERROR_OUT_OF_MEMORY;
4835
0
  }
4836
0
4837
0
  if (canHaveHorizontal && canHaveVertical) {
4838
0
    nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr,
4839
0
                                            kNameSpaceID_XUL,
4840
0
                                            nsINode::ELEMENT_NODE);
4841
0
    NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo.forget());
4842
0
    if (mIsRoot) {
4843
0
      mScrollCornerContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
4844
0
                                        reinterpret_cast<void*>(true));
4845
0
    }
4846
0
    if (!aElements.AppendElement(mScrollCornerContent))
4847
0
      return NS_ERROR_OUT_OF_MEMORY;
4848
0
  }
4849
0
4850
0
  return NS_OK;
4851
0
}
4852
4853
void
4854
ScrollFrameHelper::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
4855
                                                uint32_t aFilter)
4856
0
{
4857
0
  if (mHScrollbarContent) {
4858
0
    aElements.AppendElement(mHScrollbarContent);
4859
0
  }
4860
0
4861
0
  if (mVScrollbarContent) {
4862
0
    aElements.AppendElement(mVScrollbarContent);
4863
0
  }
4864
0
4865
0
  if (mScrollCornerContent) {
4866
0
    aElements.AppendElement(mScrollCornerContent);
4867
0
  }
4868
0
4869
0
  if (mResizerContent) {
4870
0
    aElements.AppendElement(mResizerContent);
4871
0
  }
4872
0
}
4873
4874
void
4875
ScrollFrameHelper::Destroy(PostDestroyData& aPostDestroyData)
4876
0
{
4877
0
  if (mScrollbarActivity) {
4878
0
    mScrollbarActivity->Destroy();
4879
0
    mScrollbarActivity = nullptr;
4880
0
  }
4881
0
4882
0
  // Unbind the content created in CreateAnonymousContent later...
4883
0
  aPostDestroyData.AddAnonymousContent(mHScrollbarContent.forget());
4884
0
  aPostDestroyData.AddAnonymousContent(mVScrollbarContent.forget());
4885
0
  aPostDestroyData.AddAnonymousContent(mScrollCornerContent.forget());
4886
0
  aPostDestroyData.AddAnonymousContent(mResizerContent.forget());
4887
0
4888
0
  if (mPostedReflowCallback) {
4889
0
    mOuter->PresShell()->CancelReflowCallback(this);
4890
0
    mPostedReflowCallback = false;
4891
0
  }
4892
0
4893
0
  if (mDisplayPortExpiryTimer) {
4894
0
    mDisplayPortExpiryTimer->Cancel();
4895
0
    mDisplayPortExpiryTimer = nullptr;
4896
0
  }
4897
0
  if (mActivityExpirationState.IsTracked()) {
4898
0
    gScrollFrameActivityTracker->RemoveObject(this);
4899
0
  }
4900
0
  if (gScrollFrameActivityTracker &&
4901
0
      gScrollFrameActivityTracker->IsEmpty()) {
4902
0
    delete gScrollFrameActivityTracker;
4903
0
    gScrollFrameActivityTracker = nullptr;
4904
0
  }
4905
0
4906
0
  if (mScrollActivityTimer) {
4907
0
    mScrollActivityTimer->Cancel();
4908
0
    mScrollActivityTimer = nullptr;
4909
0
  }
4910
0
  if (mAsyncScroll) {
4911
0
    mAsyncScroll->RemoveObserver();
4912
0
  }
4913
0
  if (mAsyncSmoothMSDScroll) {
4914
0
    mAsyncSmoothMSDScroll->RemoveObserver();
4915
0
  }
4916
0
}
4917
4918
/**
4919
 * Called when we want to update the scrollbar position, either because scrolling happened
4920
 * or the user moved the scrollbar position and we need to undo that (e.g., when the user
4921
 * clicks to scroll and we're using smooth scrolling, so we need to put the thumb back
4922
 * to its initial position for the start of the smooth sequence).
4923
 */
4924
void
4925
ScrollFrameHelper::UpdateScrollbarPosition()
4926
0
{
4927
0
  AutoWeakFrame weakFrame(mOuter);
4928
0
  mFrameIsUpdatingScrollbar = true;
4929
0
4930
0
  nsPoint pt = GetScrollPosition();
4931
0
  if (mVScrollbarBox) {
4932
0
    SetCoordAttribute(mVScrollbarBox->GetContent()->AsElement(),
4933
0
                      nsGkAtoms::curpos, pt.y - GetScrolledRect().y);
4934
0
    if (!weakFrame.IsAlive()) {
4935
0
      return;
4936
0
    }
4937
0
  }
4938
0
  if (mHScrollbarBox) {
4939
0
    SetCoordAttribute(mHScrollbarBox->GetContent()->AsElement(), nsGkAtoms::curpos,
4940
0
                      pt.x - GetScrolledRect().x);
4941
0
    if (!weakFrame.IsAlive()) {
4942
0
      return;
4943
0
    }
4944
0
  }
4945
0
4946
0
  mFrameIsUpdatingScrollbar = false;
4947
0
}
4948
4949
void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent,
4950
                                               bool aDoScroll)
4951
0
{
4952
0
  NS_ASSERTION(aContent, "aContent must not be null");
4953
0
  NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
4954
0
               (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
4955
0
               "unexpected child");
4956
0
  MOZ_ASSERT(aContent->IsElement());
4957
0
4958
0
  // Attribute changes on the scrollbars happen in one of three ways:
4959
0
  // 1) The scrollbar changed the attribute in response to some user event
4960
0
  // 2) We changed the attribute in response to a ScrollPositionDidChange
4961
0
  // callback from the scrolling view
4962
0
  // 3) We changed the attribute to adjust the scrollbars for the start
4963
0
  // of a smooth scroll operation
4964
0
  //
4965
0
  // In cases 2 and 3 we do not need to scroll because we're just
4966
0
  // updating our scrollbar.
4967
0
  if (mFrameIsUpdatingScrollbar)
4968
0
    return;
4969
0
4970
0
  nsRect scrolledRect = GetScrolledRect();
4971
0
4972
0
  nsPoint current = GetScrollPosition() - scrolledRect.TopLeft();
4973
0
  nsPoint dest;
4974
0
  nsRect allowedRange;
4975
0
  dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x,
4976
0
                             &allowedRange.x, &allowedRange.width);
4977
0
  dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y,
4978
0
                             &allowedRange.y, &allowedRange.height);
4979
0
  current += scrolledRect.TopLeft();
4980
0
  dest += scrolledRect.TopLeft();
4981
0
  allowedRange += scrolledRect.TopLeft();
4982
0
4983
0
  // Don't try to scroll if we're already at an acceptable place.
4984
0
  // Don't call Contains here since Contains returns false when the point is
4985
0
  // on the bottom or right edge of the rectangle.
4986
0
  if (allowedRange.ClampPoint(current) == current) {
4987
0
    return;
4988
0
  }
4989
0
4990
0
  if (mScrollbarActivity) {
4991
0
    RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
4992
0
    scrollbarActivity->ActivityOccurred();
4993
0
  }
4994
0
4995
0
  const bool isSmooth =
4996
0
    aContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::smooth);
4997
0
  if (isSmooth) {
4998
0
    // Make sure an attribute-setting callback occurs even if the view
4999
0
    // didn't actually move yet.  We need to make sure other listeners
5000
0
    // see that the scroll position is not (yet) what they thought it
5001
0
    // was.
5002
0
    AutoWeakFrame weakFrame(mOuter);
5003
0
    UpdateScrollbarPosition();
5004
0
    if (!weakFrame.IsAlive()) {
5005
0
      return;
5006
0
    }
5007
0
  }
5008
0
5009
0
  if (aDoScroll) {
5010
0
    ScrollToWithOrigin(dest,
5011
0
                       isSmooth ? nsIScrollableFrame::SMOOTH : nsIScrollableFrame::INSTANT,
5012
0
                       nsGkAtoms::scrollbars, &allowedRange);
5013
0
  }
5014
0
  // 'this' might be destroyed here
5015
0
}
5016
5017
/* ============= Scroll events ========== */
5018
5019
ScrollFrameHelper::ScrollEvent::ScrollEvent(ScrollFrameHelper* aHelper)
5020
  : Runnable("ScrollFrameHelper::ScrollEvent")
5021
  , mHelper(aHelper)
5022
0
{
5023
0
  mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this);
5024
0
}
5025
5026
NS_IMETHODIMP
5027
ScrollFrameHelper::ScrollEvent::Run()
5028
0
{
5029
0
  if (mHelper) {
5030
0
    mHelper->FireScrollEvent();
5031
0
  }
5032
0
  return NS_OK;
5033
0
}
5034
5035
ScrollFrameHelper::ScrollEndEvent::ScrollEndEvent(ScrollFrameHelper* aHelper)
5036
  : Runnable("ScrollFrameHelper::ScrollEndEvent")
5037
  , mHelper(aHelper)
5038
0
{
5039
0
  mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this);
5040
0
}
5041
5042
NS_IMETHODIMP
5043
ScrollFrameHelper::ScrollEndEvent::Run()
5044
0
{
5045
0
  if (mHelper) {
5046
0
    mHelper->FireScrollEndEvent();
5047
0
  }
5048
0
  return NS_OK;
5049
0
}
5050
5051
void
5052
ScrollFrameHelper::FireScrollEvent()
5053
0
{
5054
0
  AUTO_PROFILER_TRACING("Paint", "FireScrollEvent");
5055
0
  MOZ_ASSERT(mScrollEvent);
5056
0
  mScrollEvent->Revoke();
5057
0
  mScrollEvent = nullptr;
5058
0
5059
0
  ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter);
5060
0
  WidgetGUIEvent event(true, eScroll, nullptr);
5061
0
  nsEventStatus status = nsEventStatus_eIgnore;
5062
0
  nsIContent* content = mOuter->GetContent();
5063
0
  nsPresContext* prescontext = mOuter->PresContext();
5064
0
  // Fire viewport scroll events at the document (where they
5065
0
  // will bubble to the window)
5066
0
  mozilla::layers::ScrollLinkedEffectDetector detector(content->GetComposedDoc());
5067
0
  if (mIsRoot) {
5068
0
    nsIDocument* doc = content->GetUncomposedDoc();
5069
0
    if (doc) {
5070
0
      EventDispatcher::Dispatch(doc, prescontext, &event, nullptr,  &status);
5071
0
    }
5072
0
  } else {
5073
0
    // scroll events fired at elements don't bubble (although scroll events
5074
0
    // fired at documents do, to the window)
5075
0
    event.mFlags.mBubbles = false;
5076
0
    EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status);
5077
0
  }
5078
0
  ActiveLayerTracker::SetCurrentScrollHandlerFrame(nullptr);
5079
0
}
5080
5081
void
5082
ScrollFrameHelper::PostScrollEvent()
5083
0
{
5084
0
  if (mScrollEvent) {
5085
0
    return;
5086
0
  }
5087
0
5088
0
  // The ScrollEvent constructor registers itself with the refresh driver.
5089
0
  mScrollEvent = new ScrollEvent(this);
5090
0
}
5091
5092
NS_IMETHODIMP
5093
ScrollFrameHelper::AsyncScrollPortEvent::Run()
5094
0
{
5095
0
  if (mHelper) {
5096
0
    mHelper->mOuter->PresContext()->Document()->
5097
0
      FlushPendingNotifications(FlushType::InterruptibleLayout);
5098
0
  }
5099
0
  return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
5100
0
}
5101
5102
bool
5103
nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
5104
0
{
5105
0
  if (!mHelper.mHScrollbarBox) {
5106
0
    return true;
5107
0
  }
5108
0
5109
0
  return AddRemoveScrollbar(aState, aOnBottom, true, true);
5110
0
}
5111
5112
bool
5113
nsXULScrollFrame::AddVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
5114
0
{
5115
0
  if (!mHelper.mVScrollbarBox) {
5116
0
    return true;
5117
0
  }
5118
0
5119
0
  return AddRemoveScrollbar(aState, aOnRight, false, true);
5120
0
}
5121
5122
void
5123
nsXULScrollFrame::RemoveHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
5124
0
{
5125
0
  // removing a scrollbar should always fit
5126
0
  DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnBottom, true, false);
5127
0
  NS_ASSERTION(result, "Removing horizontal scrollbar failed to fit??");
5128
0
}
5129
5130
void
5131
nsXULScrollFrame::RemoveVerticalScrollbar(nsBoxLayoutState& aState, bool aOnRight)
5132
0
{
5133
0
  // removing a scrollbar should always fit
5134
0
  DebugOnly<bool> result = AddRemoveScrollbar(aState, aOnRight, false, false);
5135
0
  NS_ASSERTION(result, "Removing vertical scrollbar failed to fit??");
5136
0
}
5137
5138
bool
5139
nsXULScrollFrame::AddRemoveScrollbar(nsBoxLayoutState& aState,
5140
                                     bool aOnRightOrBottom, bool aHorizontal, bool aAdd)
5141
0
{
5142
0
  if (aHorizontal) {
5143
0
     if (mHelper.mNeverHasHorizontalScrollbar || !mHelper.mHScrollbarBox)
5144
0
       return false;
5145
0
5146
0
     nsSize hSize = mHelper.mHScrollbarBox->GetXULPrefSize(aState);
5147
0
     nsBox::AddMargin(mHelper.mHScrollbarBox, hSize);
5148
0
5149
0
     ScrollFrameHelper::SetScrollbarVisibility(mHelper.mHScrollbarBox, aAdd);
5150
0
5151
0
     // We can't directly pass mHasHorizontalScrollbar as the bool outparam for
5152
0
     // AddRemoveScrollbar() because it's a bool:1 bitfield. Hence this var:
5153
0
     bool hasHorizontalScrollbar;
5154
0
     bool fit = AddRemoveScrollbar(hasHorizontalScrollbar,
5155
0
                                   mHelper.mScrollPort.y,
5156
0
                                   mHelper.mScrollPort.height,
5157
0
                                   hSize.height, aOnRightOrBottom, aAdd);
5158
0
     mHelper.mHasHorizontalScrollbar = hasHorizontalScrollbar;
5159
0
     if (!fit) {
5160
0
       ScrollFrameHelper::SetScrollbarVisibility(mHelper.mHScrollbarBox, !aAdd);
5161
0
     }
5162
0
     return fit;
5163
0
  } else {
5164
0
     if (mHelper.mNeverHasVerticalScrollbar || !mHelper.mVScrollbarBox)
5165
0
       return false;
5166
0
5167
0
     nsSize vSize = mHelper.mVScrollbarBox->GetXULPrefSize(aState);
5168
0
     nsBox::AddMargin(mHelper.mVScrollbarBox, vSize);
5169
0
5170
0
     ScrollFrameHelper::SetScrollbarVisibility(mHelper.mVScrollbarBox, aAdd);
5171
0
5172
0
     // We can't directly pass mHasVerticalScrollbar as the bool outparam for
5173
0
     // AddRemoveScrollbar() because it's a bool:1 bitfield. Hence this var:
5174
0
     bool hasVerticalScrollbar;
5175
0
     bool fit = AddRemoveScrollbar(hasVerticalScrollbar,
5176
0
                                   mHelper.mScrollPort.x,
5177
0
                                   mHelper.mScrollPort.width,
5178
0
                                   vSize.width, aOnRightOrBottom, aAdd);
5179
0
     mHelper.mHasVerticalScrollbar = hasVerticalScrollbar;
5180
0
     if (!fit) {
5181
0
       ScrollFrameHelper::SetScrollbarVisibility(mHelper.mVScrollbarBox, !aAdd);
5182
0
     }
5183
0
     return fit;
5184
0
  }
5185
0
}
5186
5187
bool
5188
nsXULScrollFrame::AddRemoveScrollbar(bool& aHasScrollbar, nscoord& aXY,
5189
                                     nscoord& aSize, nscoord aSbSize,
5190
                                     bool aOnRightOrBottom, bool aAdd)
5191
0
{
5192
0
   nscoord size = aSize;
5193
0
   nscoord xy = aXY;
5194
0
5195
0
   if (size != NS_INTRINSICSIZE) {
5196
0
     if (aAdd) {
5197
0
        size -= aSbSize;
5198
0
        if (!aOnRightOrBottom && size >= 0)
5199
0
          xy += aSbSize;
5200
0
     } else {
5201
0
        size += aSbSize;
5202
0
        if (!aOnRightOrBottom)
5203
0
          xy -= aSbSize;
5204
0
     }
5205
0
   }
5206
0
5207
0
   // not enough room? Yes? Return true.
5208
0
   if (size >= 0) {
5209
0
       aHasScrollbar = aAdd;
5210
0
       aSize = size;
5211
0
       aXY = xy;
5212
0
       return true;
5213
0
   }
5214
0
5215
0
   aHasScrollbar = false;
5216
0
   return false;
5217
0
}
5218
5219
void
5220
nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
5221
                                   const nsPoint& aScrollPosition)
5222
0
{
5223
0
  uint32_t oldflags = aState.LayoutFlags();
5224
0
  nsRect childRect = nsRect(mHelper.mScrollPort.TopLeft() - aScrollPosition,
5225
0
                            mHelper.mScrollPort.Size());
5226
0
  int32_t flags = NS_FRAME_NO_MOVE_VIEW;
5227
0
5228
0
  nsSize minSize = mHelper.mScrolledFrame->GetXULMinSize(aState);
5229
0
5230
0
  if (minSize.height > childRect.height)
5231
0
    childRect.height = minSize.height;
5232
0
5233
0
  if (minSize.width > childRect.width)
5234
0
    childRect.width = minSize.width;
5235
0
5236
0
  // TODO: Handle transformed children that inherit perspective
5237
0
  // from this frame. See AdjustForPerspective for how we handle
5238
0
  // this for HTML scroll frames.
5239
0
5240
0
  aState.SetLayoutFlags(flags);
5241
0
  ClampAndSetBounds(aState, childRect, aScrollPosition);
5242
0
  mHelper.mScrolledFrame->XULLayout(aState);
5243
0
5244
0
  childRect = mHelper.mScrolledFrame->GetRect();
5245
0
5246
0
  if (childRect.width < mHelper.mScrollPort.width ||
5247
0
      childRect.height < mHelper.mScrollPort.height)
5248
0
  {
5249
0
    childRect.width = std::max(childRect.width, mHelper.mScrollPort.width);
5250
0
    childRect.height = std::max(childRect.height, mHelper.mScrollPort.height);
5251
0
5252
0
    // remove overflow areas when we update the bounds,
5253
0
    // because we've already accounted for it
5254
0
    // REVIEW: Have we accounted for both?
5255
0
    ClampAndSetBounds(aState, childRect, aScrollPosition, true);
5256
0
  }
5257
0
5258
0
  aState.SetLayoutFlags(oldflags);
5259
0
5260
0
}
5261
5262
void ScrollFrameHelper::PostOverflowEvent()
5263
0
{
5264
0
  if (mAsyncScrollPortEvent.IsPending()) {
5265
0
    return;
5266
0
  }
5267
0
5268
0
  // Keep this in sync with FireScrollPortEvent().
5269
0
  nsSize scrollportSize = mScrollPort.Size();
5270
0
  nsSize childSize = GetScrolledRect().Size();
5271
0
5272
0
  bool newVerticalOverflow = childSize.height > scrollportSize.height;
5273
0
  bool vertChanged = mVerticalOverflow != newVerticalOverflow;
5274
0
5275
0
  bool newHorizontalOverflow = childSize.width > scrollportSize.width;
5276
0
  bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
5277
0
5278
0
  if (!vertChanged && !horizChanged) {
5279
0
    return;
5280
0
  }
5281
0
5282
0
  nsRootPresContext* rpc = mOuter->PresContext()->GetRootPresContext();
5283
0
  if (!rpc) {
5284
0
    return;
5285
0
  }
5286
0
5287
0
  mAsyncScrollPortEvent = new AsyncScrollPortEvent(this);
5288
0
  rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get());
5289
0
}
5290
5291
nsIFrame*
5292
ScrollFrameHelper::GetFrameForDir() const
5293
0
{
5294
0
  nsIFrame *frame = mOuter;
5295
0
  // XXX This is a bit on the slow side.
5296
0
  if (mIsRoot) {
5297
0
    // If we're the root scrollframe, we need the root element's style data.
5298
0
    nsPresContext *presContext = mOuter->PresContext();
5299
0
    nsIDocument *document = presContext->Document();
5300
0
    Element *root = document->GetRootElement();
5301
0
5302
0
    // But for HTML and XHTML we want the body element.
5303
0
    nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
5304
0
    if (htmlDoc) {
5305
0
      Element *bodyElement = document->GetBodyElement();
5306
0
      if (bodyElement) {
5307
0
        root = bodyElement; // we can trust the document to hold on to it
5308
0
      }
5309
0
    }
5310
0
5311
0
    if (root) {
5312
0
      nsIFrame *rootsFrame = root->GetPrimaryFrame();
5313
0
      if (rootsFrame) {
5314
0
        frame = rootsFrame;
5315
0
      }
5316
0
    }
5317
0
  }
5318
0
5319
0
  return frame;
5320
0
}
5321
5322
bool
5323
ScrollFrameHelper::IsScrollbarOnRight() const
5324
0
{
5325
0
  nsPresContext *presContext = mOuter->PresContext();
5326
0
5327
0
  // The position of the scrollbar in top-level windows depends on the pref
5328
0
  // layout.scrollbar.side. For non-top-level elements, it depends only on the
5329
0
  // directionaliy of the element (equivalent to a value of "1" for the pref).
5330
0
  if (!mIsRoot) {
5331
0
    return IsPhysicalLTR();
5332
0
  }
5333
0
  switch (presContext->GetCachedIntPref(kPresContext_ScrollbarSide)) {
5334
0
    default:
5335
0
    case 0: // UI directionality
5336
0
      return presContext->GetCachedIntPref(kPresContext_BidiDirection)
5337
0
             == IBMBIDI_TEXTDIRECTION_LTR;
5338
0
    case 1: // Document / content directionality
5339
0
      return IsPhysicalLTR();
5340
0
    case 2: // Always right
5341
0
      return true;
5342
0
    case 3: // Always left
5343
0
      return false;
5344
0
  }
5345
0
}
5346
5347
bool
5348
ScrollFrameHelper::IsMaybeScrollingActive() const
5349
0
{
5350
0
  const nsStyleDisplay* disp = mOuter->StyleDisplay();
5351
0
  if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) {
5352
0
    return true;
5353
0
  }
5354
0
5355
0
  nsIContent* content = mOuter->GetContent();
5356
0
  return mHasBeenScrolledRecently ||
5357
0
         IsAlwaysActive() ||
5358
0
         nsLayoutUtils::HasDisplayPort(content) ||
5359
0
         nsContentUtils::HasScrollgrab(content);
5360
0
}
5361
5362
bool
5363
ScrollFrameHelper::IsScrollingActive(nsDisplayListBuilder* aBuilder) const
5364
0
{
5365
0
  const nsStyleDisplay* disp = mOuter->StyleDisplay();
5366
0
  if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL) &&
5367
0
    aBuilder->IsInWillChangeBudget(mOuter, GetVisualViewportSize())) {
5368
0
    return true;
5369
0
  }
5370
0
5371
0
  nsIContent* content = mOuter->GetContent();
5372
0
  return mHasBeenScrolledRecently ||
5373
0
         IsAlwaysActive() ||
5374
0
         nsLayoutUtils::HasDisplayPort(content) ||
5375
0
         nsContentUtils::HasScrollgrab(content);
5376
0
}
5377
5378
/**
5379
 * Reflow the scroll area if it needs it and return its size. Also determine if the reflow will
5380
 * cause any of the scrollbars to need to be reflowed.
5381
 */
5382
nsresult
5383
nsXULScrollFrame::XULLayout(nsBoxLayoutState& aState)
5384
0
{
5385
0
  bool scrollbarRight = IsScrollbarOnRight();
5386
0
  bool scrollbarBottom = true;
5387
0
5388
0
  // get the content rect
5389
0
  nsRect clientRect(0,0,0,0);
5390
0
  GetXULClientRect(clientRect);
5391
0
5392
0
  nsRect oldScrollAreaBounds = mHelper.mScrollPort;
5393
0
  nsPoint oldScrollPosition = mHelper.GetLogicalScrollPosition();
5394
0
5395
0
  // the scroll area size starts off as big as our content area
5396
0
  mHelper.mScrollPort = clientRect;
5397
0
5398
0
  /**************
5399
0
   Our basic strategy here is to first try laying out the content with
5400
0
   the scrollbars in their current state. We're hoping that that will
5401
0
   just "work"; the content will overflow wherever there's a scrollbar
5402
0
   already visible. If that does work, then there's no need to lay out
5403
0
   the scrollarea. Otherwise we fix up the scrollbars; first we add a
5404
0
   vertical one to scroll the content if necessary, or remove it if
5405
0
   it's not needed. Then we reflow the content if the scrollbar
5406
0
   changed.  Then we add a horizontal scrollbar if necessary (or
5407
0
   remove if not needed), and if that changed, we reflow the content
5408
0
   again. At this point, any scrollbars that are needed to scroll the
5409
0
   content have been added.
5410
0
5411
0
   In the second phase we check to see if any scrollbars are too small
5412
0
   to display, and if so, we remove them. We check the horizontal
5413
0
   scrollbar first; removing it might make room for the vertical
5414
0
   scrollbar, and if we have room for just one scrollbar we'll save
5415
0
   the vertical one.
5416
0
5417
0
   Finally we position and size the scrollbars and scrollcorner (the
5418
0
   square that is needed in the corner of the window when two
5419
0
   scrollbars are visible), and reflow any fixed position views
5420
0
   (if we're the viewport and we added or removed a scrollbar).
5421
0
   **************/
5422
0
5423
0
  ScrollStyles styles = GetScrollStyles();
5424
0
5425
0
  // Look at our style do we always have vertical or horizontal scrollbars?
5426
0
  if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL)
5427
0
    mHelper.mHasHorizontalScrollbar = true;
5428
0
  if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL)
5429
0
    mHelper.mHasVerticalScrollbar = true;
5430
0
5431
0
  if (mHelper.mHasHorizontalScrollbar)
5432
0
    AddHorizontalScrollbar(aState, scrollbarBottom);
5433
0
5434
0
  if (mHelper.mHasVerticalScrollbar)
5435
0
    AddVerticalScrollbar(aState, scrollbarRight);
5436
0
5437
0
  // layout our the scroll area
5438
0
  LayoutScrollArea(aState, oldScrollPosition);
5439
0
5440
0
  // now look at the content area and see if we need scrollbars or not
5441
0
  bool needsLayout = false;
5442
0
5443
0
  // if we have 'auto' scrollbars look at the vertical case
5444
0
  if (styles.mVertical != NS_STYLE_OVERFLOW_SCROLL) {
5445
0
    // These are only good until the call to LayoutScrollArea.
5446
0
    nsRect scrolledRect = mHelper.GetScrolledRect();
5447
0
5448
0
    // There are two cases to consider
5449
0
    if (scrolledRect.height <= mHelper.mScrollPort.height ||
5450
0
        styles.mVertical != NS_STYLE_OVERFLOW_AUTO) {
5451
0
      if (mHelper.mHasVerticalScrollbar) {
5452
0
        // We left room for the vertical scrollbar, but it's not needed;
5453
0
        // remove it.
5454
0
        RemoveVerticalScrollbar(aState, scrollbarRight);
5455
0
        needsLayout = true;
5456
0
      }
5457
0
    } else {
5458
0
      if (!mHelper.mHasVerticalScrollbar) {
5459
0
        // We didn't leave room for the vertical scrollbar, but it turns
5460
0
        // out we needed it
5461
0
        if (AddVerticalScrollbar(aState, scrollbarRight)) {
5462
0
          needsLayout = true;
5463
0
        }
5464
0
      }
5465
0
    }
5466
0
5467
0
    // ok layout at the right size
5468
0
    if (needsLayout) {
5469
0
      nsBoxLayoutState resizeState(aState);
5470
0
      LayoutScrollArea(resizeState, oldScrollPosition);
5471
0
      needsLayout = false;
5472
0
    }
5473
0
  }
5474
0
5475
0
5476
0
  // if scrollbars are auto look at the horizontal case
5477
0
  if (styles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL)
5478
0
  {
5479
0
    // These are only good until the call to LayoutScrollArea.
5480
0
    nsRect scrolledRect = mHelper.GetScrolledRect();
5481
0
5482
0
    // if the child is wider that the scroll area
5483
0
    // and we don't have a scrollbar add one.
5484
0
    if ((scrolledRect.width > mHelper.mScrollPort.width)
5485
0
        && styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) {
5486
0
5487
0
      if (!mHelper.mHasHorizontalScrollbar) {
5488
0
        // no scrollbar?
5489
0
        if (AddHorizontalScrollbar(aState, scrollbarBottom)) {
5490
0
5491
0
          // if we added a horizontal scrollbar and we did not have a vertical
5492
0
          // there is a chance that by adding the horizontal scrollbar we will
5493
0
          // suddenly need a vertical scrollbar. Is a special case but it's
5494
0
          // important.
5495
0
          //
5496
0
          // But before we do that we need to relayout, since it's
5497
0
          // possible that the contents will flex as a result of adding a
5498
0
          // horizontal scrollbar and avoid the need for a vertical
5499
0
          // scrollbar.
5500
0
          //
5501
0
          // So instead of setting needsLayout to true here, do the
5502
0
          // layout immediately, and then consider whether to add the
5503
0
          // vertical scrollbar (and then maybe layout again).
5504
0
          {
5505
0
            nsBoxLayoutState resizeState(aState);
5506
0
            LayoutScrollArea(resizeState, oldScrollPosition);
5507
0
            needsLayout = false;
5508
0
          }
5509
0
5510
0
          // Refresh scrolledRect because we called LayoutScrollArea.
5511
0
          scrolledRect = mHelper.GetScrolledRect();
5512
0
5513
0
          if (styles.mVertical == NS_STYLE_OVERFLOW_AUTO &&
5514
0
              !mHelper.mHasVerticalScrollbar &&
5515
0
              scrolledRect.height > mHelper.mScrollPort.height) {
5516
0
            if (AddVerticalScrollbar(aState, scrollbarRight)) {
5517
0
              needsLayout = true;
5518
0
            }
5519
0
          }
5520
0
        }
5521
0
5522
0
      }
5523
0
    } else {
5524
0
      // if the area is smaller or equal to and we have a scrollbar then
5525
0
      // remove it.
5526
0
      if (mHelper.mHasHorizontalScrollbar) {
5527
0
        RemoveHorizontalScrollbar(aState, scrollbarBottom);
5528
0
        needsLayout = true;
5529
0
      }
5530
0
    }
5531
0
  }
5532
0
5533
0
  // we only need to set the rect. The inner child stays the same size.
5534
0
  if (needsLayout) {
5535
0
    nsBoxLayoutState resizeState(aState);
5536
0
    LayoutScrollArea(resizeState, oldScrollPosition);
5537
0
    needsLayout = false;
5538
0
  }
5539
0
5540
0
  // get the preferred size of the scrollbars
5541
0
  nsSize hMinSize(0, 0);
5542
0
  if (mHelper.mHScrollbarBox && mHelper.mHasHorizontalScrollbar) {
5543
0
    GetScrollbarMetrics(aState, mHelper.mHScrollbarBox, &hMinSize, nullptr, false);
5544
0
  }
5545
0
  nsSize vMinSize(0, 0);
5546
0
  if (mHelper.mVScrollbarBox && mHelper.mHasVerticalScrollbar) {
5547
0
    GetScrollbarMetrics(aState, mHelper.mVScrollbarBox, &vMinSize, nullptr, true);
5548
0
  }
5549
0
5550
0
  // Disable scrollbars that are too small
5551
0
  // Disable horizontal scrollbar first. If we have to disable only one
5552
0
  // scrollbar, we'd rather keep the vertical scrollbar.
5553
0
  // Note that we always give horizontal scrollbars their preferred height,
5554
0
  // never their min-height. So check that there's room for the preferred height.
5555
0
  if (mHelper.mHasHorizontalScrollbar &&
5556
0
      (hMinSize.width > clientRect.width - vMinSize.width
5557
0
       || hMinSize.height > clientRect.height)) {
5558
0
    RemoveHorizontalScrollbar(aState, scrollbarBottom);
5559
0
    needsLayout = true;
5560
0
  }
5561
0
  // Now disable vertical scrollbar if necessary
5562
0
  if (mHelper.mHasVerticalScrollbar &&
5563
0
      (vMinSize.height > clientRect.height - hMinSize.height
5564
0
       || vMinSize.width > clientRect.width)) {
5565
0
    RemoveVerticalScrollbar(aState, scrollbarRight);
5566
0
    needsLayout = true;
5567
0
  }
5568
0
5569
0
  // we only need to set the rect. The inner child stays the same size.
5570
0
  if (needsLayout) {
5571
0
    nsBoxLayoutState resizeState(aState);
5572
0
    LayoutScrollArea(resizeState, oldScrollPosition);
5573
0
  }
5574
0
5575
0
  if (!mHelper.mSuppressScrollbarUpdate) {
5576
0
    mHelper.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds);
5577
0
  }
5578
0
  if (!mHelper.mPostedReflowCallback) {
5579
0
    // Make sure we'll try scrolling to restored position
5580
0
    PresShell()->PostReflowCallback(&mHelper);
5581
0
    mHelper.mPostedReflowCallback = true;
5582
0
  }
5583
0
  if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
5584
0
    mHelper.mHadNonInitialReflow = true;
5585
0
  }
5586
0
5587
0
  mHelper.UpdateSticky();
5588
0
5589
0
  // Set up overflow areas for block frames for the benefit of
5590
0
  // text-overflow.
5591
0
  nsIFrame* f = mHelper.mScrolledFrame->GetContentInsertionFrame();
5592
0
  if (nsLayoutUtils::GetAsBlock(f)) {
5593
0
    nsRect origRect = f->GetRect();
5594
0
    nsRect clippedRect = origRect;
5595
0
    clippedRect.MoveBy(mHelper.mScrollPort.TopLeft());
5596
0
    clippedRect.IntersectRect(clippedRect, mHelper.mScrollPort);
5597
0
    nsOverflowAreas overflow = f->GetOverflowAreas();
5598
0
    f->FinishAndStoreOverflow(overflow, clippedRect.Size());
5599
0
    clippedRect.MoveTo(origRect.TopLeft());
5600
0
    f->SetRect(clippedRect);
5601
0
  }
5602
0
5603
0
  mHelper.UpdatePrevScrolledRect();
5604
0
5605
0
  mHelper.PostOverflowEvent();
5606
0
  return NS_OK;
5607
0
}
5608
5609
void
5610
ScrollFrameHelper::FinishReflowForScrollbar(Element* aElement,
5611
                                            nscoord aMinXY, nscoord aMaxXY,
5612
                                            nscoord aCurPosXY,
5613
                                            nscoord aPageIncrement,
5614
                                            nscoord aIncrement)
5615
0
{
5616
0
  // Scrollbars assume zero is the minimum position, so translate for them.
5617
0
  SetCoordAttribute(aElement, nsGkAtoms::curpos, aCurPosXY - aMinXY);
5618
0
  SetScrollbarEnabled(aElement, aMaxXY - aMinXY);
5619
0
  SetCoordAttribute(aElement, nsGkAtoms::maxpos, aMaxXY - aMinXY);
5620
0
  SetCoordAttribute(aElement, nsGkAtoms::pageincrement, aPageIncrement);
5621
0
  SetCoordAttribute(aElement, nsGkAtoms::increment, aIncrement);
5622
0
}
5623
5624
bool
5625
ScrollFrameHelper::ReflowFinished()
5626
0
{
5627
0
  mPostedReflowCallback = false;
5628
0
5629
0
  bool doScroll = true;
5630
0
  if (NS_SUBTREE_DIRTY(mOuter)) {
5631
0
    // We will get another call after the next reflow and scrolling
5632
0
    // later is less janky.
5633
0
    doScroll = false;
5634
0
  }
5635
0
5636
0
  nsAutoScriptBlocker scriptBlocker;
5637
0
5638
0
  if (doScroll) {
5639
0
    ScrollToRestoredPosition();
5640
0
5641
0
    // Clamp current scroll position to new bounds. Normally this won't
5642
0
    // do anything.
5643
0
    nsPoint currentScrollPos = GetScrollPosition();
5644
0
    ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
5645
0
    if (!mAsyncScroll && !mAsyncSmoothMSDScroll && !mApzSmoothScrollDestination) {
5646
0
      // We need to have mDestination track the current scroll position,
5647
0
      // in case it falls outside the new reflow area. mDestination is used
5648
0
      // by ScrollBy as its starting position.
5649
0
      mDestination = GetScrollPosition();
5650
0
    }
5651
0
  }
5652
0
5653
0
  if (!mUpdateScrollbarAttributes) {
5654
0
    return false;
5655
0
  }
5656
0
  mUpdateScrollbarAttributes = false;
5657
0
5658
0
  // Update scrollbar attributes.
5659
0
  nsPresContext* presContext = mOuter->PresContext();
5660
0
5661
0
  if (mMayHaveDirtyFixedChildren) {
5662
0
    mMayHaveDirtyFixedChildren = false;
5663
0
    nsIFrame* parentFrame = mOuter->GetParent();
5664
0
    for (nsIFrame* fixedChild =
5665
0
           parentFrame->GetChildList(nsIFrame::kFixedList).FirstChild();
5666
0
         fixedChild; fixedChild = fixedChild->GetNextSibling()) {
5667
0
      // force a reflow of the fixed child
5668
0
      presContext->PresShell()->
5669
0
        FrameNeedsReflow(fixedChild, nsIPresShell::eResize,
5670
0
                         NS_FRAME_HAS_DIRTY_CHILDREN);
5671
0
    }
5672
0
  }
5673
0
5674
0
  nsRect scrolledContentRect = GetScrolledRect();
5675
0
  nsSize scrollClampingScrollPort = GetVisualViewportSize();
5676
0
  nscoord minX = scrolledContentRect.x;
5677
0
  nscoord maxX = scrolledContentRect.XMost() - scrollClampingScrollPort.width;
5678
0
  nscoord minY = scrolledContentRect.y;
5679
0
  nscoord maxY = scrolledContentRect.YMost() - scrollClampingScrollPort.height;
5680
0
5681
0
  // Suppress handling of the curpos attribute changes we make here.
5682
0
  NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
5683
0
  mFrameIsUpdatingScrollbar = true;
5684
0
5685
0
  // FIXME(emilio): Why this instead of mHScrollbarContent / mVScrollbarContent?
5686
0
  RefPtr<Element> vScroll =
5687
0
    mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement() : nullptr;
5688
0
  RefPtr<Element> hScroll =
5689
0
    mHScrollbarBox ? mHScrollbarBox->GetContent()->AsElement() : nullptr;
5690
0
5691
0
  // Note, in some cases mOuter may get deleted while finishing reflow
5692
0
  // for scrollbars. XXXmats is this still true now that we have a script
5693
0
  // blocker in this scope? (if not, remove the weak frame checks below).
5694
0
  if (vScroll || hScroll) {
5695
0
    AutoWeakFrame weakFrame(mOuter);
5696
0
    nsPoint scrollPos = GetScrollPosition();
5697
0
    nsSize lineScrollAmount = GetLineScrollAmount();
5698
0
    if (vScroll) {
5699
0
      const double kScrollMultiplier =
5700
0
        Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
5701
0
                            NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
5702
0
      nscoord increment = lineScrollAmount.height * kScrollMultiplier;
5703
0
      // We normally use (scrollArea.height - increment) for height
5704
0
      // of page scrolling.  However, it is too small when
5705
0
      // increment is very large. (If increment is larger than
5706
0
      // scrollArea.height, direction of scrolling will be opposite).
5707
0
      // To avoid it, we use (float(scrollArea.height) * 0.8) as
5708
0
      // lower bound value of height of page scrolling. (bug 383267)
5709
0
      // XXX shouldn't we use GetPageScrollAmount here?
5710
0
      nscoord pageincrement = nscoord(scrollClampingScrollPort.height - increment);
5711
0
      nscoord pageincrementMin = nscoord(float(scrollClampingScrollPort.height) * 0.8);
5712
0
      FinishReflowForScrollbar(vScroll, minY, maxY, scrollPos.y,
5713
0
                               std::max(pageincrement, pageincrementMin),
5714
0
                               increment);
5715
0
    }
5716
0
    if (hScroll) {
5717
0
      const double kScrollMultiplier =
5718
0
        Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
5719
0
                            NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
5720
0
      nscoord increment = lineScrollAmount.width * kScrollMultiplier;
5721
0
      FinishReflowForScrollbar(hScroll, minX, maxX, scrollPos.x,
5722
0
                               nscoord(float(scrollClampingScrollPort.width) * 0.8),
5723
0
                               increment);
5724
0
    }
5725
0
    NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
5726
0
  }
5727
0
5728
0
  mFrameIsUpdatingScrollbar = false;
5729
0
  // We used to rely on the curpos attribute changes above to scroll the
5730
0
  // view.  However, for scrolling to the left of the viewport, we
5731
0
  // rescale the curpos attribute, which means that operations like
5732
0
  // resizing the window while it is scrolled all the way to the left
5733
0
  // hold the curpos attribute constant at 0 while still requiring
5734
0
  // scrolling.  So we suppress the effect of the changes above with
5735
0
  // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
5736
0
  // (It actually even works some of the time without this, thanks to
5737
0
  // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
5738
0
  // we hide the scrollbar on a large size change, such as
5739
0
  // maximization.)
5740
0
  if (!mHScrollbarBox && !mVScrollbarBox)
5741
0
    return false;
5742
0
  CurPosAttributeChanged(mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement()
5743
0
                                        : mHScrollbarBox->GetContent()->AsElement(),
5744
0
                         doScroll);
5745
0
  return doScroll;
5746
0
}
5747
5748
void
5749
ScrollFrameHelper::ReflowCallbackCanceled()
5750
0
{
5751
0
  mPostedReflowCallback = false;
5752
0
}
5753
5754
bool
5755
ScrollFrameHelper::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
5756
0
{
5757
0
  nsIScrollableFrame* sf = do_QueryFrame(mOuter);
5758
0
  ScrollStyles ss = sf->GetScrollStyles();
5759
0
5760
0
  // Reflow when the change in overflow leads to one of our scrollbars
5761
0
  // changing or might require repositioning the scrolled content due to
5762
0
  // reduced extents.
5763
0
  nsRect scrolledRect = GetScrolledRect();
5764
0
  uint32_t overflowChange = GetOverflowChange(scrolledRect, mPrevScrolledRect);
5765
0
  mPrevScrolledRect = scrolledRect;
5766
0
5767
0
  bool needReflow = false;
5768
0
  nsPoint scrollPosition = GetScrollPosition();
5769
0
  if (overflowChange & nsIScrollableFrame::HORIZONTAL) {
5770
0
    if (ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN || scrollPosition.x) {
5771
0
      needReflow = true;
5772
0
    }
5773
0
  }
5774
0
  if (overflowChange & nsIScrollableFrame::VERTICAL) {
5775
0
    if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN || scrollPosition.y) {
5776
0
      needReflow = true;
5777
0
    }
5778
0
  }
5779
0
5780
0
  if (needReflow) {
5781
0
    // If there are scrollbars, or we're not at the beginning of the pane,
5782
0
    // the scroll position may change. In this case, mark the frame as
5783
0
    // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means
5784
0
    // we have to reflow the frame and all its descendants, and we don't
5785
0
    // have to do that here. Only this frame needs to be reflowed.
5786
0
    mOuter->PresShell()->FrameNeedsReflow(
5787
0
      mOuter, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
5788
0
    // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip
5789
0
    // updating the scrollbars. (Because the overflow area of the scrolled
5790
0
    // frame has probably just been updated, Reflow won't see it change.)
5791
0
    mSkippedScrollbarLayout = true;
5792
0
    return false;  // reflowing will update overflow
5793
0
  }
5794
0
  PostOverflowEvent();
5795
0
  return mOuter->nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
5796
0
}
5797
5798
void
5799
ScrollFrameHelper::UpdateSticky()
5800
0
{
5801
0
  StickyScrollContainer* ssc = StickyScrollContainer::
5802
0
    GetStickyScrollContainerForScrollFrame(mOuter);
5803
0
  if (ssc) {
5804
0
    nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter);
5805
0
    ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter);
5806
0
  }
5807
0
}
5808
5809
void
5810
ScrollFrameHelper::UpdatePrevScrolledRect()
5811
0
{
5812
0
  mPrevScrolledRect = GetScrolledRect();
5813
0
}
5814
5815
void
5816
ScrollFrameHelper::AdjustScrollbarRectForResizer(
5817
                         nsIFrame* aFrame, nsPresContext* aPresContext,
5818
                         nsRect& aRect, bool aHasResizer, bool aVertical)
5819
0
{
5820
0
  if ((aVertical ? aRect.width : aRect.height) == 0) {
5821
0
    return;
5822
0
  }
5823
0
5824
0
  // if a content resizer is present, use its size. Otherwise, check if the
5825
0
  // widget has a resizer.
5826
0
  nsRect resizerRect;
5827
0
  if (aHasResizer) {
5828
0
    resizerRect = mResizerBox->GetRect();
5829
0
  }
5830
0
  else {
5831
0
    nsPoint offset;
5832
0
    nsIWidget* widget = aFrame->GetNearestWidget(offset);
5833
0
    LayoutDeviceIntRect widgetRect;
5834
0
    if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) {
5835
0
      return;
5836
0
    }
5837
0
5838
0
    resizerRect = nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
5839
0
                         aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
5840
0
                         aPresContext->DevPixelsToAppUnits(widgetRect.width),
5841
0
                         aPresContext->DevPixelsToAppUnits(widgetRect.height));
5842
0
  }
5843
0
5844
0
  if (resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) {
5845
0
    if (aVertical) {
5846
0
      aRect.height = std::max(0, resizerRect.y - aRect.y);
5847
0
    } else {
5848
0
      aRect.width = std::max(0, resizerRect.x - aRect.x);
5849
0
    }
5850
0
  } else if (resizerRect.Contains(aRect.BottomLeft() + nsPoint(1, -1))) {
5851
0
    if (aVertical) {
5852
0
      aRect.height = std::max(0, resizerRect.y - aRect.y);
5853
0
    } else {
5854
0
      nscoord xmost = aRect.XMost();
5855
0
      aRect.x = std::max(aRect.x, resizerRect.XMost());
5856
0
      aRect.width = xmost - aRect.x;
5857
0
    }
5858
0
  }
5859
0
}
5860
5861
static void
5862
AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect)
5863
0
{
5864
0
  if (aVRect.IsEmpty() || aHRect.IsEmpty())
5865
0
    return;
5866
0
5867
0
  const nsRect oldVRect = aVRect;
5868
0
  const nsRect oldHRect = aHRect;
5869
0
  if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
5870
0
    aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
5871
0
  } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
5872
0
    nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
5873
0
    aHRect.x += overlap;
5874
0
    aHRect.width -= overlap;
5875
0
  }
5876
0
  if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
5877
0
    aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
5878
0
  }
5879
0
}
5880
5881
void
5882
ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState,
5883
                                        const nsRect& aContentArea,
5884
                                        const nsRect& aOldScrollArea)
5885
0
{
5886
0
  NS_ASSERTION(!mSuppressScrollbarUpdate,
5887
0
               "This should have been suppressed");
5888
0
5889
0
  nsIPresShell* presShell = mOuter->PresShell();
5890
0
5891
0
  bool hasResizer = HasResizer();
5892
0
  bool scrollbarOnLeft = !IsScrollbarOnRight();
5893
0
  bool overlayScrollBarsWithZoom =
5894
0
    mIsRoot && LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) &&
5895
0
    presShell->IsVisualViewportSizeSet();
5896
0
5897
0
  nsSize scrollPortClampingSize = mScrollPort.Size();
5898
0
  double res = 1.0;
5899
0
  if (overlayScrollBarsWithZoom) {
5900
0
    scrollPortClampingSize = presShell->GetVisualViewportSize();
5901
0
    res = presShell->GetCumulativeResolution();
5902
0
  }
5903
0
5904
0
  // place the scrollcorner
5905
0
  if (mScrollCornerBox || mResizerBox) {
5906
0
    MOZ_ASSERT(!mScrollCornerBox || mScrollCornerBox->IsXULBoxFrame(), "Must be a box frame!");
5907
0
5908
0
    nsRect r(0, 0, 0, 0);
5909
0
    if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
5910
0
      // scrollbar (if any) on left
5911
0
      r.x = aContentArea.x;
5912
0
      r.width = mScrollPort.x - aContentArea.x;
5913
0
      NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
5914
0
    } else {
5915
0
      // scrollbar (if any) on right
5916
0
      r.width = aContentArea.XMost() - mScrollPort.XMost();
5917
0
      r.x = aContentArea.XMost() - r.width;
5918
0
      NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
5919
0
    }
5920
0
    if (aContentArea.y != mScrollPort.y) {
5921
0
      NS_ERROR("top scrollbars not supported");
5922
0
    } else {
5923
0
      // scrollbar (if any) on bottom
5924
0
      r.height = aContentArea.YMost() - mScrollPort.YMost();
5925
0
      r.y = aContentArea.YMost() - r.height;
5926
0
      NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
5927
0
    }
5928
0
5929
0
    if (mScrollCornerBox) {
5930
0
      nsBoxFrame::LayoutChildAt(aState, mScrollCornerBox, r);
5931
0
    }
5932
0
5933
0
    if (hasResizer) {
5934
0
      // if a resizer is present, get its size. Assume a default size of 15 pixels.
5935
0
      nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
5936
0
      nsSize resizerMinSize = mResizerBox->GetXULMinSize(aState);
5937
0
5938
0
      nscoord vScrollbarWidth = mVScrollbarBox ?
5939
0
        mVScrollbarBox->GetXULPrefSize(aState).width : defaultSize;
5940
0
      r.width = std::max(std::max(r.width, vScrollbarWidth), resizerMinSize.width);
5941
0
      if (aContentArea.x == mScrollPort.x && !scrollbarOnLeft) {
5942
0
        r.x = aContentArea.XMost() - r.width;
5943
0
      }
5944
0
5945
0
      nscoord hScrollbarHeight = mHScrollbarBox ?
5946
0
        mHScrollbarBox->GetXULPrefSize(aState).height : defaultSize;
5947
0
      r.height = std::max(std::max(r.height, hScrollbarHeight), resizerMinSize.height);
5948
0
      if (aContentArea.y == mScrollPort.y) {
5949
0
        r.y = aContentArea.YMost() - r.height;
5950
0
      }
5951
0
5952
0
      nsBoxFrame::LayoutChildAt(aState, mResizerBox, r);
5953
0
    } else if (mResizerBox) {
5954
0
      // otherwise lay out the resizer with an empty rectangle
5955
0
      nsBoxFrame::LayoutChildAt(aState, mResizerBox, nsRect());
5956
0
    }
5957
0
  }
5958
0
5959
0
  nsPresContext* presContext = mScrolledFrame->PresContext();
5960
0
  nsRect vRect;
5961
0
  if (mVScrollbarBox) {
5962
0
    MOZ_ASSERT(mVScrollbarBox->IsXULBoxFrame(), "Must be a box frame!");
5963
0
    vRect = mScrollPort;
5964
0
    if (overlayScrollBarsWithZoom) {
5965
0
      vRect.height = NSToCoordRound(res * scrollPortClampingSize.height);
5966
0
    }
5967
0
    vRect.width = aContentArea.width - mScrollPort.width;
5968
0
    vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.x + NSToCoordRound(res * scrollPortClampingSize.width);
5969
0
    if (mHasVerticalScrollbar) {
5970
0
      nsMargin margin;
5971
0
      mVScrollbarBox->GetXULMargin(margin);
5972
0
      vRect.Deflate(margin);
5973
0
    }
5974
0
    AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true);
5975
0
  }
5976
0
5977
0
  nsRect hRect;
5978
0
  if (mHScrollbarBox) {
5979
0
    MOZ_ASSERT(mHScrollbarBox->IsXULBoxFrame(), "Must be a box frame!");
5980
0
    hRect = mScrollPort;
5981
0
    if (overlayScrollBarsWithZoom) {
5982
0
      hRect.width = NSToCoordRound(res * scrollPortClampingSize.width);
5983
0
    }
5984
0
    hRect.height = aContentArea.height - mScrollPort.height;
5985
0
    hRect.y = mScrollPort.y + NSToCoordRound(res * scrollPortClampingSize.height);
5986
0
    if (mHasHorizontalScrollbar) {
5987
0
      nsMargin margin;
5988
0
      mHScrollbarBox->GetXULMargin(margin);
5989
0
      hRect.Deflate(margin);
5990
0
    }
5991
0
    AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
5992
0
  }
5993
0
5994
0
  if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) {
5995
0
    AdjustOverlappingScrollbars(vRect, hRect);
5996
0
  }
5997
0
  if (mVScrollbarBox) {
5998
0
    nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
5999
0
  }
6000
0
  if (mHScrollbarBox) {
6001
0
    nsBoxFrame::LayoutChildAt(aState, mHScrollbarBox, hRect);
6002
0
  }
6003
0
6004
0
  // may need to update fixed position children of the viewport,
6005
0
  // if the client area changed size because of an incremental
6006
0
  // reflow of a descendant.  (If the outer frame is dirty, the fixed
6007
0
  // children will be re-laid out anyway)
6008
0
  if (aOldScrollArea.Size() != mScrollPort.Size() &&
6009
0
      !(mOuter->GetStateBits() & NS_FRAME_IS_DIRTY) &&
6010
0
      mIsRoot) {
6011
0
    mMayHaveDirtyFixedChildren = true;
6012
0
  }
6013
0
6014
0
  // post reflow callback to modify scrollbar attributes
6015
0
  mUpdateScrollbarAttributes = true;
6016
0
  if (!mPostedReflowCallback) {
6017
0
    aState.PresShell()->PostReflowCallback(this);
6018
0
    mPostedReflowCallback = true;
6019
0
  }
6020
0
}
6021
6022
#if DEBUG
6023
static bool ShellIsAlive(nsWeakPtr& aWeakPtr)
6024
{
6025
  nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aWeakPtr));
6026
  return !!shell;
6027
}
6028
#endif
6029
6030
void
6031
ScrollFrameHelper::SetScrollbarEnabled(Element* aElement, nscoord aMaxPos)
6032
0
{
6033
0
  DebugOnly<nsWeakPtr> weakShell(
6034
0
    do_GetWeakReference(mOuter->PresShell()));
6035
0
  if (aMaxPos) {
6036
0
    aElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
6037
0
  } else {
6038
0
    aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
6039
0
                      NS_LITERAL_STRING("true"), true);
6040
0
  }
6041
0
  MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
6042
0
}
6043
6044
void
6045
ScrollFrameHelper::SetCoordAttribute(Element* aElement, nsAtom* aAtom,
6046
                                     nscoord aSize)
6047
0
{
6048
0
  DebugOnly<nsWeakPtr> weakShell(
6049
0
    do_GetWeakReference(mOuter->PresShell()));
6050
0
  // convert to pixels
6051
0
  int32_t pixelSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
6052
0
6053
0
  // only set the attribute if it changed.
6054
0
6055
0
  nsAutoString newValue;
6056
0
  newValue.AppendInt(pixelSize);
6057
0
6058
0
  if (aElement->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters)) {
6059
0
    return;
6060
0
  }
6061
0
6062
0
  AutoWeakFrame weakFrame(mOuter);
6063
0
  RefPtr<Element> kungFuDeathGrip = aElement;
6064
0
  aElement->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
6065
0
  MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
6066
0
  if (!weakFrame.IsAlive()) {
6067
0
    return;
6068
0
  }
6069
0
6070
0
  if (mScrollbarActivity) {
6071
0
    RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
6072
0
    scrollbarActivity->ActivityOccurred();
6073
0
  }
6074
0
}
6075
6076
static void
6077
ReduceRadii(nscoord aXBorder, nscoord aYBorder,
6078
            nscoord& aXRadius, nscoord& aYRadius)
6079
0
{
6080
0
  // In order to ensure that the inside edge of the border has no
6081
0
  // curvature, we need at least one of its radii to be zero.
6082
0
  if (aXRadius <= aXBorder || aYRadius <= aYBorder)
6083
0
    return;
6084
0
6085
0
  // For any corner where we reduce the radii, preserve the corner's shape.
6086
0
  double ratio = std::max(double(aXBorder) / aXRadius,
6087
0
                        double(aYBorder) / aYRadius);
6088
0
  aXRadius *= ratio;
6089
0
  aYRadius *= ratio;
6090
0
}
6091
6092
/**
6093
 * Implement an override for nsIFrame::GetBorderRadii to ensure that
6094
 * the clipping region for the border radius does not clip the scrollbars.
6095
 *
6096
 * In other words, we require that the border radius be reduced until the
6097
 * inner border radius at the inner edge of the border is 0 wherever we
6098
 * have scrollbars.
6099
 */
6100
bool
6101
ScrollFrameHelper::GetBorderRadii(const nsSize& aFrameSize,
6102
                                  const nsSize& aBorderArea,
6103
                                  Sides aSkipSides,
6104
                                  nscoord aRadii[8]) const
6105
0
{
6106
0
  if (!mOuter->nsContainerFrame::GetBorderRadii(aFrameSize, aBorderArea,
6107
0
                                                aSkipSides, aRadii)) {
6108
0
    return false;
6109
0
  }
6110
0
6111
0
  // Since we can use GetActualScrollbarSizes (rather than
6112
0
  // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
6113
0
  // probably should.
6114
0
  nsMargin sb = GetActualScrollbarSizes();
6115
0
  nsMargin border = mOuter->GetUsedBorder();
6116
0
6117
0
  if (sb.left > 0 || sb.top > 0) {
6118
0
    ReduceRadii(border.left, border.top,
6119
0
                aRadii[eCornerTopLeftX],
6120
0
                aRadii[eCornerTopLeftY]);
6121
0
  }
6122
0
6123
0
  if (sb.top > 0 || sb.right > 0) {
6124
0
    ReduceRadii(border.right, border.top,
6125
0
                aRadii[eCornerTopRightX],
6126
0
                aRadii[eCornerTopRightY]);
6127
0
  }
6128
0
6129
0
  if (sb.right > 0 || sb.bottom > 0) {
6130
0
    ReduceRadii(border.right, border.bottom,
6131
0
                aRadii[eCornerBottomRightX],
6132
0
                aRadii[eCornerBottomRightY]);
6133
0
  }
6134
0
6135
0
  if (sb.bottom > 0 || sb.left > 0) {
6136
0
    ReduceRadii(border.left, border.bottom,
6137
0
                aRadii[eCornerBottomLeftX],
6138
0
                aRadii[eCornerBottomLeftY]);
6139
0
  }
6140
0
6141
0
  return true;
6142
0
}
6143
6144
static nscoord
6145
SnapCoord(nscoord aCoord, double aRes, nscoord aAppUnitsPerPixel)
6146
0
{
6147
0
  double snappedToLayerPixels = NS_round((aRes*aCoord)/aAppUnitsPerPixel);
6148
0
  return NSToCoordRoundWithClamp(snappedToLayerPixels*aAppUnitsPerPixel/aRes);
6149
0
}
6150
6151
nsRect
6152
ScrollFrameHelper::GetScrolledRect() const
6153
0
{
6154
0
  nsRect result =
6155
0
    GetUnsnappedScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
6156
0
                                     mScrollPort.Size());
6157
0
6158
0
  if (result.width < mScrollPort.width) {
6159
0
    NS_WARNING("Scrolled rect smaller than scrollport?");
6160
0
  }
6161
0
  if (result.height < mScrollPort.height) {
6162
0
    NS_WARNING("Scrolled rect smaller than scrollport?");
6163
0
  }
6164
0
6165
0
  // Expand / contract the result by up to half a layer pixel so that scrolling
6166
0
  // to the right / bottom edge does not change the layer pixel alignment of
6167
0
  // the scrolled contents.
6168
0
6169
0
  if (result.x == 0 && result.y == 0 &&
6170
0
      result.width == mScrollPort.width &&
6171
0
      result.height == mScrollPort.height) {
6172
0
    // The edges that we would snap are already aligned with the scroll port,
6173
0
    // so we can skip all the work below.
6174
0
    return result;
6175
0
  }
6176
0
6177
0
  // For that, we first convert the scroll port and the scrolled rect to rects
6178
0
  // relative to the reference frame, since that's the space where painting does
6179
0
  // snapping.
6180
0
  nsSize visualViewportSize = GetVisualViewportSize();
6181
0
  const nsIFrame* referenceFrame =
6182
0
    mReferenceFrameDuringPainting ? mReferenceFrameDuringPainting : nsLayoutUtils::GetReferenceFrame(mOuter);
6183
0
  nsPoint toReferenceFrame = mOuter->GetOffsetToCrossDoc(referenceFrame);
6184
0
  nsRect scrollPort(mScrollPort.TopLeft() + toReferenceFrame, visualViewportSize);
6185
0
  nsRect scrolledRect = result + scrollPort.TopLeft();
6186
0
6187
0
  if (scrollPort.Overflows() || scrolledRect.Overflows()) {
6188
0
    return result;
6189
0
  }
6190
0
6191
0
  // Now, snap the bottom right corner of both of these rects.
6192
0
  // We snap to layer pixels, so we need to respect the layer's scale.
6193
0
  nscoord appUnitsPerDevPixel = mScrolledFrame->PresContext()->AppUnitsPerDevPixel();
6194
0
  gfxSize scale = FrameLayerBuilder::GetPaintedLayerScaleForFrame(mScrolledFrame);
6195
0
  if (scale.IsEmpty()) {
6196
0
    scale = gfxSize(1.0f, 1.0f);
6197
0
  }
6198
0
6199
0
  // Compute bounds for the scroll position, and computed the snapped scrolled
6200
0
  // rect from the scroll position bounds.
6201
0
  nscoord snappedScrolledAreaBottom = SnapCoord(scrolledRect.YMost(), scale.height, appUnitsPerDevPixel);
6202
0
  nscoord snappedScrollPortBottom = SnapCoord(scrollPort.YMost(), scale.height, appUnitsPerDevPixel);
6203
0
  nscoord maximumScrollOffsetY = snappedScrolledAreaBottom - snappedScrollPortBottom;
6204
0
  result.SetBottomEdge(scrollPort.height + maximumScrollOffsetY);
6205
0
6206
0
  if (GetScrolledFrameDir() == NS_STYLE_DIRECTION_LTR) {
6207
0
    nscoord snappedScrolledAreaRight = SnapCoord(scrolledRect.XMost(), scale.width, appUnitsPerDevPixel);
6208
0
    nscoord snappedScrollPortRight = SnapCoord(scrollPort.XMost(), scale.width, appUnitsPerDevPixel);
6209
0
    nscoord maximumScrollOffsetX = snappedScrolledAreaRight - snappedScrollPortRight;
6210
0
    result.SetRightEdge(scrollPort.width + maximumScrollOffsetX);
6211
0
  } else {
6212
0
    // In RTL, the scrolled area's right edge is at scrollPort.XMost(),
6213
0
    // and the scrolled area's x position is zero or negative. We want
6214
0
    // the right edge to stay flush with the scroll port, so we snap the
6215
0
    // left edge.
6216
0
    nscoord snappedScrolledAreaLeft = SnapCoord(scrolledRect.x, scale.width, appUnitsPerDevPixel);
6217
0
    nscoord snappedScrollPortLeft = SnapCoord(scrollPort.x, scale.width, appUnitsPerDevPixel);
6218
0
    nscoord minimumScrollOffsetX = snappedScrolledAreaLeft - snappedScrollPortLeft;
6219
0
    result.SetLeftEdge(minimumScrollOffsetX);
6220
0
  }
6221
0
6222
0
  return result;
6223
0
}
6224
6225
6226
uint8_t
6227
ScrollFrameHelper::GetScrolledFrameDir() const
6228
0
{
6229
0
  // If the scrolled frame has unicode-bidi: plaintext, the paragraph
6230
0
  // direction set by the text content overrides the direction of the frame
6231
0
  if (mScrolledFrame->StyleTextReset()->mUnicodeBidi &
6232
0
      NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
6233
0
    nsIFrame* childFrame = mScrolledFrame->PrincipalChildList().FirstChild();
6234
0
    if (childFrame) {
6235
0
      return (nsBidiPresUtils::ParagraphDirection(childFrame) == NSBIDI_LTR)
6236
0
             ? NS_STYLE_DIRECTION_LTR : NS_STYLE_DIRECTION_RTL;
6237
0
    }
6238
0
  }
6239
0
6240
0
  return IsBidiLTR() ? NS_STYLE_DIRECTION_LTR : NS_STYLE_DIRECTION_RTL;
6241
0
}
6242
6243
nsRect
6244
ScrollFrameHelper::GetUnsnappedScrolledRectInternal(const nsRect& aScrolledFrameOverflowArea,
6245
                                                    const nsSize& aScrollPortSize) const
6246
0
{
6247
0
  return nsLayoutUtils::GetScrolledRect(mScrolledFrame,
6248
0
                                        aScrolledFrameOverflowArea,
6249
0
                                        aScrollPortSize, GetScrolledFrameDir());
6250
0
}
6251
6252
nsMargin
6253
ScrollFrameHelper::GetActualScrollbarSizes() const
6254
0
{
6255
0
  nsRect r = mOuter->GetPaddingRect() - mOuter->GetPosition();
6256
0
6257
0
  return nsMargin(mScrollPort.y - r.y,
6258
0
                  r.XMost() - mScrollPort.XMost(),
6259
0
                  r.YMost() - mScrollPort.YMost(),
6260
0
                  mScrollPort.x - r.x);
6261
0
}
6262
6263
void
6264
ScrollFrameHelper::SetScrollbarVisibility(nsIFrame* aScrollbar, bool aVisible)
6265
0
{
6266
0
  nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
6267
0
  if (scrollbar) {
6268
0
    // See if we have a mediator.
6269
0
    nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
6270
0
    if (mediator) {
6271
0
      // Inform the mediator of the visibility change.
6272
0
      mediator->VisibilityChanged(aVisible);
6273
0
    }
6274
0
  }
6275
0
}
6276
6277
nscoord
6278
ScrollFrameHelper::GetCoordAttribute(nsIFrame* aBox, nsAtom* aAtom,
6279
                                         nscoord aDefaultValue,
6280
                                         nscoord* aRangeStart,
6281
                                         nscoord* aRangeLength)
6282
0
{
6283
0
  if (aBox) {
6284
0
    nsIContent* content = aBox->GetContent();
6285
0
6286
0
    nsAutoString value;
6287
0
    if (content->IsElement()) {
6288
0
      content->AsElement()->GetAttr(kNameSpaceID_None, aAtom, value);
6289
0
    }
6290
0
    if (!value.IsEmpty()) {
6291
0
      nsresult error;
6292
0
      // convert it to appunits
6293
0
      nscoord result = nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
6294
0
      nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
6295
0
      // Any nscoord value that would round to the attribute value when converted
6296
0
      // to CSS pixels is allowed.
6297
0
      *aRangeStart = result - halfPixel;
6298
0
      *aRangeLength = halfPixel*2 - 1;
6299
0
      return result;
6300
0
    }
6301
0
  }
6302
0
6303
0
  // Only this exact default value is allowed.
6304
0
  *aRangeStart = aDefaultValue;
6305
0
  *aRangeLength = 0;
6306
0
  return aDefaultValue;
6307
0
}
6308
6309
UniquePtr<PresState>
6310
ScrollFrameHelper::SaveState() const
6311
0
{
6312
0
  nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
6313
0
  if (mediator) {
6314
0
    // child handles its own scroll state, so don't bother saving state here
6315
0
    return nullptr;
6316
0
  }
6317
0
6318
0
  // Don't store a scroll state if we never have been scrolled or restored
6319
0
  // a previous scroll state, and we're not in the middle of a smooth scroll.
6320
0
  bool isInSmoothScroll = IsProcessingAsyncScroll() || mLastSmoothScrollOrigin;
6321
0
  if (!mHasBeenScrolled && !mDidHistoryRestore && !isInSmoothScroll) {
6322
0
    return nullptr;
6323
0
  }
6324
0
6325
0
  UniquePtr<PresState> state = NewPresState();
6326
0
  bool allowScrollOriginDowngrade =
6327
0
    !nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) ||
6328
0
    mAllowScrollOriginDowngrade;
6329
0
  // Save mRestorePos instead of our actual current scroll position, if it's
6330
0
  // valid and we haven't moved since the last update of mLastPos (same check
6331
0
  // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
6332
0
  // while we're in the process of loading content to scroll to a restored
6333
0
  // position, we'll keep trying after the reframe. Similarly, if we're in the
6334
0
  // middle of a smooth scroll, store the destination so that when we restore
6335
0
  // we'll jump straight to the end of the scroll animation, rather than
6336
0
  // effectively dropping it. Note that the mRestorePos will override the
6337
0
  // smooth scroll destination if both are present.
6338
0
  nsPoint pt = GetLogicalScrollPosition();
6339
0
  if (isInSmoothScroll) {
6340
0
    pt = mDestination;
6341
0
    allowScrollOriginDowngrade = false;
6342
0
  }
6343
0
  if (mRestorePos.y != -1 && pt == mLastPos) {
6344
0
    pt = mRestorePos;
6345
0
  }
6346
0
  state->scrollState() = pt;
6347
0
  state->allowScrollOriginDowngrade() = allowScrollOriginDowngrade;
6348
0
  if (mIsRoot) {
6349
0
    // Only save resolution properties for root scroll frames
6350
0
    nsIPresShell* shell = mOuter->PresShell();
6351
0
    state->resolution() = shell->GetResolution();
6352
0
    state->scaleToResolution() = shell->ScaleToResolution();
6353
0
  }
6354
0
  return state;
6355
0
}
6356
6357
void
6358
ScrollFrameHelper::RestoreState(PresState* aState)
6359
0
{
6360
0
  mRestorePos = aState->scrollState();
6361
0
  MOZ_ASSERT(mLastScrollOrigin == nsGkAtoms::other);
6362
0
  mAllowScrollOriginDowngrade = aState->allowScrollOriginDowngrade();
6363
0
  mDidHistoryRestore = true;
6364
0
  mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0);
6365
0
6366
0
  // Resolution properties should only exist on root scroll frames.
6367
0
  MOZ_ASSERT(mIsRoot || (!aState->scaleToResolution() &&
6368
0
                         aState->resolution() == 1.0));
6369
0
6370
0
  if (mIsRoot) {
6371
0
    nsIPresShell* presShell = mOuter->PresShell();
6372
0
    if (aState->scaleToResolution()) {
6373
0
      presShell->SetResolutionAndScaleTo(aState->resolution());
6374
0
    } else {
6375
0
      presShell->SetResolution(aState->resolution());
6376
0
    }
6377
0
  }
6378
0
}
6379
6380
void
6381
ScrollFrameHelper::PostScrolledAreaEvent()
6382
0
{
6383
0
  if (mScrolledAreaEvent.IsPending()) {
6384
0
    return;
6385
0
  }
6386
0
  mScrolledAreaEvent = new ScrolledAreaEvent(this);
6387
0
  nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
6388
0
}
6389
6390
////////////////////////////////////////////////////////////////////////////////
6391
// ScrolledArea change event dispatch
6392
6393
NS_IMETHODIMP
6394
ScrollFrameHelper::ScrolledAreaEvent::Run()
6395
0
{
6396
0
  if (mHelper) {
6397
0
    mHelper->FireScrolledAreaEvent();
6398
0
  }
6399
0
  return NS_OK;
6400
0
}
6401
6402
void
6403
ScrollFrameHelper::FireScrolledAreaEvent()
6404
0
{
6405
0
  mScrolledAreaEvent.Forget();
6406
0
6407
0
  InternalScrollAreaEvent event(true, eScrolledAreaChanged, nullptr);
6408
0
  nsPresContext *prescontext = mOuter->PresContext();
6409
0
  nsIContent* content = mOuter->GetContent();
6410
0
6411
0
  event.mArea = mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
6412
0
6413
0
  nsIDocument *doc = content->GetUncomposedDoc();
6414
0
  if (doc) {
6415
0
    EventDispatcher::Dispatch(doc, prescontext, &event, nullptr);
6416
0
  }
6417
0
}
6418
6419
uint32_t
6420
nsIScrollableFrame::GetPerceivedScrollingDirections() const
6421
0
{
6422
0
  nscoord oneDevPixel = GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
6423
0
  uint32_t directions = GetScrollbarVisibility();
6424
0
  nsRect scrollRange = GetScrollRange();
6425
0
  if (scrollRange.width >= oneDevPixel) {
6426
0
    directions |= HORIZONTAL;
6427
0
  }
6428
0
  if (scrollRange.height >= oneDevPixel) {
6429
0
    directions |= VERTICAL;
6430
0
  }
6431
0
  return directions;
6432
0
}
6433
6434
/**
6435
 * Collect the scroll-snap-coordinates of frames in the subtree rooted at
6436
 * |aFrame|, relative to |aScrolledFrame|, into |aOutCoords|.
6437
 */
6438
static void
6439
CollectScrollSnapCoordinates(nsIFrame* aFrame, nsIFrame* aScrolledFrame,
6440
                             nsTArray<nsPoint>& aOutCoords)
6441
0
{
6442
0
  nsIFrame::ChildListIterator childLists(aFrame);
6443
0
  for (; !childLists.IsDone(); childLists.Next()) {
6444
0
    nsFrameList::Enumerator childFrames(childLists.CurrentList());
6445
0
    for (; !childFrames.AtEnd(); childFrames.Next()) {
6446
0
      nsIFrame* f = childFrames.get();
6447
0
6448
0
      const nsStyleDisplay* styleDisplay = f->StyleDisplay();
6449
0
      size_t coordCount = styleDisplay->mScrollSnapCoordinate.Length();
6450
0
6451
0
      if (coordCount) {
6452
0
        nsRect frameRect = f->GetRect();
6453
0
        nsPoint offset = f->GetOffsetTo(aScrolledFrame);
6454
0
        nsRect edgesRect = nsRect(offset, frameRect.Size());
6455
0
        for (size_t coordNum = 0; coordNum < coordCount; coordNum++) {
6456
0
          const Position& coordPosition =
6457
0
            f->StyleDisplay()->mScrollSnapCoordinate[coordNum];
6458
0
          nsPoint coordPoint = edgesRect.TopLeft();
6459
0
          coordPoint += nsPoint(coordPosition.mXPosition.mLength,
6460
0
                                coordPosition.mYPosition.mLength);
6461
0
          if (coordPosition.mXPosition.mHasPercent) {
6462
0
            coordPoint.x += NSToCoordRound(coordPosition.mXPosition.mPercent *
6463
0
                                           frameRect.width);
6464
0
          }
6465
0
          if (coordPosition.mYPosition.mHasPercent) {
6466
0
            coordPoint.y += NSToCoordRound(coordPosition.mYPosition.mPercent *
6467
0
                                           frameRect.height);
6468
0
          }
6469
0
6470
0
          aOutCoords.AppendElement(coordPoint);
6471
0
        }
6472
0
      }
6473
0
6474
0
      CollectScrollSnapCoordinates(f, aScrolledFrame, aOutCoords);
6475
0
    }
6476
0
  }
6477
0
}
6478
6479
static layers::ScrollSnapInfo
6480
ComputeScrollSnapInfo(const ScrollFrameHelper& aScrollFrame)
6481
0
{
6482
0
  ScrollSnapInfo result;
6483
0
6484
0
  ScrollStyles styles = aScrollFrame.GetScrollStylesFromFrame();
6485
0
6486
0
  if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
6487
0
      styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
6488
0
    // We won't be snapping, short-circuit the computation.
6489
0
    return result;
6490
0
  }
6491
0
6492
0
  result.mScrollSnapTypeX = styles.mScrollSnapTypeX;
6493
0
  result.mScrollSnapTypeY = styles.mScrollSnapTypeY;
6494
0
6495
0
  nsSize scrollPortSize = aScrollFrame.GetScrollPortRect().Size();
6496
0
6497
0
  result.mScrollSnapDestination = nsPoint(styles.mScrollSnapDestinationX.mLength,
6498
0
                                          styles.mScrollSnapDestinationY.mLength);
6499
0
  if (styles.mScrollSnapDestinationX.mHasPercent) {
6500
0
    result.mScrollSnapDestination.x +=
6501
0
        NSToCoordFloorClamped(styles.mScrollSnapDestinationX.mPercent *
6502
0
                              scrollPortSize.width);
6503
0
  }
6504
0
  if (styles.mScrollSnapDestinationY.mHasPercent) {
6505
0
    result.mScrollSnapDestination.y +=
6506
0
        NSToCoordFloorClamped(styles.mScrollSnapDestinationY.mPercent *
6507
0
                              scrollPortSize.height);
6508
0
  }
6509
0
6510
0
  if (styles.mScrollSnapPointsX.GetUnit() != eStyleUnit_None) {
6511
0
    result.mScrollSnapIntervalX = Some(
6512
0
      styles.mScrollSnapPointsX.ComputeCoordPercentCalc(scrollPortSize.width));
6513
0
  }
6514
0
  if (styles.mScrollSnapPointsY.GetUnit() != eStyleUnit_None) {
6515
0
    result.mScrollSnapIntervalY = Some(
6516
0
      styles.mScrollSnapPointsY.ComputeCoordPercentCalc(scrollPortSize.height));
6517
0
  }
6518
0
6519
0
  CollectScrollSnapCoordinates(aScrollFrame.GetScrolledFrame(),
6520
0
                               aScrollFrame.GetScrolledFrame(),
6521
0
                               result.mScrollSnapCoordinates);
6522
0
6523
0
  return result;
6524
0
}
6525
6526
layers::ScrollSnapInfo
6527
ScrollFrameHelper::GetScrollSnapInfo() const
6528
0
{
6529
0
  // TODO(botond): Should we cache it?
6530
0
  return ComputeScrollSnapInfo(*this);
6531
0
}
6532
6533
bool
6534
ScrollFrameHelper::GetSnapPointForDestination(nsIScrollableFrame::ScrollUnit aUnit,
6535
                                              nsPoint aStartPos,
6536
                                              nsPoint &aDestination)
6537
0
{
6538
0
  Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
6539
0
      GetScrollSnapInfo(), aUnit, mScrollPort.Size(),
6540
0
      GetScrollRangeForClamping(), aStartPos, aDestination);
6541
0
  if (snapPoint) {
6542
0
    aDestination = snapPoint.ref();
6543
0
    return true;
6544
0
  }
6545
0
  return false;
6546
0
}
6547
6548
bool
6549
ScrollFrameHelper::UsesContainerScrolling() const
6550
0
{
6551
0
  if (gfxPrefs::LayoutUseContainersForRootFrames()) {
6552
0
    return mIsRoot;
6553
0
  }
6554
0
  return false;
6555
0
}
6556
6557
bool
6558
ScrollFrameHelper::DragScroll(WidgetEvent* aEvent)
6559
0
{
6560
0
  // Dragging is allowed while within a 20 pixel border. Note that device pixels
6561
0
  // are used so that the same margin is used even when zoomed in or out.
6562
0
  nscoord margin = 20 * mOuter->PresContext()->AppUnitsPerDevPixel();
6563
0
6564
0
  // Don't drag scroll for small scrollareas.
6565
0
  if (mScrollPort.width < margin * 2 || mScrollPort.height < margin * 2) {
6566
0
    return false;
6567
0
  }
6568
0
6569
0
  // If willScroll is computed as false, then the frame is already scrolled as
6570
0
  // far as it can go in both directions. Return false so that an ancestor
6571
0
  // scrollframe can scroll instead.
6572
0
  bool willScroll = false;
6573
0
  nsPoint pnt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, mOuter);
6574
0
  nsPoint scrollPoint = GetScrollPosition();
6575
0
  nsRect rangeRect = GetScrollRangeForClamping();
6576
0
6577
0
  // Only drag scroll when a scrollbar is present.
6578
0
  nsPoint offset;
6579
0
  if (mHasHorizontalScrollbar) {
6580
0
    if (pnt.x >= mScrollPort.x && pnt.x <= mScrollPort.x + margin) {
6581
0
      offset.x = -margin;
6582
0
      if (scrollPoint.x > 0) {
6583
0
        willScroll = true;
6584
0
      }
6585
0
    } else if (pnt.x >= mScrollPort.XMost() - margin && pnt.x <= mScrollPort.XMost()) {
6586
0
      offset.x = margin;
6587
0
      if (scrollPoint.x < rangeRect.width) {
6588
0
        willScroll = true;
6589
0
      }
6590
0
    }
6591
0
  }
6592
0
6593
0
  if (mHasVerticalScrollbar) {
6594
0
    if (pnt.y >= mScrollPort.y && pnt.y <= mScrollPort.y + margin) {
6595
0
      offset.y = -margin;
6596
0
      if (scrollPoint.y > 0) {
6597
0
        willScroll = true;
6598
0
      }
6599
0
    } else if (pnt.y >= mScrollPort.YMost() - margin && pnt.y <= mScrollPort.YMost()) {
6600
0
      offset.y = margin;
6601
0
      if (scrollPoint.y < rangeRect.height) {
6602
0
        willScroll = true;
6603
0
      }
6604
0
    }
6605
0
  }
6606
0
6607
0
  if (offset.x || offset.y) {
6608
0
    ScrollTo(GetScrollPosition() + offset, nsIScrollableFrame::NORMAL);
6609
0
  }
6610
0
6611
0
  return willScroll;
6612
0
}
6613
6614
static void
6615
AsyncScrollbarDragRejected(nsIFrame* aScrollbar)
6616
0
{
6617
0
  if (!aScrollbar) {
6618
0
    return;
6619
0
  }
6620
0
6621
0
  for (nsIFrame::ChildListIterator childLists(aScrollbar);
6622
0
       !childLists.IsDone();
6623
0
       childLists.Next()) {
6624
0
    for (nsIFrame* frame : childLists.CurrentList()) {
6625
0
      if (nsSliderFrame* sliderFrame = do_QueryFrame(frame)) {
6626
0
        sliderFrame->AsyncScrollbarDragRejected();
6627
0
      }
6628
0
    }
6629
0
  }
6630
0
}
6631
6632
void
6633
ScrollFrameHelper::AsyncScrollbarDragRejected()
6634
0
{
6635
0
  // We don't get told which scrollbar requested the async drag,
6636
0
  // so we notify both.
6637
0
  ::AsyncScrollbarDragRejected(mHScrollbarBox);
6638
0
  ::AsyncScrollbarDragRejected(mVScrollbarBox);
6639
0
}