Bug 579276. Part 2: If a transparent layer is over a solid background color, hoist that color into the layer to make it opaque. r=tnikkel
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 02 Sep 2010 21:18:39 +1200
changeset 51875 e9376ab8b26ab8434a1ef277dc2f7573245631d8
parent 51874 f2aaa2cc5059e73dfcf720d76297d1c3f62c9e31
child 51876 f2a5d669a7f97f3af53a0295c8448d1d59c828e8
push idunknown
push userunknown
push dateunknown
reviewerstnikkel
bugs579276
milestone2.0b6pre
Bug 579276. Part 2: If a transparent layer is over a solid background color, hoist that color into the layer to make it opaque. r=tnikkel
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -59,20 +59,23 @@ namespace {
  */
 class LayerManagerData : public LayerUserData {
 public:
   LayerManagerData() :
     mInvalidateAllThebesContent(PR_FALSE),
     mInvalidateAllLayers(PR_FALSE)
   {
     MOZ_COUNT_CTOR(LayerManagerData);
-
     mFramesWithLayers.Init();
   }
   ~LayerManagerData() {
+    // Remove display item data properties now, since we won't be able
+    // to find these frames again without mFramesWithLayers.
+    mFramesWithLayers.EnumerateEntries(
+        FrameLayerBuilder::RemoveDisplayItemDataForFrame, nsnull);
     MOZ_COUNT_DTOR(LayerManagerData);
   }
 
   /**
    * Tracks which frames have layers associated with them.
    */
   nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesWithLayers;
   PRPackedBool mInvalidateAllThebesContent;
@@ -246,16 +249,22 @@ protected:
   void CollectOldLayers();
   /**
    * If aItem used to belong to a ThebesLayer, invalidates the area of
    * aItem in that layer. If aNewLayer is a ThebesLayer, invalidates the area of
    * aItem in that layer.
    */
   void InvalidateForLayerChange(nsDisplayItem* aItem, Layer* aNewLayer);
   /**
+   * Try to determine whether the ThebesLayer at aThebesLayerIndex
+   * has an opaque single color covering the visible area behind it.
+   * If successful, return that color, otherwise return NS_RGBA(0,0,0,0).
+   */
+  nscolor FindOpaqueBackgroundColorFor(PRInt32 aThebesLayerIndex);
+  /**
    * Indicate that we are done adding items to the ThebesLayer at the top of
    * mThebesLayerDataStack. Set the final visible region and opaque-content
    * flag, and pop it off the stack.
    */
   void PopThebesLayerData();
   /**
    * Find the ThebesLayer to which we should assign the next display item.
    * We scan the ThebesLayerData stack to find the topmost ThebesLayer
@@ -302,24 +311,33 @@ protected:
   nsAutoTArray<nsRefPtr<Layer>,1>  mNewChildLayers;
   nsTArray<nsRefPtr<ThebesLayer> > mRecycledThebesLayers;
   nsTArray<nsRefPtr<ColorLayer> >  mRecycledColorLayers;
   PRUint32                         mNextFreeRecycledThebesLayer;
   PRUint32                         mNextFreeRecycledColorLayer;
   PRPackedBool                     mInvalidateAllThebesContent;
 };
 
+class ThebesDisplayItemLayerUserData : public LayerUserData
+{
+public:
+  ThebesDisplayItemLayerUserData() :
+    mForcedBackgroundColor(NS_RGBA(0,0,0,0)) {}
+
+  nscolor mForcedBackgroundColor;
+};
+
 /**
  * The address of gThebesDisplayItemLayerUserData is used as the user
  * data key for ThebesLayers created by FrameLayerBuilder.
  * It identifies ThebesLayers used to draw non-layer content, which are
  * therefore eligible for recycling. We want display items to be able to
  * create their own dedicated ThebesLayers in BuildLayer, if necessary,
  * and we wouldn't want to accidentally recycle those.
- * The user data is null.
+ * The user data is a ThebesDisplayItemLayerUserData.
  */
 PRUint8 gThebesDisplayItemLayerUserData;
 /**
  * The address of gColorLayerUserData is used as the user
  * data key for ColorLayers created by FrameLayerBuilder.
  * The user data is null.
  */
 PRUint8 gColorLayerUserData;
@@ -457,17 +475,17 @@ FrameLayerBuilder::WillEndTransaction(La
 /* static */ PLDHashOperator
 FrameLayerBuilder::UpdateDisplayItemDataForFrame(nsPtrHashKey<nsIFrame>* aEntry,
                                                  void* aUserArg)
 {
   FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>(aUserArg);
   nsIFrame* f = aEntry->GetKey();
   FrameProperties props = f->Properties();
   DisplayItemDataEntry* newDisplayItems =
-    builder->mNewDisplayItemData.GetEntry(f);
+    builder ? builder->mNewDisplayItemData.GetEntry(f) : nsnull;
   if (!newDisplayItems) {
     // This frame was visible, but isn't anymore.
     PRBool found;
     void* prop = props.Remove(DisplayItemDataProperty(), &found);
     NS_ASSERTION(found, "How can the frame property be missing?");
     // Pass PR_FALSE to not remove from mFramesWithLayers, we'll remove it
     // by returning PL_DHASH_REMOVE below.
     // Note that DestroyDisplayItemData would delete the user data
@@ -644,17 +662,18 @@ ContainerState::CreateOrRecycleThebesLay
     // InvalidateAllThebesLayerContents has ensured
     // the area is invalidated in the widget.
   } else {
     // Create a new thebes layer
     layer = mManager->CreateThebesLayer();
     if (!layer)
       return nsnull;
     // Mark this layer as being used for Thebes-painting display items
-    layer->SetUserData(&gThebesDisplayItemLayerUserData, nsnull);
+    layer->SetUserData(&gThebesDisplayItemLayerUserData,
+        new ThebesDisplayItemLayerUserData());
   }
 
   // Set up transform so that 0,0 in the Thebes layer corresponds to the
   // (pixel-snapped) top-left of the aActiveScrolledRoot.
   nsPoint offset = mBuilder->ToReferenceFrame(aActiveScrolledRoot);
   nsIntPoint pixOffset = offset.ToNearestPixels(
       aActiveScrolledRoot->PresContext()->AppUnitsPerDevPixel());
   gfxMatrix matrix;
@@ -704,39 +723,57 @@ SetVisibleRectForLayer(Layer* aLayer, co
       NS_WARNING("Visible rect transformed out of bounds");
     }
     aLayer->SetVisibleRegion(visibleRect);
   } else {
     NS_ERROR("Only 2D transformations currently supported");
   }
 }
 
+nscolor
+ContainerState::FindOpaqueBackgroundColorFor(PRInt32 aThebesLayerIndex)
+{
+  ThebesLayerData* target = mThebesLayerDataStack[aThebesLayerIndex];
+  for (PRInt32 i = aThebesLayerIndex - 1; i >= 0; --i) {
+    ThebesLayerData* candidate = mThebesLayerDataStack[i];
+    nsIntRegion visibleAboveIntersection;
+    visibleAboveIntersection.And(candidate->mVisibleAboveRegion, target->mVisibleRegion);
+    if (!visibleAboveIntersection.IsEmpty()) {
+      // Some non-Thebes content between target and candidate; this is
+      // hopeless
+      break;
+    }
+
+    nsIntRegion intersection;
+    intersection.And(candidate->mVisibleRegion, target->mVisibleRegion);
+    if (intersection.IsEmpty()) {
+      // The layer doesn't intersect our target, ignore it and move on
+      continue;
+    }
+ 
+    // The candidate intersects our target. If any layer has a solid-color
+    // area behind our target, this must be it. Scan its display items.
+    nsPresContext* presContext = mContainerFrame->PresContext();
+    nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+    nsRect rect =
+      target->mVisibleRegion.GetBounds().ToAppUnits(appUnitsPerDevPixel);
+    return mBuilder->LayerBuilder()->
+      FindOpaqueColorCovering(mBuilder, candidate->mLayer, rect);
+  }
+  return NS_RGBA(0,0,0,0);
+}
+
 void
 ContainerState::PopThebesLayerData()
 {
   NS_ASSERTION(!mThebesLayerDataStack.IsEmpty(), "Can't pop");
 
   PRInt32 lastIndex = mThebesLayerDataStack.Length() - 1;
   ThebesLayerData* data = mThebesLayerDataStack[lastIndex];
 
-  if (lastIndex > 0) {
-    // Since we're going to pop off the last ThebesLayerData, the
-    // mVisibleAboveRegion of the second-to-last item will need to include
-    // the regions of the last item.
-    ThebesLayerData* nextData = mThebesLayerDataStack[lastIndex - 1];
-    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
-                                     data->mVisibleAboveRegion);
-    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
-                                     data->mVisibleRegion);
-    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
-                                     data->mDrawAboveRegion);
-    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
-                                     data->mDrawRegion);
-  }
-
   Layer* layer;
   if (data->mIsSolidColorInVisibleRegion) {
     nsRefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer();
     colorLayer->SetColor(data->mSolidColor);
 
     NS_ASSERTION(!mNewChildLayers.Contains(colorLayer), "Layer already in list???");
     nsTArray_base::index_type index = mNewChildLayers.IndexOf(data->mLayer);
     NS_ASSERTION(index != nsTArray_base::NoIndex, "Thebes layer not found?");
@@ -773,17 +810,57 @@ ContainerState::PopThebesLayerData()
     rgn.MoveBy(-nsIntPoint(PRInt32(transform.x0), PRInt32(transform.y0)));
     layer->SetVisibleRegion(rgn);
   } else {
     NS_ERROR("Only 2D transformations currently supported");
   }
 
   nsIntRegion transparentRegion;
   transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
