diff options
Diffstat (limited to 'layout/svg/nsSVGGradientFrame.cpp')
-rw-r--r-- | layout/svg/nsSVGGradientFrame.cpp | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/layout/svg/nsSVGGradientFrame.cpp b/layout/svg/nsSVGGradientFrame.cpp new file mode 100644 index 000000000..217ab8c4a --- /dev/null +++ b/layout/svg/nsSVGGradientFrame.cpp @@ -0,0 +1,678 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Main header first: +#include "nsSVGGradientFrame.h" +#include <algorithm> + +// Keep others in (case-insensitive) order: +#include "gfxPattern.h" +#include "mozilla/dom/SVGGradientElement.h" +#include "mozilla/dom/SVGStopElement.h" +#include "nsContentUtils.h" +#include "nsSVGEffects.h" +#include "nsSVGAnimatedTransformList.h" + +// XXX Tight coupling with content classes ahead! + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +//---------------------------------------------------------------------- +// Helper classes + +class MOZ_RAII nsSVGGradientFrame::AutoGradientReferencer +{ +public: + explicit AutoGradientReferencer(nsSVGGradientFrame *aFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mFrame(aFrame) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + // Reference loops should normally be detected in advance and handled, so + // we're not expecting to encounter them here + MOZ_ASSERT(!mFrame->mLoopFlag, "Undetected reference loop!"); + mFrame->mLoopFlag = true; + } + ~AutoGradientReferencer() { + mFrame->mLoopFlag = false; + } +private: + nsSVGGradientFrame *mFrame; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +//---------------------------------------------------------------------- +// Implementation + +nsSVGGradientFrame::nsSVGGradientFrame(nsStyleContext* aContext) + : nsSVGPaintServerFrame(aContext) + , mLoopFlag(false) + , mNoHRefURI(false) +{ +} + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult +nsSVGGradientFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::gradientUnits || + aAttribute == nsGkAtoms::gradientTransform || + aAttribute == nsGkAtoms::spreadMethod)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } else if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // Blow away our reference, if any + Properties().Delete(nsSVGEffects::HrefAsPaintingProperty()); + mNoHRefURI = false; + // And update whoever references us + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + return nsSVGPaintServerFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +//---------------------------------------------------------------------- + +uint16_t +nsSVGGradientFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault) +{ + const nsSVGEnum& thisEnum = + static_cast<dom::SVGGradientElement*>(mContent)->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) + return thisEnum.GetAnimValue(); + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetEnumValue(aIndex, aDefault) : + static_cast<dom::SVGGradientElement*>(aDefault)-> + mEnumAttributes[aIndex].GetAnimValue(); +} + +uint16_t +nsSVGGradientFrame::GetGradientUnits() +{ + // This getter is called every time the others are called - maybe cache it? + return GetEnumValue(dom::SVGGradientElement::GRADIENTUNITS); +} + +uint16_t +nsSVGGradientFrame::GetSpreadMethod() +{ + return GetEnumValue(dom::SVGGradientElement::SPREADMETHOD); +} + +const nsSVGAnimatedTransformList* +nsSVGGradientFrame::GetGradientTransformList(nsIContent* aDefault) +{ + nsSVGAnimatedTransformList *thisTransformList = + static_cast<dom::SVGGradientElement*>(mContent)->GetAnimatedTransformList(); + + if (thisTransformList && thisTransformList->IsExplicitlySet()) + return thisTransformList; + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetGradientTransformList(aDefault) : + static_cast<const dom::SVGGradientElement*>(aDefault) + ->mGradientTransform.get(); +} + +gfxMatrix +nsSVGGradientFrame::GetGradientTransform(nsIFrame *aSource, + const gfxRect *aOverrideBounds) +{ + gfxMatrix bboxMatrix; + + uint16_t gradientUnits = GetGradientUnits(); + if (gradientUnits != SVG_UNIT_TYPE_USERSPACEONUSE) { + NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); + // objectBoundingBox is the default anyway + + gfxRect bbox = + aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(aSource); + bboxMatrix = + gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y()); + } + + const nsSVGAnimatedTransformList* animTransformList = + GetGradientTransformList(mContent); + if (!animTransformList) + return bboxMatrix; + + gfxMatrix gradientTransform = + animTransformList->GetAnimValue().GetConsolidationMatrix(); + return bboxMatrix.PreMultiply(gradientTransform); +} + +dom::SVGLinearGradientElement* +nsSVGGradientFrame::GetLinearGradientWithLength(uint32_t aIndex, + dom::SVGLinearGradientElement* aDefault) +{ + // If this was a linear gradient with the required length, we would have + // already found it in nsSVGLinearGradientFrame::GetLinearGradientWithLength. + // Since we didn't find the length, continue looking down the chain. + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetLinearGradientWithLength(aIndex, aDefault) : aDefault; +} + +dom::SVGRadialGradientElement* +nsSVGGradientFrame::GetRadialGradientWithLength(uint32_t aIndex, + dom::SVGRadialGradientElement* aDefault) +{ + // If this was a radial gradient with the required length, we would have + // already found it in nsSVGRadialGradientFrame::GetRadialGradientWithLength. + // Since we didn't find the length, continue looking down the chain. + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetRadialGradientWithLength(aIndex, aDefault) : aDefault; +} + +//---------------------------------------------------------------------- +// nsSVGPaintServerFrame methods: + +//helper +static void GetStopInformation(nsIFrame* aStopFrame, + float *aOffset, + nscolor *aStopColor, + float *aStopOpacity) +{ + nsIContent* stopContent = aStopFrame->GetContent(); + MOZ_ASSERT(stopContent && stopContent->IsSVGElement(nsGkAtoms::stop)); + + static_cast<SVGStopElement*>(stopContent)-> + GetAnimatedNumberValues(aOffset, nullptr); + + *aOffset = mozilla::clamped(*aOffset, 0.0f, 1.0f); + *aStopColor = aStopFrame->StyleSVGReset()->mStopColor; + *aStopOpacity = aStopFrame->StyleSVGReset()->mStopOpacity; +} + +already_AddRefed<gfxPattern> +nsSVGGradientFrame::GetPaintServerPattern(nsIFrame* aSource, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, + const gfxRect* aOverrideBounds) +{ + uint16_t gradientUnits = GetGradientUnits(); + MOZ_ASSERT(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX || + gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE); + if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) { + // Set mSource for this consumer. + // If this gradient is applied to text, our caller will be the glyph, which + // is not an element, so we need to get the parent + mSource = aSource->GetContent()->IsNodeOfType(nsINode::eTEXT) ? + aSource->GetParent() : aSource; + } + + AutoTArray<nsIFrame*,8> stopFrames; + GetStopFrames(&stopFrames); + + uint32_t nStops = stopFrames.Length(); + + // SVG specification says that no stops should be treated like + // the corresponding fill or stroke had "none" specified. + if (nStops == 0) { + RefPtr<gfxPattern> pattern = new gfxPattern(Color()); + return pattern.forget(); + } + + if (nStops == 1 || GradientVectorLengthIsZero()) { + // The gradient paints a single colour, using the stop-color of the last + // gradient step if there are more than one. + float stopOpacity = stopFrames[nStops-1]->StyleSVGReset()->mStopOpacity; + nscolor stopColor = stopFrames[nStops-1]->StyleSVGReset()->mStopColor; + + Color stopColor2 = Color::FromABGR(stopColor); + stopColor2.a *= stopOpacity * aGraphicOpacity; + RefPtr<gfxPattern> pattern = new gfxPattern(stopColor2); + return pattern.forget(); + } + + // Get the transform list (if there is one). We do this after the returns + // above since this call can be expensive when "gradientUnits" is set to + // "objectBoundingBox" (since that requiring a GetBBox() call). + gfxMatrix patternMatrix = GetGradientTransform(aSource, aOverrideBounds); + + if (patternMatrix.IsSingular()) { + return nullptr; + } + + // revert any vector effect transform so that the gradient appears unchanged + if (aFillOrStroke == &nsStyleSVG::mStroke) { + gfxMatrix userToOuterSVG; + if (nsSVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) { + patternMatrix *= userToOuterSVG; + } + } + + if (!patternMatrix.Invert()) { + return nullptr; + } + + RefPtr<gfxPattern> gradient = CreateGradient(); + if (!gradient || gradient->CairoStatus()) + return nullptr; + + uint16_t aSpread = GetSpreadMethod(); + if (aSpread == SVG_SPREADMETHOD_PAD) + gradient->SetExtend(ExtendMode::CLAMP); + else if (aSpread == SVG_SPREADMETHOD_REFLECT) + gradient->SetExtend(ExtendMode::REFLECT); + else if (aSpread == SVG_SPREADMETHOD_REPEAT) + gradient->SetExtend(ExtendMode::REPEAT); + + gradient->SetMatrix(patternMatrix); + + // setup stops + float lastOffset = 0.0f; + + for (uint32_t i = 0; i < nStops; i++) { + float offset, stopOpacity; + nscolor stopColor; + + GetStopInformation(stopFrames[i], &offset, &stopColor, &stopOpacity); + + if (offset < lastOffset) + offset = lastOffset; + else + lastOffset = offset; + + Color stopColor2 = Color::FromABGR(stopColor); + stopColor2.a *= stopOpacity * aGraphicOpacity; + gradient->AddColorStop(offset, stopColor2); + } + + return gradient.forget(); +} + +// Private (helper) methods + +nsSVGGradientFrame * +nsSVGGradientFrame::GetReferencedGradient() +{ + if (mNoHRefURI) + return nullptr; + + nsSVGPaintingProperty *property = + Properties().Get(nsSVGEffects::HrefAsPaintingProperty()); + + if (!property) { + // Fetch our gradient element's href or xlink:href attribute + dom::SVGGradientElement* grad = + static_cast<dom::SVGGradientElement*>(mContent); + nsAutoString href; + if (grad->mStringAttributes[dom::SVGGradientElement::HREF] + .IsExplicitlySet()) { + grad->mStringAttributes[dom::SVGGradientElement::HREF] + .GetAnimValue(href, grad); + } else { + grad->mStringAttributes[dom::SVGGradientElement::XLINK_HREF] + .GetAnimValue(href, grad); + } + + if (href.IsEmpty()) { + mNoHRefURI = true; + return nullptr; // no URL + } + + // Convert href to an nsIURI + nsCOMPtr<nsIURI> targetURI; + nsCOMPtr<nsIURI> base = mContent->GetBaseURI(); + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, + mContent->GetUncomposedDoc(), base); + + property = + nsSVGEffects::GetPaintingProperty(targetURI, this, + nsSVGEffects::HrefAsPaintingProperty()); + if (!property) + return nullptr; + } + + nsIFrame *result = property->GetReferencedFrame(); + if (!result) + return nullptr; + + nsIAtom* frameType = result->GetType(); + if (frameType != nsGkAtoms::svgLinearGradientFrame && + frameType != nsGkAtoms::svgRadialGradientFrame) + return nullptr; + + return static_cast<nsSVGGradientFrame*>(result); +} + +nsSVGGradientFrame * +nsSVGGradientFrame::GetReferencedGradientIfNotInUse() +{ + nsSVGGradientFrame *referenced = GetReferencedGradient(); + if (!referenced) + return nullptr; + + if (referenced->mLoopFlag) { + // XXXjwatt: we should really send an error to the JavaScript Console here: + NS_WARNING("gradient reference loop detected while inheriting attribute!"); + return nullptr; + } + + return referenced; +} + +void +nsSVGGradientFrame::GetStopFrames(nsTArray<nsIFrame*>* aStopFrames) +{ + nsIFrame *stopFrame = nullptr; + for (stopFrame = mFrames.FirstChild(); stopFrame; + stopFrame = stopFrame->GetNextSibling()) { + if (stopFrame->GetType() == nsGkAtoms::svgStopFrame) { + aStopFrames->AppendElement(stopFrame); + } + } + if (aStopFrames->Length() > 0) { + return; + } + + // Our gradient element doesn't have stops - try to "inherit" them + + AutoGradientReferencer gradientRef(this); + nsSVGGradientFrame* next = GetReferencedGradientIfNotInUse(); + if (!next) { + return; + } + + return next->GetStopFrames(aStopFrames); +} + +// ------------------------------------------------------------------------- +// Linear Gradients +// ------------------------------------------------------------------------- + +#ifdef DEBUG +void +nsSVGLinearGradientFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::linearGradient), + "Content is not an SVG linearGradient"); + + nsSVGGradientFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom* +nsSVGLinearGradientFrame::GetType() const +{ + return nsGkAtoms::svgLinearGradientFrame; +} + +nsresult +nsSVGLinearGradientFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x1 || + aAttribute == nsGkAtoms::y1 || + aAttribute == nsGkAtoms::x2 || + aAttribute == nsGkAtoms::y2)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + return nsSVGGradientFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +//---------------------------------------------------------------------- + +float +nsSVGLinearGradientFrame::GetLengthValue(uint32_t aIndex) +{ + dom::SVGLinearGradientElement* lengthElement = + GetLinearGradientWithLength(aIndex, + static_cast<dom::SVGLinearGradientElement*>(mContent)); + // We passed in mContent as a fallback, so, assuming mContent is non-null, the + // return value should also be non-null. + MOZ_ASSERT(lengthElement, + "Got unexpected null element from GetLinearGradientWithLength"); + const nsSVGLength2 &length = lengthElement->mLengthAttributes[aIndex]; + + // Object bounding box units are handled by setting the appropriate + // transform in GetGradientTransform, but we need to handle user + // space units as part of the individual Get* routines. Fixes 323669. + + uint16_t gradientUnits = GetGradientUnits(); + if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) { + return nsSVGUtils::UserSpace(mSource, &length); + } + + NS_ASSERTION( + gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); + + return length.GetAnimValue(static_cast<SVGSVGElement*>(nullptr)); +} + +dom::SVGLinearGradientElement* +nsSVGLinearGradientFrame::GetLinearGradientWithLength(uint32_t aIndex, + dom::SVGLinearGradientElement* aDefault) +{ + dom::SVGLinearGradientElement* thisElement = + static_cast<dom::SVGLinearGradientElement*>(mContent); + const nsSVGLength2 &length = thisElement->mLengthAttributes[aIndex]; + + if (length.IsExplicitlySet()) { + return thisElement; + } + + return nsSVGGradientFrame::GetLinearGradientWithLength(aIndex, aDefault); +} + +bool +nsSVGLinearGradientFrame::GradientVectorLengthIsZero() +{ + return GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1) == + GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2) && + GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1) == + GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2); +} + +already_AddRefed<gfxPattern> +nsSVGLinearGradientFrame::CreateGradient() +{ + float x1, y1, x2, y2; + + x1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1); + y1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1); + x2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2); + y2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2); + + RefPtr<gfxPattern> pattern = new gfxPattern(x1, y1, x2, y2); + return pattern.forget(); +} + +// ------------------------------------------------------------------------- +// Radial Gradients +// ------------------------------------------------------------------------- + +#ifdef DEBUG +void +nsSVGRadialGradientFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::radialGradient), + "Content is not an SVG radialGradient"); + + nsSVGGradientFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom* +nsSVGRadialGradientFrame::GetType() const +{ + return nsGkAtoms::svgRadialGradientFrame; +} + +nsresult +nsSVGRadialGradientFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::r || + aAttribute == nsGkAtoms::cx || + aAttribute == nsGkAtoms::cy || + aAttribute == nsGkAtoms::fx || + aAttribute == nsGkAtoms::fy)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + return nsSVGGradientFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +//---------------------------------------------------------------------- + +float +nsSVGRadialGradientFrame::GetLengthValue(uint32_t aIndex) +{ + dom::SVGRadialGradientElement* lengthElement = + GetRadialGradientWithLength(aIndex, + static_cast<dom::SVGRadialGradientElement*>(mContent)); + // We passed in mContent as a fallback, so, assuming mContent is non-null, + // the return value should also be non-null. + MOZ_ASSERT(lengthElement, + "Got unexpected null element from GetRadialGradientWithLength"); + return GetLengthValueFromElement(aIndex, *lengthElement); +} + +float +nsSVGRadialGradientFrame::GetLengthValue(uint32_t aIndex, float aDefaultValue) +{ + dom::SVGRadialGradientElement* lengthElement = + GetRadialGradientWithLength(aIndex, nullptr); + + return lengthElement ? GetLengthValueFromElement(aIndex, *lengthElement) + : aDefaultValue; +} + +float +nsSVGRadialGradientFrame::GetLengthValueFromElement(uint32_t aIndex, + dom::SVGRadialGradientElement& aElement) +{ + const nsSVGLength2 &length = aElement.mLengthAttributes[aIndex]; + + // Object bounding box units are handled by setting the appropriate + // transform in GetGradientTransform, but we need to handle user + // space units as part of the individual Get* routines. Fixes 323669. + + uint16_t gradientUnits = GetGradientUnits(); + if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) { + return nsSVGUtils::UserSpace(mSource, &length); + } + + NS_ASSERTION( + gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); + + return length.GetAnimValue(static_cast<SVGSVGElement*>(nullptr)); +} + +dom::SVGRadialGradientElement* +nsSVGRadialGradientFrame::GetRadialGradientWithLength(uint32_t aIndex, + dom::SVGRadialGradientElement* aDefault) +{ + dom::SVGRadialGradientElement* thisElement = + static_cast<dom::SVGRadialGradientElement*>(mContent); + const nsSVGLength2 &length = thisElement->mLengthAttributes[aIndex]; + + if (length.IsExplicitlySet()) { + return thisElement; + } + + return nsSVGGradientFrame::GetRadialGradientWithLength(aIndex, aDefault); +} + +bool +nsSVGRadialGradientFrame::GradientVectorLengthIsZero() +{ + return GetLengthValue(dom::SVGRadialGradientElement::ATTR_R) == 0; +} + +already_AddRefed<gfxPattern> +nsSVGRadialGradientFrame::CreateGradient() +{ + float cx, cy, r, fx, fy; + + cx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CX); + cy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CY); + r = GetLengthValue(dom::SVGRadialGradientElement::ATTR_R); + // If fx or fy are not set, use cx/cy instead + fx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FX, cx); + fy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FY, cy); + + if (fx != cx || fy != cy) { + // The focal point (fFx and fFy) must be clamped to be *inside* - not on - + // the circumference of the gradient or we'll get rendering anomalies. We + // calculate the distance from the focal point to the gradient center and + // make sure it is *less* than the gradient radius. + // 1/128 is the limit of the fractional part of cairo's 24.8 fixed point + // representation divided by 2 to ensure that we get different cairo + // fractions + double dMax = std::max(0.0, r - 1.0/128); + float dx = fx - cx; + float dy = fy - cy; + double d = sqrt((dx * dx) + (dy * dy)); + if (d > dMax) { + double angle = atan2(dy, dx); + fx = (float)(dMax * cos(angle)) + cx; + fy = (float)(dMax * sin(angle)) + cy; + } + } + + RefPtr<gfxPattern> pattern = new gfxPattern(fx, fy, 0, cx, cy, r); + return pattern.forget(); +} + +// ------------------------------------------------------------------------- +// Public functions +// ------------------------------------------------------------------------- + +nsIFrame* +NS_NewSVGLinearGradientFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGLinearGradientFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGLinearGradientFrame) + +nsIFrame* +NS_NewSVGRadialGradientFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGRadialGradientFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGRadialGradientFrame) |