Bug 1442190 - Part 5: Use FLBDisplayItemIterator and flatten inactive nsDisplayOpacity in more cases r=mattwoodrow
authorMiko Mynttinen <mikokm@gmail.com>
Tue, 13 Mar 2018 15:55:56 +0100
changeset 413041 7c3ec4cce4893c84cb6bc47d2f4c185bc66a2d83
parent 413040 3bb3af9020e1431b1af0b323e873949f8dec7310
child 413042 665843c6cfd1a3049128597afbef8a68bcc1e086
push id33831
push userrgurzau@mozilla.com
push dateThu, 12 Apr 2018 23:07:19 +0000
treeherdermozilla-central@46615d425bcb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1442190
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1442190 - Part 5: Use FLBDisplayItemIterator and flatten inactive nsDisplayOpacity in more cases r=mattwoodrow MozReview-Commit-ID: 9I8YvMDEFHy
layout/painting/FrameLayerBuilder.cpp
layout/painting/FrameLayerBuilder.h
layout/painting/nsDisplayList.cpp
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -571,17 +571,18 @@ public:
    * @param aSolidColor if non-null, the visible area of the item is
    * a constant color given by *aSolidColor
    */
   void Accumulate(ContainerState* aState,
                   nsDisplayItem* aItem,
                   const nsIntRect& aVisibleRect,
                   const DisplayItemClip& aClip,
                   LayerState aLayerState,
-                  nsDisplayList *aList);
+                  nsDisplayList *aList,
+                  DisplayItemEntryType aType);
   AnimatedGeometryRoot* GetAnimatedGeometryRoot() { return mAnimatedGeometryRoot; }
 
   /**
    * A region including the horizontal pan, vertical pan, and no action regions.
    */
   nsRegion CombinedTouchActionRegion();
 
   /**
@@ -767,17 +768,21 @@ public:
    * This is a conservative approximation: it contains the true region.
    */
   nsIntRegion mVisibleAboveRegion;
   /**
    * All the display items that have been assigned to this painted layer.
    * These items get added by Accumulate().
    */
   nsTArray<AssignedDisplayItem> mAssignedDisplayItems;
-
+  /**
+   * Tracks the active opacity markers by holding the indices to PUSH_OPACITY
+   * items in |mAssignedDisplayItems|.
+   */
+  nsTArray<size_t> mOpacityIndices;
 };
 
 struct NewLayerEntry {
   NewLayerEntry()
     : mAnimatedGeometryRoot(nullptr)
     , mASR(nullptr)
     , mClipChain(nullptr)
     , mScrollMetadataASR(nullptr)
@@ -2731,16 +2736,23 @@ ContainerState::FindOpaqueBackgroundColo
   *aOutIntersectsLayer = true;
 
   // Scan the candidate's display items.
   nsIntRect deviceRect = aRect;
   nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel);
   appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);
 
   for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) {
+    if (assignedItem.mType != DisplayItemEntryType::ITEM ||
+        assignedItem.mHasOpacity) {
+      // |assignedItem| is either an effect marker, or within a flatten opacity
+      // group. In both cases, there is no opaque area.
+      continue;
+    }
+
     nsDisplayItem* item = assignedItem.mItem;
     bool snap;
     nsRect bounds = item->GetBounds(mBuilder, &snap);
     if (snap && mSnappingEnabled) {
       nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
       if (!snappedBounds.Intersects(deviceRect))
         continue;
 
@@ -3300,16 +3312,18 @@ SetBackfaceHiddenForLayer(bool aBackface
   }
 }
 
 template<typename FindOpaqueBackgroundColorCallbackType>
 void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor)
 {
   PaintedLayerData* data = &aData;
 
+  MOZ_ASSERT(data->mOpacityIndices.IsEmpty());
+
   if (!data->mLayer) {
     // No layer was recycled, so we create a new one.
     RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data);
     data->mLayer = paintedLayer;
 
     NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
                  "Layer already in list???");
     mNewChildLayers[data->mNewChildLayersIndex].mLayer = paintedLayer.forget();
