Bug 686281 - Create nsStyleImageLayers; r?:dbaron draft
authorCJKu <cku@mozilla.com>
Thu, 12 Nov 2015 17:04:49 +0800
changeset 308383 087a535782746f9734dd68001c613031accc59b8
parent 308382 a8ed7dd831d1969a5a1a8636e63bd93d6aeaf94a
child 308384 a329a6fea0d2ea235eaf51c25ca644fa2732163f
push id7465
push usercku@mozilla.com
push dateThu, 12 Nov 2015 09:05:57 +0000
bugs686281
milestone45.0a1
Bug 686281 - Create nsStyleImageLayers; r?:dbaron
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2300,16 +2300,252 @@ nsStyleImage::operator==(const nsStyleIm
 
   if (mType == eStyleImageType_Element)
     return NS_strcmp(mElementId, aOther.mElementId) == 0;
 
   return true;
 }
 
 // --------------------
+// nsStyleImageLayers
+//
+
+nsStyleImageLayers::nsStyleImageLayers()
+  : mAttachmentCount(1)
+  , mClipCount(1)
+  , mOriginCount(1)
+  , mRepeatCount(1)
+  , mPositionCount(1)
+  , mImageCount(1)
+  , mSizeCount(1)
+  , mBlendModeCount(1)
+{
+  mLayers.AppendElement();
+  NS_ASSERTION(mLayers.Length() == 1, "auto array must have room for 1 element");
+}
+
+nsStyleImageLayers::nsStyleImageLayers(const nsStyleImageLayers &aSource)
+  : mAttachmentCount(aSource.mAttachmentCount)
+  , mClipCount(aSource.mClipCount)
+  , mOriginCount(aSource.mOriginCount)
+  , mRepeatCount(aSource.mRepeatCount)
+  , mPositionCount(aSource.mPositionCount)
+  , mImageCount(aSource.mImageCount)
+  , mSizeCount(aSource.mSizeCount)
+  , mBlendModeCount(aSource.mBlendModeCount)
+  , mLayers(aSource.mLayers) // deep copy
+{
+  // If the deep copy of mLayers failed, truncate the counts.
+  uint32_t count = mLayers.Length();
+  if (count != aSource.mLayers.Length()) {
+    NS_WARNING("truncating counts due to out-of-memory");
+    mAttachmentCount = std::max(mAttachmentCount, count);
+    mClipCount = std::max(mClipCount, count);
+    mOriginCount = std::max(mOriginCount, count);
+    mRepeatCount = std::max(mRepeatCount, count);
+    mPositionCount = std::max(mPositionCount, count);
+    mImageCount = std::max(mImageCount, count);
+    mSizeCount = std::max(mSizeCount, count);
+    mBlendModeCount = std::max(mBlendModeCount, count);
+  }
+}
+
+void
+nsStyleImageLayers::Position::SetInitialPercentValues(float aPercentVal)
+{
+  mXPosition.mPercent = aPercentVal;
+  mXPosition.mLength = 0;
+  mXPosition.mHasPercent = true;
+  mYPosition.mPercent = aPercentVal;
+  mYPosition.mLength = 0;
+  mYPosition.mHasPercent = true;
+}
+
+void
+nsStyleImageLayers::Position::SetInitialZeroValues()
+{
+  mXPosition.mPercent = 0;
+  mXPosition.mLength = 0;
+  mXPosition.mHasPercent = false;
+  mYPosition.mPercent = 0;
+  mYPosition.mLength = 0;
+  mYPosition.mHasPercent = false;
+}
+
+bool
+nsStyleImageLayers::Size::DependsOnPositioningAreaSize(const nsStyleImage& aImage) const
+{
+  MOZ_ASSERT(aImage.GetType() != eStyleImageType_Null,
+             "caller should have handled this");
+
+  // If either dimension contains a non-zero percentage, rendering for that
+  // dimension straightforwardly depends on frame size.
+  if ((mWidthType == eLengthPercentage && mWidth.mPercent != 0.0f) ||
+      (mHeightType == eLengthPercentage && mHeight.mPercent != 0.0f)) {
+    return true;
+  }
+
+  // So too for contain and cover.
+  if (mWidthType == eContain || mWidthType == eCover) {
+    return true;
+  }
+
+  // If both dimensions are fixed lengths, there's no dependency.
+  if (mWidthType == eLengthPercentage && mHeightType == eLengthPercentage) {
+    return false;
+  }
+
+  MOZ_ASSERT((mWidthType == eLengthPercentage && mHeightType == eAuto) ||
+             (mWidthType == eAuto && mHeightType == eLengthPercentage) ||
+             (mWidthType == eAuto && mHeightType == eAuto),
+             "logic error");
+
+  nsStyleImageType type = aImage.GetType();
+
+  // Gradient rendering depends on frame size when auto is involved because
+  // gradients have no intrinsic ratio or dimensions, and therefore the relevant
+  // dimension is "treat[ed] as 100%".
+  if (type == eStyleImageType_Gradient) {
+    return true;
+  }
+
+  // XXX Element rendering for auto or fixed length doesn't depend on frame size
+  //     according to the spec.  However, we don't implement the spec yet, so
+  //     for now we bail and say element() plus auto affects ultimate size.
+  if (type == eStyleImageType_Element) {
+    return true;
+  }
+
+  if (type == eStyleImageType_Image) {
+    nsCOMPtr<imgIContainer> imgContainer;
+    aImage.GetImageData()->GetImage(getter_AddRefs(imgContainer));
+    if (imgContainer) {
+      CSSIntSize imageSize;
+      nsSize imageRatio;
+      bool hasWidth, hasHeight;
+      nsLayoutUtils::ComputeSizeForDrawing(imgContainer, imageSize, imageRatio,
+                                           hasWidth, hasHeight);
+
+      // If the image has a fixed width and height, rendering never depends on
+      // the frame size.
+      if (hasWidth && hasHeight) {
+        return false;
+      }
+
+      // If the image has an intrinsic ratio, rendering will depend on frame
+      // size when background-size is all auto.
+      if (imageRatio != nsSize(0, 0)) {
+        return mWidthType == mHeightType;
+      }
+
+      // Otherwise, rendering depends on frame size when the image dimensions
+      // and background-size don't complement each other.
+      return !(hasWidth && mHeightType == eLengthPercentage) &&
+             !(hasHeight && mWidthType == eLengthPercentage);
+    }
+  } else {
+    NS_NOTREACHED("missed an enum value");
+  }
+
+  // Passed the gauntlet: no dependency.
+  return false;
+}
+
+void
+nsStyleImageLayers::Size::SetInitialValues()
+{
+  mWidthType = mHeightType = eAuto;
+}
+
+bool
+nsStyleImageLayers::Size::operator==(const Size& aOther) const
+{
+  MOZ_ASSERT(mWidthType < eDimensionType_COUNT,
+             "bad mWidthType for this");
+  MOZ_ASSERT(mHeightType < eDimensionType_COUNT,
+             "bad mHeightType for this");
+  MOZ_ASSERT(aOther.mWidthType < eDimensionType_COUNT,
+             "bad mWidthType for aOther");
+  MOZ_ASSERT(aOther.mHeightType < eDimensionType_COUNT,
+             "bad mHeightType for aOther");
+
+  return mWidthType == aOther.mWidthType &&
+         mHeightType == aOther.mHeightType &&
+         (mWidthType != eLengthPercentage || mWidth == aOther.mWidth) &&
+         (mHeightType != eLengthPercentage || mHeight == aOther.mHeight);
+}
+
+void
+nsStyleImageLayers::Repeat::SetInitialValues()
+{
+  mXRepeat = NS_STYLE_BG_REPEAT_REPEAT;
+  mYRepeat = NS_STYLE_BG_REPEAT_REPEAT;
+}
+
+nsStyleImageLayers::Layer::Layer()
+: mClip(NS_STYLE_BG_CLIP_BORDER),
+  mOrigin(NS_STYLE_BG_ORIGIN_PADDING),
+  mAttachment(NS_STYLE_BG_ATTACHMENT_SCROLL),
+  mBlendMode(NS_STYLE_BLEND_NORMAL)
+{
+  mPosition.SetInitialPercentValues(0.0f); // Initial value is "0% 0%"
+  mImage.SetNull();
+  mRepeat.SetInitialValues();
+  mSize.SetInitialValues();
+}
+
+nsStyleImageLayers::Layer::~Layer()
+{
+}
+
+bool
+nsStyleImageLayers::Layer::RenderingMightDependOnPositioningAreaSizeChange() const
+{
+  // Do we even have an image?
+  if (mImage.IsEmpty()) {
+    return false;
+  }
+
+  return mPosition.DependsOnPositioningAreaSize() ||
+      mSize.DependsOnPositioningAreaSize(mImage);
+}
+
+bool
+nsStyleImageLayers::Layer::operator==(const Layer& aOther) const
+{
+  return mAttachment == aOther.mAttachment &&
+         mClip == aOther.mClip &&
+         mOrigin == aOther.mOrigin &&
+         mRepeat == aOther.mRepeat &&
+         mBlendMode == aOther.mBlendMode &&
+         mPosition == aOther.mPosition &&
+         mSize == aOther.mSize &&
+         mImage == aOther.mImage;
+}
+
+nsChangeHint
+nsStyleImageLayers::Layer::CalcDifference(const nsStyleImageLayers::Layer& aOther) const
+{
+  nsChangeHint hint = nsChangeHint(0);
+  if (mAttachment != aOther.mAttachment ||
+      mClip != aOther.mClip ||
+      mOrigin != aOther.mOrigin ||
+      mRepeat != aOther.mRepeat ||
+      mBlendMode != aOther.mBlendMode ||
+      mSize != aOther.mSize ||
+      mImage != aOther.mImage) {
+    hint |= nsChangeHint_RepaintFrame;
+  }
+  if (mPosition != aOther.mPosition) {
+    hint |= nsChangeHint_SchedulePaint;
+  }
+  return hint;
+}
+
+// --------------------
 // nsStyleBackground
 //
 
 nsStyleBackground::nsStyleBackground()
   : mAttachmentCount(1)
   , mClipCount(1)
   , mOriginCount(1)
   , mRepeatCount(1)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -378,16 +378,228 @@ struct nsStyleColor {
       FreeByObjectID(mozilla::eArenaObjectID_nsStyleColor, this);
   }
 
   // Don't add ANY members to this struct!  We can achieve caching in the rule
   // tree (rather than the style tree) by letting color stay by itself! -dwh
   nscolor mColor;                 // [inherited]
 };
 
