Bug 739591 - Implement filter xlink:href support. r=roc
authorRobert Longson <longsonr@gmail.com>
Wed, 28 Mar 2012 12:31:47 +0100
changeset 93837 24b7f6c27ee1153c25331015d67f0757fa227944
parent 93814 6e232128a8ca37d57bda0fb4e9f50d83781fdb65
child 93838 2961cc5557921d26a33f4964897151b94d3f4948
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs739591
milestone14.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 739591 - Implement filter xlink:href support. r=roc
layout/reftests/svg/linked-filter-01.svg
layout/reftests/svg/reftest.list
layout/svg/base/src/nsSVGFilterFrame.cpp
layout/svg/base/src/nsSVGFilterFrame.h
layout/svg/base/src/nsSVGFilterInstance.h
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/linked-filter-01.svg
@@ -0,0 +1,18 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" 
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+  <title>Test for linked filter</title>
+  <defs>
+    <filter id="filter01" filterUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
+      <feFlood flood-color="lime" />
+    </filter>
+    <filter id="filter02" xlink:href="#filter01"/>
+  </defs>
+
+  <rect width="100%" height="100%" fill="red"/>
+  <rect width="100%" height="100%" filter="url(#filter02)"/>
+</svg>
+
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -106,16 +106,17 @@ random-if(/^Windows\x20NT\x205\.1/.test(
 == dynamic-use-01.svg pass.svg
 == dynamic-use-02.svg pass.svg
 == dynamic-use-03.svg pass.svg
 == dynamic-use-04.svg pass.svg
 == dynamic-use-05.svg pass.svg
 == dynamic-use-06.svg pass.svg
 random == dynamic-use-nested-01.svg dynamic-use-nested-01-ref.svg # bug 467498
 == dynamic-use-remove-width.svg dynamic-use-remove-width-ref.svg
+== linked-filter-01.svg pass.svg
 == linked-pattern-01.svg pass.svg
 == use-01.svg pass.svg
 == use-01-extref.svg pass.svg
 == use-02-extref.svg use-02-extref-ref.svg
 == use-extref-dataURI-01.svg pass.svg
 == use-children.svg pass.svg
 == fallback-color-01a.svg pass.svg
 == fallback-color-01b.svg pass.svg
--- a/layout/svg/base/src/nsSVGFilterFrame.cpp
+++ b/layout/svg/base/src/nsSVGFilterFrame.cpp
@@ -69,16 +69,34 @@ MapDeviceRectToFilterSpace(const gfxMatr
     nsIntRect intRect;
     if (gfxUtils::GfxRectToIntRect(r, &intRect)) {
       rect = intRect;
     }
   }
   return rect;
 }
 
+class nsSVGFilterFrame::AutoFilterReferencer
+{
+public:
+  AutoFilterReferencer(nsSVGFilterFrame *aFrame)
+    : mFrame(aFrame)
+  {
+    // Reference loops should normally be detected in advance and handled, so
+    // we're not expecting to encounter them here
+    NS_ABORT_IF_FALSE(!mFrame->mLoopFlag, "Undetected reference loop!");
+    mFrame->mLoopFlag = true;
+  }
+  ~AutoFilterReferencer() {
+    mFrame->mLoopFlag = false;
+  }
+private:
+  nsSVGFilterFrame *mFrame;
+};
+
 class NS_STACK_CLASS nsAutoFilterInstance {
 public:
   nsAutoFilterInstance(nsIFrame *aTarget,
                        nsSVGFilterFrame *aFilterFrame,
                        nsSVGFilterPaintCallback *aPaint,
                        const nsIntRect *aDirtyOutputRect,
                        const nsIntRect *aDirtyInputRect,
                        const nsIntRect *aOverrideSourceBBox);
@@ -99,23 +117,22 @@ nsAutoFilterInstance::nsAutoFilterInstan
                                            nsSVGFilterFrame *aFilterFrame,
                                            nsSVGFilterPaintCallback *aPaint,
                                            const nsIntRect *aDirtyOutputRect,
                                            const nsIntRect *aDirtyInputRect,
                                            const nsIntRect *aOverrideSourceBBox)
 {
   mTarget = do_QueryFrame(aTarget);
 
-  nsSVGFilterElement *filter =
-    static_cast<nsSVGFilterElement*>(aFilterFrame->GetContent());
+  const nsSVGFilterElement *filter = aFilterFrame->GetFilterContent();
 
   PRUint16 filterUnits =
-    filter->mEnumAttributes[nsSVGFilterElement::FILTERUNITS].GetAnimValue();
+    aFilterFrame->GetEnumValue(nsSVGFilterElement::FILTERUNITS);
   PRUint16 primitiveUnits =
-    filter->mEnumAttributes[nsSVGFilterElement::PRIMITIVEUNITS].GetAnimValue();
+    aFilterFrame->GetEnumValue(nsSVGFilterElement::PRIMITIVEUNITS);
 
   gfxRect bbox;
   if (aOverrideSourceBBox) {
     bbox = gfxRect(aOverrideSourceBBox->x, aOverrideSourceBBox->y,
                    aOverrideSourceBBox->width, aOverrideSourceBBox->height);
   } else {
     bbox = nsSVGUtils::GetBBox(aTarget);
   }
@@ -127,18 +144,26 @@ nsAutoFilterInstance::nsAutoFilterInstan
   // with units. This is a common mistake and can result in filterRes being
   // *massive* below (because we ignore the units and interpret the number as
   // a factor of the bbox width/height). We should also send a warning if the
   // user uses a number without units (a future SVG spec should really
   // deprecate that, since it's too confusing for a bare number to be sometimes
   // interpreted as a fraction of the bounding box and sometimes as user-space
   // units). So really only percentage values should be used in this case.
   
+  nsSVGLength2 XYWH[4];
+  NS_ABORT_IF_FALSE(sizeof(filter->mLengthAttributes) == sizeof(XYWH),
+                    "XYWH size incorrect");
+  memcpy(XYWH, filter->mLengthAttributes, sizeof(filter->mLengthAttributes));
+  XYWH[0] = *aFilterFrame->GetLengthValue(nsSVGFilterElement::X);
+  XYWH[1] = *aFilterFrame->GetLengthValue(nsSVGFilterElement::Y);
+  XYWH[2] = *aFilterFrame->GetLengthValue(nsSVGFilterElement::WIDTH);
+  XYWH[3] = *aFilterFrame->GetLengthValue(nsSVGFilterElement::HEIGHT);
   gfxRect filterRegion = nsSVGUtils::GetRelativeRect(filterUnits,
-    filter->mLengthAttributes, bbox, aTarget);
+    XYWH, bbox, aTarget);
 
   if (filterRegion.Width() <= 0 || filterRegion.Height() <= 0) {
     // 0 disables rendering, < 0 is error. dispatch error console warning
     // or error as appropriate.
     return;
   }
 
   gfxMatrix userToDeviceSpace = nsSVGUtils::GetCanvasTM(aTarget);
@@ -146,21 +171,21 @@ nsAutoFilterInstance::nsAutoFilterInstan
     // nothing to draw
     return;
   }
   
   // Calculate filterRes (the width and height of the pixel buffer of the
   // temporary offscreen surface that we'll paint into):
 
   gfxIntSize filterRes;
