зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1592474 - Add some heuristics to disable scroll anchoring in pathological cases. r=dholbert
The idea of these are not to penalize legit uses of scroll anchoring, and catching pathological cases fast. The current algorithm I thought of is just whether the average of all the consecutive scroll anchoring adjustments is less than a given threshold. If the average adjustment is close to zero and the user is not scrolling, it means that we're not making much progress. It is important that zero adjustments don't get counted, since those are common during window resizes and don't have side-effects anyway. Exact number may need tuning, let me know if you want it nightly-and-early-beta-only for now or something. Depends on D51038 Differential Revision: https://phabricator.services.mozilla.com/D51024 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
4510f3e34a
Коммит
f70cc8c005
|
@ -23,15 +23,15 @@ using namespace mozilla::dom;
|
|||
#define ANCHOR_LOG(...)
|
||||
|
||||
/*
|
||||
#define ANCHOR_LOG(fmt, ...) \
|
||||
printf_stderr("ANCHOR(%p, %s): " fmt, this, \
|
||||
Frame() \
|
||||
->PresContext() \
|
||||
->Document() \
|
||||
->GetDocumentURI() \
|
||||
->GetSpecOrDefault() \
|
||||
.get(), \
|
||||
##__VA_ARGS__)
|
||||
#define ANCHOR_LOG(fmt, ...) \
|
||||
printf_stderr("ANCHOR(%p, %s, root: %d): " fmt, this, \
|
||||
Frame() \
|
||||
->PresContext() \
|
||||
->Document() \
|
||||
->GetDocumentURI() \
|
||||
->GetSpecOrDefault() \
|
||||
.get(), \
|
||||
mScrollFrame->mIsRoot, ##__VA_ARGS__)
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -41,6 +41,7 @@ ScrollAnchorContainer::ScrollAnchorContainer(ScrollFrameHelper* aScrollFrame)
|
|||
: mScrollFrame(aScrollFrame),
|
||||
mAnchorNode(nullptr),
|
||||
mLastAnchorOffset(0),
|
||||
mDisabled(false),
|
||||
mAnchorNodeIsDirty(true),
|
||||
mApplyingAnchorAdjustment(false),
|
||||
mSuppressAnchorAdjustment(false) {}
|
||||
|
@ -199,7 +200,7 @@ void ScrollAnchorContainer::SelectAnchor() {
|
|||
MOZ_ASSERT(mScrollFrame->mScrolledFrame);
|
||||
MOZ_ASSERT(mAnchorNodeIsDirty);
|
||||
|
||||
if (!StaticPrefs::layout_css_scroll_anchoring_enabled()) {
|
||||
if (mDisabled || !StaticPrefs::layout_css_scroll_anchoring_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -285,6 +286,56 @@ void ScrollAnchorContainer::UserScrolled() {
|
|||
return;
|
||||
}
|
||||
InvalidateAnchor();
|
||||
mConsecutiveScrollAnchoringAdjustments = SaturateUint32(0);
|
||||
mConsecutiveScrollAnchoringAdjustmentLength = 0;
|
||||
}
|
||||
|
||||
void ScrollAnchorContainer::AdjustmentMade(nscoord aAdjustment) {
|
||||
// A reasonably large number of times that we want to check for this. If we
|
||||
// haven't hit this limit after these many attempts we assume we'll never hit
|
||||
// it.
|
||||
//
|
||||
// This is to prevent the number getting too large and making the limit round
|
||||
// to zero by mere precision error.
|
||||
//
|
||||
// 100k should be enough for anyone :)
|
||||
static const uint32_t kAnchorCheckCountLimit = 100000;
|
||||
|
||||
// Zero-length adjustments are common & don't have side effects, so we don't
|
||||
// want them to consider them here; they'd bias our average towards 0.
|
||||
MOZ_ASSERT(aAdjustment, "Don't call this API for zero-length adjustments");
|
||||
|
||||
mConsecutiveScrollAnchoringAdjustments++;
|
||||
mConsecutiveScrollAnchoringAdjustmentLength = NSCoordSaturatingAdd(
|
||||
mConsecutiveScrollAnchoringAdjustmentLength, aAdjustment);
|
||||
|
||||
uint32_t maxConsecutiveAdjustments =
|
||||
StaticPrefs::layout_css_scroll_anchoring_max_consecutive_adjustments();
|
||||
|
||||
if (!maxConsecutiveAdjustments) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t consecutiveAdjustments =
|
||||
mConsecutiveScrollAnchoringAdjustments.value();
|
||||
if (consecutiveAdjustments < maxConsecutiveAdjustments ||
|
||||
consecutiveAdjustments > kAnchorCheckCountLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto cssPixels =
|
||||
CSSPixel::FromAppUnits(mConsecutiveScrollAnchoringAdjustmentLength);
|
||||
double average = double(cssPixels) / consecutiveAdjustments;
|
||||
uint32_t minAverage = StaticPrefs::
|
||||
layout_css_scroll_anchoring_min_average_adjustment_threshold();
|
||||
if (MOZ_UNLIKELY(std::abs(average) < double(minAverage))) {
|
||||
ANCHOR_LOG(
|
||||
"Disabled scroll anchoring for container: "
|
||||
"%f average, %f total out of %u consecutive adjustments\n",
|
||||
average, float(cssPixels), mConsecutiveScrollAnchoringAdjustments);
|
||||
|
||||
mDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollAnchorContainer::SuppressAdjustments() {
|
||||
|
@ -302,7 +353,7 @@ void ScrollAnchorContainer::InvalidateAnchor(ScheduleSelection aSchedule) {
|
|||
mAnchorNodeIsDirty = true;
|
||||
mLastAnchorOffset = 0;
|
||||
|
||||
if (aSchedule == ScheduleSelection::No ||
|
||||
if (mDisabled || aSchedule == ScheduleSelection::No ||
|
||||
!StaticPrefs::layout_css_scroll_anchoring_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
@ -315,14 +366,15 @@ void ScrollAnchorContainer::Destroy() {
|
|||
}
|
||||
|
||||
void ScrollAnchorContainer::ApplyAdjustments() {
|
||||
if (!mAnchorNode || mAnchorNodeIsDirty ||
|
||||
if (!mAnchorNode || mAnchorNodeIsDirty || mDisabled ||
|
||||
mScrollFrame->HasPendingScrollRestoration() ||
|
||||
mScrollFrame->IsProcessingScrollEvent() ||
|
||||
mScrollFrame->IsProcessingAsyncScroll()) {
|
||||
ANCHOR_LOG(
|
||||
"Ignoring post-reflow (anchor=%p, dirty=%d, pendingRestoration=%d, "
|
||||
"scrollevent=%d asyncScroll=%d pendingSuppression=%d container=%p).\n",
|
||||
mAnchorNode, mAnchorNodeIsDirty,
|
||||
"Ignoring post-reflow (anchor=%p, dirty=%d, disabled=%d, "
|
||||
"pendingRestoration=%d, scrollevent=%d, asyncScroll=%d, "
|
||||
"pendingSuppression=%d, container=%p).\n",
|
||||
mAnchorNode, mAnchorNodeIsDirty, mDisabled,
|
||||
mScrollFrame->HasPendingScrollRestoration(),
|
||||
mScrollFrame->IsProcessingScrollEvent(),
|
||||
mScrollFrame->IsProcessingAsyncScroll(), mSuppressAnchorAdjustment,
|
||||
|
@ -357,6 +409,8 @@ void ScrollAnchorContainer::ApplyAdjustments() {
|
|||
ANCHOR_LOG("Applying anchor adjustment of %d in %s with anchor %p.\n",
|
||||
logicalAdjustment, writingMode.DebugString(), mAnchorNode);
|
||||
|
||||
AdjustmentMade(logicalAdjustment);
|
||||
|
||||
nsPoint physicalAdjustment;
|
||||
switch (writingMode.GetBlockDir()) {
|
||||
case WritingMode::eBlockTB: {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define mozilla_layout_ScrollAnchorContainer_h_
|
||||
|
||||
#include "nsPoint.h"
|
||||
#include "mozilla/Saturate.h"
|
||||
|
||||
class nsFrameList;
|
||||
class nsIFrame;
|
||||
|
@ -129,6 +130,10 @@ class ScrollAnchorContainer final {
|
|||
// anchor node, if one was found in this child list, or null otherwise.
|
||||
nsIFrame* FindAnchorInList(const nsFrameList& aFrameList) const;
|
||||
|
||||
// Notes that a given adjustment has happened, and maybe disables scroll
|
||||
// anchoring on this scroller altogether based on various prefs.
|
||||
void AdjustmentMade(nscoord aAdjustment);
|
||||
|
||||
// The owner of this scroll anchor container
|
||||
ScrollFrameHelper* mScrollFrame;
|
||||
|
||||
|
@ -143,6 +148,20 @@ class ScrollAnchorContainer final {
|
|||
// the anchor node in the same relative position
|
||||
nscoord mLastAnchorOffset;
|
||||
|
||||
// The number of consecutive scroll anchoring adjustments that have happened
|
||||
// without a user scroll.
|
||||
SaturateUint32 mConsecutiveScrollAnchoringAdjustments{0};
|
||||
|
||||
// The total length that has been adjusted by all the consecutive adjustments
|
||||
// referenced above. Note that this is a sum, so that oscillating adjustments
|
||||
// average towards zero.
|
||||
nscoord mConsecutiveScrollAnchoringAdjustmentLength{0};
|
||||
|
||||
// True if we've been disabled by the heuristic controlled by
|
||||
// layout.css.scroll-anchoring.max-consecutive-adjustments and
|
||||
// layout.css.scroll-anchoring.min-adjustment-threshold.
|
||||
bool mDisabled : 1;
|
||||
|
||||
// True if we should recalculate our anchor node at the next chance
|
||||
bool mAnchorNodeIsDirty : 1;
|
||||
// True if we are applying a scroll anchor adjustment
|
||||
|
|
|
@ -5266,6 +5266,31 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# Pref to control how many consecutive scroll-anchoring adjustments (since the
|
||||
# most recent user scroll) we'll average, before we consider whether to
|
||||
# automatically turn off scroll anchoring. When we hit this threshold, the
|
||||
# actual decision to disable also depends on the
|
||||
# min-average-adjustment-threshold pref, see below for more details.
|
||||
#
|
||||
# Zero disables the heuristic.
|
||||
- name: layout.css.scroll-anchoring.max-consecutive-adjustments
|
||||
type: uint32_t
|
||||
value: 10
|
||||
mirror: always
|
||||
|
||||
# Pref to control whether we should disable scroll anchoring on a scroller
|
||||
# where at least max-consecutive-adjustments have happened, and which the
|
||||
# average adjustment ends up being less than this number, in CSS pixels.
|
||||
#
|
||||
# So, for example, given max-consecutive-adjustments=10 and
|
||||
# min-average-adjustment-treshold=3, we'll block scroll anchoring if there have
|
||||
# been 10 consecutive adjustments without a user scroll or more, and the
|
||||
# average offset difference between them amount to less than 3 CSS pixels.
|
||||
- name: layout.css.scroll-anchoring.min-average-adjustment-threshold
|
||||
type: uint32_t
|
||||
value: 3
|
||||
mirror: always
|
||||
|
||||
# Pref to control disabling scroll anchoring suppression triggers, see
|
||||
#
|
||||
# https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
|
||||
|
|
Загрузка…
Ссылка в новой задаче