Bug 1305957 part 8 - Suppress scroll offset adjustment when some layout affecting properties are changed on scroll anchor or its ancestors. r=hiro

This commit implements the first half of the heuristics to detect style changes
that could lead to feedback loops with scroll anchoring. [1]

When these style changes are made, a suppression flag is added to the anchor
container to ignore any adjustments that would be made at the next layout flush
and to invalidate the anchor at that time.

[1] https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers

Differential Revision: https://phabricator.services.mozilla.com/D13271

--HG--
extra : rebase_source : db0bf373e51325b7ef537c4c245ede4b4219d0d7
extra : source : d9445a5f3458b560fbdb7aee5faabb5a7a9327f3
This commit is contained in:
Ryan Hunt 2018-11-27 15:38:43 -06:00
Родитель 43e96a0c82
Коммит 43b7ef66cb
5 изменённых файлов: 63 добавлений и 7 удалений

Просмотреть файл

@ -21,7 +21,8 @@ ScrollAnchorContainer::ScrollAnchorContainer(ScrollFrameHelper* aScrollFrame)
mAnchorNode(nullptr),
mLastAnchorPos(0, 0),
mAnchorNodeIsDirty(true),
mApplyingAnchorAdjustment(false) {}
mApplyingAnchorAdjustment(false),
mSuppressAnchorAdjustment(false) {}
ScrollAnchorContainer::~ScrollAnchorContainer() {}

Просмотреть файл

@ -71,6 +71,12 @@ class ScrollAnchorContainer final {
*/
void ApplyAdjustments();
/**
* Notify the scroll anchor container that it should suppress any scroll
* adjustment that may happen after the next layout flush.
*/
void SuppressAdjustments();
/**
* Notify this scroll anchor container that its anchor node should be
* invalidated and recomputed at the next available opportunity.
@ -135,6 +141,8 @@ class ScrollAnchorContainer final {
bool mAnchorNodeIsDirty : 1;
// True if we are applying a scroll anchor adjustment
bool mApplyingAnchorAdjustment : 1;
// True if we should suppress anchor adjustments
bool mSuppressAnchorAdjustment : 1;
};
} // namespace layout

Просмотреть файл

@ -1053,6 +1053,11 @@ void nsIFrame::MarkNeedsDisplayItemRebuild() {
AddAndRemoveImageAssociations(this, oldLayers, newLayers);
if (aOldComputedStyle) {
// Detect style changes that should trigger a scroll anchor adjustment
// suppression.
// https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
bool needAnchorSuppression = false;
// If we detect a change on margin, padding or border, we store the old
// values on the frame itself between now and reflow, so if someone
// calls GetUsed(Margin|Border|Padding)() before the next reflow, we
@ -1062,17 +1067,21 @@ void nsIFrame::MarkNeedsDisplayItemRebuild() {
nsMargin newValue(0, 0, 0, 0);
const nsStyleMargin* oldMargin = aOldComputedStyle->PeekStyleMargin();
if (oldMargin && oldMargin->GetMargin(oldValue)) {
if ((!StyleMargin()->GetMargin(newValue) || oldValue != newValue) &&
!HasProperty(UsedMarginProperty())) {
AddProperty(UsedMarginProperty(), new nsMargin(oldValue));
if (!StyleMargin()->GetMargin(newValue) || oldValue != newValue) {
if (!HasProperty(UsedMarginProperty())) {
AddProperty(UsedMarginProperty(), new nsMargin(oldValue));
}
needAnchorSuppression = true;
}
}
const nsStylePadding* oldPadding = aOldComputedStyle->PeekStylePadding();
if (oldPadding && oldPadding->GetPadding(oldValue)) {
if ((!StylePadding()->GetPadding(newValue) || oldValue != newValue) &&
!HasProperty(UsedPaddingProperty())) {
AddProperty(UsedPaddingProperty(), new nsMargin(oldValue));
if (!StylePadding()->GetPadding(newValue) || oldValue != newValue) {
if (!HasProperty(UsedPaddingProperty())) {
AddProperty(UsedPaddingProperty(), new nsMargin(oldValue));
}
needAnchorSuppression = true;
}
}
@ -1084,6 +1093,31 @@ void nsIFrame::MarkNeedsDisplayItemRebuild() {
AddProperty(UsedBorderProperty(), new nsMargin(oldValue));
}
}
if (mInScrollAnchorChain) {
const nsStylePosition* oldPosition =
aOldComputedStyle->PeekStylePosition();
if (oldPosition &&
(oldPosition->mOffset != StylePosition()->mOffset ||
oldPosition->mWidth != StylePosition()->mWidth ||
oldPosition->mMinWidth != StylePosition()->mMinWidth ||
oldPosition->mMaxWidth != StylePosition()->mMaxWidth ||
oldPosition->mHeight != StylePosition()->mHeight ||
oldPosition->mMinHeight != StylePosition()->mMinHeight ||
oldPosition->mMaxHeight != StylePosition()->mMaxHeight)) {
needAnchorSuppression = true;
}
const nsStyleDisplay* oldDisp = aOldComputedStyle->PeekStyleDisplay();
if (oldDisp && (oldDisp->mPosition != StyleDisplay()->mPosition ||
oldDisp->TransformChanged(*StyleDisplay()))) {
needAnchorSuppression = true;
}
}
if (mInScrollAnchorChain && needAnchorSuppression) {
ScrollAnchorContainer::FindFor(this)->SuppressAdjustments();
}
}
ImageLoader* imageLoader = PresContext()->Document()->StyleImageLoader();

Просмотреть файл

@ -3147,6 +3147,12 @@ void nsStyleDisplay::FinishStyle(nsPresContext* aPresContext,
GenerateCombinedIndividualTransform();
}
static inline bool TransformListChanged(
const RefPtr<nsCSSValueSharedList>& aList,
const RefPtr<nsCSSValueSharedList>& aNewList) {
return !aList != !aNewList || (aList && *aList != *aNewList);
}
static inline nsChangeHint CompareTransformValues(
const RefPtr<nsCSSValueSharedList>& aList,
const RefPtr<nsCSSValueSharedList>& aNewList) {
@ -3425,6 +3431,11 @@ nsChangeHint nsStyleDisplay::CalcDifference(
return hint;
}
bool nsStyleDisplay::TransformChanged(const nsStyleDisplay& aNewData) const {
return TransformListChanged(mSpecifiedTransform,
aNewData.mSpecifiedTransform);
}
void nsStyleDisplay::GenerateCombinedIndividualTransform() {
// FIXME(emilio): This should probably be called from somewhere like what we
// do for image layers, instead of FinishStyle.

Просмотреть файл

@ -1881,6 +1881,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay {
nsChangeHint CalcDifference(const nsStyleDisplay& aNewData) const;
bool TransformChanged(const nsStyleDisplay& aNewData) const;
// We guarantee that if mBinding is non-null, so are mBinding->GetURI() and
// mBinding->mOriginPrincipal.
RefPtr<mozilla::css::URLValue> mBinding;