-  const nsSVGIntegerPair& filterResAttrs =
-    filter->mIntegerPairAttributes[nsSVGFilterElement::FILTERRES];
-  if (filterResAttrs.IsExplicitlySet()) {
-    PRInt32 filterResX = filterResAttrs.GetAnimValue(nsSVGIntegerPair::eFirst);
-    PRInt32 filterResY = filterResAttrs.GetAnimValue(nsSVGIntegerPair::eSecond);
+  const nsSVGIntegerPair* filterResAttrs =
+    aFilterFrame->GetIntegerPairValue(nsSVGFilterElement::FILTERRES);
+  if (filterResAttrs->IsExplicitlySet()) {
+    PRInt32 filterResX = filterResAttrs->GetAnimValue(nsSVGIntegerPair::eFirst);
+    PRInt32 filterResY = filterResAttrs->GetAnimValue(nsSVGIntegerPair::eSecond);
     if (filterResX <= 0 || filterResY <= 0) {
       // 0 disables rendering, < 0 is error. dispatch error console warning?
       return;
     }
 
     filterRegion.Scale(filterResX, filterResY);
     filterRegion.RoundOut();
     filterRegion.Scale(1.0 / filterResX, 1.0 / filterResY);
@@ -221,31 +246,163 @@ nsAutoFilterInstance::nsAutoFilterInstan
                                       dirtyOutputRect, dirtyInputRect,
                                       primitiveUnits);
 }
 
 nsAutoFilterInstance::~nsAutoFilterInstance()
 {
 }
 