@@ -3368,16 +3382,21 @@ void ContainerState::FinishPaintedLayerD
     layer->SetClipRect(Nothing());
     FLB_LOG_PAINTED_LAYER_DECISION(data, "  Selected painted layer=%p\n", layer.get());
   }
 
   for (auto& item : data->mAssignedDisplayItems) {
     MOZ_ASSERT(item.mItem->GetType() != DisplayItemType::TYPE_LAYER_EVENT_REGIONS);
     MOZ_ASSERT(item.mItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO);
 
+    if (item.mType == DisplayItemEntryType::POP_OPACITY) {
+      // Do not invalidate for end markers.
+      continue;
+    }
+
     InvalidateForLayerChange(item.mItem, data->mLayer, item.mDisplayItemData);
     mLayerBuilder->AddPaintedDisplayItem(data, item, *this, layer);
     item.mDisplayItemData = nullptr;
   }
 
   if (mLayerBuilder->IsBuildingRetainedLayers()) {
     newLayerEntry->mVisibleRegion = data->mVisibleRegion;
     newLayerEntry->mOpaqueRegion = data->mOpaqueRegion;
@@ -3575,42 +3594,78 @@ IsItemAreaInWindowOpaqueRegion(nsDisplay
     // the window.
     return false;
   }
   return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds);
 }
 
 void
 PaintedLayerData::Accumulate(ContainerState* aState,
-                            nsDisplayItem* aItem,
-                            const nsIntRect& aVisibleRect,
-                            const DisplayItemClip& aClip,
-                            LayerState aLayerState,
-                            nsDisplayList* aList)
+                             nsDisplayItem* aItem,
+                             const nsIntRect& aVisibleRect,
+                             const DisplayItemClip& aClip,
+                             LayerState aLayerState,
+                             nsDisplayList* aList,
+                             DisplayItemEntryType aType)
 {
   FLB_LOG_PAINTED_LAYER_DECISION(this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(), aItem, aItem->Frame(), this);
 
+  const bool hasOpacity = mOpacityIndices.Length() > 0;
+
+  if (aType == DisplayItemEntryType::POP_OPACITY) {
+    MOZ_ASSERT(!mOpacityIndices.IsEmpty());
+    mOpacityIndices.RemoveLastElement();
+
+    AssignedDisplayItem item(aItem, aClip, aLayerState,
+                             nullptr, aType, hasOpacity);
+    mAssignedDisplayItems.AppendElement(Move(item));
+    return;
+  }
+
   if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) {
     mForceTransparentSurface = true;
   }
+
+  nsRect componentAlphaBounds;
   if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
     // Disable component alpha.
-    // Note that the transform (if any) on the PaintedLayer is always an integer translation so
-    // we don't have to factor that in here.
+    // Note that the transform (if any) on the PaintedLayer is always an integer
+    // translation so we don't have to factor that in here.
     aItem->DisableComponentAlpha();
+  } else {
+    componentAlphaBounds = aItem->GetComponentAlphaBounds(aState->mBuilder);
+
+    if (!componentAlphaBounds.IsEmpty()) {
+      // This display item needs background copy when pushing opacity group.
+      for (size_t i : mOpacityIndices) {
+        AssignedDisplayItem& item = mAssignedDisplayItems[i];
+        MOZ_ASSERT(item.mType == DisplayItemEntryType::PUSH_OPACITY ||
+                   item.mType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG);
+        item.mType = DisplayItemEntryType::PUSH_OPACITY_WITH_BG;
+      }
+    }
   }
 
   bool clipMatches = mItemClip == aClip;
   mItemClip = aClip;
 
+  DisplayItemData* currentData =
+    aItem->HasMergedFrames() ? nullptr : aItem->GetDisplayItemData();
+
   DisplayItemData* oldData =