-  layer->SetIsOpaqueContent(transparentRegion.IsEmpty());
+  PRBool isOpaque = transparentRegion.IsEmpty();
+  // For translucent ThebesLayers, try to find an opaque background
+  // color that covers the entire area beneath it so we can pull that
+  // color into this layer to make it opaque.
+  if (layer == data->mLayer) {
+    nscolor backgroundColor = NS_RGBA(0,0,0,0);
+    if (!isOpaque) {
+      backgroundColor = FindOpaqueBackgroundColorFor(lastIndex);
+      if (NS_GET_A(backgroundColor) == 255) {
+        isOpaque = PR_TRUE;
+      }
+    }
+
+    // Store the background color
+    ThebesDisplayItemLayerUserData* userData =
+      static_cast<ThebesDisplayItemLayerUserData*>
+        (data->mLayer->GetUserData(&gThebesDisplayItemLayerUserData));
+    NS_ASSERTION(userData, "where did our user data go?");
+    if (userData->mForcedBackgroundColor != backgroundColor) {
+      // Invalidate the entire target ThebesLayer since we're changing
+      // the background color
+      data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion());
+    }
+    userData->mForcedBackgroundColor = backgroundColor;
+  }
+  layer->SetIsOpaqueContent(isOpaque);
+
+  if (lastIndex > 0) {
+    // Since we're going to pop off the last ThebesLayerData, the
+    // mVisibleAboveRegion of the second-to-last item will need to include
+    // the regions of the last item.
+    ThebesLayerData* nextData = mThebesLayerDataStack[lastIndex - 1];
+    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
+                                     data->mVisibleAboveRegion);
+    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
+                                     data->mVisibleRegion);
+    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
+                                     data->mDrawAboveRegion);
+    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
+                                     data->mDrawRegion);
+  }
 
   mThebesLayerDataStack.RemoveElementAt(lastIndex);
 }
 
 void
 ContainerState::ThebesLayerData::Accumulate(const nsIntRect& aVisibleRect,
                                             const nsIntRect& aDrawRect,
                                             const nsIntRect* aOpaqueRect,
@@ -1121,16 +1198,38 @@ FrameLayerBuilder::AddLayerDisplayItem(L
 
   nsIFrame* f = aItem->GetUnderlyingFrame();
   DisplayItemDataEntry* entry = mNewDisplayItemData.PutEntry(f);
   if (entry) {
     entry->mData.AppendElement(DisplayItemData(aLayer, aItem->GetPerFrameKey()));
   }
 }
 
+nscolor
+FrameLayerBuilder::FindOpaqueColorCovering(nsDisplayListBuilder* aBuilder,
+                                           ThebesLayer* aLayer,
+                                           const nsRect& aRect)
+{
+  ThebesLayerItemsEntry* entry = mThebesLayerItems.GetEntry(aLayer);
+  NS_ASSERTION(entry, "Must know about this layer!");
+  for (PRInt32 i = entry->mItems.Length() - 1; i >= 0; --i) {
+    nsDisplayItem* item = entry->mItems[i].mItem;
+    const nsRect& visible = item->GetVisibleRect();
+    if (!visible.Intersects(aRect))
+      continue;
+
+    nscolor color;
+    if (visible.Contains(aRect) && item->IsUniform(aBuilder, &color) &&
+        NS_GET_A(color) == 255)
+      return color;
+    break;
+  }
+  return NS_RGBA(0,0,0,0);
+}
+
 void
 ContainerState::CollectOldLayers()
 {
   for (Layer* layer = mContainerLayer->GetFirstChild(); layer;
        layer = layer->GetNextSibling()) {
     if (layer->HasUserData(&gColorLayerUserData)) {
       mRecycledColorLayers.AppendElement(static_cast<ColorLayer*>(layer));
     } else if (layer->HasUserData(&gThebesDisplayItemLayerUserData)) {
@@ -1417,16 +1516,25 @@ FrameLayerBuilder::DrawThebesLayer(Thebe
     NS_ASSERTION(entry, "We shouldn't be drawing into a layer with no items!");
     items.SwapElements(entry->mItems);
     containerLayerFrame = entry->mContainerLayerFrame;
     // Later after this point, due to calls to DidEndTransaction
     // for temporary layer managers, mThebesLayerItems can change,
     // so 'entry' could become invalid.
   }
 
+  ThebesDisplayItemLayerUserData* userData =
+    static_cast<ThebesDisplayItemLayerUserData*>
+      (aLayer->GetUserData(&gThebesDisplayItemLayerUserData));
+  NS_ASSERTION(userData, "where did our user data go?");
+  if (NS_GET_A(userData->mForcedBackgroundColor) > 0) {
+    aContext->SetColor(gfxRGBA(userData->mForcedBackgroundColor));
+    aContext->Paint();
+  }
+
   gfxMatrix transform;
   if (!aLayer->GetTransform().Is2D(&transform)) {
     NS_ERROR("non-2D transform in our Thebes layer!");
     return;
   }
   NS_ASSERTION(!transform.HasNonIntegerTranslation(),
                "Matrix not just an integer translation?");
   // make the origin of the context coincide with the origin of the
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -238,16 +238,36 @@ public:
    * Given a frame and a display item key that uniquely identifies a
    * display item for the frame, find the layer that was last used to
    * render that display item. Returns null if there is no such layer.
    * This could be a dedicated layer for the display item, or a ThebesLayer
    * that renders many display items.
    */
   Layer* GetOldLayerFor(nsIFrame* aFrame, PRUint32 aDisplayItemKey);
 
+  /**
+   * A useful hashtable iteration function that removes the
+   * DisplayItemData property for the frame, clears its
+   * NS_FRAME_HAS_CONTAINER_LAYER bit and returns PL_DHASH_REMOVE.
+   * aClosure is ignored.
+   */
+  static PLDHashOperator RemoveDisplayItemDataForFrame(nsPtrHashKey<nsIFrame>* aEntry,
+                                                       void* aClosure)
+  {
+    return UpdateDisplayItemDataForFrame(aEntry, nsnull);
+  }
+
+  /**
+   * Try to determine whether the ThebesLayer aLayer paints an opaque
+   * single color everywhere it's visible in aRect.
+   * If successful, return that color, otherwise return NS_RGBA(0,0,0,0).
+   */
+  nscolor FindOpaqueColorCovering(nsDisplayListBuilder* aBuilder,
+                                  ThebesLayer* aLayer, const nsRect& aRect);
+
 protected:
   /**
    * We store an array of these for each frame that is associated with
    * one or more retained layers. Each DisplayItemData records the layer
    * used to render one of the frame's display items.
    */
   class DisplayItemData {
   public: