Bug 1238564 - Set the innermost possible scroll clip on opacity items during creation. r?mattwoodrow
Always use an ancestor scroll clip of all direct children, or the original
scroll clip if the children don't share the same scroll clip tree.
Unfortunately this requires another pass over the stacking context display list.
Also, fix clips, scroll clips and creation order of blend items:
If a clipped mix-blend-mode item contains absolute / fixed positioned items,
those items should not be clipped, same for blend container items.
When a transform item contains blend modes, create the blend container inside
the transform.
Don't do tree comparisons on scroll clips from different scroll clip trees.
If the inner scroll clip is nullptr, because it was cleared, it will look like
it's the ancestor of the outer non-nullptr scroll clip.
These changes don't look very related, but it was very hard to get tests passing
with only some of the changes and not the others, and after having spent two
weeks on this patch I'm not thrilled about going back and checking exactly which
change was necessary to fix which test failure.
MozReview-Commit-ID: IKGciUBrdNa
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -3808,29 +3808,16 @@ ContainerState::ProcessDisplayItems(nsDi
}
topLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
}
if (!animatedGeometryRootForClip) {
animatedGeometryRootForClip = animatedGeometryRoot;
}
const DisplayItemScrollClip* itemScrollClip = item->ScrollClip();
- if (itemType == nsDisplayItem::TYPE_OPACITY && layerState == LAYER_INACTIVE) {
- // This is an unfortunate hack. For opacity items, we usually want to
- // apply clips and scroll transforms to their descendants. However, if
- // an opacity is inactive and gets painted into a PaintedLayer, we can't
- // apply async scrolling offsets to the inactive layer manager contents;
- // instead, we need to move the opacity item itself, by moving the
- // PaintedLayer that it gets painted into.
- itemScrollClip = InnermostScrollClipApplicableToAGR(
- static_cast<nsDisplayOpacity*>(item)->ScrollClipForSameAGRChildren(),
- animatedGeometryRootForClip);
- item->SetScrollClip(itemScrollClip);
- item->UpdateBounds(mBuilder);
- }
// Now we need to separate the item's scroll clip chain into those scroll
// clips that can be applied to the whole layer (i.e. to all items
// sharing the item's animated geometry root), and those that need to be
// applied to the item itself.
const DisplayItemScrollClip* agrScrollClip =
InnermostScrollClipApplicableToAGR(itemScrollClip, animatedGeometryRootForClip);
MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(agrScrollClip, itemScrollClip));
@@ -3999,17 +3986,17 @@ ContainerState::ProcessDisplayItems(nsDi
// contents' current bounds and doesn't anticipate any animations.
// Time will tell whether this is good enough, or whether we need to do
// something more sophisticated here.
mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
&itemVisibleRect, uniformColorPtr);
}
mParameters.mBackgroundColor = uniformColor;
- mParameters.mScrollClip = DisplayItemScrollClip::PickInnermost(agrScrollClip, mContainerScrollClip);
+ mParameters.mScrollClip = agrScrollClip;
// Just use its layer.
// Set layerContentsVisibleRect.width/height to -1 to indicate we
// currently don't know. If BuildContainerLayerFor gets called by
// item->BuildLayer, this will be set to a proper rect.
nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
mParameters.mLayerContentsVisibleRect = &layerContentsVisibleRect;
RefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, mParameters);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -4122,20 +4122,19 @@ nsresult nsDisplayWrapper::WrapListsInPl
rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this);
NS_ENSURE_SUCCESS(rv, rv);
// The outlines may not be in-flow
return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this);
}
nsDisplayOpacity::nsDisplayOpacity(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList,
- const DisplayItemScrollClip* aScrollClipForSameAGRChildren,
+ const DisplayItemScrollClip* aScrollClip,
bool aForEventsOnly)
- : nsDisplayWrapList(aBuilder, aFrame, aList)
- , mScrollClipForSameAGRChildren(aScrollClipForSameAGRChildren)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
, mOpacity(aFrame->StyleDisplay()->mOpacity)
, mForEventsOnly(aForEventsOnly)
{
MOZ_COUNT_CTOR(nsDisplayOpacity);
}
#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayOpacity::~nsDisplayOpacity() {
@@ -4323,18 +4322,20 @@ bool nsDisplayOpacity::TryMerge(nsDispla
void
nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream)
{
aStream << " (opacity " << mOpacity << ")";
}
nsDisplayMixBlendMode::nsDisplayMixBlendMode(nsDisplayListBuilder* aBuilder,
- nsIFrame* aFrame, nsDisplayList* aList)
-: nsDisplayWrapList(aBuilder, aFrame, aList) {
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
+{
MOZ_COUNT_CTOR(nsDisplayMixBlendMode);
}
#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayMixBlendMode::~nsDisplayMixBlendMode() {
MOZ_COUNT_DTOR(nsDisplayMixBlendMode);
}
#endif
@@ -4400,20 +4401,29 @@ bool nsDisplayMixBlendMode::TryMerge(nsD
if (aItem->ScrollClip() != ScrollClip())
return false;
MergeFromTrackingMergedFrames(static_cast<nsDisplayMixBlendMode*>(aItem));
return true;
}
nsDisplayBlendContainer::nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList,
- const BlendModeSet& aContainedBlendModes)
+ const DisplayItemScrollClip* aScrollClip)
+ : nsDisplayWrapList(aBuilder, aFrame, aList, aScrollClip)
+ , mIndex(0)
+ , mCanBeActive(true)
+{
+ MOZ_COUNT_CTOR(nsDisplayBlendContainer);
+}
+
+nsDisplayBlendContainer::nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList)
: nsDisplayWrapList(aBuilder, aFrame, aList)
- , mIndex(aContainedBlendModes.isEmpty() ? 1 : 0)
- , mCanBeActive(!aContainedBlendModes.isEmpty())
+ , mIndex(1)
+ , mCanBeActive(false)
{
MOZ_COUNT_CTOR(nsDisplayBlendContainer);
}
#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayBlendContainer::~nsDisplayBlendContainer() {
MOZ_COUNT_DTOR(nsDisplayBlendContainer);
}
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -3351,17 +3351,17 @@ protected:
/**
* The standard display item to paint a stacking context with translucency
* set by the stacking context root frame's 'opacity' style.
*/
class nsDisplayOpacity : public nsDisplayWrapList {
public:
nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList,
- const DisplayItemScrollClip* aScrollClipForSameAGRChildren,
+ const DisplayItemScrollClip* aScrollClip,
bool aForEventsOnly);
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayOpacity();
#endif
virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
bool* aSnap) override;
virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
@@ -3385,29 +3385,26 @@ public:
virtual bool CanApplyOpacity() const override;
virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder);
NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY)
virtual void WriteDebugInfo(std::stringstream& aStream) override;
bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
- const DisplayItemScrollClip* ScrollClipForSameAGRChildren() const
- { return mScrollClipForSameAGRChildren; }
-
private:
- const DisplayItemScrollClip* mScrollClipForSameAGRChildren;
float mOpacity;
bool mForEventsOnly;
};
class nsDisplayMixBlendMode : public nsDisplayWrapList {
public:
nsDisplayMixBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
- nsDisplayList* aList);
+ nsDisplayList* aList,
+ const DisplayItemScrollClip* aScrollClip);
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayMixBlendMode();
#endif
nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
bool* aSnap) override;
virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
@@ -3428,19 +3425,23 @@ public:
virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
return false;
}
NS_DISPLAY_DECL_NAME("MixBlendMode", TYPE_MIX_BLEND_MODE)
};
class nsDisplayBlendContainer : public nsDisplayWrapList {
public:
+ // Use this constructor for blend containers that can have active child layers.
nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList,
- const BlendModeSet& aContainedBlendModes = BlendModeSet());
+ const DisplayItemScrollClip* aScrollClip);
+ // Use this constructor for background-blend-mode blend containers.
+ nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList);
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayBlendContainer();
#endif
virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aContainerParameters) override;
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -78,16 +78,17 @@
#include "nsChangeHint.h"
#include "nsDeckFrame.h"
#include "nsSubDocumentFrame.h"
#include "SVGTextFrame.h"
#include "gfxContext.h"
#include "nsRenderingContext.h"
#include "nsAbsoluteContainingBlock.h"
+#include "DisplayItemScrollClip.h"
#include "StickyScrollContainer.h"
#include "nsFontInflationData.h"
#include "gfxASurface.h"
#include "nsRegion.h"
#include "nsIFrameInlines.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EffectCompositor.h"
@@ -1902,34 +1903,41 @@ WrapSeparatorTransform(nsDisplayListBuil
aDirtyRect, Matrix4x4(), aIndex);
sepIdItem->SetNoExtendContext();
aTarget->AppendToTop(sepIdItem);
}
}
static void
CreateOpacityItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
- nsDisplayList& aList, bool aItemForEventsOnly)
+ nsDisplayList& aList, bool aItemForEventsOnly,
+ const DisplayItemScrollClip* aScrollClip)
{
// 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.
- // We clear both regular clips and scroll clips. If this item's animated
- // geometry root has async scrolling, then the async scroll transform will
- // be applied on the opacity's descendants (because that's where the
- // scroll clip will be). However, this won't work if the opacity item is
- // inactive, which is why we record the pre-clear scroll clip here.
- const DisplayItemScrollClip* scrollClipForSameAGRChildren =
- aBuilder->ClipState().GetCurrentInnermostScrollClip();
DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
- opacityClipState.ClearIncludingScrollClip();
+ opacityClipState.Clear();
aList.AppendNewToTop(
new (aBuilder) nsDisplayOpacity(aBuilder, aFrame, &aList,
- scrollClipForSameAGRChildren,
- aItemForEventsOnly));
+ aScrollClip, aItemForEventsOnly));
+}
+
+static const DisplayItemScrollClip*
+FindCommonAncestorScrollClip(nsDisplayList& aList, const DisplayItemScrollClip* aInitial)
+{
+ const DisplayItemScrollClip* ancestorScrollClip = aInitial;
+ for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
+ const DisplayItemScrollClip* itemScrollClip = i->ScrollClip();
+ if (!DisplayItemScrollClip::IsAncestor(ancestorScrollClip, itemScrollClip)) {
+ MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(itemScrollClip, ancestorScrollClip));
+ ancestorScrollClip = itemScrollClip;
+ }
+ }
+ return ancestorScrollClip;
}
void
nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
nsDisplayList* aList) {
if (GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
return;
@@ -2039,27 +2047,38 @@ nsIFrame::BuildDisplayListForStackingCon
mozilla::gfx::VRDeviceProxy* vrHMDInfo = nullptr;
if ((GetStateBits() & NS_FRAME_HAS_VR_CONTENT)) {
vrHMDInfo = static_cast<mozilla::gfx::VRDeviceProxy*>(mContent->GetProperty(nsGkAtoms::vr_state));
}
DisplayListClipState::AutoSaveRestore clipState(aBuilder);
- if (isTransformed || useBlendMode || usingSVGEffects || useFixedPosition || useStickyPosition) {
+ // The scroll clip to use for the container items that we create here.
+ // We can create multiple different container items in this function, and not
+ // all of them will use the same scroll clip depending on whether we reset
+ // the clip, so the value of containerItemScrollClip will be updated whenever
+ // we clear or restore the clip.
+ const DisplayItemScrollClip* containerItemScrollClip =
+ aBuilder->ClipState().GetCurrentInnermostScrollClip();
+ bool didResetClip = false;
+
+ if (isTransformed || usingSVGEffects || useFixedPosition || useStickyPosition) {
// We don't need to pass ancestor clipping down to our children;
// everything goes inside a display item's child list, and the display
// item itself will be clipped.
// For transforms we also need to clear ancestor clipping because it's
// relative to the wrong display item reference frame anyway.
// We clear both regular and scroll clips here. Our content needs to be
// able to walk up the complete cross stacking context scroll clip chain,
// so we call a special method on the clip state that keeps the ancestor
// scroll clip around.
clipState.ClearForStackingContextContents();
+ didResetClip = true;
+ containerItemScrollClip = nullptr;
}
nsDisplayListCollection set;
{
DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
nsDisplayListBuilder::AutoInTransformSetter
inTransformSetter(aBuilder, inTransform);
nsDisplayListBuilder::AutoSaveRestorePerspectiveIndex
@@ -2143,20 +2162,52 @@ nsIFrame::BuildDisplayListForStackingCon
}
#ifdef DEBUG
DisplayDebugBorders(aBuilder, this, set);
#endif
resultList.AppendToTop(set.Outlines());
// 8, 9: non-negative z-index children
resultList.AppendToTop(set.PositionedDescendants());
+ if (!didResetClip) {
+ // The current scroll clip for this frame was containerItemScrollClip, but
+ // some of our children might have received a scroll clip from outside this
+ // frame, and containerItemScrollClip would not be an ancestor of that
+ // scroll clip. So we need to find a scroll clip that is an ancestor of all
+ // containened scroll clips.
+ // We don't need to do this if we reset the clip - in that case, the
+ // pre-clear scroll clip and and the scroll clips of our contents are in
+ // different scroll clip trees. (This is very confusing and I apologize.)
+ containerItemScrollClip = FindCommonAncestorScrollClip(resultList,
+ containerItemScrollClip);
+ }
+
+ /* If adding both a nsDisplayBlendContainer and a nsDisplayMixBlendMode to the
+ * same list, the nsDisplayBlendContainer should be added first. This only
+ * happens when the element creating this stacking context has mix-blend-mode
+ * and also contains a child which has mix-blend-mode.
+ * The nsDisplayBlendContainer must be added to the list first, so it does not
+ * isolate the containing element blending as well.
+ */
+
+ if (aBuilder->ContainsBlendMode()) {
+ DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder);
+ blendContainerClipState.Clear();
+ resultList.AppendNewToTop(
+ new (aBuilder) nsDisplayBlendContainer(aBuilder, this, &resultList,
+ containerItemScrollClip));
+ }
+
if (!isTransformed) {
// Restore saved clip state now so that any display items we create below
// are clipped properly.
clipState.Restore();
+ if (didResetClip) {
+ containerItemScrollClip = aBuilder->ClipState().GetCurrentInnermostScrollClip();
+ }
}
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
* output even if the element being filtered wouldn't otherwise do so.
@@ -2175,17 +2226,18 @@ nsIFrame::BuildDisplayListForStackingCon
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);
+ 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
* anything.
*
* For the preserve-3d case we want to individually wrap every child in the list with
@@ -2223,16 +2275,19 @@ nsIFrame::BuildDisplayListForStackingCon
WrapSeparatorTransform(aBuilder, this, dirtyRect,
&nonparticipants, &participants, index++);
resultList.AppendToTop(&participants);
}
// Restore clip state now so nsDisplayTransform is clipped properly.
if (!HasPerspective()) {
clipState.Restore();
+ if (didResetClip) {
+ containerItemScrollClip = aBuilder->ClipState().GetCurrentInnermostScrollClip();
+ }
}
// Revert to the dirtyrect coming in from the parent, without our transform
// taken into account.
buildingDisplayList.SetDirtyRect(dirtyRectOutsideTransform);
// Revert to the outer reference frame and offset because all display
// items we create from now on are outside the transform.
nsPoint toOuterReferenceFrame;
const nsIFrame* outerReferenceFrame = this;
@@ -2244,27 +2299,31 @@ nsIFrame::BuildDisplayListForStackingCon
GetOffsetToCrossDoc(outerReferenceFrame));
nsDisplayTransform *transformItem =
new (aBuilder) nsDisplayTransform(aBuilder, this, &resultList, dirtyRect);
resultList.AppendNewToTop(transformItem);
if (HasPerspective()) {
clipState.Restore();
+ if (didResetClip) {
+ containerItemScrollClip = aBuilder->ClipState().GetCurrentInnermostScrollClip();
+ }
resultList.AppendNewToTop(
new (aBuilder) nsDisplayPerspective(
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);
+ CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly,
+ containerItemScrollClip);
}
}
/* If we have sticky positioning, wrap it in a sticky position item.
*/
if (useFixedPosition) {
resultList.AppendNewToTop(
new (aBuilder) nsDisplayFixedPosition(aBuilder, this, &resultList));
@@ -2275,37 +2334,27 @@ nsIFrame::BuildDisplayListForStackingCon
/* If we're doing VR rendering, then we need to wrap everything in a nsDisplayVR
*/
if (vrHMDInfo && !resultList.IsEmpty()) {
resultList.AppendNewToTop(
new (aBuilder) nsDisplayVR(aBuilder, this, &resultList, vrHMDInfo));
}
- /* If adding both a nsDisplayBlendContainer and a nsDisplayMixBlendMode to the
- * same list, the nsDisplayBlendContainer should be added first. This only
- * happens when the element creating this stacking context has mix-blend-mode
- * and also contains a child which has mix-blend-mode.
- * The nsDisplayBlendContainer must be added to the list first, so it does not
- * isolate the containing element blending as well.
- */
-
- if (aBuilder->ContainsBlendMode()) {
- resultList.AppendNewToTop(
- new (aBuilder) nsDisplayBlendContainer(aBuilder, this, &resultList, aBuilder->ContainedBlendModes()));
- }
-
/* If there's blending, wrap up the list in a blend-mode item. Note
* that opacity can be applied before blending as the blend color is
* not affected by foreground opacity (only background alpha).
*/
if (useBlendMode && !resultList.IsEmpty()) {
+ DisplayListClipState::AutoSaveRestore mixBlendClipState(aBuilder);
+ mixBlendClipState.Clear();
resultList.AppendNewToTop(
- new (aBuilder) nsDisplayMixBlendMode(aBuilder, this, &resultList));
+ new (aBuilder) nsDisplayMixBlendMode(aBuilder, this, &resultList,
+ containerItemScrollClip));
}
CreateOwnLayerIfNeeded(aBuilder, &resultList);
aList->AppendToTop(&resultList);
}
static nsDisplayItem*
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-1.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en"
+ reftest-async-scroll
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="1200">
+<meta charset="utf-8">
+<title>Scrolled blend mode</title>
+
+<style>
+
+body {
+ margin: 0;
+ padding: 100px;
+ height: 4000px;
+ background: white;
+}
+
+#outer {
+ height: 4000px;
+ overflow: hidden;
+}
+
+#inner-contents {
+ border: 1px solid black;
+ box-sizing: border-box;
+ width: 50px;
+ height: 50px;
+ mix-blend-mode: multiply;
+ margin-top: 1400px;
+}
+
+</style>
+
+<div id="outer">
+ <div id="inner">
+ <div id="inner-contents"></div>
+ </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-2.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en"
+ reftest-async-scroll
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="1200">
+<meta charset="utf-8">
+<title>Scrolled blend mode</title>
+
+<style>
+
+body {
+ margin: 0;
+ padding: 100px;
+ height: 4000px;
+ background: white;
+}
+
+#outer {
+ height: 4000px;
+ overflow: hidden;
+}
+
+#inner {
+ transform: translateX(0);
+}
+
+#inner-contents {
+ border: 1px solid black;
+ box-sizing: border-box;
+ width: 50px;
+ height: 50px;
+ mix-blend-mode: multiply;
+ margin-top: 1400px;
+}
+
+</style>
+
+<div id="outer">
+ <div id="inner">
+ <div id="inner-contents"></div>
+ </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-3.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en"
+ reftest-async-scroll
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="1200">
+<meta charset="utf-8">
+<title>Scrolled blend mode</title>
+
+<style>
+
+body {
+ margin: 0;
+ padding: 100px;
+ height: 4000px;
+ background: white;
+}
+
+#outer {
+ height: 4000px;
+ overflow: hidden;
+}
+
+#inner-contents {
+ border: 1px solid black;
+ box-sizing: border-box;
+ width: 50px;
+ height: 50px;
+ mix-blend-mode: multiply;
+ margin-top: 1400px;
+ transform: translateX(0);
+}
+
+</style>
+
+<div id="outer">
+ <div id="inner">
+ <div id="inner-contents"></div>
+ </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-4.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en"
+ reftest-async-scroll
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="800" reftest-displayport-h="2000"
+ reftest-async-scroll-x="0" reftest-async-scroll-y="1200">
+<meta charset="utf-8">
+<title>Scrolled blend mode</title>
+
+<style>
+
+body {
+ margin: 0;
+ padding: 100px;
+ height: 4000px;
+ background: white;
+}
+
+#outer {
+ height: 4000px;
+ overflow: hidden;
+}
+
+#inner {
+ transform: translateX(0);
+}
+
+#inner-contents {
+ border: 1px solid black;
+ box-sizing: border-box;
+ width: 50px;
+ height: 50px;
+ mix-blend-mode: multiply;
+ margin-top: 1400px;
+ transform: translateX(0);
+}
+
+</style>
+
+<div id="outer">
+ <div id="inner">
+ <div id="inner-contents"></div>
+ </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-clipped-blendmode-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Scrolled blend mode</title>
+
+<style>
+
+body {
+ margin: 0;
+ padding: 100px;
+ height: 4000px;
+ background: white;
+}
+
+#inner-contents {
+ border: 1px solid black;
+ box-sizing: border-box;
+ width: 50px;
+ height: 50px;
+ margin-top: 1400px;
+}
+
+</style>
+
+<div id="inner-contents"></div>
+
+<script>
+
+document.documentElement.scrollTop = 1200;
+
+</script>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -27,16 +27,20 @@ fuzzy-if(skiaContent,1,32000) fuzzy-if(b
== fixed-pos-scrollable-1.html fixed-pos-scrollable-1-ref.html
== culling-1.html culling-1-ref.html
== position-fixed-iframe-1.html position-fixed-iframe-1-ref.html
== position-fixed-iframe-2.html position-fixed-iframe-2-ref.html
fuzzy-if(skiaContent||(browserIsRemote&&cocoaWidget),1,10000) skip-if(!asyncPan) == position-fixed-in-scroll-container.html position-fixed-in-scroll-container-ref.html
fuzzy(1,60000) skip-if(!asyncPan) == group-opacity-surface-size-1.html group-opacity-surface-size-1-ref.html
== position-sticky-transformed.html position-sticky-transformed-ref.html
== offscreen-prerendered-active-opacity.html offscreen-prerendered-active-opacity-ref.html
+fuzzy-if(Android,6,4) == offscreen-clipped-blendmode-1.html offscreen-clipped-blendmode-ref.html
+fuzzy-if(Android,6,4) == offscreen-clipped-blendmode-2.html offscreen-clipped-blendmode-ref.html
+fuzzy-if(Android,6,4) skip == offscreen-clipped-blendmode-3.html offscreen-clipped-blendmode-ref.html # bug 1251588 - wrong AGR on mix-blend-mode item
+fuzzy-if(Android,6,4) == offscreen-clipped-blendmode-4.html offscreen-clipped-blendmode-ref.html
# for the following tests, we want to disable the low-precision buffer
# as it will expand the displayport beyond what the test specifies in
# its reftest-displayport attributes, and interfere with where we expect
# checkerboarding to occur
default-preferences pref(layers.low-precision-buffer,false)
== checkerboard-1.html checkerboard-1-ref.html
== checkerboard-2.html checkerboard-2-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff-ref.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Blend mode items shouldn't clip unclipped children to their own clip</title>
+
+<style>
+
+body {
+ margin: 0;
+ background: white; /* this shouldn't be necessary, bug ??????? */
+}
+
+.content {
+ box-sizing: border-box;
+ border: 10px solid yellow;
+ width: 100px;
+ height: 100px;
+}
+
+.clip {
+ box-sizing: border-box;
+ width: 300px;
+ height: 200px;
+ border: 10px solid black;
+ background-color: white;
+ overflow: hidden;
+}
+
+.absolutelyPositioned {
+ position: absolute;
+ top: 20px;
+ left: 250px;
+}
+
+.inner {
+ margin-left: auto;
+ border-color: magenta;
+}
+
+.blendedOverlay {
+ position: absolute;
+ top: 20px;
+ left: 290px;
+ width: 10px;
+ border-left: 0;
+ border-right: 0;
+ border-color: blue;
+}
+
+</style>
+
+<div class="clip">
+ <div class="absolutelyPositioned content"></div>
+ <div class="inner content"></div>
+</div>
+
+<div class="blendedOverlay content"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-blending/clipped-mixblendmode-containing-unclipped-stuff.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Blend mode items shouldn't clip unclipped children to their own clip</title>
+
+<style>
+
+body {
+ margin: 0;
+ background: white; /* this shouldn't be necessary, bug ??????? */
+}
+
+.content {
+ box-sizing: border-box;
+ border: 10px solid blue;
+ width: 100px;
+ height: 100px;
+}
+
+.clip {
+ box-sizing: border-box;
+ width: 300px;
+ height: 200px;
+ border: 10px solid black;
+ background-color: white;
+ overflow: hidden;
+}
+
+.mixBlendMode {
+ mix-blend-mode: exclusion;
+}
+
+.absolutelyPositioned {
+ position: absolute;
+ top: 20px;
+ left: 250px;
+}
+
+.inner {
+ margin-left: auto;
+ border-color: lime;
+}
+
+</style>
+
+<div class="clip">
+ <div class="mixBlendMode">
+ <div class="absolutelyPositioned content"></div>
+ <div class="inner content"></div>
+ </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode-ref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Blend mode containers shouldn't clip unclipped children to their own clip</title>
+
+<style>
+
+body {
+ margin: 0;
+}
+
+.content {
+ box-sizing: border-box;
+ border: 10px solid black;
+ width: 100px;
+ height: 100px;
+}
+
+.clip {
+ box-sizing: border-box;
+ width: 300px;
+ height: 200px;
+ border: 10px solid black;
+ overflow: hidden;
+}
+
+.opacity {
+ opacity: 0.5;
+}
+
+.absolutelyPositioned {
+ position: absolute;
+ top: 20px;
+ left: 250px;
+}
+
+.mixBlendMode {
+ border-color: blue;
+ margin-left: auto;
+}
+
+</style>
+
+<div class="clip">
+ <div class="opacity">
+ <div class="absolutelyPositioned content"></div>
+ <div class="mixBlendMode content"></div>
+ </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-blending/clipped-opacity-containing-unclipped-mixblendmode.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Blend mode containers shouldn't clip unclipped children to their own clip</title>
+
+<style>
+
+body {
+ margin: 0;
+}
+
+.content {
+ box-sizing: border-box;
+ border: 10px solid black;
+ width: 100px;
+ height: 100px;
+}
+
+.clip {
+ box-sizing: border-box;
+ width: 300px;
+ height: 200px;
+ border: 10px solid black;
+ overflow: hidden;
+}
+
+.opacity {
+ opacity: 0.5;
+}
+
+.absolutelyPositioned {
+ position: absolute;
+ top: 20px;
+ left: 250px;
+}
+
+.mixBlendMode {
+ mix-blend-mode: multiply;
+ border-color: blue;
+ margin-left: auto;
+}
+
+</style>
+
+<div class="clip">
+ <div class="opacity">
+ <div class="absolutelyPositioned content"></div>
+ <div class="mixBlendMode content"></div>
+ </div>
+</div>
--- a/layout/reftests/css-blending/reftest.list
+++ b/layout/reftests/css-blending/reftest.list
@@ -43,16 +43,18 @@ fuzzy(64,53) pref(layout.css.mix-blend-m
fuzzy-if(d3d11,49,200) pref(layout.css.mix-blend-mode.enabled,true) pref(layout.css.filters.enabled,true) == mix-blend-mode-and-filter.html mix-blend-mode-and-filter-ref.html
fuzzy-if(d3d11,1,3) pref(layout.css.mix-blend-mode.enabled,true) pref(layout.css.filters.enabled,true) == mix-blend-mode-and-filter.svg mix-blend-mode-and-filter-ref.svg
fuzzy(2,14400) pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-child-of-blended-has-opacity.html mix-blend-mode-child-of-blended-has-opacity-ref.html
pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-nested-976533.html mix-blend-mode-nested-976533-ref.html
pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-culling-1207041.html mix-blend-mode-culling-1207041-ref.html
pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-dest-alpha-1135271.html mix-blend-mode-dest-alpha-1135271-ref.html
+== clipped-mixblendmode-containing-unclipped-stuff.html clipped-mixblendmode-containing-unclipped-stuff-ref.html
+fuzzy(1,6800) == clipped-opacity-containing-unclipped-mixblendmode.html clipped-opacity-containing-unclipped-mixblendmode-ref.html
# Test plan 5.3.1 Blending between the background layers and the background color for an element with background-blend-mode
# Test 9
pref(layout.css.background-blend-mode.enabled,true) == background-blending-image-color-svg-as-data-uri.html background-blending-image-color-ref.html
# Test 10
pref(layout.css.background-blend-mode.enabled,true) == background-blending-image-color-gif.html background-blending-image-color-gif-ref.html
pref(layout.css.background-blend-mode.enabled,true) == background-blending-image-color-transform3d.html background-blending-image-color-ref.html