-      aState->mLayerBuilder->GetOldLayerForFrame(aItem->Frame(),
-                                         aItem->GetPerFrameKey(),
-                                         aItem->HasMergedFrames() ? nullptr : aItem->GetDisplayItemData());
-  mAssignedDisplayItems.AppendElement(AssignedDisplayItem(aItem, aClip, aLayerState, oldData));
+    aState->mLayerBuilder->GetOldLayerForFrame(aItem->Frame(),
+                                               aItem->GetPerFrameKey(),
+                                               currentData);
+  AssignedDisplayItem item(aItem, aClip, aLayerState,
+                           oldData, aType, hasOpacity);
+  mAssignedDisplayItems.AppendElement(Move(item));
+
+  if (aType == DisplayItemEntryType::PUSH_OPACITY) {
+    mOpacityIndices.AppendElement(mAssignedDisplayItems.Length() - 1);
+  }
 
   if (aItem->MustPaintOnContentSide()) {
      mShouldPaintOnContentSide = true;
   }
 
   if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) &&
       mVisibleRegion.Contains(aVisibleRect) && !mImage) {
     // A very common case! Most pages have a PaintedLayer with the page
@@ -3619,37 +3674,45 @@ PaintedLayerData::Accumulate(ContainerSt
     // The rest of this method won't do anything. mVisibleRegion and mOpaqueRegion
     // don't need updating. mVisibleRegion contains aVisibleRect already,
     // mOpaqueRegion contains aVisibleRect and therefore whatever the opaque
     // region of the item is. mVisibleRegion must contain mOpaqueRegion
     // and therefore aVisibleRect.
     return;
   }
 
-  nsIntRegion opaquePixels = aState->ComputeOpaqueRect(aItem,
-                                                       mAnimatedGeometryRoot, mASR, aClip, aList,
-                                                       &mHideAllLayersBelow, &mOpaqueForAnimatedGeometryRootParent);
-  opaquePixels.AndWith(aVisibleRect);
+  nsIntRegion opaquePixels;
+
+  // Active opacity means no opaque pixels.
+  if (!hasOpacity) {
+    opaquePixels = aState->ComputeOpaqueRect(aItem, mAnimatedGeometryRoot, mASR,
+                                             aClip, aList, &mHideAllLayersBelow,
+                                             &mOpaqueForAnimatedGeometryRootParent);
+    opaquePixels.AndWith(aVisibleRect);
+  }
 
   /* Mark as available for conversion to image layer if this is a nsDisplayImage and
    * it's the only thing visible in this layer.
    */
   if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) &&
       opaquePixels.Contains(mVisibleRegion) &&
       aItem->SupportsOptimizingToImage()) {
     mImage = static_cast<nsDisplayImageContainer*>(aItem);
     FLB_LOG_PAINTED_LAYER_DECISION(this, "  Tracking image: nsDisplayImageContainer covers the layer\n");
   } else if (mImage) {
     FLB_LOG_PAINTED_LAYER_DECISION(this, "  No longer tracking image\n");
     mImage = nullptr;
   }
 
   bool isFirstVisibleItem = mVisibleRegion.IsEmpty();
 
-  Maybe<nscolor> uniformColor = aItem->IsUniform(aState->mBuilder);
+  Maybe<nscolor> uniformColor;
+  if (!hasOpacity) {
+    uniformColor = aItem->IsUniform(aState->mBuilder);
+  }
 
   // Some display items have to exist (so they can set forceTransparentSurface
   // below) but don't draw anything. They'll return true for isUniform but
   // a color with opacity 0.
   if (!uniformColor || NS_GET_A(*uniformColor) > 0) {
     // Make sure that the visible area is covered by uniform pixels. In
     // particular this excludes cases where the edges of the item are not
     // pixel-aligned (thus the item will not be truly uniform).
@@ -3697,39 +3760,37 @@ PaintedLayerData::Accumulate(ContainerSt
        // transparent are always added to the opaque region. This helps ensure
        // that we get as much subpixel-AA as possible in the chrome.
        if (tmp.GetNumRects() <= 4 || aItem->Frame()->PresContext()->IsChrome()) {
         mOpaqueRegion = Move(tmp);
       }
     }
   }
 