+struct nsStyleImageLayers {
+  nsStyleImageLayers();
+  nsStyleImageLayers(const nsStyleImageLayers &aSource);
+
+  struct Position;
+  friend struct Position;
+  struct Position {
+    typedef nsStyleCoord::CalcValue PositionCoord;
+    PositionCoord mXPosition, mYPosition;
+
+    // Initialize nothing
+    Position() {}
+
+    // Sets both mXPosition and mYPosition to the given percent value for the
+    // initial property-value (e.g. 0.0f for "0% 0%", or 0.5f for "50% 50%")
+    void SetInitialPercentValues(float aPercentVal);
+
+    // Sets both mXPosition and mYPosition to 0 (app units) for the
+    // initial property-value as a length with no percentage component.
+    void SetInitialZeroValues();
+
+    // True if the effective background image position described by this depends
+    // on the size of the corresponding frame.
+    bool DependsOnPositioningAreaSize() const {
+      return mXPosition.mPercent != 0.0f || mYPosition.mPercent != 0.0f;
+    }
+
+    bool operator==(const Position& aOther) const {
+      return mXPosition == aOther.mXPosition &&
+             mYPosition == aOther.mYPosition;
+    }
+    bool operator!=(const Position& aOther) const {
+      return !(*this == aOther);
+    }
+  };
+
+  struct Size;
+  friend struct Size;
+  struct Size {
+    struct Dimension : public nsStyleCoord::CalcValue {
+      nscoord ResolveLengthPercentage(nscoord aAvailable) const {
+        double d = double(mPercent) * double(aAvailable) + double(mLength);
+        if (d < 0.0)
+          return 0;
+        return NSToCoordRoundWithClamp(float(d));
+      }
+    };
+    Dimension mWidth, mHeight;
+
+    nscoord ResolveWidthLengthPercentage(const nsSize& aBgPositioningArea) const {
+      MOZ_ASSERT(mWidthType == eLengthPercentage,
+                 "resolving non-length/percent dimension!");
+      return mWidth.ResolveLengthPercentage(aBgPositioningArea.width);
+    }
+
+    nscoord ResolveHeightLengthPercentage(const nsSize& aBgPositioningArea) const {
+      MOZ_ASSERT(mHeightType == eLengthPercentage,
+                 "resolving non-length/percent dimension!");
+      return mHeight.ResolveLengthPercentage(aBgPositioningArea.height);
+    }
+
+    // Except for eLengthPercentage, Dimension types which might change
+    // how a layer is painted when the corresponding frame's dimensions
+    // change *must* precede all dimension types which are agnostic to
+    // frame size; see DependsOnDependsOnPositioningAreaSizeSize.
+    enum DimensionType {
+      // If one of mWidth and mHeight is eContain or eCover, then both are.
+      // NOTE: eContain and eCover *must* be equal to NS_STYLE_BG_SIZE_CONTAIN
+      // and NS_STYLE_BG_SIZE_COVER (in kBackgroundSizeKTable).
+      eContain, eCover,
+
+      eAuto,
+      eLengthPercentage,
+      eDimensionType_COUNT
+    };
+    uint8_t mWidthType, mHeightType;
+
+    // True if the effective image size described by this depends on the size of
+    // the corresponding frame, when aImage (which must not have null type) is
+    // the background image.
+    bool DependsOnPositioningAreaSize(const nsStyleImage& aImage) const;
+
+    // Initialize nothing
+    Size() {}
+
+    // Initialize to initial values
+    void SetInitialValues();
+
+    bool operator==(const Size& aOther) const;
+    bool operator!=(const Size& aOther) const {
+      return !(*this == aOther);
+    }
+  };
+
+  struct Repeat;
+  friend struct Repeat;
+  struct Repeat {
+    uint8_t mXRepeat, mYRepeat;
+
+    // Initialize nothing
+    Repeat() {}
+
+    // Initialize to initial values
+    void SetInitialValues();
+
+    bool operator==(const Repeat& aOther) const {
+      return mXRepeat == aOther.mXRepeat &&
+             mYRepeat == aOther.mYRepeat;
+    }
+    bool operator!=(const Repeat& aOther) const {
+      return !(*this == aOther);
+    }
+  };
+
+  struct Layer;
+  friend struct Layer;
+  struct Layer {
+    nsStyleImage  mImage;         // [reset]
+    Position      mPosition;      // [reset] See nsStyleConsts.h
+    Size          mSize;          // [reset]
+    uint8_t       mClip;          // [reset] See nsStyleConsts.h
+    uint8_t       mOrigin;        // [reset] See nsStyleConsts.h
+    uint8_t       mAttachment;    // [reset] See nsStyleConsts.h
+                                  // background-only property
+                                  // This property is used for background layer
+                                  // only. For a mask layer, it should always
+                                  // be the initial value, which is
+                                  // NS_STYLE_BG_ATTACHMENT_SCROLL.
+    uint8_t       mBlendMode;     // [reset] See nsStyleConsts.h
+                                  // background-only property
+                                  // This property is used for background layer
+                                  // only. For a mask layer, it should always
+                                  // be the initial value, which is
+                                  // NS_STYLE_BLEND_NORMAL.
+    Repeat        mRepeat;        // [reset] See nsStyleConsts.h
+
+    // Initializes only mImage
+    Layer();
+    ~Layer();
+
+    // Register/unregister images with the document. We do this only
+    // after the dust has settled in ComputeBackgroundData.
+    void TrackImages(nsPresContext* aContext) {
+      if (mImage.GetType() == eStyleImageType_Image)
+        mImage.TrackImage(aContext);
+    }
+    void UntrackImages(nsPresContext* aContext) {
+      if (mImage.GetType() == eStyleImageType_Image)
+        mImage.UntrackImage(aContext);
+    }
+
+    // True if the rendering of this layer might change when the size
+    // of the background positioning area changes.  This is true for any
+    // non-solid-color background whose position or size depends on
+    // the size of the positioning area.  It's also true for SVG images
+    // whose root <svg> node has a viewBox.
+    bool RenderingMightDependOnPositioningAreaSizeChange() const;
+
+    // Compute the change hint required by changes in just this layer.
+    nsChangeHint CalcDifference(const Layer& aOther) const;
+
+    // An equality operator that compares the images using URL-equality
+    // rather than pointer-equality.
+    bool operator==(const Layer& aOther) const;
+    bool operator!=(const Layer& aOther) const {
+      return !(*this == aOther);
+    }
+  };
+
+  // The (positive) number of computed values of each property, since
+  // the lengths of the lists are independent.
+  uint32_t mAttachmentCount,
+           mClipCount,
+           mOriginCount,
+           mRepeatCount,
+           mPositionCount,
+           mImageCount,
+           mSizeCount,
+           mBlendModeCount;
+
+  // Layers are stored in an array, matching the top-to-bottom order in
+  // which they are specified in CSS.  The number of layers to be used
+  // should come from the background-image property.  We create
+  // additional |Layer| objects for *any* property, not just
+  // background-image.  This means that the bottommost layer that
+  // callers in layout care about (which is also the one whose
+  // background-clip applies to the background-color) may not be last
+  // layer.  In layers below the bottom layer, properties will be
+  // uninitialized unless their count, above, indicates that they are
+  // present.
+  nsAutoTArray<Layer, 1> mLayers;
+
+  const Layer& BottomLayer() const { return mLayers[mImageCount - 1]; }
+
+  void TrackImages(nsPresContext* aContext) {
+    for (uint32_t i = 0; i < mImageCount; ++i) {
+        mLayers[i].TrackImages(aContext);
+    }
+  }
+  void UntrackImages(nsPresContext* aContext) {
+    for (uint32_t i = 0; i < mImageCount; ++i)
+      mLayers[i].UntrackImages(aContext);
+  }
+
+  #define NS_FOR_VISIBLE_IMAGELAYER_BACK_TO_FRONT(var_, layers_) \
+    for (uint32_t var_ = layers_.mImageCount; var_-- != 0; )
+  #define NS_FOR_VISIBLE_IMAGELAYER_BACK_TO_FRONT_WITH_RANGE(var_, layers_, start_, count_) \
+    NS_ASSERTION((int32_t)(start_) >= 0 && (uint32_t)(start_) < (layers_.mImageCount), "Invalid layer start!"); \
+    NS_ASSERTION((count_) > 0 && (count_) <= (start_) + 1, "Invalid layer range!"); \
+    for (uint32_t var_ = (start_) + 1; var_-- != (uint32_t)((start_) + 1 - (count_)); )
+};
+
 struct nsStyleBackground {
   nsStyleBackground();
   nsStyleBackground(const nsStyleBackground& aOther);
   ~nsStyleBackground();
 
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
     return aContext->PresShell()->
       AllocateByObjectID(mozilla::eArenaObjectID_nsStyleBackground, sz);