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 781483 7c3ec4cce4893c84cb6bc47d2f4c185bc66a2d83
parent 781482 3bb3af9020e1431b1af0b323e873949f8dec7310
child 781484 665843c6cfd1a3049128597afbef8a68bcc1e086
push id106312
push userhaftandilian@mozilla.com
push dateThu, 12 Apr 2018 23:43:57 +0000
reviewersmattwoodrow
bugs1442190
milestone61.0a1
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