-  if (!aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
-    nsRect componentAlpha = aItem->GetComponentAlphaBounds(aState->mBuilder);
-    if (!componentAlpha.IsEmpty()) {
-      nsIntRect componentAlphaRect =
-        aState->ScaleToOutsidePixels(componentAlpha, false).Intersect(aVisibleRect);
-      if (!mOpaqueRegion.Contains(componentAlphaRect)) {
-        if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem,
-              componentAlpha.Intersect(aItem->GetVisibleRect()))) {
-          mNeedComponentAlpha = true;
-        } else {
-          aItem->DisableComponentAlpha();
-        }
+  if (!aState->mParameters.mDisableSubpixelAntialiasingInDescendants &&
+      !componentAlphaBounds.IsEmpty()) {
+    nsIntRect componentAlphaRect =
+      aState->ScaleToOutsidePixels(componentAlphaBounds, false).Intersect(aVisibleRect);
+
+    if (!mOpaqueRegion.Contains(componentAlphaRect)) {
+      if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem,
+            componentAlphaBounds.Intersect(aItem->GetVisibleRect()))) {
+        mNeedComponentAlpha = true;
+      } else {
+        aItem->DisableComponentAlpha();
       }
     }
   }
 
   // Ensure animated text does not get flattened, even if it forces other
   // content in the container to be layerized. The content backend might
   // not support subpixel positioning of text that animated transforms can
   // generate. bug 633097
   if (aState->mParameters.mInActiveTransformedSubtree &&
-       (mNeedComponentAlpha ||
-         !aItem->GetComponentAlphaBounds(aState->mBuilder).IsEmpty())) {
+       (mNeedComponentAlpha || !componentAlphaBounds.IsEmpty())) {
     mDisableFlattening = true;
   }
 }
 
 nsRegion
 PaintedLayerData::CombinedTouchActionRegion()
 {
   nsRegion result;
@@ -4216,21 +4277,30 @@ ContainerState::ProcessDisplayItems(nsDi
 #endif
 
   if (!mManager->IsWidgetLayerManager()) {
     mPaintedLayerDataTree.InitializeForInactiveLayer(mContainerAnimatedGeometryRoot);
   }
 
   AnimatedGeometryRoot* lastAnimatedGeometryRoot = nullptr;
   nsPoint lastTopLeft;
-  FlattenedDisplayItemIterator iter(mBuilder, aList);
-  while (nsDisplayItem* i = iter.GetNext()) {
+
+  // Tracks the PaintedLayerData that the item will be accumulated in, if it is
+  // non-null. Currently only used with PUSH_OPACITY and POP_OPACITY markers.
+  PaintedLayerData* selectedPLD = nullptr;
+
+  FLBDisplayItemIterator iter(mBuilder, aList, this);
+  while (iter.HasNext()) {
+    DisplayItemEntry e = iter.GetNextEntry();
+    nsDisplayItem* i = e.mItem;
+    DisplayItemEntryType marker = e.mType;
+
+
     nsDisplayItem* item = i;
     MOZ_ASSERT(item);
-
     DisplayItemType itemType = item->GetType();
 
     // If the item is a event regions item, but is empty (has no regions in it)
     // then we should just throw it out
     if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
 #ifdef DEBUG
       hadLayerEventRegions = true;
 #endif
@@ -4252,30 +4322,33 @@ ContainerState::ProcessDisplayItems(nsDi
       if (hitTestInfo->Area().IsEmpty()) {
         continue;
       }
     }
 
     // Only allow either LayerEventRegions or CompositorHitTestInfo items.
     MOZ_ASSERT(!(hadLayerEventRegions && hadCompositorHitTestInfo));
 
-
     // Peek ahead to the next item and see if it can be merged with the current
     // item. We create a list of consecutive items that can be merged together.
     AutoTArray<nsDisplayItem*, 1> mergedItems;
-    mergedItems.AppendElement(item);
-    while (nsDisplayItem* peek = iter.PeekNext()) {
-      if (!item->CanMerge(peek)) {
-        break;
+
+    if (marker == DisplayItemEntryType::ITEM) {
+      mergedItems.AppendElement(item);
+
+      while (nsDisplayItem* peek = iter.PeekNext()) {
+        if (!item->CanMerge(peek)) {
+          break;
+        }
+
+        mergedItems.AppendElement(peek);
+
+        // Move the iterator forward since we will merge this item.
+        i = iter.GetNext();
       }
-
-      mergedItems.AppendElement(peek);
-
-      // Move the iterator forward since we will merge this item.
-      i = iter.GetNext();
     }
 
     if (mergedItems.Length() > 1) {
       // We have items that can be merged together. Merge them into a temporary
       // item and process that item immediately.
       item = mBuilder->MergeItems(mergedItems);
       MOZ_ASSERT(item && itemType == item->GetType());
     }
@@ -4291,20 +4364,24 @@ ContainerState::ProcessDisplayItems(nsDi
 
     if (mParameters.mForEventsAndPluginsOnly && !item->GetChildren() &&
         (itemType != DisplayItemType::TYPE_LAYER_EVENT_REGIONS &&
          itemType != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO &&
          itemType != DisplayItemType::TYPE_PLUGIN)) {
       continue;
     }
 
-    LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters);
-    if (layerState == LAYER_INACTIVE &&
-        nsDisplayItem::ForceActiveLayers()) {
-      layerState = LAYER_ACTIVE;
+    LayerState layerState = LAYER_NONE;
+
+    if (marker == DisplayItemEntryType::ITEM) {
+      layerState = item->GetLayerState(mBuilder, mManager, mParameters);
+
+      if (layerState == LAYER_INACTIVE && nsDisplayItem::ForceActiveLayers()) {
+        layerState = LAYER_ACTIVE;
+      }
     }
 
     bool forceInactive = false;
     AnimatedGeometryRoot* animatedGeometryRoot;
     const ActiveScrolledRoot* itemASR = nullptr;
     const DisplayItemClipChain* layerClipChain = nullptr;
 
     if (mManager->IsWidgetLayerManager()) {
@@ -4397,16 +4474,21 @@ ContainerState::ProcessDisplayItems(nsDi
     if (layerState == LAYER_ACTIVE_FORCE ||
         (layerState == LAYER_INACTIVE && !mManager->IsWidgetLayerManager()) ||
         (!forceInactive &&
          (layerState == LAYER_ACTIVE_EMPTY ||
           layerState == LAYER_ACTIVE))) {
 
       layerCount++;
 
+      // Currently we do not support flattening effects within nested inactive
+      // layer trees.
+      MOZ_ASSERT(selectedPLD == nullptr);
+      MOZ_ASSERT(marker == DisplayItemEntryType::ITEM);
+
       // LAYER_ACTIVE_EMPTY means the layer is created just for its metadata.
       // We should never see an empty layer with any visible content!
       NS_ASSERTION(layerState != LAYER_ACTIVE_EMPTY ||
                    itemVisibleRect.IsEmpty(),
                    "State is LAYER_ACTIVE_EMPTY but visible rect is not.");
 
       // As long as the new layer isn't going to be a PaintedLayer,
       // InvalidateForLayerChange doesn't need the new layer pointer.
@@ -4716,35 +4798,43 @@ ContainerState::ProcessDisplayItems(nsDi
         oldData =
           mLayerBuilder->GetOldLayerForFrame(item->Frame(), item->GetPerFrameKey());
         mLayerBuilder->StoreDataForFrame(item, ownLayer, layerState, oldData);
       }
     } else {
       const bool backfaceHidden = item->In3DContextAndBackfaceIsHidden();
       const nsIFrame* referenceFrame = item->ReferenceFrame();
 
-      PaintedLayerData* paintedLayerData =
-        mPaintedLayerDataTree.FindPaintedLayerFor(animatedGeometryRoot, itemASR, layerClipChain,
-                                                  itemVisibleRect, backfaceHidden,
-                                                  [&](PaintedLayerData* aData) {
-          NewPaintedLayerData(aData, animatedGeometryRoot, itemASR,
-                              layerClipChain, scrollMetadataASR, topLeft,
-                              referenceFrame, backfaceHidden);
+      PaintedLayerData* paintedLayerData = selectedPLD;
+
+      if (!selectedPLD) {
+        MOZ_ASSERT(marker != DisplayItemEntryType::POP_OPACITY);
+
+        paintedLayerData =
+          mPaintedLayerDataTree.FindPaintedLayerFor(animatedGeometryRoot, itemASR, layerClipChain,
+                                                    itemVisibleRect, backfaceHidden,
+                                                    [&](PaintedLayerData* aData) {
+            NewPaintedLayerData(aData, animatedGeometryRoot, itemASR,
+                                layerClipChain, scrollMetadataASR, topLeft,
+                                referenceFrame, backfaceHidden);
         });
+      }
+      MOZ_ASSERT(paintedLayerData);
 
       if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
         nsDisplayLayerEventRegions* eventRegions =
           static_cast<nsDisplayLayerEventRegions*>(item);
         paintedLayerData->AccumulateEventRegions(this, eventRegions);
       } else if (itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
         nsDisplayCompositorHitTestInfo* hitTestInfo =
           static_cast<nsDisplayCompositorHitTestInfo*>(item);
         paintedLayerData->AccumulateHitTestInfo(this, hitTestInfo);
       } else {
-        paintedLayerData->Accumulate(this, item, itemVisibleRect, itemClip, layerState, aList);
+        paintedLayerData->Accumulate(this, item, itemVisibleRect, itemClip,
+                                     layerState, aList, marker);
 
         if (!paintedLayerData->mLayer) {
           // Try to recycle the old layer of this display item.
           RefPtr<PaintedLayer> layer =
             AttemptToRecyclePaintedLayer(animatedGeometryRoot, item,
                                          topLeft, referenceFrame);
           if (layer) {
             paintedLayerData->mLayer = layer;
@@ -4753,23 +4843,37 @@ ContainerState::ProcessDisplayItems(nsDi
             paintedLayerData->mAssignedDisplayItems.SetCapacity(userData->mLastItemCount);
 
             NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
                          "Layer already in list???");
             mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer = layer.forget();
           }
         }
       }
+
+      if (marker == DisplayItemEntryType::PUSH_OPACITY) {
+        selectedPLD = paintedLayerData;
+      }
+
+      if (marker == DisplayItemEntryType::POP_OPACITY ) {
+        MOZ_ASSERT(selectedPLD);
+
+        if (selectedPLD->mOpacityIndices.IsEmpty()) {
+          selectedPLD = nullptr;
+        }
+      }
     }
 
     nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
     if (childItems && childItems->NeedsTransparentSurface()) {
       aList->SetNeedsTransparentSurface();
     }
   }
+
+  MOZ_ASSERT(selectedPLD == nullptr);
 }
 
 void
 ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem,
                                          PaintedLayer* aNewLayer,
                                          DisplayItemData* aData)
 {
   NS_ASSERTION(aItem->GetPerFrameKey(),
@@ -5092,23 +5196,27 @@ FrameLayerBuilder::StoreDataForFrame(nsI
   data->BeginUpdate(aLayer, aState);
 
   lmd->mDisplayItems.PutEntry(data);
 }
 
 AssignedDisplayItem::AssignedDisplayItem(nsDisplayItem* aItem,
                                          const DisplayItemClip& aClip,
                                          LayerState aLayerState,
-                                         DisplayItemData* aData)
+                                         DisplayItemData* aData,
+                                         DisplayItemEntryType aType,
+                                         const bool aHasOpacity)
   : mItem(aItem)
   , mClip(aClip)
   , mLayerState(aLayerState)
   , mDisplayItemData(aData)
   , mReused(aItem->IsReused())
   , mMerged(aItem->HasMergedFrames())
+  , mType(aType)
+  , mHasOpacity(aHasOpacity)
 {}
 
 AssignedDisplayItem::~AssignedDisplayItem()
 {
   if (mInactiveLayerManager) {
     mInactiveLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
   }
 }
@@ -6039,16 +6147,26 @@ FrameLayerBuilder::RecomputeVisibilityFo
                  NSIntPixelsToAppUnits(aOffset.y, aAppUnitsPerDevPixel));
   visible.ScaleInverseRoundOut(aXScale, aYScale);
 
   for (i = aItems.Length(); i > 0; --i) {
     AssignedDisplayItem* cdi = &aItems[i - 1];
     if (!cdi->mItem) {
       continue;
     }
+
+    if (cdi->mType == DisplayItemEntryType::POP_OPACITY ||
+        (cdi->mType == DisplayItemEntryType::ITEM && cdi->mHasOpacity)) {
+      // The visibility calculations are skipped when the item is an effect end
+      // marker, or when the display item is within a flattened opacity group.
+      // This is because RecomputeVisibility has already been called for the
+      // group item, and all the children.
+      continue;
+    }
+
     const DisplayItemClip& clip = cdi->mItem->GetClip();
 
     NS_ASSERTION(AppUnitsPerDevPixel(cdi->mItem) == aAppUnitsPerDevPixel,
                  "a painted layer should contain items only at the same zoom");
 
     MOZ_ASSERT(clip.HasClip() || clip.GetRoundedRectCount() == 0,
                "If we have rounded rects, we must have a clip rect");
 
@@ -6073,16 +6191,80 @@ FrameLayerBuilder::RecomputeVisibilityFo
       // Don't let the visible region get too complex.
       if (newVisible.GetNumRects() <= 15) {
         visible = Move(newVisible);
       }
     }
   }
 }
 
+/**
+ * Sets the clip chain and starts a new opacity group.
+ */
+static void
+PushOpacity(gfxContext* aContext,
+            const nsRect& aPaintRect,
+            AssignedDisplayItem& aItem,
+            const int32_t aAUPDP)
+{
+  MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PUSH_OPACITY ||
+             aItem.mType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG);
+  MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_OPACITY);
+
+  aContext->Save();
+
+  DisplayItemClip clip;
+  clip.SetTo(aPaintRect);
+  clip.IntersectWith(aItem.mItem->GetClip());
+  clip.ApplyTo(aContext, aAUPDP);
+
+  nsDisplayOpacity* opacityItem = static_cast<nsDisplayOpacity*>(aItem.mItem);
+  const float opacity = opacityItem->GetOpacity();
+
+  if (aItem.mType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG) {
+    aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, opacity);
+  } else {
+    aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+  }
+}
+
+/**
+ * Tracks item clips per opacity nesting level.
+ */
+struct ClipTracker {
+  explicit ClipTracker(gfxContext* aContext)
+    : mContext(aContext)
+  {}
+
+  bool HasClip(int aOpacityNesting) const
+  {
+    return !mClips.IsEmpty() &&
+            mClips.LastElement() == aOpacityNesting;
+  }
+
+  void PopClipIfNeeded(int aOpacityNesting)
+  {
+    if (!HasClip(aOpacityNesting)) {
+      return;
+    }
+
+    mContext->Restore();
+    mClips.RemoveLastElement();
+  };
+
+  void SaveClip(int aOpacityNesting)
+  {
+    mContext->Save();
+    mClips.AppendElement(aOpacityNesting);
+  };
+
+  AutoTArray<int, 2> mClips;
+  gfxContext* mContext;
+};
+
 void
 FrameLayerBuilder::PaintItems(nsTArray<AssignedDisplayItem>& aItems,
                               const nsIntRect& aRect,
                               gfxContext *aContext,
                               nsDisplayListBuilder* aBuilder,
                               nsPresContext* aPresContext,
                               const nsIntPoint& aOffset,
                               float aXScale, float aYScale)
@@ -6090,83 +6272,112 @@ FrameLayerBuilder::PaintItems(nsTArray<A
   DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
 
   int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
   nsRect boundRect = ToAppUnits(aRect, appUnitsPerDevPixel);
   boundRect.MoveBy(NSIntPixelsToAppUnits(aOffset.x, appUnitsPerDevPixel),
                  NSIntPixelsToAppUnits(aOffset.y, appUnitsPerDevPixel));
   boundRect.ScaleInverseRoundOut(aXScale, aYScale);
 
-  DisplayItemClip currentClip;
-  bool currentClipIsSetInContext = false;
-  DisplayItemClip tmpClip;
+  DisplayItemClip currentClip, tmpClip;
+
+  int opacityNesting = 0;
+  ClipTracker clipTracker(aContext);
 
   for (uint32_t i = 0; i < aItems.Length(); ++i) {
-    AssignedDisplayItem* cdi = &aItems[i];
-    if (!cdi->mItem) {
+    AssignedDisplayItem& cdi = aItems[i];
+    nsDisplayItem* item = cdi.mItem;
+
+    if (!item) {
+      MOZ_ASSERT(cdi.mType == DisplayItemEntryType::ITEM);
       continue;
     }
 
-    nsRect paintRect = cdi->mItem->GetVisibleRect().Intersect(boundRect);
-    if (paintRect.IsEmpty())
+    const nsRect& visibleRect = item->GetVisibleRect();
+
+    nsRect paintRect = visibleRect.Intersect(boundRect);
+    if (paintRect.IsEmpty()) {
       continue;
+    }
 
 #ifdef MOZ_DUMP_PAINTING
     AUTO_PROFILER_LABEL_DYNAMIC_CSTR("FrameLayerBuilder::PaintItems", GRAPHICS,
-                                     cdi->mItem->Name());
+                                     item->Name());
 #else
     AUTO_PROFILER_LABEL("FrameLayerBuilder::PaintItems", GRAPHICS);
 #endif
 
+    MOZ_ASSERT((opacityNesting == 0 && !cdi.mHasOpacity) ||
+               (opacityNesting > 0 && cdi.mHasOpacity));
+
+    if (cdi.mType == DisplayItemEntryType::PUSH_OPACITY ||
+        cdi.mType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG) {
+      clipTracker.PopClipIfNeeded(opacityNesting);
+      PushOpacity(aContext, paintRect, cdi, appUnitsPerDevPixel);
+      opacityNesting++;
+    }
+
+    if (cdi.mType == DisplayItemEntryType::POP_OPACITY) {
+      MOZ_ASSERT(item->GetType() == DisplayItemType::TYPE_OPACITY);
+      MOZ_ASSERT(opacityNesting > 0);
+
+      clipTracker.PopClipIfNeeded(opacityNesting);
+      aContext->PopGroupAndBlend();
+      aContext->Restore();
+      opacityNesting--;
+    }
+
+    if (cdi.mType != DisplayItemEntryType::ITEM) {
+      continue;
+    }
+
     // If the new desired clip state is different from the current state,
     // update the clip.
-    const DisplayItemClip* clip = &cdi->mItem->GetClip();
+    const DisplayItemClip* clip = &item->GetClip();
     if (clip->GetRoundedRectCount() > 0 &&
-        !clip->IsRectClippedByRoundedCorner(cdi->mItem->GetVisibleRect())) {
+        !clip->IsRectClippedByRoundedCorner(visibleRect)) {
       tmpClip = *clip;
       tmpClip.RemoveRoundedCorners();
       clip = &tmpClip;
     }
-    if (currentClipIsSetInContext != clip->HasClip() ||
+    if (clipTracker.HasClip(opacityNesting) != clip->HasClip() ||
         (clip->HasClip() && *clip != currentClip)) {
-      if (currentClipIsSetInContext) {
-        aContext->Restore();
-      }
-      currentClipIsSetInContext = clip->HasClip();
-      if (currentClipIsSetInContext) {
+      clipTracker.PopClipIfNeeded(opacityNesting);
+
+      if (clip->HasClip()) {
         currentClip = *clip;
-        aContext->Save();
-        currentClip.ApplyTo(aContext, aPresContext->AppUnitsPerDevPixel());
+        clipTracker.SaveClip(opacityNesting);
+        currentClip.ApplyTo(aContext, appUnitsPerDevPixel);
         aContext->NewPath();
       }
     }
 
-    if (cdi->mInactiveLayerManager) {
+    if (cdi.mInactiveLayerManager) {
       bool saved = aDrawTarget.GetPermitSubpixelAA();
-      PaintInactiveLayer(aBuilder, cdi->mInactiveLayerManager, cdi->mItem, aContext, aContext);
+      PaintInactiveLayer(aBuilder, cdi.mInactiveLayerManager,
+                         item, aContext, aContext);
       aDrawTarget.SetPermitSubpixelAA(saved);
     } else {
-      nsIFrame* frame = cdi->mItem->Frame();
+      nsIFrame* frame = item->Frame();
       if (aBuilder->IsPaintingToWindow()) {
         frame->AddStateBits(NS_FRAME_PAINTED_THEBES);
       }
 #ifdef MOZ_DUMP_PAINTING
       if (gfxEnv::DumpPaintItems()) {
-        DebugPaintItem(aDrawTarget, aPresContext, cdi->mItem, aBuilder);
+        DebugPaintItem(aDrawTarget, aPresContext, item, aBuilder);
       } else
 #endif
       {
-        cdi->mItem->Paint(aBuilder, aContext);
+        item->Paint(aBuilder, aContext);
       }
     }
   }
 
-  if (currentClipIsSetInContext) {
-    aContext->Restore();
-  }
+  clipTracker.PopClipIfNeeded(opacityNesting);
+  MOZ_ASSERT(opacityNesting == 0);
 }
 
 /**
  * Returns true if it is preferred to draw the list of display
  * items separately for each rect in the visible region rather
  * than clipping to a complex region.
  */
 static bool
--- a/layout/painting/FrameLayerBuilder.h
+++ b/layout/painting/FrameLayerBuilder.h
@@ -43,16 +43,17 @@ class FrameLayerBuilder;
 class LayerManagerData;
 class PaintedLayerData;
 class ContainerState;
 class PaintedDisplayItemLayerUserData;
 
 enum class DisplayItemEntryType {
   ITEM,
   PUSH_OPACITY,
+  PUSH_OPACITY_WITH_BG,
   POP_OPACITY
 };
 
 /**
   * Retained data storage:
   *
   * Each layer manager (widget, and inactive) stores a LayerManagerData object
   * that keeps a hash-set of DisplayItemData items that were drawn into it.
@@ -216,33 +217,38 @@ public:
   bool mIsInfinite;
 };
 
 struct AssignedDisplayItem
 {
   AssignedDisplayItem(nsDisplayItem* aItem,
                       const DisplayItemClip& aClip,
                       LayerState aLayerState,
-                      DisplayItemData* aData);
+                      DisplayItemData* aData,
+                      DisplayItemEntryType aType,
+                      const bool aHasOpacity);
   ~AssignedDisplayItem();
 
   nsDisplayItem* mItem;
   DisplayItemClip mClip;
   LayerState mLayerState;
   DisplayItemData* mDisplayItemData;
 
   /**
    * If the display item is being rendered as an inactive
    * layer, then this stores the layer manager being
    * used for the inactive transaction.
    */
   RefPtr<layers::LayerManager> mInactiveLayerManager;
 
   bool mReused;
   bool mMerged;
+
+  DisplayItemEntryType mType;
+  bool mHasOpacity;
 };
 
 
 struct ContainerLayerParameters {
   ContainerLayerParameters()
     : mXScale(1)
     , mYScale(1)
     , mLayerContentsVisibleRect(nullptr)
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6607,17 +6607,20 @@ nsDisplayOpacity::ShouldFlattenAway(nsDi
     // might trigger repainting).
     return false;
   }
 
   if (mList.IsEmpty()) {
     return false;
   }
 
-  return ApplyOpacityToChildren(aBuilder);
+  // Return true if we successfully applied opacity to child items, or if
+  // WebRender is not in use. In the latter case, the opacity gets flattened and
+  // applied during layer building.
+  return ApplyOpacityToChildren(aBuilder) || !gfxVars::UseWebRender();
 }
 
 nsDisplayItem::LayerState
 nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
                                 LayerManager* aManager,
                                 const ContainerLayerParameters& aParameters) {
   // If we only created this item so that we'd get correct nsDisplayEventRegions for child
   // frames, then force us to inactive to avoid unnecessary layerization changes for content