commit fbcdc966d0c3c5ae670fb45f8b1039effd52d3e0 parent 7890c2d85217267e7740c254475f97df2bf77b72 Author: Emilio Cobos Álvarez <emilio@crisal.io> Date: Tue, 2 Dec 2025 08:40:08 +0000 Bug 1924231 - Implement of position-visibility: anchors-visible. r=layout-anchor-positioning-reviewers,firefox-style-system-reviewers,layout-reviewers,dshin Adjust some tests to explicitly use `center` in their `position-area` value, since otherwise it trips into our lack of `anchor-center` support (which is in progress). It doesn't change the rendering of the test, nor meaningfully changes what's tested otherwise, but that way we get coverage for this in the interim. Differential Revision: https://phabricator.services.mozilla.com/D274640 Diffstat:
29 files changed, 175 insertions(+), 95 deletions(-)
diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -10,6 +10,7 @@ #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_apz.h" +#include "mozilla/dom/DOMIntersectionObserver.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "nsCanvasFrame.h" @@ -27,6 +28,16 @@ namespace mozilla { namespace { +bool IsScrolled(const nsIFrame* aFrame) { + switch (aFrame->Style()->GetPseudoType()) { + case PseudoStyleType::scrolledContent: + case PseudoStyleType::scrolledCanvas: + return true; + default: + return false; + } +} + bool DoTreeScopedPropertiesOfElementApplyToContent( const nsINode* aStylePropertyElement, const nsINode* aStyledContent) { // XXX: The proper implementation is deferred to bug 1988038 @@ -800,7 +811,7 @@ const nsAtom* AnchorPositioningUtils::GetUsedAnchorName( return nullptr; } -const nsIFrame* AnchorPositioningUtils::GetAnchorPosImplicitAnchor( +nsIFrame* AnchorPositioningUtils::GetAnchorPosImplicitAnchor( const nsIFrame* aFrame) { const auto* frameContent = aFrame->GetContent(); const bool hasElement = frameContent && frameContent->IsElement(); @@ -824,7 +835,7 @@ const nsIFrame* AnchorPositioningUtils::GetAnchorPosImplicitAnchor( return nullptr; } - const auto* pseudoRootFrame = pseudoRoot->GetPrimaryFrame(); + auto* pseudoRootFrame = pseudoRoot->GetPrimaryFrame(); if (!pseudoRootFrame) { return nullptr; } @@ -848,7 +859,7 @@ AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize( // TODO(dshin, bug 1989292): This just gets local containing block. const auto* cb = aPositioned->GetParent(); MOZ_ASSERT(cb); - if (cb->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { + if (IsScrolled(cb)) { cb = aPositioned->GetParent(); } return ContainingBlockInfo{cb->GetPaddingRectRelativeToSelf()}; @@ -866,7 +877,7 @@ bool AnchorPositioningUtils::FitsInContainingBlock( const auto rect = [&]() { auto rect = aPositioned->GetMarginRect(); const auto* cb = aPositioned->GetParent(); - if (cb->Style()->GetPseudoType() != PseudoStyleType::scrolledContent) { + if (!IsScrolled(cb)) { return rect; } const ScrollContainerFrame* scrollContainer = @@ -908,6 +919,130 @@ nsIFrame* AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( return anchor; } +static bool TriggerFallbackReflow(PresShell* aPresShell, nsIFrame* aPositioned, + AnchorPosReferenceData* aReferencedAnchors, + bool aEvaluateAllFallbacksIfNeeded) { + auto totalFallbacks = + aPositioned->StylePosition()->mPositionTryFallbacks._0.Length(); + if (!totalFallbacks) { + // No fallbacks specified. + return false; + } + + const bool positionedFitsInCB = AnchorPositioningUtils::FitsInContainingBlock( + AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize(aPositioned), + aPositioned, aReferencedAnchors); + if (positionedFitsInCB) { + return false; + } + + // TODO(bug 1987964): Try to only do this when the scroll offset changes? + auto* lastSuccessfulPosition = + aPositioned->GetProperty(nsIFrame::LastSuccessfulPositionFallback()); + const bool needsRetry = + aEvaluateAllFallbacksIfNeeded || + (lastSuccessfulPosition && !lastSuccessfulPosition->mTriedAllFallbacks); + if (!needsRetry) { + return false; + } + // We want to retry from the first position; remove the last position + // property so all potential positions are re-evaluated. + aPositioned->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); + aPresShell->FrameNeedsReflow(aPositioned, mozilla::IntrinsicDirty::None, + NS_FRAME_IS_DIRTY); + return true; +} + +static bool AnchorIsEffectivelyHidden(nsIFrame* aAnchor) { + if (!aAnchor->StyleVisibility()->IsVisible()) { + return true; + } + for (auto* anchor = aAnchor; anchor; anchor = anchor->GetParent()) { + if (anchor->HasAnyStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN)) { + return true; + } + } + return false; +} + +static bool ComputePositionVisibility( + PresShell* aPresShell, nsIFrame* aPositioned, + AnchorPosReferenceData* aReferencedAnchors) { + auto vis = aPositioned->StylePosition()->mPositionVisibility; + if (vis & StylePositionVisibility::ALWAYS) { + MOZ_ASSERT(vis == StylePositionVisibility::ALWAYS, + "always can't be combined"); + return true; + } + if (vis & StylePositionVisibility::ANCHORS_VALID) { + for (const auto& ref : *aReferencedAnchors) { + if (ref.GetData().isNothing()) { + return false; + } + } + } + if (vis & StylePositionVisibility::NO_OVERFLOW) { + const bool positionedFitsInCB = + AnchorPositioningUtils::FitsInContainingBlock( + AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize( + aPositioned), + aPositioned, aReferencedAnchors); + if (!positionedFitsInCB) { + return false; + } + } + if (vis & StylePositionVisibility::ANCHORS_VISIBLE) { + const auto* defaultAnchorName = + aReferencedAnchors->mDefaultAnchorName.get(); + if (defaultAnchorName) { + auto* defaultAnchor = + aPresShell->GetAnchorPosAnchor(defaultAnchorName, aPositioned); + if (defaultAnchor && AnchorIsEffectivelyHidden(defaultAnchor)) { + return false; + } + // If both are in the same cb the expectation is that this doesn't apply + // because there are no intervening clips. I think that's broken, see + // https://github.com/w3c/csswg-drafts/issues/13176 + if (defaultAnchor && + defaultAnchor->GetParent() != aPositioned->GetParent()) { + auto* intersectionRoot = aPositioned->GetParent(); + nsRect rootRect = intersectionRoot->InkOverflowRectRelativeToSelf(); + if (IsScrolled(intersectionRoot)) { + intersectionRoot = intersectionRoot->GetParent(); + ScrollContainerFrame* sc = do_QueryFrame(intersectionRoot); + rootRect = sc->GetScrollPortRectAccountingForDynamicToolbar(); + } + const auto* doc = aPositioned->PresContext()->Document(); + const nsINode* root = + intersectionRoot->GetContent() + ? static_cast<nsINode*>(intersectionRoot->GetContent()) + : doc; + rootRect = nsLayoutUtils::TransformFrameRectToAncestor( + intersectionRoot, rootRect, + nsLayoutUtils::GetContainingBlockForClientRect(intersectionRoot)); + const auto input = dom::IntersectionInput{ + .mIsImplicitRoot = false, + .mRootNode = root, + .mRootFrame = intersectionRoot, + .mRootRect = rootRect, + .mRootMargin = {}, + .mScrollMargin = {}, + .mRemoteDocumentVisibleRect = {}, + }; + const auto output = + dom::DOMIntersectionObserver::Intersect(input, defaultAnchor); + // NOTE(emilio): It is a bit weird to also check that mIntersectionRect + // is non-empty, see https://github.com/w3c/csswg-drafts/issues/13176. + if (!output.Intersects() || (output.mIntersectionRect->IsEmpty() && + !defaultAnchor->GetRect().IsEmpty())) { + return false; + } + } + } + } + return true; +} + bool AnchorPositioningUtils::TriggerLayoutOnOverflow( PresShell* aPresShell, bool aEvaluateAllFallbacksIfNeeded) { bool didLayoutPositionedItems = false; @@ -919,38 +1054,25 @@ bool AnchorPositioningUtils::TriggerLayoutOnOverflow( continue; } - auto totalFallbacks = - positioned->StylePosition()->mPositionTryFallbacks._0.Length(); - if (!totalFallbacks) { - // No fallbacks specified. - continue; + if (TriggerFallbackReflow(aPresShell, positioned, referencedAnchors, + aEvaluateAllFallbacksIfNeeded)) { + didLayoutPositionedItems = true; } - const bool positionedFitsInCB = - AnchorPositioningUtils::FitsInContainingBlock( - AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize( - positioned), - positioned, referencedAnchors); - if (positionedFitsInCB) { + if (didLayoutPositionedItems) { + // We'll come back to evaluate position-visibility later. continue; } - - // TODO(bug 1987964): Try to only do this when the scroll offset changes? - auto* lastSuccessfulPosition = - positioned->GetProperty(nsIFrame::LastSuccessfulPositionFallback()); - const bool needsRetry = - aEvaluateAllFallbacksIfNeeded || - (lastSuccessfulPosition && !lastSuccessfulPosition->mTriedAllFallbacks); - if (needsRetry) { - // We want to retry from the first position; remove the last position - // property so all potential positions are re-evaluated. - positioned->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); - aPresShell->FrameNeedsReflow(positioned, mozilla::IntrinsicDirty::None, - NS_FRAME_IS_DIRTY); - didLayoutPositionedItems = true; + const bool shouldBeVisible = + ComputePositionVisibility(aPresShell, positioned, referencedAnchors); + const bool isVisible = + !positioned->HasAnyStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN); + if (shouldBeVisible != isVisible) { + positioned->AddOrRemoveStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN, + !shouldBeVisible); + positioned->InvalidateFrameSubtree(); } } - return didLayoutPositionedItems; } diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -283,7 +283,7 @@ struct AnchorPositioningUtils { * element. For popovers, this returns the primary frame of the invoker. In * all other cases, returns null. */ - static const nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame); + static nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame); struct NearestScrollFrameInfo { const nsIFrame* mScrollContainer = nullptr; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp @@ -11336,7 +11336,7 @@ nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) { aFrame, nsCSSFrameConstructor::ABS_POS); } -const nsIFrame* PresShell::GetAnchorPosAnchor( +nsIFrame* PresShell::GetAnchorPosAnchor( const nsAtom* aName, const nsIFrame* aPositionedFrame) const { MOZ_ASSERT(aName); MOZ_ASSERT(!aName->IsEmpty()); @@ -11348,7 +11348,6 @@ const nsIFrame* PresShell::GetAnchorPosAnchor( return AnchorPositioningUtils::FindFirstAcceptableAnchor( aName, aPositionedFrame, entry.Data()); } - return nullptr; } @@ -11695,16 +11694,12 @@ static bool CheckOverflow(nsIFrame* aPositioned, const AnchorPosReferenceData& aData) { const auto* stylePos = aPositioned->StylePosition(); const auto hasFallbacks = !stylePos->mPositionTryFallbacks._0.IsEmpty(); - const auto visibilityDependsOnOverflow = - stylePos->mPositionVisibility == StylePositionVisibility::NO_OVERFLOW; - if (!hasFallbacks && !visibilityDependsOnOverflow) { + if (!hasFallbacks) { return false; } const auto overflows = !AnchorPositioningUtils::FitsInContainingBlock( AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize(aPositioned), aPositioned, &aData); - aPositioned->AddOrRemoveStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN, - visibilityDependsOnOverflow && overflows); return hasFallbacks && overflows; } diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h @@ -763,8 +763,8 @@ class PresShell final : public nsStubDocumentObserver, nsIFrame* GetAbsoluteContainingBlock(nsIFrame* aFrame); // https://drafts.csswg.org/css-anchor-position-1/#target - const nsIFrame* GetAnchorPosAnchor(const nsAtom* aName, - const nsIFrame* aPositionedFrame) const; + nsIFrame* GetAnchorPosAnchor(const nsAtom* aName, + const nsIFrame* aPositionedFrame) const; void AddAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame); void RemoveAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame); enum class AnchorPosUpdateResult { diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp @@ -1605,13 +1605,6 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( aKidFrame->UpdateOverflow(); }(); - // If author asked for `position-visibility: no-overflow` and we overflow - // `usedCB`, treat as "strongly hidden". - aKidFrame->AddOrRemoveStateBits( - NS_FRAME_POSITION_VISIBILITY_HIDDEN, - isOverflowingCB && aKidFrame->StylePosition()->mPositionVisibility == - StylePositionVisibility::NO_OVERFLOW); - if (currentFallbackIndex) { aKidFrame->SetOrUpdateDeletableProperty( nsIFrame::LastSuccessfulPositionFallback(), *currentFallbackIndex, @@ -1625,6 +1618,16 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( ToString(aKidFrame->GetRect())); } #endif + // If author asked for `position-visibility: no-overflow` and we overflow + // `usedCB`, treat as "strongly hidden". Note that for anchored frames this + // happens in ComputePositionVisibility. But no-overflow also applies to + // non-anchored frames. + if (!aAnchorPosResolutionCache) { + aKidFrame->AddOrRemoveStateBits( + NS_FRAME_POSITION_VISIBILITY_HIDDEN, + isOverflowingCB && aKidFrame->StylePosition()->mPositionVisibility & + StylePositionVisibility::NO_OVERFLOW); + } if (aOverflowAreas) { aOverflowAreas->UnionWith(aKidFrame->GetOverflowAreasRelativeToParent()); diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp @@ -1014,7 +1014,7 @@ nsStylePosition::nsStylePosition() mMinHeight(StyleSize::Auto()), mMaxHeight(StyleMaxSize::None()), mPositionAnchor(StylePositionAnchor::None()), - mPositionVisibility(StylePositionVisibility::ALWAYS), + mPositionVisibility(StylePositionVisibility::ANCHORS_VISIBLE), mPositionTryFallbacks(StylePositionTryFallbacks()), mPositionTryOrder(StylePositionTryOrder::Normal), mFlexBasis(StyleFlexBasis::Size(StyleSize::Auto())), diff --git a/servo/components/style/properties/longhands/position.mako.rs b/servo/components/style/properties/longhands/position.mako.rs @@ -273,9 +273,9 @@ ${helpers.predefined_type( ${helpers.predefined_type( "position-visibility", "PositionVisibility", - "computed::PositionVisibility::ALWAYS", + "computed::PositionVisibility::ANCHORS_VISIBLE", engines="gecko", - initial_specified_value="specified::PositionVisibility::ALWAYS", + initial_specified_value="specified::PositionVisibility::ANCHORS_VISIBLE", animation_type="discrete", gecko_pref="layout.css.anchor-positioning.enabled", spec="https://drafts.csswg.org/css-anchor-position-1/#propdef-position-visibility", diff --git a/testing/web-platform/meta/css/css-anchor-position/parsing/position-visibility-computed.html.ini b/testing/web-platform/meta/css/css-anchor-position/parsing/position-visibility-computed.html.ini @@ -1,6 +0,0 @@ -[position-visibility-computed.html] - [Property position-visibility has initial value anchors-visible] - expected: FAIL - - [Property position-visibility value 'initial'] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-add-no-overflow.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-add-no-overflow.html.ini @@ -1,2 +0,0 @@ -[position-visibility-add-no-overflow.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-document.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-document.html.ini @@ -1,4 +0,0 @@ -[position-visibility-anchors-visible-after-scroll-in-document.html] - expected: - if os == "android": PASS - FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-after-scroll-in.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-after-scroll-out.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-001.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-001.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-chained-001.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-002.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-002.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-chained-002.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-003.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-003.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-chained-003.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-004.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-chained-004.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-chained-004.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-change-anchor.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-change-css-visibility.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-css-visibility.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-css-visibility.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-position-fixed.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-position-fixed.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-position-fixed.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-stacked-child.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.tentative.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-stacked-child.tentative.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible-stacked-child.tentative.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible.html.ini @@ -1,2 +0,0 @@ -[position-visibility-anchors-visible.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-initial.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-initial.html.ini @@ -1,2 +0,0 @@ -[position-visibility-initial.html] - expected: FAIL diff --git a/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-document.html b/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-document.html @@ -23,7 +23,7 @@ #target { position-anchor: --anchor; position-visibility: anchors-visible; - position-area: left; + position-area: left center; width: 100px; height: 100px; background: green; diff --git a/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html b/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.html @@ -29,7 +29,7 @@ #target { position-anchor: --a1; position-visibility: anchors-visible; - position-area: block-end; + position-area: block-end center; width: 100px; height: 100px; background: green; diff --git a/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-chained-004.html b/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-chained-004.html @@ -34,7 +34,7 @@ .anchored { position-visibility: anchors-visible; - position-area: bottom; + position-area: bottom center; width: 100px; height: 50px; background: green; diff --git a/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html b/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.html @@ -33,7 +33,7 @@ #target { position-anchor: --a2; position-visibility: anchors-visible; - position-area: bottom; + position-area: bottom center; width: 100px; height: 100px; background: green; diff --git a/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html b/testing/web-platform/tests/css/css-anchor-position/position-visibility-anchors-visible-change-css-visibility.html @@ -22,7 +22,7 @@ #target { position-anchor: --a1; position-visibility: anchors-visible; - position-area: bottom; + position-area: bottom center; width: 100px; height: 100px; background: green;