Bug 1250718 - Don't flatten opacity to an intermediate surface when used in the middle of preserve-3d. r=thinker

--HG--
extra : rebase_source : 867bab1b5369c88db3a845478f73d3a58845938c
This commit is contained in:
Matt Woodrow 2016-03-18 09:23:51 +13:00
parent 8d2b64d662
commit dbe7f4888a
14 changed files with 366 additions and 63 deletions

View File

@ -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;

View File

@ -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();

View File

@ -2003,7 +2003,8 @@ void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
bool same3DContext =
(itemType == nsDisplayItem::TYPE_TRANSFORM &&
static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) ||
(itemType == nsDisplayItem::TYPE_PERSPECTIVE &&
((itemType == nsDisplayItem::TYPE_PERSPECTIVE ||
itemType == nsDisplayItem::TYPE_OPACITY) &&
static_cast<nsDisplayPerspective*>(item)->Frame()->Extend3DContext());
if (same3DContext &&
!static_cast<nsDisplayTransform*>(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();
}

View File

@ -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 {

View File

@ -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<nsDisplayPerspective*>(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) {

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
transform: translateZ(10px);
background-color: red;
opacity: 0.5;
}
.second {
transform: translateZ(5px);
background-color: yellow;
top: 28px;
}
.third {
background-color: green;
top: 48px;
opacity: 0.5;
}
.fourth {
background-color: blue;
top: 68px;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
}
</style>
</head><body>
<div class="leaf first"></div>
<div class="leaf second"></div>
<div class="leaf third"></div>
<div class="leaf fourth"></div>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
transform: translateZ(10px);
background-color: blue;
top: 60px;
}
.second {
transform: translateZ(5px);
background-color: green;
top: 40px;
}
.third {
transform: translateZ(-5px);
background-color: yellow;
top: 20px;
}
.fourth {
transform: translateZ(-10px);
background-color: red;
}
.preserve {
transform-style: preserve-3d;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
}
</style>
</head><body>
<div class="preserve">
<div class="leaf first"></div>
<div class="preserve" style="opacity:0.5">
<div class="leaf second"></div>
<div class="leaf fourth"></div>
</div>
<div class="leaf third"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
background-color: green;
}
.second {
background-color: blue;
top: 58px;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
opacity: 0.5;
}
</style>
</head><body>
<div class="leaf first"></div>
<div class="leaf second"></div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
transform: translateZ(10px);
background-color: blue;
top: 50px;
}
.second {
transform: translateZ(5px);
background-color: green;
}
.preserve {
transform-style: preserve-3d;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
}
</style>
</head><body>
<div class="preserve" style="opacity:0.5">
<div class="leaf first"></div>
<div class="leaf second"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
background-color: green;
top: 48px;
}
.second {
top: 88px;
}
.third {
background-color: blue;
opacity: 0.5;
}
.group {
opacity: 0.5;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
}
</style>
</head><body>
<div class="group">
<div class="leaf first"></div>
<canvas class="leaf second" width="100px" height="100px" id="canvas"></canvas>
</div>
<div class="leaf third"></div>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
</script>
</body>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
transform: translateZ(10px);
background-color: blue;
top: 0px;
}
.second {
background-color: green;
top: 40px;
}
.third {
top: 80px;
}
.preserve {
transform-style: preserve-3d;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
}
</style>
</head><body>
<div class="preserve">
<div class="preserve" style="opacity:0.5">
<div class="leaf first"></div>
<div class="leaf second"></div>
<canvas class="leaf third" width="100px" height="100px" id="canvas"></canvas>
</div>
</div>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
</script>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
background-color: blue;
}
.second {
background-color: green;
top: 48px;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
opacity: 0.5;
}
</style>
</head><body>
<canvas class="leaf second" width="100px" height="100px" id="canvas"></canvas>
<div class="leaf first"></div>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html><head>
<style>
.first {
transform: translateZ(10px);
background-color: blue;
top: 0px;
}
.second {
background-color: green;
top: 40px;
}
.preserve {
transform-style: preserve-3d;
}
.leaf {
width: 100px;
height: 100px;
position:absolute;
}
</style>
</head><body>
<div class="preserve">
<div class="preserve" style="opacity:0.5">
<div class="leaf first"></div>
<canvas class="leaf second" width="100px" height="100px" id="canvas"></canvas>
</div>
</div>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
</script>
</body>
</html>

View File

@ -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