Bug 1765615 - Handle most changes to CSS `contain` and `content-visibility` without needing to reconstruct frames. r=layout-reviewers,emilio

Right now, we reconstruct frames in response to a change in the CSS
`contain` property or `content-visibility`. This patch tries to optimize
this a bit:

1. Updates involving style containment change continue to force a
  reconstruction, due to the need to handle counters/quotes.

2. Updates involving paint/layout containment change only force a
   reconstruction if it's needed to handle absolutely/fixed
   positioned descendants or floats (for this one, see also bug 1874826).

3. Other containment changes will only force a reflow and repaint.

Per the CSS contain spec, layout, style and paint containments are
enabled for `content-visibility: hidden` and `content-visibility: auto`.
As a consequence, changing `content-visibility` between `hidden` and
`auto` values no longer requires reconstruction. Changing between these
values and `visible` may need a reconstruction although authors may
generally avoid that in practice by forcing `style` containment.

Differential Revision: https://phabricator.services.mozilla.com/D197043
This commit is contained in:
Frédéric Wang 2024-01-17 08:22:48 +00:00
Родитель a8d122991e
Коммит 56a3e0d495
12 изменённых файлов: 485 добавлений и 112 удалений

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

@ -2192,8 +2192,6 @@ void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
mPendingScrollAnchorAdjustment.Remove(scrollableFrame);
mPendingScrollResnap.Remove(scrollableFrame);
}
mContentVisibilityAutoFrames.Remove(aFrame);
}
}
@ -11996,15 +11994,12 @@ void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() {
return;
}
bool isRelevantContentChanged = false;
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
isRelevantContentChanged |=
frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
}
if (isRelevantContentChanged) {
if (nsPresContext* presContext = GetPresContext()) {
presContext->UpdateHiddenByContentVisibilityForAnimations();
}
if (nsPresContext* presContext = GetPresContext()) {
presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
}
mContentVisibilityRelevancyToUpdate.clear();
@ -12038,7 +12033,6 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
auto input = DOMIntersectionObserver::ComputeInput(
*mDocument, /* aRoot = */ nullptr, &rootMargin);
bool isRelevantContentChanged = false;
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
auto* element = frame->GetContent()->AsElement();
result.mAnyScrollIntoViewFlag |=
@ -12059,8 +12053,7 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
.Intersects();
element->SetVisibleForContentVisibility(intersects);
if (oldVisibility.isNothing() || *oldVisibility != intersects) {
isRelevantContentChanged |=
frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
}
// 14.2.3.3
@ -12068,10 +12061,8 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
result.mHadInitialDetermination = true;
}
}
if (isRelevantContentChanged) {
if (nsPresContext* presContext = GetPresContext()) {
presContext->UpdateHiddenByContentVisibilityForAnimations();
}
if (nsPresContext* presContext = GetPresContext()) {
presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
}
return result;

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

@ -1748,6 +1748,9 @@ class PresShell final : public nsStubDocumentObserver,
void RegisterContentVisibilityAutoFrame(nsIFrame* aFrame) {
mContentVisibilityAutoFrames.Insert(aFrame);
}
void UnregisterContentVisibilityAutoFrame(nsIFrame* aFrame) {
mContentVisibilityAutoFrames.Remove(aFrame);
}
bool HasContentVisibilityAutoFrames() const {
return !mContentVisibilityAutoFrames.IsEmpty();
}

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

@ -686,7 +686,8 @@ nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
"AddOrRemoveTransform",
"ScrollbarChange",
"UpdateTableCellSpans",
"VisibilityChange"};
"VisibilityChange",
"UpdateBFC"};
static_assert(nsChangeHint_AllHints ==
static_cast<uint32_t>((1ull << ArrayLength(names)) - 1),
"Name list doesn't match change hints.");
@ -1552,6 +1553,31 @@ static void TryToHandleContainingBlockChange(nsChangeHint& aHint,
}
}
static void TryToHandleBlockFormattingContextChange(nsChangeHint& aHint,
nsIFrame* aFrame) {
if (!(aHint & nsChangeHint_UpdateBFC)) {
return;
}
if (aHint & nsChangeHint_ReconstructFrame) {
return;
}
MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) {
if (blockFrame->MaybeHasFloats()) {
// The frame descendants may contain floats that could change their float
// manager, so reconstruct this.
// FIXME(bug 1874826): If we could fix this up rather than reconstructing,
// we could move all this logic to nsBlockFrame::DidSetComputedStyle, and
// remove UpdateBFC.
aHint |= nsChangeHint_ReconstructFrame;
return;
}
blockFrame->AddOrRemoveStateBits(NS_BLOCK_DYNAMIC_BFC,
blockFrame->IsDynamicBFC());
}
}
void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
"Someone forgot a script blocker");
@ -1663,6 +1689,7 @@ void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
TryToDealWithScrollbarChange(hint, content, frame, presContext);
TryToHandleContainingBlockChange(hint, frame);
TryToHandleBlockFormattingContextChange(hint, frame);
if (hint & nsChangeHint_ReconstructFrame) {
// If we ever start passing true here, be careful of restyles
@ -2709,6 +2736,15 @@ enum class ServoPostTraversalFlags : uint32_t {
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags)
static bool IsVisibleForA11y(const ComputedStyle& aStyle) {
return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert();
}
static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) {
return aStyle.StyleDisplay()->mContentVisibility !=
StyleContentVisibility::Hidden;
}
// Send proper accessibility notifications and return post traversal
// flags for kids.
static ServoPostTraversalFlags SendA11yNotifications(
@ -2744,8 +2780,9 @@ static ServoPostTraversalFlags SendA11yNotifications(
}
bool needsNotify = false;
const bool isVisible = aNewStyle.StyleVisibility()->IsVisible() &&
!aNewStyle.StyleUI()->IsInert();
const bool isVisible = IsVisibleForA11y(aNewStyle);
const bool wasVisible = IsVisibleForA11y(aOldStyle);
if (aFlags & Flags::SendA11yNotificationsIfShown) {
if (!isVisible) {
// Propagate the sending-if-shown flag to descendants.
@ -2756,11 +2793,13 @@ static ServoPostTraversalFlags SendA11yNotifications(
// this element is visible, so we need to add it back.
needsNotify = true;
} else {
// If we shouldn't skip in any case, we need to check whether our
// own visibility has changed.
const bool wasVisible = aOldStyle.StyleVisibility()->IsVisible() &&
!aOldStyle.StyleUI()->IsInert();
needsNotify = wasVisible != isVisible;
// If we shouldn't skip in any case, we need to check whether our own
// visibility has changed.
// Also notify if the subtree visibility change due to content-visibility.
const bool isSubtreeVisible = IsSubtreeVisibleForA11y(aNewStyle);
const bool wasSubtreeVisible = IsSubtreeVisibleForA11y(aOldStyle);
needsNotify =
wasVisible != isVisible || wasSubtreeVisible != isSubtreeVisible;
}
if (needsNotify) {
@ -2772,10 +2811,12 @@ static ServoPostTraversalFlags SendA11yNotifications(
// descendants, so we should just skip them from notifying.
return Flags::SkipA11yNotifications;
}
// Remove the subtree of this invisible element, and ask any shown
// descendant to add themselves back.
accService->ContentRemoved(presShell, aElement);
return Flags::SendA11yNotificationsIfShown;
if (wasVisible) {
// Remove the subtree of this invisible element, and ask any shown
// descendant to add themselves back.
accService->ContentRemoved(presShell, aElement);
return Flags::SendA11yNotificationsIfShown;
}
}
#endif
@ -3295,6 +3336,7 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
doc->ClearServoRestyleRoot();
presContext->FinishedContainerQueryUpdate();
presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
ClearSnapshots();
styleSet->AssertTreeIsClean();

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

@ -236,6 +236,11 @@ enum nsChangeHint : uint32_t {
*/
nsChangeHint_VisibilityChange = 1u << 28,
/**
* Indicates that NS_BLOCK_DYNAMIC_BFC should be updated.
*/
nsChangeHint_UpdateBFC = 1u << 29,
// IMPORTANT NOTE: When adding a new hint, you will need to add it to
// one of:
//
@ -251,7 +256,7 @@ enum nsChangeHint : uint32_t {
/**
* Dummy hint value for all hints. It exists for compile time check.
*/
nsChangeHint_AllHints = uint32_t((1ull << 29) - 1),
nsChangeHint_AllHints = uint32_t((1ull << 30) - 1),
};
// Redefine these operators to return nothing. This will catch any use
@ -332,7 +337,8 @@ inline nsChangeHint operator^=(nsChangeHint& aLeft, nsChangeHint aRight) {
nsChangeHint_UpdateOverflow | nsChangeHint_UpdateParentOverflow | \
nsChangeHint_UpdatePostTransformOverflow | \
nsChangeHint_UpdateTableCellSpans | nsChangeHint_UpdateTransformLayer | \
nsChangeHint_UpdateUsesOpacity | nsChangeHint_AddOrRemoveTransform)
nsChangeHint_UpdateUsesOpacity | nsChangeHint_AddOrRemoveTransform | \
nsChangeHint_UpdateBFC)
// The change hints that are sometimes considered to be handled for descendants.
#define nsChangeHint_Hints_SometimesHandledForDescendants \

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

@ -293,6 +293,7 @@ nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType)
mHadFirstContentfulPaint(false),
mHadNonTickContentfulPaint(false),
mHadContentfulPaintComposite(false),
mNeedsToUpdateHiddenByContentVisibilityForAnimations(false),
mUserInputEventsAllowed(false),
#ifdef DEBUG
mInitialized(false),
@ -1039,11 +1040,15 @@ void nsPresContext::DetachPresShell() {
struct QueryContainerState {
nsSize mSize;
WritingMode mWm;
StyleContainerType mType;
nscoord GetInlineSize() const { return LogicalSize(mWm, mSize).ISize(mWm); }
bool Changed(const QueryContainerState& aNewState, StyleContainerType aType) {
switch (aType) {
bool Changed(const QueryContainerState& aNewState) {
if (mType != aNewState.mType) {
return true;
}
switch (mType) {
case StyleContainerType::Normal:
break;
case StyleContainerType::Size:
@ -1086,13 +1091,13 @@ bool nsPresContext::UpdateContainerQueryStyles() {
auto type = frame->StyleDisplay()->mContainerType;
MOZ_ASSERT(type != StyleContainerType::Normal,
"Non-container frames shouldn't be in this type");
"Non-container frames shouldn't be in this set");
const QueryContainerState newState{frame->GetSize(),
frame->GetWritingMode()};
frame->GetWritingMode(), type};
QueryContainerState* oldState = frame->GetProperty(ContainerState());
const bool changed = !oldState || oldState->Changed(newState, type);
const bool changed = !oldState || oldState->Changed(newState);
// Make sure to update the state regardless. It's cheap and it keeps tracks
// of both axes correctly even if only one axis is contained.
@ -3093,7 +3098,9 @@ PerformanceMainThread* nsPresContext::GetPerformanceMainThread() const {
return nullptr;
}
void nsPresContext::UpdateHiddenByContentVisibilityForAnimations() {
void nsPresContext::DoUpdateHiddenByContentVisibilityForAnimations() {
MOZ_ASSERT(NeedsToUpdateHiddenByContentVisibilityForAnimations());
mNeedsToUpdateHiddenByContentVisibilityForAnimations = false;
mDocument->UpdateHiddenByContentVisibilityForAnimations();
TimelineManager()->UpdateHiddenByContentVisibilityForAnimations();
}

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

@ -1093,9 +1093,20 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
return mFontPaletteValueSet;
}
void UpdateHiddenByContentVisibilityForAnimations();
bool NeedsToUpdateHiddenByContentVisibilityForAnimations() const {
return mNeedsToUpdateHiddenByContentVisibilityForAnimations;
}
void SetNeedsToUpdateHiddenByContentVisibilityForAnimations() {
mNeedsToUpdateHiddenByContentVisibilityForAnimations = true;
}
void UpdateHiddenByContentVisibilityForAnimationsIfNeeded() {
if (mNeedsToUpdateHiddenByContentVisibilityForAnimations) {
DoUpdateHiddenByContentVisibilityForAnimations();
}
}
protected:
void DoUpdateHiddenByContentVisibilityForAnimations();
friend class nsRunnableMethod<nsPresContext>;
void ThemeChangedInternal();
void RefreshSystemMetrics();
@ -1389,6 +1400,9 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
// Has NotifyDidPaintForSubtree been called for a contentful paint?
unsigned mHadContentfulPaintComposite : 1;
// Whether we might need to update c-v state for animations.
unsigned mNeedsToUpdateHiddenByContentVisibilityForAnimations : 1;
unsigned mUserInputEventsAllowed : 1;
#ifdef DEBUG
unsigned mInitialized : 1;

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

@ -7760,7 +7760,7 @@ void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
AddStateBits(NS_BLOCK_STATIC_BFC);
}
if (StyleDisplay()->IsContainPaint() || StyleDisplay()->IsContainLayout()) {
if (IsDynamicBFC()) {
AddStateBits(NS_BLOCK_DYNAMIC_BFC);
}

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

@ -654,13 +654,6 @@ class nsBlockFrame : public nsContainerFrame {
*/
bool IsInLineClampContext() const;
protected:
/** grab overflow lines from this block's prevInFlow, and make them
* part of this block's mLines list.
* @return true if any lines were drained.
*/
bool DrainOverflowLines();
/**
* @return false iff this block does not have a float on any child list.
* This function is O(1).
@ -680,6 +673,21 @@ class nsBlockFrame : public nsContainerFrame {
return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
/**
* @return true if NS_BLOCK_DYNAMIC_BFC should be set on this frame.
*/
bool IsDynamicBFC() const {
return StyleDisplay()->IsContainPaint() ||
StyleDisplay()->IsContainLayout();
}
protected:
/** grab overflow lines from this block's prevInFlow, and make them
* part of this block's mLines list.
* @return true if any lines were drained.
*/
bool DrainOverflowLines();
/**
* Moves frames from our PushedFloats list back into our mFloats list.
*/

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

@ -753,25 +753,35 @@ void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
void nsIFrame::InitPrimaryFrame() {
MOZ_ASSERT(IsPrimaryFrame());
HandlePrimaryFrameStyleChange(nullptr);
}
void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) {
const nsStyleDisplay* disp = StyleDisplay();
if (disp->mContainerType != StyleContainerType::Normal) {
PresContext()->RegisterContainerQueryFrame(this);
const nsStyleDisplay* oldDisp =
aOldStyle ? aOldStyle->StyleDisplay() : nullptr;
if (!oldDisp || oldDisp->mContainerType != disp->mContainerType) {
auto* pc = PresContext();
if (disp->mContainerType != StyleContainerType::Normal) {
pc->RegisterContainerQueryFrame(this);
} else {
pc->UnregisterContainerQueryFrame(this);
}
}
if (StyleDisplay()->ContentVisibility(*this) ==
StyleContentVisibility::Auto) {
PresShell()->RegisterContentVisibilityAutoFrame(this);
} else if (auto* element = Element::FromNodeOrNull(GetContent())) {
element->ClearContentRelevancy();
const auto cv = disp->ContentVisibility(*this);
if (!oldDisp || oldDisp->ContentVisibility(*this) != cv) {
if (cv == StyleContentVisibility::Auto) {
PresShell()->RegisterContentVisibilityAutoFrame(this);
} else {
if (auto* element = Element::FromNodeOrNull(GetContent())) {
element->ClearContentRelevancy();
}
PresShell()->UnregisterContentVisibilityAutoFrame(this);
}
PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
}
// TODO(mrobinson): Once bug 1765615 is fixed, this should be called on
// layout changes. In addition, when `content-visibility: auto` is implemented
// this should also be called when scrolling or focus causes content to be
// skipped or unskipped.
UpdateAnimationVisibility();
HandleLastRememberedSize();
}
@ -796,19 +806,21 @@ void nsIFrame::Destroy(DestroyContext& aContext) {
}
}
if (disp->mContainerType != StyleContainerType::Normal) {
PresContext()->UnregisterContainerQueryFrame(this);
}
nsPresContext* presContext = PresContext();
mozilla::PresShell* presShell = presContext->GetPresShell();
if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
if (nsPlaceholderFrame* placeholder = GetPlaceholderFrame()) {
placeholder->SetOutOfFlowFrame(nullptr);
}
}
nsPresContext* pc = PresContext();
mozilla::PresShell* ps = pc->GetPresShell();
if (IsPrimaryFrame()) {
if (disp->mContainerType != StyleContainerType::Normal) {
pc->UnregisterContainerQueryFrame(this);
}
if (disp->ContentVisibility(*this) == StyleContentVisibility::Auto) {
ps->UnregisterContentVisibilityAutoFrame(this);
}
// This needs to happen before we clear our Properties() table.
ActiveLayerTracker::TransferActivityToContent(this, mContent);
}
@ -826,7 +838,7 @@ void nsIFrame::Destroy(DestroyContext& aContext) {
// If no new frame for this element is created by the end of the
// restyling process, stop animations and transitions for this frame
RestyleManager::AnimationsWithDestroyedFrame* adf =
presContext->RestyleManager()->GetAnimationsWithDestroyedFrame();
pc->RestyleManager()->GetAnimationsWithDestroyedFrame();
// AnimationsWithDestroyedFrame only lives during the restyling process.
if (adf) {
adf->Put(mContent, mComputedStyle);
@ -841,12 +853,12 @@ void nsIFrame::Destroy(DestroyContext& aContext) {
DisableVisibilityTracking();
// Ensure that we're not in the approximately visible list anymore.
PresContext()->GetPresShell()->RemoveFrameFromApproximatelyVisibleList(this);
ps->RemoveFrameFromApproximatelyVisibleList(this);
presShell->NotifyDestroyingFrame(this);
ps->NotifyDestroyingFrame(this);
if (HasAnyStateBits(NS_FRAME_EXTERNAL_REFERENCE)) {
presShell->ClearFrameRefs(this);
ps->ClearFrameRefs(this);
}
nsView* view = GetView();
@ -884,7 +896,7 @@ void nsIFrame::Destroy(DestroyContext& aContext) {
#ifdef DEBUG
{
nsIFrame* rootFrame = presShell->GetRootFrame();
nsIFrame* rootFrame = ps->GetRootFrame();
MOZ_ASSERT(rootFrame);
if (this != rootFrame) {
auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(rootFrame);
@ -904,7 +916,7 @@ void nsIFrame::Destroy(DestroyContext& aContext) {
// Now that we're totally cleaned out, we need to add ourselves to
// the presshell's recycler.
presShell->FreeFrame(id, this);
ps->FreeFrame(id, this);
}
std::pair<int32_t, int32_t> nsIFrame::GetOffsets() const {
@ -1375,7 +1387,8 @@ void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
}
if (IsPrimaryFrame()) {
HandleLastRememberedSize();
MOZ_ASSERT(aOldComputedStyle);
HandlePrimaryFrameStyleChange(aOldComputedStyle);
}
RemoveStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS | NS_FRAME_SIMPLE_DISPLAYLIST);
@ -7107,6 +7120,7 @@ bool nsIFrame::UpdateIsRelevantContent(
}
HandleLastRememberedSize();
PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
PresShell()->FrameNeedsReflow(
this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
InvalidateFrame();
@ -11445,29 +11459,6 @@ void nsIFrame::UpdateVisibleDescendantsState() {
}
}
void nsIFrame::UpdateAnimationVisibility() {
auto* animationCollection = AnimationCollection<CSSAnimation>::Get(this);
auto* transitionCollection = AnimationCollection<CSSTransition>::Get(this);
if ((!animationCollection || animationCollection->mAnimations.IsEmpty()) &&
(!transitionCollection || transitionCollection->mAnimations.IsEmpty())) {
return;
}
bool hidden = IsHiddenByContentVisibilityOnAnyAncestor();
if (animationCollection) {
for (auto& animation : animationCollection->mAnimations) {
animation->SetHiddenByContentVisibility(hidden);
}
}
if (transitionCollection) {
for (auto& transition : transitionCollection->mAnimations) {
transition->SetHiddenByContentVisibility(hidden);
}
}
}
nsIFrame::PhysicalAxes nsIFrame::ShouldApplyOverflowClipping(
const nsStyleDisplay* aDisp) const {
MOZ_ASSERT(aDisp == StyleDisplay(), "Wrong display struct");

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

@ -2388,6 +2388,13 @@ class nsIFrame : public nsQueryFrame {
* Called when this frame becomes primary for mContent.
*/
void InitPrimaryFrame();
/**
* Called when the primary frame style changes.
*
* Kind of like DidSetComputedStyle, but the first computed style is set
* before SetPrimaryFrame, so we need this tweak.
*/
void HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle);
public:
/**
@ -3304,8 +3311,7 @@ class nsIFrame : public nsQueryFrame {
* https://drafts.csswg.org/css-contain-2/#relevant-to-the-user.
* Returns true if the over-all relevancy changed.
*/
[[nodiscard]] bool UpdateIsRelevantContent(
const ContentRelevancy& aRelevancyToUpdate);
bool UpdateIsRelevantContent(const ContentRelevancy& aRelevancyToUpdate);
/**
* Get the "type" of the frame.
@ -4696,8 +4702,6 @@ class nsIFrame : public nsQueryFrame {
// Update mAllDescendantsAreInvisible flag for this frame and ancestors.
void UpdateVisibleDescendantsState();
void UpdateAnimationVisibility();
/**
* If this returns true, the frame it's called on should get the
* NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly

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

@ -2248,24 +2248,12 @@ static bool AppearanceValueAffectsFrames(StyleAppearance aAppearance,
nsChangeHint nsStyleDisplay::CalcDifference(
const nsStyleDisplay& aNewData, const ComputedStyle& aOldStyle) const {
if (mDisplay != aNewData.mDisplay || mContain != aNewData.mContain ||
if (mDisplay != aNewData.mDisplay ||
(mFloat == StyleFloat::None) != (aNewData.mFloat == StyleFloat::None) ||
mTopLayer != aNewData.mTopLayer || mResize != aNewData.mResize) {
return nsChangeHint_ReconstructFrame;
}
// `content-visibility` can impact whether or not this frame has containment,
// so we reconstruct the frame like we do above.
// TODO: We should avoid reconstruction here, per bug 1765615.
if (mContentVisibility != aNewData.mContentVisibility) {
return nsChangeHint_ReconstructFrame;
}
// Same issue as above for now.
if (mContainerType != aNewData.mContainerType) {
return nsChangeHint_ReconstructFrame;
}
auto oldAppearance = EffectiveAppearance();
auto newAppearance = aNewData.EffectiveAppearance();
if (oldAppearance != newAppearance) {
@ -2279,6 +2267,21 @@ nsChangeHint nsStyleDisplay::CalcDifference(
}
auto hint = nsChangeHint(0);
const auto containmentDiff =
mEffectiveContainment ^ aNewData.mEffectiveContainment;
if (containmentDiff) {
if (containmentDiff & StyleContain::STYLE) {
// Style containment affects counters so we need to re-frame.
return nsChangeHint_ReconstructFrame;
}
if (containmentDiff & (StyleContain::PAINT | StyleContain::LAYOUT)) {
// Paint and layout containment boxes are absolutely/fixed positioning
// containers and establishes an independent formatting context.
hint |= nsChangeHint_UpdateContainingBlock | nsChangeHint_UpdateBFC;
}
// The other container types only need a reflow.
hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
}
if (mPosition != aNewData.mPosition) {
if (IsAbsolutelyPositionedStyle() ||
aNewData.IsAbsolutelyPositionedStyle()) {
@ -2511,8 +2514,13 @@ nsChangeHint nsStyleDisplay::CalcDifference(
// TODO(emilio): Figure out change hints for container-name, maybe it needs to
// be handled by the style system as a special-case (since it changes
// container-query selection on descendants).
// container-type / contain / content-visibility are handled by the
// mEffectiveContainment check.
if (!hint && (mWillChange != aNewData.mWillChange ||
mOverflowAnchor != aNewData.mOverflowAnchor ||
mContentVisibility != aNewData.mContentVisibility ||
mContainerType != aNewData.mContainerType ||
mContain != aNewData.mContain ||
mContainerName != aNewData.mContainerName)) {
hint |= nsChangeHint_NeutralChange;
}

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

@ -34,6 +34,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
#tableCellWithAbsPosChild {
display: table-cell;
}
.blockmargin {
margin: 25px 0;
}
</style>
<div id="display">
<div id="content">
@ -76,6 +79,24 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
<div id="tableCellWithAbsPosChild">
<div style="position: absolute"></div>
</div>
<div id="containNode">
<div></div>
</div>
<div id="containNodeWithAbsPosChild">
<div style="position: absolute"></div>
</div>
<div id="containNodeWithFixedPosChild">
<div style="position: fixed"></div>
</div>
<div id="containNodeWithFloatingChild">
<div style="float: left"></div>
</div>
<div id="containNodeWithMarginCollapsing" class="blockmargin">
<div class="blockmargin"></div>
</div>
<div id="contentVisibilityNode">
<div></div>
</div>
</div>
<pre id="test">
<script>
@ -633,6 +654,284 @@ const gTestcases = [
expectConstruction: false,
expectReflow: false,
},
// Style containment affects counters so we need to re-frame.
// For other containments, we only need to reflow.
{
elem: containNode,
beforeStyle: "contain: none",
afterStyle: "contain: style",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: style",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: none",
afterStyle: "contain: paint",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: paint",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: none",
afterStyle: "contain: layout",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: layout",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: none",
afterStyle: "contain: size",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNode,
beforeStyle: "contain: size",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
// paint/layout containment boxes establish an absolute positioning
// containing block, so need a re-frame to handle abs/fixed positioned boxes.
{
elem: containNodeWithAbsPosChild,
beforeStyle: "contain: none",
afterStyle: "contain: paint",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithAbsPosChild,
beforeStyle: "contain: paint",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithAbsPosChild,
beforeStyle: "contain: none",
afterStyle: "contain: layout",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithAbsPosChild,
beforeStyle: "contain: layout",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithAbsPosChild,
beforeStyle: "contain: none",
afterStyle: "contain: size",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithAbsPosChild,
beforeStyle: "contain: size",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithFixedPosChild,
beforeStyle: "contain: none",
afterStyle: "contain: paint",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFixedPosChild,
beforeStyle: "contain: paint",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFixedPosChild,
beforeStyle: "contain: none",
afterStyle: "contain: layout",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFixedPosChild,
beforeStyle: "contain: layout",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFixedPosChild,
beforeStyle: "contain: none",
afterStyle: "contain: size",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithFixedPosChild,
beforeStyle: "contain: size",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
// paint/layout containment boxes establish an independent formatting context,
// so need a re-frame to handle floated boxes.
// FIXME(bug 1874826): Try and avoid a full construction.
{
elem: containNodeWithFloatingChild,
beforeStyle: "contain: none",
afterStyle: "contain: paint",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFloatingChild,
beforeStyle: "contain: paint",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFloatingChild,
beforeStyle: "contain: none",
afterStyle: "contain: layout",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFloatingChild,
beforeStyle: "contain: layout",
afterStyle: "contain: none",
expectConstruction: true,
expectReflow: true,
},
{
elem: containNodeWithFloatingChild,
beforeStyle: "contain: none",
afterStyle: "contain: size",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithFloatingChild,
beforeStyle: "contain: size",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
// However, a reflow is enough to update margin collapsing.
{
elem: containNodeWithMarginCollapsing,
beforeStyle: "contain: none",
afterStyle: "contain: paint",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithMarginCollapsing,
beforeStyle: "contain: paint",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithMarginCollapsing,
beforeStyle: "contain: none",
afterStyle: "contain: layout",
expectConstruction: false,
expectReflow: true,
},
{
elem: containNodeWithMarginCollapsing,
beforeStyle: "contain: layout",
afterStyle: "contain: none",
expectConstruction: false,
expectReflow: true,
},
// content-visibility: auto/hidden implies style containment contrary to
// content-visibility: visible, so we generally need a re-frame (see above)
// when going from one case to the other.
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: visible",
afterStyle: "content-visibility: hidden",
expectConstruction: true,
expectReflow: true,
},
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: hidden",
afterStyle: "content-visibility: visible",
expectConstruction: true,
expectReflow: true,
},
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: visible",
afterStyle: "content-visibility: auto",
expectConstruction: true,
expectReflow: true,
},
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: auto",
afterStyle: "content-visibility: visible",
expectConstruction: true,
expectReflow: true,
},
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: hidden",
afterStyle: "content-visibility: auto",
expectConstruction: false,
expectReflow: true,
},
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: auto",
afterStyle: "content-visibility: hidden",
expectConstruction: false,
expectReflow: true,
},
// However that's not the case if we force style containment explicitly.
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: visible; contain: style",
afterStyle: "content-visibility: hidden; contain: style",
expectConstruction: false,
expectReflow: true,
},
{
elem: contentVisibilityNode,
beforeStyle: "content-visibility: hidden; contain: style",
afterStyle: "content-visibility: visible; contain: style",
expectConstruction: false,
expectReflow: true,
},
];
// Helper function to let us call either "is" or "isnot" & assemble