зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a8d122991e
Коммит
56a3e0d495
|
@ -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);
|
||||
}
|
||||
if (isRelevantContentChanged) {
|
||||
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
presContext->UpdateHiddenByContentVisibilityForAnimations();
|
||||
}
|
||||
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,7 +12053,6 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
|||
.Intersects();
|
||||
element->SetVisibleForContentVisibility(intersects);
|
||||
if (oldVisibility.isNothing() || *oldVisibility != intersects) {
|
||||
isRelevantContentChanged |=
|
||||
frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
|
||||
}
|
||||
|
||||
|
@ -12068,10 +12061,8 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
|||
result.mHadInitialDetermination = true;
|
||||
}
|
||||
}
|
||||
if (isRelevantContentChanged) {
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
presContext->UpdateHiddenByContentVisibilityForAnimations();
|
||||
}
|
||||
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,11 +2811,13 @@ static ServoPostTraversalFlags SendA11yNotifications(
|
|||
// descendants, so we should just skip them from notifying.
|
||||
return Flags::SkipA11yNotifications;
|
||||
}
|
||||
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
|
||||
|
||||
return Flags::Empty;
|
||||
|
@ -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,24 +753,34 @@ void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
|
|||
|
||||
void nsIFrame::InitPrimaryFrame() {
|
||||
MOZ_ASSERT(IsPrimaryFrame());
|
||||
const nsStyleDisplay* disp = StyleDisplay();
|
||||
|
||||
if (disp->mContainerType != StyleContainerType::Normal) {
|
||||
PresContext()->RegisterContainerQueryFrame(this);
|
||||
HandlePrimaryFrameStyleChange(nullptr);
|
||||
}
|
||||
|
||||
if (StyleDisplay()->ContentVisibility(*this) ==
|
||||
StyleContentVisibility::Auto) {
|
||||
void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) {
|
||||
const nsStyleDisplay* disp = StyleDisplay();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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())) {
|
||||
} else {
|
||||
if (auto* element = Element::FromNodeOrNull(GetContent())) {
|
||||
element->ClearContentRelevancy();
|
||||
}
|
||||
|
||||
// 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();
|
||||
PresShell()->UnregisterContentVisibilityAutoFrame(this);
|
||||
}
|
||||
PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
|
||||
}
|
||||
|
||||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче