зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1789255: Anonymous children of elements with content-visibility should not be skipped for layout r=emilio
Some anonymous children are important for properly sizing their parents even when those parents hide content with `content-visibility`. This is shown by regressions in the proper layout of some form elements with `content-visibility`. This change introduces a more conservative approach for avoiding layout of hidden content. Instead of leaving all children dirty during reflow, reflow anonymous frames (and nsComboboxDisplayFrame, a specialized kind of anonymous frame). This change means that frames may only lay out some of their children, so it must introduce some more changes to assumptions during line layout. In addition, this change renames `content-visibility` related methods in nsIFrame in order to make it more obvious what they do. Differential Revision: https://phabricator.services.mozilla.com/D157306
This commit is contained in:
Родитель
99644a51e5
Коммит
631f1807ed
|
@ -613,7 +613,7 @@ IntersectionOutput DOMIntersectionObserver::Intersect(
|
|||
// true even if both the root and the target elements are in the skipped
|
||||
// contents."
|
||||
// https://drafts.csswg.org/css-contain/#cv-notes
|
||||
if (targetFrame->AncestorHidesContent()) {
|
||||
if (targetFrame->IsHiddenByContentVisibilityOnAnyAncestor()) {
|
||||
return {isSimilarOrigin};
|
||||
}
|
||||
|
||||
|
|
|
@ -724,7 +724,7 @@ bool Element::CheckVisibility(const CheckVisibilityOptions& aOptions) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (f->AncestorHidesContent()) {
|
||||
if (f->IsHiddenByContentVisibilityOnAnyAncestor()) {
|
||||
// 2. If a shadow-including ancestor of this has content-visibility: hidden,
|
||||
// return false.
|
||||
return false;
|
||||
|
|
|
@ -3145,11 +3145,11 @@ static bool IsVisibleAndNotInReplacedElement(nsIFrame* aFrame) {
|
|||
aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
|
||||
return false;
|
||||
}
|
||||
if (aFrame->IsContentHidden()) {
|
||||
if (aFrame->HidesContent()) {
|
||||
return false;
|
||||
}
|
||||
for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
|
||||
if (f->IsContentHidden()) {
|
||||
if (f->HidesContent()) {
|
||||
return false;
|
||||
}
|
||||
if (f->IsFrameOfType(nsIFrame::eReplaced) &&
|
||||
|
|
|
@ -3720,7 +3720,7 @@ void PresShell::DoScrollContentIntoView() {
|
|||
NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
|
||||
|
||||
nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
|
||||
if (!frame || frame->AncestorHidesContent()) {
|
||||
if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor()) {
|
||||
mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
|
||||
mContentToScrollTo = nullptr;
|
||||
return;
|
||||
|
@ -3799,7 +3799,7 @@ bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
|
|||
ScrollAxis aHorizontal,
|
||||
ScrollFlags aScrollFlags,
|
||||
const nsIFrame* aTarget) {
|
||||
if (aFrame->AncestorHidesContent()) {
|
||||
if (aFrame->IsHiddenByContentVisibilityOnAnyAncestor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -11866,7 +11866,7 @@ void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) {
|
|||
nsIFrame* topmostFrameWithContentHidden = nullptr;
|
||||
for (nsIFrame* cur = aFrame->GetInFlowParent(); cur;
|
||||
cur = cur->GetInFlowParent()) {
|
||||
if (cur->IsContentHidden()) {
|
||||
if (cur->HidesContent()) {
|
||||
topmostFrameWithContentHidden = cur;
|
||||
mHiddenContentInForcedLayout.Insert(cur->GetContent());
|
||||
}
|
||||
|
|
|
@ -1314,7 +1314,7 @@ void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
|
|||
void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) {
|
||||
if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) {
|
||||
if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
|
||||
FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
|
||||
return;
|
||||
}
|
||||
|
@ -2579,10 +2579,6 @@ static bool LinesAreEmpty(const nsLineList& aList) {
|
|||
}
|
||||
|
||||
void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
|
||||
if (IsContentHiddenForLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool keepGoing = true;
|
||||
bool repositionViews = false; // should we really need this?
|
||||
bool foundAnyClears = aState.mFloatBreakType != StyleClear::None;
|
||||
|
@ -3369,6 +3365,15 @@ void nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine,
|
|||
aLine->InvalidateCachedIsEmpty();
|
||||
aLine->ClearHadFloatPushed();
|
||||
|
||||
// If this line contains a single block that is hidden by `content-visibility`
|
||||
// don't reflow the line. If this line contains inlines and the first one is
|
||||
// hidden by `content-visibility`, all of them are, so avoid reflow in that
|
||||
// case as well.
|
||||
nsIFrame* firstChild = aLine->mFirstChild;
|
||||
if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we know what kind of line we have, reflow it
|
||||
if (aLine->IsBlock()) {
|
||||
ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
|
||||
|
@ -3577,6 +3582,10 @@ static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) {
|
|||
|
||||
/* virtual */
|
||||
bool nsBlockFrame::IsSelfEmpty() {
|
||||
if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Blocks which are margin-roots (including inline-blocks) cannot be treated
|
||||
// as empty for margin-collapsing and other purposes. They're more like
|
||||
// replaced elements.
|
||||
|
|
|
@ -4532,7 +4532,7 @@ void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
|
|||
ReflowOutput& aReflowOutput,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) {
|
||||
if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) {
|
||||
if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -5267,9 +5267,10 @@ std::tuple<nscoord, bool> nsFlexContainerFrame::ReflowChildren(
|
|||
const LogicalMargin& aBorderPadding,
|
||||
const nscoord aSumOfPrevInFlowsChildrenBlockSize,
|
||||
const FlexboxAxisTracker& aAxisTracker, FlexLayoutResult& aFlr) {
|
||||
if (IsContentHiddenForLayout()) {
|
||||
if (HidesContentForLayout()) {
|
||||
return {0, false};
|
||||
}
|
||||
|
||||
// Before giving each child a final reflow, calculate the origin of the
|
||||
// flex container's content box (with respect to its border-box), so that
|
||||
// we can compute our flex item's final positions.
|
||||
|
@ -5476,7 +5477,7 @@ void nsFlexContainerFrame::PopulateReflowOutput(
|
|||
// as their baseline, or our content is hidden in which case, we'll use the
|
||||
// wrong baseline (but no big deal).
|
||||
NS_WARNING_ASSERTION(
|
||||
IsContentHidden() || aLines[0].IsEmpty(),
|
||||
HidesContentForLayout() || aLines[0].IsEmpty(),
|
||||
"Have flex items but didn't get an ascent - that's odd (or there are "
|
||||
"just gigantic sizes involved)");
|
||||
// Per spec, synthesize baseline from the flex container's content box
|
||||
|
|
|
@ -8428,12 +8428,11 @@ nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aState,
|
|||
nsReflowStatus& aStatus) {
|
||||
WritingMode wm = aState.mReflowInput->GetWritingMode();
|
||||
nscoord bSize = aContentArea.BSize(wm);
|
||||
if (IsContentHiddenForLayout()) {
|
||||
return bSize;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aState.mReflowInput);
|
||||
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
|
||||
if (HidesContentForLayout()) {
|
||||
return bSize;
|
||||
}
|
||||
|
||||
OverflowAreas ocBounds;
|
||||
nsReflowStatus ocStatus;
|
||||
|
@ -8536,7 +8535,7 @@ void nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
|
|||
ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) {
|
||||
if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) {
|
||||
if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -519,7 +519,7 @@ void nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|||
|
||||
DisplayBorderBackgroundOutline(aBuilder, aLists);
|
||||
|
||||
if (IsContentHidden()) {
|
||||
if (HidesContent()) {
|
||||
DisplaySelectionOverlay(aBuilder, aLists.Content(),
|
||||
nsISelectionDisplay::DISPLAY_IMAGES);
|
||||
return;
|
||||
|
|
|
@ -378,7 +378,7 @@ bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const {
|
|||
nsView* view = frame->GetView();
|
||||
if (view && view->GetVisibility() == nsViewVisibility_kHide) return false;
|
||||
|
||||
if (this != frame && frame->IsContentHidden()) return false;
|
||||
if (this != frame && frame->HidesContent()) return false;
|
||||
|
||||
nsIFrame* parent = frame->GetParent();
|
||||
nsDeckFrame* deck = do_QueryFrame(parent);
|
||||
|
@ -4019,7 +4019,7 @@ void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsContentHidden()) {
|
||||
if (HidesContent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6750,7 +6750,7 @@ void nsIFrame::DidReflow(nsPresContext* aPresContext,
|
|||
const ReflowInput* aReflowInput) {
|
||||
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsIFrame::DidReflow"));
|
||||
|
||||
if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) {
|
||||
if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6858,7 +6858,7 @@ bool nsIFrame::IsContentDisabled() const {
|
|||
return element && element->IsDisabled();
|
||||
}
|
||||
|
||||
bool nsIFrame::IsContentHidden() const {
|
||||
bool nsIFrame::HidesContent() const {
|
||||
if (!StyleDisplay()->IsContentVisibilityHidden()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -6866,18 +6866,30 @@ bool nsIFrame::IsContentHidden() const {
|
|||
return IsFrameOfType(nsIFrame::eReplaced) || !StyleDisplay()->IsInlineFlow();
|
||||
}
|
||||
|
||||
bool nsIFrame::IsContentHiddenForLayout() const {
|
||||
return IsContentHidden() &&
|
||||
!PresShell()->IsForcingLayoutForHiddenContent(this);
|
||||
bool nsIFrame::HidesContentForLayout() const {
|
||||
return HidesContent() && !PresShell()->IsForcingLayoutForHiddenContent(this);
|
||||
}
|
||||
|
||||
bool nsIFrame::AncestorHidesContent() const {
|
||||
bool nsIFrame::IsHiddenByContentVisibilityOfInFlowParentForLayout() const {
|
||||
const auto* parent = GetInFlowParent();
|
||||
return parent && parent->HidesContentForLayout() && !Style()->IsAnonBox();
|
||||
}
|
||||
|
||||
bool nsIFrame::IsHiddenByContentVisibilityOnAnyAncestor() const {
|
||||
if (!StaticPrefs::layout_css_content_visibility_enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAnonymousBox = Style()->IsAnonBox();
|
||||
for (nsIFrame* cur = GetInFlowParent(); cur; cur = cur->GetInFlowParent()) {
|
||||
if (cur->IsContentHidden()) {
|
||||
// Anonymous boxes are not hidden by the content-visibility of their first
|
||||
// non-anonymous ancestor, but can be hidden by ancestors further up the
|
||||
// tree.
|
||||
if (isAnonymousBox && !cur->Style()->IsAnonBox()) {
|
||||
isAnonymousBox = false;
|
||||
}
|
||||
|
||||
if (!isAnonymousBox && cur->HidesContent()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -8184,16 +8196,22 @@ bool nsIFrame::IsVisibleOrCollapsedForPainting() {
|
|||
}
|
||||
|
||||
/* virtual */
|
||||
bool nsIFrame::IsEmpty() { return false; }
|
||||
bool nsIFrame::IsEmpty() {
|
||||
return IsHiddenByContentVisibilityOfInFlowParentForLayout();
|
||||
}
|
||||
|
||||
bool nsIFrame::CachedIsEmpty() {
|
||||
MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY),
|
||||
"Must only be called on reflowed lines");
|
||||
MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
|
||||
IsHiddenByContentVisibilityOfInFlowParentForLayout(),
|
||||
"Must only be called on reflowed lines or those hidden by "
|
||||
"content-visibility.");
|
||||
return IsEmpty();
|
||||
}
|
||||
|
||||
/* virtual */
|
||||
bool nsIFrame::IsSelfEmpty() { return false; }
|
||||
bool nsIFrame::IsSelfEmpty() {
|
||||
return IsHiddenByContentVisibilityOfInFlowParentForLayout();
|
||||
}
|
||||
|
||||
nsresult nsIFrame::GetSelectionController(nsPresContext* aPresContext,
|
||||
nsISelectionController** aSelCon) {
|
||||
|
@ -11637,7 +11655,7 @@ void nsIFrame::UpdateAnimationVisibility() {
|
|||
return;
|
||||
}
|
||||
|
||||
bool hidden = AncestorHidesContent();
|
||||
bool hidden = IsHiddenByContentVisibilityOnAnyAncestor();
|
||||
if (animationCollection) {
|
||||
for (auto& animation : animationCollection->mAnimations) {
|
||||
animation->SetHiddenByContentVisibility(hidden);
|
||||
|
|
|
@ -3146,22 +3146,30 @@ class nsIFrame : public nsQueryFrame {
|
|||
bool IsContentDisabled() const;
|
||||
|
||||
/**
|
||||
* Whether the content is hidden via the `content-visibilty` property.
|
||||
* Whether this frame hides its contents via the `content-visibility`
|
||||
* property.
|
||||
*/
|
||||
bool IsContentHidden() const;
|
||||
bool HidesContent() const;
|
||||
|
||||
/**
|
||||
* Whether the content is hidden via the `content-visibilty` property for
|
||||
* layout. Hidden content might not be hidden for layout when forcing layout
|
||||
* for size queries.
|
||||
* Whether this frame hides its contents via the `content-visibility`
|
||||
* property, while doing layout. This might be true when `HidesContent()` is
|
||||
* true in the case that hidden content is being forced to lay out by position
|
||||
* or size queries from script.
|
||||
*/
|
||||
bool IsContentHiddenForLayout() const;
|
||||
bool HidesContentForLayout() const;
|
||||
|
||||
/**
|
||||
* Returns true if this frame is entirely hidden due the `content-visibility`
|
||||
* property on an ancestor.
|
||||
*/
|
||||
bool AncestorHidesContent() const;
|
||||
bool IsHiddenByContentVisibilityOnAnyAncestor() const;
|
||||
|
||||
/**
|
||||
* Returns true is this frame is hidden by its first unskipped in flow
|
||||
* ancestor due to `content-visibility`.
|
||||
*/
|
||||
bool IsHiddenByContentVisibilityOfInFlowParentForLayout() const;
|
||||
|
||||
/**
|
||||
* Get the "type" of the frame.
|
||||
|
|
|
@ -2315,7 +2315,7 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|||
|
||||
DisplayBorderBackgroundOutline(aBuilder, aLists);
|
||||
|
||||
if (IsContentHidden()) {
|
||||
if (HidesContent()) {
|
||||
DisplaySelectionOverlay(aBuilder, aLists.Content(),
|
||||
nsISelectionDisplay::DISPLAY_IMAGES);
|
||||
return;
|
||||
|
|
|
@ -356,7 +356,7 @@ void nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsContentHidden()) {
|
||||
if (HidesContent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -720,7 +720,7 @@ void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|||
|
||||
DisplayBorderBackgroundOutline(aBuilder, aLists);
|
||||
|
||||
if (IsContentHidden()) {
|
||||
if (HidesContent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3605,7 +3605,7 @@ static nscoord Resolve(const StyleContainIntrinsicSize& aSize,
|
|||
Maybe<float> lastSize = aAxis == eLogicalAxisBlock
|
||||
? element->GetLastRememberedBSize()
|
||||
: element->GetLastRememberedISize();
|
||||
if (lastSize && aFrame.IsContentHidden()) {
|
||||
if (lastSize && aFrame.HidesContent()) {
|
||||
return CSSPixel::ToAppUnits(*lastSize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
[auto-007.html]
|
||||
[.test 37]
|
||||
expected: FAIL
|
||||
|
||||
[.test 16]
|
||||
expected: FAIL
|
||||
|
||||
[.test 17]
|
||||
expected: FAIL
|
||||
|
||||
[.test 18]
|
||||
expected: FAIL
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype HTML>
|
||||
<html>
|
||||
<meta charset="utf8">
|
||||
<title>CSS Content Visibility: fieldset hiding content</title>
|
||||
<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
|
||||
|
||||
<style>
|
||||
fieldset {
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset></fieldset>
|
|
@ -0,0 +1,17 @@
|
|||
<!doctype HTML>
|
||||
<html>
|
||||
<meta charset="utf8">
|
||||
<title>CSS Content Visibility: fieldset hiding content</title>
|
||||
<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
|
||||
<link rel="match" href="content-visibility-fieldset-size-ref.html">
|
||||
<meta name="assert" content="A fieldset hiding content with content-visibility should be equivalent to an empty fieldset">
|
||||
|
||||
<style>
|
||||
fieldset {
|
||||
background: green;
|
||||
content-visibility: hidden
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset><legend>Hidden legend content</legend>Hidden fieldset content</fieldset>
|
|
@ -143,7 +143,8 @@ static bool IsVisibleNode(const nsINode* aNode) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (frame->IsContentHidden() || frame->AncestorHidesContent()) {
|
||||
if (frame->HidesContent() ||
|
||||
frame->IsHiddenByContentVisibilityOnAnyAncestor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче