diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 3e4868cf409b..42f7de5ff3d4 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -1387,10 +1387,17 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS useIntermediateSurface = true; #endif } else { + /* Don't use an intermediate surface for opacity when it's within a 3d + * context, since we'd rather keep the 3d effects. This matches the + * WebKit/blink behaviour, but is changing in the latest spec. + */ float opacity = GetEffectiveOpacity(); CompositionOp blendMode = GetEffectiveMixBlendMode(); - if (((opacity != 1.0f || blendMode != CompositionOp::OP_OVER) && (HasMultipleChildren() || Creates3DContextWithExtendingChildren())) || - (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren())) { + if ((HasMultipleChildren() || Creates3DContextWithExtendingChildren()) && + ((opacity != 1.0f && !Extend3DContext()) || + (blendMode != CompositionOp::OP_OVER))) { + useIntermediateSurface = true; + } else if (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren()) { useIntermediateSurface = true; } else { useIntermediateSurface = false; diff --git a/gfx/layers/basic/BasicContainerLayer.cpp b/gfx/layers/basic/BasicContainerLayer.cpp index c43b95c95fca..ee155e30735c 100644 --- a/gfx/layers/basic/BasicContainerLayer.cpp +++ b/gfx/layers/basic/BasicContainerLayer.cpp @@ -83,7 +83,7 @@ BasicContainerLayer::ComputeEffectiveTransforms(const Matrix4x4& aTransformToSur GetMaskLayer() || GetForceIsolatedGroup() || (GetMixBlendMode() != CompositionOp::OP_OVER && HasMultipleChildren()) || - (GetEffectiveOpacity() != 1.0 && (HasMultipleChildren() || hasSingleBlendingChild)); + (GetEffectiveOpacity() != 1.0 && ((HasMultipleChildren() && !Extend3DContext()) || hasSingleBlendingChild)); if (!Extend3DContext()) { idealTransform.ProjectTo2D(); diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 0ae510fc456d..b0d78d3de95f 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -2003,7 +2003,8 @@ void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, bool same3DContext = (itemType == nsDisplayItem::TYPE_TRANSFORM && static_cast(item)->IsParticipating3DContext()) || - (itemType == nsDisplayItem::TYPE_PERSPECTIVE && + ((itemType == nsDisplayItem::TYPE_PERSPECTIVE || + itemType == nsDisplayItem::TYPE_OPACITY) && static_cast(item)->Frame()->Extend3DContext()); if (same3DContext && !static_cast(item)->IsLeafOf3DContext()) { @@ -4152,6 +4153,7 @@ nsDisplayOpacity::nsDisplayOpacity(nsDisplayListBuilder* aBuilder, : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip) , mOpacity(aFrame->StyleDisplay()->mOpacity) , mForEventsOnly(aForEventsOnly) + , mParticipatesInPreserve3D(false) { MOZ_COUNT_CTOR(nsDisplayOpacity); } @@ -4189,6 +4191,12 @@ nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder, nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder, this, mFrame, eCSSProperty_opacity); + + if (mParticipatesInPreserve3D) { + container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_EXTEND_3D_CONTEXT); + } else { + container->SetContentFlags(container->GetContentFlags() & ~Layer::CONTENT_EXTEND_3D_CONTEXT); + } return container.forget(); } diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index e3099df3a1d0..13ff34a9c2c3 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3375,9 +3375,14 @@ public: bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; + void SetParticipatesInPreserve3D(bool aParticipatesInPreserve3D) + { + mParticipatesInPreserve3D = aParticipatesInPreserve3D; + } private: float mOpacity; bool mForEventsOnly; + bool mParticipatesInPreserve3D; }; class nsDisplayBlendMode : public nsDisplayWrapList { diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index b85ded0fbc49..8c8176d4a782 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -1159,12 +1159,6 @@ nsIFrame::Extend3DContext() const return false; } - // Opacity can only be only the root or leaves of a preserve-3d context - // as it requires flattening. - if (HasOpacity() && Combines3DTransformWithAncestors()) { - return false; - } - nsRect temp; return !nsFrame::ShouldApplyOverflowClipping(this, disp) && !GetClipPropClipRect(disp, &temp, GetSize()) && @@ -1881,7 +1875,8 @@ static bool ItemParticipatesIn3DContext(nsIFrame* aAncestor, nsDisplayItem* aItem) { nsIFrame* transformFrame; - if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM) { + if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM || + aItem->GetType() == nsDisplayItem::TYPE_OPACITY) { transformFrame = aItem->Frame(); } else if (aItem->GetType() == nsDisplayItem::TYPE_PERSPECTIVE) { transformFrame = static_cast(aItem)->TransformFrame(); @@ -1911,16 +1906,21 @@ WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, static void CreateOpacityItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList& aList, bool aItemForEventsOnly, - const DisplayItemScrollClip* aScrollClip) + const DisplayItemScrollClip* aScrollClip, + bool aParticipatesInPreserve3D) { // Don't clip nsDisplayOpacity items. We clip their descendants instead. // The clip we would set on an element with opacity would clip // all descendant content, but some should not be clipped. DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder); opacityClipState.Clear(); - aList.AppendNewToTop( + nsDisplayOpacity* opacity = new (aBuilder) nsDisplayOpacity(aBuilder, aFrame, &aList, - aScrollClip, aItemForEventsOnly)); + aScrollClip, aItemForEventsOnly); + if (opacity) { + opacity->SetParticipatesInPreserve3D(aParticipatesInPreserve3D); + aList.AppendToTop(opacity); + } } void @@ -2021,7 +2021,7 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, aBuilder->EnterSVGEffectsContents(&hoistedScrollInfoItemsStorage); } - bool useOpacity = HasVisualOpacity() && !nsSVGUtils::CanOptimizeOpacity(this); + bool useOpacity = HasVisualOpacity() && !nsSVGUtils::CanOptimizeOpacity(this) && !usingSVGEffects; bool useBlendMode = disp->mMixBlendMode != NS_STYLE_BLEND_NORMAL; bool useStickyPosition = disp->mPosition == NS_STYLE_POSITION_STICKY && IsScrollFrameActive(aBuilder, @@ -2172,8 +2172,6 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, clipState.ExitStackingContextContents(&containerItemScrollClip); } - bool is3DContextRoot = Extend3DContext() && !Combines3DTransformWithAncestors(); - /* If there are any SVG effects, wrap the list up in an SVG effects item * (which also handles CSS group opacity). Note that we create an SVG effects * item even if resultList is empty, since a filter can produce graphical @@ -2190,18 +2188,6 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, aBuilder->ExitSVGEffectsContents(); resultList.AppendToTop(&hoistedScrollInfoItemsStorage); } - else if (useOpacity && !resultList.IsEmpty() && !is3DContextRoot) { - /* If this element is the root of a preserve-3d context, then we want - * to make sure any opacity items are on the outside of the transform - * so that they don't interfere with the chain of nsDisplayTransforms. - * Opacity on preserve-3d leaves need to be inside the transform for the - * same reason, and we do this in the general case as well to preserve - * existing behaviour. - */ - CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly, - containerItemScrollClip); - useOpacity = false; - } /* If we're going to apply a transformation and don't have preserve-3d set, wrap * everything in an nsDisplayTransform. If there's nothing in the list, don't add @@ -2214,36 +2200,49 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, * We also traverse into sublists created by nsDisplayWrapList or nsDisplayOpacity, so that * we find all the correct children. */ - if (isTransformed && !resultList.IsEmpty()) { - if (!resultList.IsEmpty() && Extend3DContext()) { - // Install dummy nsDisplayTransform as a leaf containing - // descendants not participating this 3D rendering context. - nsDisplayList nonparticipants; - nsDisplayList participants; - int index = 1; + bool hasPreserve3DChildren = false; + if (isTransformed && !resultList.IsEmpty() && Extend3DContext()) { + // Install dummy nsDisplayTransform as a leaf containing + // descendants not participating this 3D rendering context. + nsDisplayList nonparticipants; + nsDisplayList participants; + int index = 1; - while (nsDisplayItem* item = resultList.RemoveBottom()) { - if (ItemParticipatesIn3DContext(this, item) && !item->GetClip().HasClip()) { - // The frame of this item participates the same 3D context. - WrapSeparatorTransform(aBuilder, this, dirtyRect, - &nonparticipants, &participants, index++); - participants.AppendToTop(item); - } else { - // The frame of the item doesn't participate the current - // context, or has no transform. - // - // For items participating but not transformed, they are add - // to nonparticipants to get a separator layer for handling - // clips, if there is, on an intermediate surface. - // \see ContainerLayer::DefaultComputeEffectiveTransforms(). - nonparticipants.AppendToTop(item); - } + while (nsDisplayItem* item = resultList.RemoveBottom()) { + if (ItemParticipatesIn3DContext(this, item) && !item->GetClip().HasClip()) { + // The frame of this item participates the same 3D context. + WrapSeparatorTransform(aBuilder, this, dirtyRect, + &nonparticipants, &participants, index++); + participants.AppendToTop(item); + hasPreserve3DChildren = true; + } else { + // The frame of the item doesn't participate the current + // context, or has no transform. + // + // For items participating but not transformed, they are add + // to nonparticipants to get a separator layer for handling + // clips, if there is, on an intermediate surface. + // \see ContainerLayer::DefaultComputeEffectiveTransforms(). + nonparticipants.AppendToTop(item); } - WrapSeparatorTransform(aBuilder, this, dirtyRect, - &nonparticipants, &participants, index++); - resultList.AppendToTop(&participants); } + WrapSeparatorTransform(aBuilder, this, dirtyRect, + &nonparticipants, &participants, index++); + resultList.AppendToTop(&participants); + } + /* We create the opacity outside any transform separators we created, + * so that the opacity will be applied to them as groups. When we have + * opacity and preserve-3d we break the 'group' nature of opacity in order + * to maintain preserve-3d. This matches the behaviour of blink and WebKit, + * see bug 1250718. + */ + if (useOpacity && !resultList.IsEmpty()) { + CreateOpacityItem(aBuilder, this, resultList, + opacityItemForEventsOnly, containerItemScrollClip, hasPreserve3DChildren); + } + + if (isTransformed && !resultList.IsEmpty()) { // Restore clip state now so nsDisplayTransform is clipped properly. if (!HasPerspective() && !useFixedPosition && !useStickyPosition) { clipState.ExitStackingContextContents(&containerItemScrollClip); @@ -2275,14 +2274,6 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, aBuilder, this, GetContainingBlock()->GetContent()->GetPrimaryFrame(), &resultList)); } - - /* If we need an opacity item, but didn't do it earlier, add it now on the - * outside of the transform. - */ - if (useOpacity && !usingSVGEffects) { - CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly, - containerItemScrollClip); - } } if (useFixedPosition || useStickyPosition) { diff --git a/layout/reftests/transform-3d/opacity-preserve3d-1-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-1-ref.html new file mode 100644 index 000000000000..b4763f88fc0d --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-1-ref.html @@ -0,0 +1,38 @@ + + + + + +
+
+
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-1.html b/layout/reftests/transform-3d/opacity-preserve3d-1.html new file mode 100644 index 000000000000..b73be49238a7 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-1.html @@ -0,0 +1,45 @@ + + + + + +
+
+
+
+
+
+
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-2-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-2-ref.html new file mode 100644 index 000000000000..0cc05ac2a785 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-2-ref.html @@ -0,0 +1,25 @@ + + + + + +
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-2.html b/layout/reftests/transform-3d/opacity-preserve3d-2.html new file mode 100644 index 000000000000..a04b99829136 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-2.html @@ -0,0 +1,31 @@ + + + + + +
+
+
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-3-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-3-ref.html new file mode 100644 index 000000000000..d17b54b71835 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-3-ref.html @@ -0,0 +1,39 @@ + + + + + +
+
+ +
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-3.html b/layout/reftests/transform-3d/opacity-preserve3d-3.html new file mode 100644 index 000000000000..f7bbf2da886c --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-3.html @@ -0,0 +1,42 @@ + + + + + +
+
+
+
+ +
+
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-4-ref.html b/layout/reftests/transform-3d/opacity-preserve3d-4-ref.html new file mode 100644 index 000000000000..ba978f1f354b --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-4-ref.html @@ -0,0 +1,30 @@ + + + + + + +
+ + + + diff --git a/layout/reftests/transform-3d/opacity-preserve3d-4.html b/layout/reftests/transform-3d/opacity-preserve3d-4.html new file mode 100644 index 000000000000..9fd672864476 --- /dev/null +++ b/layout/reftests/transform-3d/opacity-preserve3d-4.html @@ -0,0 +1,38 @@ + + + + + +
+
+
+ +
+
+ + + + diff --git a/layout/reftests/transform-3d/reftest.list b/layout/reftests/transform-3d/reftest.list index eee727a8cabf..0e810ce16eef 100644 --- a/layout/reftests/transform-3d/reftest.list +++ b/layout/reftests/transform-3d/reftest.list @@ -72,3 +72,7 @@ fuzzy-if(cocoaWidget,128,9) == animate-preserve3d-parent.html animate-preserve3d fuzzy-if(cocoaWidget,128,9) == animate-preserve3d-child.html animate-preserve3d-ref.html # intermittently fuzzy on Mac == animate-backface-hidden.html about:blank == 1245450-1.html green-rect.html +fuzzy(1,2000) == opacity-preserve3d-1.html opacity-preserve3d-1-ref.html +fuzzy(1,15000) == opacity-preserve3d-2.html opacity-preserve3d-2-ref.html +fuzzy(1,10000) == opacity-preserve3d-3.html opacity-preserve3d-3-ref.html +fuzzy(1,10000) == opacity-preserve3d-4.html opacity-preserve3d-4-ref.html