+PRUint16
+nsSVGFilterFrame::GetEnumValue(PRUint32 aIndex, nsIContent *aDefault)
+{
+  nsSVGEnum& thisEnum =
+    static_cast<nsSVGFilterElement *>(mContent)->mEnumAttributes[aIndex];
+
+  if (thisEnum.IsExplicitlySet())
+    return thisEnum.GetAnimValue();
+
+  AutoFilterReferencer filterRef(this);
+
+  nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+  return next ? next->GetEnumValue(aIndex, aDefault) :
+    static_cast<nsSVGFilterElement *>(aDefault)->
+      mEnumAttributes[aIndex].GetAnimValue();
+}
+
+const nsSVGIntegerPair *
+nsSVGFilterFrame::GetIntegerPairValue(PRUint32 aIndex, nsIContent *aDefault)
+{
+  const nsSVGIntegerPair *thisIntegerPair =
+    &static_cast<nsSVGFilterElement *>(mContent)->mIntegerPairAttributes[aIndex];
+
+  if (thisIntegerPair->IsExplicitlySet())
+    return thisIntegerPair;
+
+  AutoFilterReferencer filterRef(this);
+
+  nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+  return next ? next->GetIntegerPairValue(aIndex, aDefault) :
+    &static_cast<nsSVGFilterElement *>(aDefault)->mIntegerPairAttributes[aIndex];
+}
+
+const nsSVGLength2 *
+nsSVGFilterFrame::GetLengthValue(PRUint32 aIndex, nsIContent *aDefault)
+{
+  const nsSVGLength2 *thisLength =
+    &static_cast<nsSVGFilterElement *>(mContent)->mLengthAttributes[aIndex];
+
+  if (thisLength->IsExplicitlySet())
+    return thisLength;
+
+  AutoFilterReferencer filterRef(this);
+
+  nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+  return next ? next->GetLengthValue(aIndex, aDefault) :
+    &static_cast<nsSVGFilterElement *>(aDefault)->mLengthAttributes[aIndex];
+}
+
+const nsSVGFilterElement *
+nsSVGFilterFrame::GetFilterContent(nsIContent *aDefault)
+{
+  PRUint32 count = mContent->GetChildCount();
+  for (PRUint32 i = 0; i < count; ++i) {
+    nsIContent* child = mContent->GetChildAt(i);
+    nsRefPtr<nsSVGFE> primitive;
+    CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive));
+    if (primitive) {
+      return static_cast<nsSVGFilterElement *>(mContent);
+    }
+  }
+
+  AutoFilterReferencer filterRef(this);
+
+  nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+  return next ? next->GetFilterContent(aDefault) :
+    static_cast<nsSVGFilterElement *>(aDefault);
+}
+
+nsSVGFilterFrame *
+nsSVGFilterFrame::GetReferencedFilter()
+{
+  if (mNoHRefURI)
+    return nsnull;
+
+  nsSVGPaintingProperty *property = static_cast<nsSVGPaintingProperty*>
+    (Properties().Get(nsSVGEffects::HrefProperty()));
+
+  if (!property) {
+    // Fetch our Filter element's xlink:href attribute
+    nsSVGFilterElement *filter = static_cast<nsSVGFilterElement *>(mContent);
+    nsAutoString href;
+    filter->mStringAttributes[nsSVGFilterElement::HREF].GetAnimValue(href, filter);
+    if (href.IsEmpty()) {
+      mNoHRefURI = true;
+      return nsnull; // no URL
+    }
+
+    // Convert href to an nsIURI
+    nsCOMPtr<nsIURI> targetURI;
+    nsCOMPtr<nsIURI> base = mContent->GetBaseURI();
+    nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
+                                              mContent->GetCurrentDoc(), base);
+
+    property =
+      nsSVGEffects::GetPaintingProperty(targetURI, this, nsSVGEffects::HrefProperty());
+    if (!property)
+      return nsnull;
+  }
+
+  nsIFrame *result = property->GetReferencedFrame();
+  if (!result)
+    return nsnull;
+
+  nsIAtom* frameType = result->GetType();
+  if (frameType != nsGkAtoms::svgFilterFrame)
+    return nsnull;
+
+  return static_cast<nsSVGFilterFrame*>(result);
+}
+
+nsSVGFilterFrame *
+nsSVGFilterFrame::GetReferencedFilterIfNotInUse()
+{
+  nsSVGFilterFrame *referenced = GetReferencedFilter();
+  if (!referenced)
+    return nsnull;
+
+  if (referenced->mLoopFlag) {
+    // XXXjwatt: we should really send an error to the JavaScript Console here:
+    NS_WARNING("Filter reference loop detected while inheriting attribute!");
+    return nsnull;
+  }
+
+  return referenced;
+}
+
 NS_IMETHODIMP
 nsSVGFilterFrame::AttributeChanged(PRInt32  aNameSpaceID,
                                    nsIAtom* aAttribute,
                                    PRInt32  aModType)
 {
-  if ((aNameSpaceID == kNameSpaceID_None &&
-       (aAttribute == nsGkAtoms::x ||
-        aAttribute == nsGkAtoms::y ||
-        aAttribute == nsGkAtoms::width ||
-        aAttribute == nsGkAtoms::height ||
-        aAttribute == nsGkAtoms::filterRes ||
-        aAttribute == nsGkAtoms::filterUnits ||
-        aAttribute == nsGkAtoms::primitiveUnits)) ||
-       (aNameSpaceID == kNameSpaceID_XLink &&
-        aAttribute == nsGkAtoms::href)) {
+  if (aNameSpaceID == kNameSpaceID_None &&
+      (aAttribute == nsGkAtoms::x ||
+       aAttribute == nsGkAtoms::y ||
+       aAttribute == nsGkAtoms::width ||
+       aAttribute == nsGkAtoms::height ||
+       aAttribute == nsGkAtoms::filterRes ||
+       aAttribute == nsGkAtoms::filterUnits ||
+       aAttribute == nsGkAtoms::primitiveUnits)) {
+    nsSVGEffects::InvalidateRenderingObservers(this);
+  } else if (aNameSpaceID == kNameSpaceID_XLink &&
+             aAttribute == nsGkAtoms::href) {
+    // Blow away our reference, if any
+    Properties().Delete(nsSVGEffects::HrefProperty());
+    mNoHRefURI = false;
+    // And update whoever references us
     nsSVGEffects::InvalidateRenderingObservers(this);
   }
   return nsSVGFilterFrameBase::AttributeChanged(aNameSpaceID,
                                                 aAttribute, aModType);
 }
 
 nsresult
 nsSVGFilterFrame::FilterPaint(nsRenderingContext *aContext,
--- a/layout/svg/base/src/nsSVGFilterFrame.h
+++ b/layout/svg/base/src/nsSVGFilterFrame.h
@@ -45,26 +45,31 @@
 
 class nsIAtom;
 class nsIContent;
 class nsIFrame;
 class nsIPresShell;
 class nsRenderingContext;
 class nsStyleContext;
 class nsSVGFilterPaintCallback;
+class nsSVGFilterElement;
+class nsSVGIntegerPair;
+class nsSVGLength2;
 
 typedef nsSVGContainerFrame nsSVGFilterFrameBase;
 
 class nsSVGFilterFrame : public nsSVGFilterFrameBase
 {
   friend nsIFrame*
   NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 protected:
   nsSVGFilterFrame(nsStyleContext* aContext)
-    : nsSVGFilterFrameBase(aContext)
+    : nsSVGFilterFrameBase(aContext),
+      mLoopFlag(false),
+      mNoHRefURI(false)
   {
     AddStateBits(NS_STATE_SVG_NONDISPLAY_CHILD);
   }
 
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
   NS_IMETHOD AttributeChanged(PRInt32         aNameSpaceID,
@@ -105,11 +110,46 @@ public:
 #endif
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::svgFilterFrame
    */
   virtual nsIAtom* GetType() const;
+
+private:
+  // Parse our xlink:href and set up our nsSVGPaintingProperty if we
+  // reference another filter and we don't have a property. Return
+  // the referenced filter's frame if available, null otherwise.
+  class AutoFilterReferencer;
+  friend class nsAutoFilterInstance;
+  nsSVGFilterFrame* GetReferencedFilter();
+  nsSVGFilterFrame* GetReferencedFilterIfNotInUse();
+
+  // Accessors to lookup filter attributes
+  PRUint16 GetEnumValue(PRUint32 aIndex, nsIContent *aDefault);
+  PRUint16 GetEnumValue(PRUint32 aIndex)
+  {
+    return GetEnumValue(aIndex, mContent);
+  }
+  const nsSVGIntegerPair *GetIntegerPairValue(PRUint32 aIndex, nsIContent *aDefault);
+  const nsSVGIntegerPair *GetIntegerPairValue(PRUint32 aIndex)
+  {
+    return GetIntegerPairValue(aIndex, mContent);
+  }
+  const nsSVGLength2 *GetLengthValue(PRUint32 aIndex, nsIContent *aDefault);
+  const nsSVGLength2 *GetLengthValue(PRUint32 aIndex)
+  {
+    return GetLengthValue(aIndex, mContent);
+  }
+  const nsSVGFilterElement *GetFilterContent(nsIContent *aDefault);
+  const nsSVGFilterElement *GetFilterContent()
+  {
+    return GetFilterContent(mContent);
+  }
+
+  // This flag is used to detect loops in xlink:href processing
+  bool                              mLoopFlag;
+  bool                              mNoHRefURI;
 };
 
 #endif
--- a/layout/svg/base/src/nsSVGFilterInstance.h
+++ b/layout/svg/base/src/nsSVGFilterInstance.h
@@ -63,17 +63,17 @@ class nsSVGFilterPaintCallback;
  * converting the filter graph to SSA. This lets us easily propagate
  * analysis data (such as bounding-boxes) over the filter primitive graph.
  */
 class NS_STACK_CLASS nsSVGFilterInstance
 {
 public:
   nsSVGFilterInstance(nsIFrame *aTargetFrame,
                       nsSVGFilterPaintCallback *aPaintCallback,
-                      nsSVGFilterElement *aFilterElement,
+                      const nsSVGFilterElement *aFilterElement,
                       const gfxRect &aTargetBBox,
                       const gfxRect& aFilterRect,
                       const nsIntSize& aFilterSpaceSize,
                       const gfxMatrix &aFilterSpaceToDeviceSpaceTransform,
                       const nsIntRect& aTargetBounds,
                       const nsIntRect& aDirtyOutputRect,
                       const nsIntRect& aDirtyInputRect,
                       PRUint16 aPrimitiveUnits) :
@@ -203,17 +203,17 @@ private:
   {
     nsIntRect filterSpace(nsIntPoint(0, 0), mFilterSpaceSize);
     aRect->IntersectRect(*aRect, filterSpace);
   }
   void ClipToGfxRect(nsIntRect* aRect, const gfxRect& aGfx) const;
 
   nsIFrame*               mTargetFrame;
   nsSVGFilterPaintCallback* mPaintCallback;
-  nsSVGFilterElement*     mFilterElement;
+  const nsSVGFilterElement* mFilterElement;
   // Bounding box of the target element, in user space
   gfxRect                 mTargetBBox;
   gfxMatrix               mFilterSpaceToDeviceSpaceTransform;
   gfxRect                 mFilterRect;
   nsIntSize               mFilterSpaceSize;
   // Filter-space bounds of the target image (SourceAlpha/SourceGraphic)
   nsIntRect               mTargetBounds;
   nsIntRect               mDirtyOutputRect;