diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/svg | |
parent | 15477ed9af4859dacb069040b5d4de600803d3bc (diff) | |
download | uxp-ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/svg')
268 files changed, 29133 insertions, 0 deletions
diff --git a/layout/svg/AutoReferenceLimiter.h b/layout/svg/AutoReferenceLimiter.h new file mode 100644 index 0000000000..5f822ba135 --- /dev/null +++ b/layout/svg/AutoReferenceLimiter.h @@ -0,0 +1,127 @@ +/* -*- 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/. */ + +#ifndef NS_AUTOREFERENCELIMITER_H +#define NS_AUTOREFERENCELIMITER_H + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ReentrancyGuard.h" +#include "nsDebug.h" + +namespace mozilla { + +/** + * This helper allows us to handle two related issues in SVG content: reference + * loops, and reference chains that we deem to be too long. + * + * SVG content may contain reference loops where an SVG effect (a clipPath, + * say) may reference itself either directly or, perhaps more likely, + * indirectly via a reference chain to other elements that eventually leads + * back to itself. This helper class allows us to detect and immediately break + * such reference loops when applying an effect so that we can prevent + * reference loops causing us to recurse until we run out of stack space and + * crash. + * + * SVG also allows for (non-loop) reference chains of arbitrary length, the + * length depending entirely on the SVG content. Some SVG authoring tools have + * been known to create absurdly long reference chains. (For example, bug + * 1253590 details a case where Adobe Illustrator created an SVG with a chain + * of 5000 clip paths which could cause us to run out of stack space and + * crash.) This helper class also allows us to limit the number of times we + * recurse into a function, thereby allowing us to limit the length ofreference + * chains. + * + * Consumers that need to handle the reference loop case should add a member + * variable (mReferencing, say) to the class that represents and applies the + * SVG effect in question (typically an nsIFrame sub-class), initialize that + * member to AutoReferenceLimiter::notReferencing in the class' constructor + * (and never touch that variable again), and then add something like the + * following at the top of the method(s) that may recurse to follow references + * when applying an effect: + * + * AutoReferenceLimiter refLoopDetector(&mInUse, 1); // only one ref allowed + * if (!refLoopDetector.Reference()) { + * return; // reference loop + * } + * + * Consumers that need to limit reference chain lengths should add something + * like the following code at the top of the method(s) that may recurse to + * follow references when applying an effect: + * + * static int16_t sChainLengthCounter = AutoReferenceLimiter::notReferencing; + * + * AutoReferenceLimiter refChainLengthLimiter(&sChainLengthCounter, MAX_LEN); + * if (!refChainLengthLimiter.Reference()) { + * return; // reference chain too long + * } + */ +class MOZ_RAII AutoReferenceLimiter +{ +public: + static const int16_t notReferencing = -2; + + AutoReferenceLimiter(int16_t* aRefCounter, int16_t aMaxReferenceCount) + { + MOZ_ASSERT(aMaxReferenceCount > 0 && + aRefCounter && + (*aRefCounter == notReferencing || + (*aRefCounter >= 0 && *aRefCounter < aMaxReferenceCount))); + + if (*aRefCounter == notReferencing) { + // initialize + *aRefCounter = aMaxReferenceCount; + } + mRefCounter = aRefCounter; + mMaxReferenceCount = aMaxReferenceCount; + } + + ~AutoReferenceLimiter() { + // If we fail this assert then there were more destructor calls than + // Reference() calls (a consumer forgot to to call Reference()), or else + // someone messed with the variable pointed to by mRefCounter. + MOZ_ASSERT(*mRefCounter < mMaxReferenceCount); + + (*mRefCounter)++; + + if (*mRefCounter == mMaxReferenceCount) { + *mRefCounter = notReferencing; // reset ready for use next time + } + } + + /** + * Returns true on success (no reference loop/reference chain length is + * within the specified limits), else returns false on failure (there is a + * reference loop/the reference chain has exceeded the specified limits). + */ + MOZ_MUST_USE bool Reference() { + // If we fail this assertion then either a consumer failed to break a + // reference loop/chain, or else they called Reference() more than once + MOZ_ASSERT(*mRefCounter >= 0); + + (*mRefCounter)--; + + if (*mRefCounter < 0) { + // TODO: This is an issue with the document, not with Mozilla code. We + // should stop using NS_WARNING and send a message to the console + // instead (but only once per document, not over and over as we repaint). + if (mMaxReferenceCount == 1) { + NS_WARNING("Reference loop detected!"); + } else { + NS_WARNING("Reference chain length limit exceeded!"); + } + return false; + } + return true; + } + +private: + int16_t* mRefCounter; + int16_t mMaxReferenceCount; +}; + +} // namespace mozilla + +#endif // NS_AUTOREFERENCELIMITER_H diff --git a/layout/svg/SVGContextPaint.cpp b/layout/svg/SVGContextPaint.cpp new file mode 100644 index 0000000000..23b9b99dcf --- /dev/null +++ b/layout/svg/SVGContextPaint.cpp @@ -0,0 +1,266 @@ +/* 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/. */ + +#include "SVGContextPaint.h" + +#include "gfxContext.h" +#include "mozilla/gfx/2D.h" +#include "nsIDocument.h" +#include "nsSVGPaintServerFrame.h" +#include "nsSVGEffects.h" +#include "nsSVGPaintServerFrame.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +/** + * Stores in |aTargetPaint| information on how to reconstruct the current + * fill or stroke pattern. Will also set the paint opacity to transparent if + * the paint is set to "none". + * @param aOuterContextPaint pattern information from the outer text context + * @param aTargetPaint where to store the current pattern information + * @param aFillOrStroke member pointer to the paint we are setting up + * @param aProperty the frame property descriptor of the fill or stroke paint + * server frame + */ +static void +SetupInheritablePaint(const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsIFrame* aFrame, + float& aOpacity, + SVGContextPaint* aOuterContextPaint, + SVGContextPaintImpl::Paint& aTargetPaint, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + nsSVGEffects::PaintingPropertyDescriptor aProperty) +{ + const nsStyleSVG *style = aFrame->StyleSVG(); + nsSVGPaintServerFrame *ps = + nsSVGEffects::GetPaintServer(aFrame, aFillOrStroke, aProperty); + + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, aDrawTarget, aContextMatrix, + aFillOrStroke, aOpacity); + if (pattern) { + aTargetPaint.SetPaintServer(aFrame, aContextMatrix, ps); + return; + } + } + if (aOuterContextPaint) { + RefPtr<gfxPattern> pattern; + switch ((style->*aFillOrStroke).Type()) { + case eStyleSVGPaintType_ContextFill: + pattern = aOuterContextPaint->GetFillPattern(aDrawTarget, aOpacity, + aContextMatrix); + break; + case eStyleSVGPaintType_ContextStroke: + pattern = aOuterContextPaint->GetStrokePattern(aDrawTarget, aOpacity, + aContextMatrix); + break; + default: + ; + } + if (pattern) { + aTargetPaint.SetContextPaint(aOuterContextPaint, (style->*aFillOrStroke).Type()); + return; + } + } + nscolor color = + nsSVGUtils::GetFallbackOrPaintColor(aFrame->StyleContext(), aFillOrStroke); + aTargetPaint.SetColor(color); +} + +DrawMode +SVGContextPaintImpl::Init(const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsIFrame* aFrame, + SVGContextPaint* aOuterContextPaint) +{ + DrawMode toDraw = DrawMode(0); + + const nsStyleSVG *style = aFrame->StyleSVG(); + + // fill: + if (style->mFill.Type() == eStyleSVGPaintType_None) { + SetFillOpacity(0.0f); + } else { + float opacity = nsSVGUtils::GetOpacity(style->FillOpacitySource(), + style->mFillOpacity, + aOuterContextPaint); + + SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame, + opacity, aOuterContextPaint, + mFillPaint, &nsStyleSVG::mFill, + nsSVGEffects::FillProperty()); + + SetFillOpacity(opacity); + + toDraw |= DrawMode::GLYPH_FILL; + } + + // stroke: + if (style->mStroke.Type() == eStyleSVGPaintType_None) { + SetStrokeOpacity(0.0f); + } else { + float opacity = nsSVGUtils::GetOpacity(style->StrokeOpacitySource(), + style->mStrokeOpacity, + aOuterContextPaint); + + SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame, + opacity, aOuterContextPaint, + mStrokePaint, &nsStyleSVG::mStroke, + nsSVGEffects::StrokeProperty()); + + SetStrokeOpacity(opacity); + + toDraw |= DrawMode::GLYPH_STROKE; + } + + return toDraw; +} + +void +SVGContextPaint::InitStrokeGeometry(gfxContext* aContext, + float devUnitsPerSVGUnit) +{ + mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit; + aContext->CurrentDash(mDashes, &mDashOffset); + for (uint32_t i = 0; i < mDashes.Length(); i++) { + mDashes[i] /= devUnitsPerSVGUnit; + } + mDashOffset /= devUnitsPerSVGUnit; +} + +/* static */ SVGContextPaint* +SVGContextPaint::GetContextPaint(nsIContent* aContent) +{ + nsIDocument* ownerDoc = aContent->OwnerDoc(); + + if (!ownerDoc->IsBeingUsedAsImage()) { + return nullptr; + } + + return static_cast<SVGContextPaint*>( + ownerDoc->GetProperty(nsGkAtoms::svgContextPaint)); +} + +already_AddRefed<gfxPattern> +SVGContextPaintImpl::GetFillPattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) +{ + return mFillPaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mFill, aCTM); +} + +already_AddRefed<gfxPattern> +SVGContextPaintImpl::GetStrokePattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) +{ + return mStrokePaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mStroke, aCTM); +} + +already_AddRefed<gfxPattern> +SVGContextPaintImpl::Paint::GetPattern(const DrawTarget* aDrawTarget, + float aOpacity, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + const gfxMatrix& aCTM) +{ + RefPtr<gfxPattern> pattern; + if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) { + // Set the pattern matrix just in case it was messed with by a previous + // caller. We should get the same matrix each time a pattern is constructed + // so this should be fine. + pattern->SetMatrix(aCTM * mPatternMatrix); + return pattern.forget(); + } + + switch (mPaintType) { + case eStyleSVGPaintType_None: + pattern = new gfxPattern(Color()); + mPatternMatrix = gfxMatrix(); + break; + case eStyleSVGPaintType_Color: { + Color color = Color::FromABGR(mPaintDefinition.mColor); + color.a *= aOpacity; + pattern = new gfxPattern(color); + mPatternMatrix = gfxMatrix(); + break; + } + case eStyleSVGPaintType_Server: + pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(mFrame, + aDrawTarget, + mContextMatrix, + aFillOrStroke, + aOpacity); + { + // m maps original-user-space to pattern space + gfxMatrix m = pattern->GetMatrix(); + gfxMatrix deviceToOriginalUserSpace = mContextMatrix; + if (!deviceToOriginalUserSpace.Invert()) { + return nullptr; + } + // mPatternMatrix maps device space to pattern space via original user space + mPatternMatrix = deviceToOriginalUserSpace * m; + } + pattern->SetMatrix(aCTM * mPatternMatrix); + break; + case eStyleSVGPaintType_ContextFill: + pattern = mPaintDefinition.mContextPaint->GetFillPattern(aDrawTarget, + aOpacity, aCTM); + // Don't cache this. mContextPaint will have cached it anyway. If we + // cache it, we'll have to compute mPatternMatrix, which is annoying. + return pattern.forget(); + case eStyleSVGPaintType_ContextStroke: + pattern = mPaintDefinition.mContextPaint->GetStrokePattern(aDrawTarget, + aOpacity, aCTM); + // Don't cache this. mContextPaint will have cached it anyway. If we + // cache it, we'll have to compute mPatternMatrix, which is annoying. + return pattern.forget(); + default: + MOZ_ASSERT(false, "invalid paint type"); + return nullptr; + } + + mPatternCache.Put(aOpacity, pattern); + return pattern.forget(); +} + +AutoSetRestoreSVGContextPaint::AutoSetRestoreSVGContextPaint( + SVGContextPaint* aContextPaint, + nsIDocument* aSVGDocument) + : mSVGDocument(aSVGDocument) + , mOuterContextPaint(aSVGDocument->GetProperty(nsGkAtoms::svgContextPaint)) +{ + // The way that we supply context paint is to temporarily set the context + // paint on the owner document of the SVG that we're painting while it's + // being painted. + + MOZ_ASSERT(aContextPaint); + MOZ_ASSERT(aSVGDocument->IsBeingUsedAsImage(), + "SVGContextPaint::GetContextPaint assumes this"); + + if (mOuterContextPaint) { + mSVGDocument->UnsetProperty(nsGkAtoms::svgContextPaint); + } + + DebugOnly<nsresult> res = + mSVGDocument->SetProperty(nsGkAtoms::svgContextPaint, aContextPaint); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(res), "Failed to set context paint"); +} + +AutoSetRestoreSVGContextPaint::~AutoSetRestoreSVGContextPaint() +{ + mSVGDocument->UnsetProperty(nsGkAtoms::svgContextPaint); + if (mOuterContextPaint) { + DebugOnly<nsresult> res = + mSVGDocument->SetProperty(nsGkAtoms::svgContextPaint, mOuterContextPaint); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(res), "Failed to restore context paint"); + } +} + +} // namespace mozilla diff --git a/layout/svg/SVGContextPaint.h b/layout/svg/SVGContextPaint.h new file mode 100644 index 0000000000..3c77647443 --- /dev/null +++ b/layout/svg/SVGContextPaint.h @@ -0,0 +1,197 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_SVGCONTEXTPAINT_H_ +#define MOZILLA_SVGCONTEXTPAINT_H_ + +#include "DrawMode.h" +#include "gfxMatrix.h" +#include "gfxPattern.h" +#include "gfxTypes.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/gfx/2D.h" +#include "nsStyleStruct.h" +#include "nsTArray.h" + +class gfxContext; +class nsIDocument; +class nsSVGPaintServerFrame; + +namespace mozilla { + +/** + * This class is used to pass information about a context element through to + * SVG painting code in order to resolve the 'context-fill' and related + * keywords. See: + * + * https://www.w3.org/TR/SVG2/painting.html#context-paint + * + * This feature allows the color in an SVG-in-OpenType glyph to come from the + * computed style for the text that is being drawn, for example, or for color + * in an SVG embedded by an <img> element to come from the embedding <img> + * element. + */ +class SVGContextPaint +{ +protected: + typedef mozilla::gfx::DrawTarget DrawTarget; + + SVGContextPaint() {} + +public: + virtual ~SVGContextPaint() {} + + virtual already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) = 0; + virtual already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) = 0; + virtual float GetFillOpacity() const = 0; + virtual float GetStrokeOpacity() const = 0; + + already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget, + const gfxMatrix& aCTM) { + return GetFillPattern(aDrawTarget, GetFillOpacity(), aCTM); + } + + already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget, + const gfxMatrix& aCTM) { + return GetStrokePattern(aDrawTarget, GetStrokeOpacity(), aCTM); + } + + static SVGContextPaint* GetContextPaint(nsIContent* aContent); + + // XXX This gets the geometry params from the gfxContext. We should get that + // information from the actual paint context! + void InitStrokeGeometry(gfxContext *aContext, + float devUnitsPerSVGUnit); + + FallibleTArray<gfxFloat>& GetStrokeDashArray() { + return mDashes; + } + + gfxFloat GetStrokeDashOffset() { + return mDashOffset; + } + + gfxFloat GetStrokeWidth() { + return mStrokeWidth; + } + +private: + // Member-vars are initialized in InitStrokeGeometry. + FallibleTArray<gfxFloat> mDashes; + MOZ_INIT_OUTSIDE_CTOR gfxFloat mDashOffset; + MOZ_INIT_OUTSIDE_CTOR gfxFloat mStrokeWidth; +}; + +/** + * RAII class used to temporarily set and remove an SVGContextPaint while a + * piece of SVG is being painted. The context paint is set on the SVG's owner + * document, as expected by SVGContextPaint::GetContextPaint. Any pre-existing + * context paint is restored after this class removes the context paint that it + * set. + */ +class MOZ_RAII AutoSetRestoreSVGContextPaint +{ +public: + AutoSetRestoreSVGContextPaint(SVGContextPaint* aContextPaint, + nsIDocument* aSVGDocument); + ~AutoSetRestoreSVGContextPaint(); +private: + nsIDocument* mSVGDocument; + // The context paint that needs to be restored by our dtor after it removes + // aContextPaint: + void* mOuterContextPaint; +}; + + +/** + * This class should be flattened into SVGContextPaint once we get rid of the + * other sub-class (SimpleTextContextPaint). + */ +struct SVGContextPaintImpl : public SVGContextPaint +{ +protected: + typedef mozilla::gfx::DrawTarget DrawTarget; +public: + DrawMode Init(const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsIFrame* aFrame, + SVGContextPaint* aOuterContextPaint); + + already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) override; + already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) override; + + void SetFillOpacity(float aOpacity) { mFillOpacity = aOpacity; } + float GetFillOpacity() const override { return mFillOpacity; } + + void SetStrokeOpacity(float aOpacity) { mStrokeOpacity = aOpacity; } + float GetStrokeOpacity() const override { return mStrokeOpacity; } + + struct Paint { + Paint() : mPaintType(eStyleSVGPaintType_None) {} + + void SetPaintServer(nsIFrame* aFrame, + const gfxMatrix& aContextMatrix, + nsSVGPaintServerFrame* aPaintServerFrame) { + mPaintType = eStyleSVGPaintType_Server; + mPaintDefinition.mPaintServerFrame = aPaintServerFrame; + mFrame = aFrame; + mContextMatrix = aContextMatrix; + } + + void SetColor(const nscolor &aColor) { + mPaintType = eStyleSVGPaintType_Color; + mPaintDefinition.mColor = aColor; + } + + void SetContextPaint(SVGContextPaint* aContextPaint, + nsStyleSVGPaintType aPaintType) { + NS_ASSERTION(aPaintType == eStyleSVGPaintType_ContextFill || + aPaintType == eStyleSVGPaintType_ContextStroke, + "Invalid context paint type"); + mPaintType = aPaintType; + mPaintDefinition.mContextPaint = aContextPaint; + } + + union { + nsSVGPaintServerFrame* mPaintServerFrame; + SVGContextPaint* mContextPaint; + nscolor mColor; + } mPaintDefinition; + + // Initialized (if needed) in SetPaintServer(): + MOZ_INIT_OUTSIDE_CTOR nsIFrame* mFrame; + // CTM defining the user space for the pattern we will use. + gfxMatrix mContextMatrix; + nsStyleSVGPaintType mPaintType; + + // Device-space-to-pattern-space + gfxMatrix mPatternMatrix; + nsRefPtrHashtable<nsFloatHashKey, gfxPattern> mPatternCache; + + already_AddRefed<gfxPattern> GetPattern(const DrawTarget* aDrawTarget, + float aOpacity, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + const gfxMatrix& aCTM); + }; + + Paint mFillPaint; + Paint mStrokePaint; + + float mFillOpacity; + float mStrokeOpacity; +}; + +} // namespace mozilla + +#endif // MOZILLA_SVGCONTEXTPAINT_H_ + diff --git a/layout/svg/SVGFEContainerFrame.cpp b/layout/svg/SVGFEContainerFrame.cpp new file mode 100644 index 0000000000..ef86470f27 --- /dev/null +++ b/layout/svg/SVGFEContainerFrame.cpp @@ -0,0 +1,108 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsContainerFrame.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsLiteralString.h" +#include "nsSVGEffects.h" +#include "nsSVGFilters.h" + +/* + * This frame is used by filter primitive elements that + * have special child elements that provide parameters. + */ +class SVGFEContainerFrame : public nsContainerFrame +{ + friend nsIFrame* + NS_NewSVGFEContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit SVGFEContainerFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) + { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType( + aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGFEContainer"), aResult); + } +#endif + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgFEContainerFrame + */ + virtual nsIAtom* GetType() const override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override { + // We don't maintain a visual overflow rect + return false; + } +}; + +nsIFrame* +NS_NewSVGFEContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) SVGFEContainerFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(SVGFEContainerFrame) + +#ifdef DEBUG +void +SVGFEContainerFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eFILTER), + "Trying to construct an SVGFEContainerFrame for a " + "content element that doesn't support the right interfaces"); + + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +SVGFEContainerFrame::GetType() const +{ + return nsGkAtoms::svgFEContainerFrame; +} + +nsresult +SVGFEContainerFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsSVGFE *element = static_cast<nsSVGFE*>(mContent); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgFilterFrame, + "Observers observe the filter, so that's what we must invalidate"); + nsSVGEffects::InvalidateDirectRenderingObservers(GetParent()); + } + + return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} diff --git a/layout/svg/SVGFEImageFrame.cpp b/layout/svg/SVGFEImageFrame.cpp new file mode 100644 index 0000000000..185096a93c --- /dev/null +++ b/layout/svg/SVGFEImageFrame.cpp @@ -0,0 +1,168 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsContainerFrame.h" +#include "nsContentUtils.h" +#include "nsFrame.h" +#include "nsGkAtoms.h" +#include "nsLiteralString.h" +#include "nsSVGEffects.h" +#include "nsSVGFilters.h" +#include "mozilla/dom/SVGFEImageElement.h" + +using namespace mozilla; +using namespace mozilla::dom; + +class SVGFEImageFrame : public nsFrame +{ + friend nsIFrame* + NS_NewSVGFEImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit SVGFEImageFrame(nsStyleContext* aContext) + : nsFrame(aContext) + { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + + // This frame isn't actually displayed, but it contains an image and we want + // to use the nsImageLoadingContent machinery for managing images, which + // requires visibility tracking, so we enable visibility tracking and + // forcibly mark it visible below. + EnableVisibilityTracking(); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGFEImage"), aResult); + } +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgFEImageFrame + */ + virtual nsIAtom* GetType() const override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + void OnVisibilityChange(Visibility aNewVisibility, + Maybe<OnNonvisible> aNonvisibleAction = Nothing()) override; + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override { + // We don't maintain a visual overflow rect + return false; + } +}; + +nsIFrame* +NS_NewSVGFEImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) SVGFEImageFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(SVGFEImageFrame) + +/* virtual */ void +SVGFEImageFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + DecApproximateVisibleCount(); + + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsFrame::mContent); + if (imageLoader) { + imageLoader->FrameDestroyed(this); + } + + nsFrame::DestroyFrom(aDestructRoot); +} + +void +SVGFEImageFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::feImage), + "Trying to construct an SVGFEImageFrame for a " + "content element that doesn't support the right interfaces"); + + nsFrame::Init(aContent, aParent, aPrevInFlow); + + // We assume that feImage's are always visible. + IncApproximateVisibleCount(); + + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsFrame::mContent); + if (imageLoader) { + imageLoader->FrameCreated(this); + } +} + +nsIAtom * +SVGFEImageFrame::GetType() const +{ + return nsGkAtoms::svgFEImageFrame; +} + +nsresult +SVGFEImageFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + SVGFEImageElement *element = static_cast<SVGFEImageElement*>(mContent); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgFilterFrame, + "Observers observe the filter, so that's what we must invalidate"); + nsSVGEffects::InvalidateDirectRenderingObservers(GetParent()); + } + if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + bool hrefIsSet = + element->mStringAttributes[SVGFEImageElement::HREF].IsExplicitlySet() || + element->mStringAttributes[SVGFEImageElement::XLINK_HREF] + .IsExplicitlySet(); + if (hrefIsSet) { + element->LoadSVGImage(true, true); + } else { + element->CancelImageRequests(true); + } + } + + return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +void +SVGFEImageFrame::OnVisibilityChange(Visibility aNewVisibility, + Maybe<OnNonvisible> aNonvisibleAction) +{ + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsFrame::mContent); + if (!imageLoader) { + MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent"); + nsFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); + return; + } + + imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); + + nsFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); +} diff --git a/layout/svg/SVGFELeafFrame.cpp b/layout/svg/SVGFELeafFrame.cpp new file mode 100644 index 0000000000..d2a5fd891e --- /dev/null +++ b/layout/svg/SVGFELeafFrame.cpp @@ -0,0 +1,107 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsContainerFrame.h" +#include "nsFrame.h" +#include "nsGkAtoms.h" +#include "nsSVGEffects.h" +#include "nsSVGFilters.h" + +/* + * This frame is used by filter primitive elements that don't + * have special child elements that provide parameters. + */ +class SVGFELeafFrame : public nsFrame +{ + friend nsIFrame* + NS_NewSVGFELeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit SVGFELeafFrame(nsStyleContext* aContext) + : nsFrame(aContext) + { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGFELeaf"), aResult); + } +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgFELeafFrame + */ + virtual nsIAtom* GetType() const override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override { + // We don't maintain a visual overflow rect + return false; + } +}; + +nsIFrame* +NS_NewSVGFELeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) SVGFELeafFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(SVGFELeafFrame) + +#ifdef DEBUG +void +SVGFELeafFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsNodeOfType(nsINode::eFILTER), + "Trying to construct an SVGFELeafFrame for a " + "content element that doesn't support the right interfaces"); + + nsFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +SVGFELeafFrame::GetType() const +{ + return nsGkAtoms::svgFELeafFrame; +} + +nsresult +SVGFELeafFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsSVGFE *element = static_cast<nsSVGFE*>(mContent); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgFilterFrame, + "Observers observe the filter, so that's what we must invalidate"); + nsSVGEffects::InvalidateDirectRenderingObservers(GetParent()); + } + + return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} diff --git a/layout/svg/SVGFEUnstyledLeafFrame.cpp b/layout/svg/SVGFEUnstyledLeafFrame.cpp new file mode 100644 index 0000000000..083c0f27d1 --- /dev/null +++ b/layout/svg/SVGFEUnstyledLeafFrame.cpp @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsContainerFrame.h" +#include "nsFrame.h" +#include "nsGkAtoms.h" +#include "nsSVGEffects.h" +#include "nsSVGFilters.h" + +class SVGFEUnstyledLeafFrame : public nsFrame +{ + friend nsIFrame* + NS_NewSVGFEUnstyledLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit SVGFEUnstyledLeafFrame(nsStyleContext* aContext) + : nsFrame(aContext) + { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGFEUnstyledLeaf"), aResult); + } +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgFEUnstyledLeafFrame + */ + virtual nsIAtom* GetType() const override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override { + // We don't maintain a visual overflow rect + return false; + } +}; + +nsIFrame* +NS_NewSVGFEUnstyledLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) SVGFEUnstyledLeafFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(SVGFEUnstyledLeafFrame) + +nsIAtom * +SVGFEUnstyledLeafFrame::GetType() const +{ + return nsGkAtoms::svgFEUnstyledLeafFrame; +} + +nsresult +SVGFEUnstyledLeafFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + SVGFEUnstyledElement *element = static_cast<SVGFEUnstyledElement*>(mContent); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT(GetParent()->GetParent()->GetType() == nsGkAtoms::svgFilterFrame, + "Observers observe the filter, so that's what we must invalidate"); + nsSVGEffects::InvalidateDirectRenderingObservers(GetParent()->GetParent()); + } + + return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} diff --git a/layout/svg/SVGImageContext.h b/layout/svg/SVGImageContext.h new file mode 100644 index 0000000000..f1e1b94d27 --- /dev/null +++ b/layout/svg/SVGImageContext.h @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_SVGCONTEXT_H_ +#define MOZILLA_SVGCONTEXT_H_ + +#include "mozilla/Maybe.h" +#include "SVGPreserveAspectRatio.h" +#include "Units.h" + +namespace mozilla { + +// SVG image-specific rendering context. For imgIContainer::Draw. +// Used to pass information such as +// - viewport information from CSS, and +// - overridden attributes from an SVG <image> element +// to the image's internal SVG document when it's drawn. +class SVGImageContext +{ +public: + SVGImageContext() + : mGlobalOpacity(1.0) + { } + + // Note: 'aIsPaintingSVGImageElement' should be used to indicate whether + // the SVG image in question is being painted for an SVG <image> element. + SVGImageContext(CSSIntSize aViewportSize, + Maybe<SVGPreserveAspectRatio> aPreserveAspectRatio, + gfxFloat aOpacity = 1.0, + bool aIsPaintingSVGImageElement = false) + : mViewportSize(aViewportSize) + , mPreserveAspectRatio(aPreserveAspectRatio) + , mGlobalOpacity(aOpacity) + , mIsPaintingSVGImageElement(aIsPaintingSVGImageElement) + { } + + const CSSIntSize& GetViewportSize() const { + return mViewportSize; + } + + const Maybe<SVGPreserveAspectRatio>& GetPreserveAspectRatio() const { + return mPreserveAspectRatio; + } + + gfxFloat GetGlobalOpacity() const { + return mGlobalOpacity; + } + + bool IsPaintingForSVGImageElement() const { + return mIsPaintingSVGImageElement; + } + + bool operator==(const SVGImageContext& aOther) const { + return mViewportSize == aOther.mViewportSize && + mPreserveAspectRatio == aOther.mPreserveAspectRatio && + mGlobalOpacity == aOther.mGlobalOpacity && + mIsPaintingSVGImageElement == aOther.mIsPaintingSVGImageElement; + } + + bool operator!=(const SVGImageContext& aOther) const { + return !(*this == aOther); + } + + uint32_t Hash() const { + return HashGeneric(mViewportSize.width, + mViewportSize.height, + mPreserveAspectRatio.map(HashPAR).valueOr(0), + HashBytes(&mGlobalOpacity, sizeof(gfxFloat)), + mIsPaintingSVGImageElement); + } + +private: + static uint32_t HashPAR(const SVGPreserveAspectRatio& aPAR) { + return aPAR.Hash(); + } + + // NOTE: When adding new member-vars, remember to update Hash() & operator==. + CSSIntSize mViewportSize; + Maybe<SVGPreserveAspectRatio> mPreserveAspectRatio; + gfxFloat mGlobalOpacity; + bool mIsPaintingSVGImageElement; +}; + +} // namespace mozilla + +#endif // MOZILLA_SVGCONTEXT_H_ diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp new file mode 100644 index 0000000000..e681260682 --- /dev/null +++ b/layout/svg/SVGTextFrame.cpp @@ -0,0 +1,5653 @@ +/* -*- 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 "SVGTextFrame.h" + +// Keep others in (case-insensitive) order: +#include "DOMSVGPoint.h" +#include "gfx2DGlue.h" +#include "gfxFont.h" +#include "gfxSkipChars.h" +#include "gfxTypes.h" +#include "gfxUtils.h" +#include "LookAndFeel.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "mozilla/Likely.h" +#include "nsAlgorithm.h" +#include "nsBlockFrame.h" +#include "nsCaret.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsIDOMSVGLength.h" +#include "nsISelection.h" +#include "nsQuickSort.h" +#include "nsRenderingContext.h" +#include "nsSVGEffects.h" +#include "nsSVGOuterSVGFrame.h" +#include "nsSVGPaintServerFrame.h" +#include "mozilla/dom/SVGRect.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGUtils.h" +#include "nsTArray.h" +#include "nsTextFrame.h" +#include "nsTextNode.h" +#include "SVGAnimatedNumberList.h" +#include "SVGContentUtils.h" +#include "SVGLengthList.h" +#include "SVGNumberList.h" +#include "SVGPathElement.h" +#include "SVGTextPathElement.h" +#include "nsLayoutUtils.h" +#include "nsFrameSelection.h" +#include "nsStyleStructInlines.h" +#include <algorithm> +#include <cmath> +#include <limits> + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +// ============================================================================ +// Utility functions + +/** + * Using the specified gfxSkipCharsIterator, converts an offset and length + * in original char indexes to skipped char indexes. + * + * @param aIterator The gfxSkipCharsIterator to use for the conversion. + * @param aOriginalOffset The original offset. + * @param aOriginalLength The original length. + */ +static gfxTextRun::Range +ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator, + uint32_t aOriginalOffset, uint32_t aOriginalLength) +{ + uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset); + aIterator.AdvanceOriginal(aOriginalLength); + return gfxTextRun::Range(start, aIterator.GetSkippedOffset()); +} + +/** + * Converts an nsPoint from app units to user space units using the specified + * nsPresContext and returns it as a gfxPoint. + */ +static gfxPoint +AppUnitsToGfxUnits(const nsPoint& aPoint, const nsPresContext* aContext) +{ + return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x), + aContext->AppUnitsToGfxUnits(aPoint.y)); +} + +/** + * Converts a gfxRect that is in app units to CSS pixels using the specified + * nsPresContext and returns it as a gfxRect. + */ +static gfxRect +AppUnitsToFloatCSSPixels(const gfxRect& aRect, const nsPresContext* aContext) +{ + return gfxRect(aContext->AppUnitsToFloatCSSPixels(aRect.x), + aContext->AppUnitsToFloatCSSPixels(aRect.y), + aContext->AppUnitsToFloatCSSPixels(aRect.width), + aContext->AppUnitsToFloatCSSPixels(aRect.height)); +} + +/** + * Scales a gfxRect around a given point. + * + * @param aRect The rectangle to scale. + * @param aPoint The point around which to scale. + * @param aScale The scale amount. + */ +static void +ScaleAround(gfxRect& aRect, const gfxPoint& aPoint, double aScale) +{ + aRect.x = aPoint.x - aScale * (aPoint.x - aRect.x); + aRect.y = aPoint.y - aScale * (aPoint.y - aRect.y); + aRect.width *= aScale; + aRect.height *= aScale; +} + +/** + * Returns whether a gfxPoint lies within a gfxRect. + */ +static bool +Inside(const gfxRect& aRect, const gfxPoint& aPoint) +{ + return aPoint.x >= aRect.x && + aPoint.x < aRect.XMost() && + aPoint.y >= aRect.y && + aPoint.y < aRect.YMost(); +} + +/** + * Gets the measured ascent and descent of the text in the given nsTextFrame + * in app units. + * + * @param aFrame The text frame. + * @param aAscent The ascent in app units (output). + * @param aDescent The descent in app units (output). + */ +static void +GetAscentAndDescentInAppUnits(nsTextFrame* aFrame, + gfxFloat& aAscent, gfxFloat& aDescent) +{ + gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated); + + gfxTextRun::Range range = ConvertOriginalToSkipped( + it, aFrame->GetContentOffset(), aFrame->GetContentLength()); + + gfxTextRun::Metrics metrics = + textRun->MeasureText(range, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr); + + aAscent = metrics.mAscent; + aDescent = metrics.mDescent; +} + +/** + * Updates an interval by intersecting it with another interval. + * The intervals are specified using a start index and a length. + */ +static void +IntersectInterval(uint32_t& aStart, uint32_t& aLength, + uint32_t aStartOther, uint32_t aLengthOther) +{ + uint32_t aEnd = aStart + aLength; + uint32_t aEndOther = aStartOther + aLengthOther; + + if (aStartOther >= aEnd || aStart >= aEndOther) { + aLength = 0; + } else { + if (aStartOther >= aStart) + aStart = aStartOther; + aLength = std::min(aEnd, aEndOther) - aStart; + } +} + +/** + * Intersects an interval as IntersectInterval does but by taking + * the offset and length of the other interval from a + * nsTextFrame::TrimmedOffsets object. + */ +static void +TrimOffsets(uint32_t& aStart, uint32_t& aLength, + const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) +{ + IntersectInterval(aStart, aLength, + aTrimmedOffsets.mStart, aTrimmedOffsets.mLength); +} + +/** + * Returns the closest ancestor-or-self node that is not an SVG <a> + * element. + */ +static nsIContent* +GetFirstNonAAncestor(nsIContent* aContent) +{ + while (aContent && aContent->IsSVGElement(nsGkAtoms::a)) { + aContent = aContent->GetParent(); + } + return aContent; +} + +/** + * Returns whether the given node is a text content element[1], taking into + * account whether it has a valid parent. + * + * For example, in: + * + * <svg xmlns="http://www.w3.org/2000/svg"> + * <text><a/><text/></text> + * <tspan/> + * </svg> + * + * true would be returned for the outer <text> element and the <a> element, + * and false for the inner <text> element (since a <text> is not allowed + * to be a child of another <text>) and the <tspan> element (because it + * must be inside a <text> subtree). + * + * Note that we don't support the <tref> element yet and this function + * returns false for it. + * + * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement + */ +static bool +IsTextContentElement(nsIContent* aContent) +{ + if (aContent->IsSVGElement(nsGkAtoms::text)) { + nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); + return !parent || !IsTextContentElement(parent); + } + + if (aContent->IsSVGElement(nsGkAtoms::textPath)) { + nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); + return parent && parent->IsSVGElement(nsGkAtoms::text); + } + + if (aContent->IsAnyOfSVGElements(nsGkAtoms::a, + nsGkAtoms::tspan)) { + return true; + } + + return false; +} + +/** + * Returns whether the specified frame is an nsTextFrame that has some text + * content. + */ +static bool +IsNonEmptyTextFrame(nsIFrame* aFrame) +{ + nsTextFrame* textFrame = do_QueryFrame(aFrame); + if (!textFrame) { + return false; + } + + return textFrame->GetContentLength() != 0; +} + +/** + * Takes an nsIFrame and if it is a text frame that has some text content, + * returns it as an nsTextFrame and its corresponding nsTextNode. + * + * @param aFrame The frame to look at. + * @param aTextFrame aFrame as an nsTextFrame (output). + * @param aTextNode The nsTextNode content of aFrame (output). + * @return true if aFrame is a non-empty text frame, false otherwise. + */ +static bool +GetNonEmptyTextFrameAndNode(nsIFrame* aFrame, + nsTextFrame*& aTextFrame, + nsTextNode*& aTextNode) +{ + nsTextFrame* text = do_QueryFrame(aFrame); + bool isNonEmptyTextFrame = text && text->GetContentLength() != 0; + + if (isNonEmptyTextFrame) { + nsIContent* content = text->GetContent(); + NS_ASSERTION(content && content->IsNodeOfType(nsINode::eTEXT), + "unexpected content type for nsTextFrame"); + + nsTextNode* node = static_cast<nsTextNode*>(content); + MOZ_ASSERT(node->TextLength() != 0, + "frame's GetContentLength() should be 0 if the text node " + "has no content"); + + aTextFrame = text; + aTextNode = node; + } + + MOZ_ASSERT(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame, + "our logic should agree with IsNonEmptyTextFrame"); + return isNonEmptyTextFrame; +} + +/** + * Returns whether the specified atom is for one of the five + * glyph positioning attributes that can appear on SVG text + * elements -- x, y, dx, dy or rotate. + */ +static bool +IsGlyphPositioningAttribute(nsIAtom* aAttribute) +{ + return aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::dx || + aAttribute == nsGkAtoms::dy || + aAttribute == nsGkAtoms::rotate; +} + +/** + * Returns the position in app units of a given baseline (using an + * SVG dominant-baseline property value) for a given nsTextFrame. + * + * @param aFrame The text frame to inspect. + * @param aTextRun The text run of aFrame. + * @param aDominantBaseline The dominant-baseline value to use. + */ +static nscoord +GetBaselinePosition(nsTextFrame* aFrame, + gfxTextRun* aTextRun, + uint8_t aDominantBaseline, + float aFontSizeScaleFactor) +{ + WritingMode writingMode = aFrame->GetWritingMode(); + gfxTextRun::Metrics metrics = + aTextRun->MeasureText(gfxFont::LOOSE_INK_EXTENTS, nullptr); + + switch (aDominantBaseline) { + case NS_STYLE_DOMINANT_BASELINE_HANGING: + case NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE: + return writingMode.IsVerticalRL() + ? metrics.mAscent + metrics.mDescent : 0; + + case NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT: + case NS_STYLE_DOMINANT_BASELINE_NO_CHANGE: + case NS_STYLE_DOMINANT_BASELINE_RESET_SIZE: + // These three should not simply map to 'baseline', but we don't + // support the complex baseline model that SVG 1.1 has and which + // css3-linebox now defines. + // (fall through) + + case NS_STYLE_DOMINANT_BASELINE_AUTO: + case NS_STYLE_DOMINANT_BASELINE_ALPHABETIC: + return writingMode.IsVerticalRL() + ? metrics.mAscent + metrics.mDescent - + aFrame->GetLogicalBaseline(writingMode) + : aFrame->GetLogicalBaseline(writingMode); + + case NS_STYLE_DOMINANT_BASELINE_MIDDLE: + return aFrame->GetLogicalBaseline(writingMode) - + SVGContentUtils::GetFontXHeight(aFrame) / 2.0 * + aFrame->PresContext()->AppUnitsPerCSSPixel() * aFontSizeScaleFactor; + + case NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE: + case NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC: + return writingMode.IsVerticalLR() + ? 0 : metrics.mAscent + metrics.mDescent; + + case NS_STYLE_DOMINANT_BASELINE_CENTRAL: + case NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL: + return (metrics.mAscent + metrics.mDescent) / 2.0; + } + + NS_NOTREACHED("unexpected dominant-baseline value"); + return aFrame->GetLogicalBaseline(writingMode); +} + +/** + * For a given text run, returns the range of skipped characters that comprise + * the ligature group and/or cluster that includes the character represented + * by the specified gfxSkipCharsIterator. + * + * @param aTextRun The text run to use for determining whether a given character + * is part of a ligature or cluster. + * @param aIterator The gfxSkipCharsIterator to use for the current position + * in the text run. + */ +static gfxTextRun::Range +ClusterRange(gfxTextRun* aTextRun, const gfxSkipCharsIterator& aIterator) +{ + uint32_t start = aIterator.GetSkippedOffset(); + uint32_t end = start + 1; + while (end < aTextRun->GetLength() && + (!aTextRun->IsLigatureGroupStart(end) || + !aTextRun->IsClusterStart(end))) { + end++; + } + return gfxTextRun::Range(start, end); +} + +/** + * Truncates an array to be at most the length of another array. + * + * @param aArrayToTruncate The array to truncate. + * @param aReferenceArray The array whose length will be used to truncate + * aArrayToTruncate to. + */ +template<typename T, typename U> +static void +TruncateTo(nsTArray<T>& aArrayToTruncate, const nsTArray<U>& aReferenceArray) +{ + uint32_t length = aReferenceArray.Length(); + if (aArrayToTruncate.Length() > length) { + aArrayToTruncate.TruncateLength(length); + } +} + +/** + * Asserts that the anonymous block child of the SVGTextFrame has been + * reflowed (or does not exist). Returns null if the child has not been + * reflowed, and the frame otherwise. + * + * We check whether the kid has been reflowed and not the frame itself + * since we sometimes need to call this function during reflow, after the + * kid has been reflowed but before we have cleared the dirty bits on the + * frame itself. + */ +static SVGTextFrame* +FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "aFrame must not be null"); + nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); + if (NS_SUBTREE_DIRTY(kid)) { + MOZ_ASSERT(false, "should have already reflowed the anonymous block child"); + return nullptr; + } + return aFrame; +} + +static double +GetContextScale(const gfxMatrix& aMatrix) +{ + // The context scale is the ratio of the length of the transformed + // diagonal vector (1,1) to the length of the untransformed diagonal + // (which is sqrt(2)). + gfxPoint p = aMatrix.Transform(gfxPoint(1, 1)) - + aMatrix.Transform(gfxPoint(0, 0)); + return SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y); +} + +// ============================================================================ +// Utility classes + +namespace mozilla { + +// ---------------------------------------------------------------------------- +// TextRenderedRun + +/** + * A run of text within a single nsTextFrame whose glyphs can all be painted + * with a single call to nsTextFrame::PaintText. A text rendered run can + * be created for a sequence of two or more consecutive glyphs as long as: + * + * - Only the first glyph has (or none of the glyphs have) been positioned + * with SVG text positioning attributes + * - All of the glyphs have zero rotation + * - The glyphs are not on a text path + * - The glyphs correspond to content within the one nsTextFrame + * + * A TextRenderedRunIterator produces TextRenderedRuns required for painting a + * whole SVGTextFrame. + */ +struct TextRenderedRun +{ + typedef gfxTextRun::Range Range; + + /** + * Constructs a TextRenderedRun that is uninitialized except for mFrame + * being null. + */ + TextRenderedRun() + : mFrame(nullptr) + { + } + + /** + * Constructs a TextRenderedRun with all of the information required to + * paint it. See the comments documenting the member variables below + * for descriptions of the arguments. + */ + TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition, + float aLengthAdjustScaleFactor, double aRotate, + float aFontSizeScaleFactor, nscoord aBaseline, + uint32_t aTextFrameContentOffset, + uint32_t aTextFrameContentLength, + uint32_t aTextElementCharIndex) + : mFrame(aFrame), + mPosition(aPosition), + mLengthAdjustScaleFactor(aLengthAdjustScaleFactor), + mRotate(static_cast<float>(aRotate)), + mFontSizeScaleFactor(aFontSizeScaleFactor), + mBaseline(aBaseline), + mTextFrameContentOffset(aTextFrameContentOffset), + mTextFrameContentLength(aTextFrameContentLength), + mTextElementCharIndex(aTextElementCharIndex) + { + } + + /** + * Returns the text run for the text frame that this rendered run is part of. + */ + gfxTextRun* GetTextRun() const + { + mFrame->EnsureTextRun(nsTextFrame::eInflated); + return mFrame->GetTextRun(nsTextFrame::eInflated); + } + + /** + * Returns whether this rendered run is RTL. + */ + bool IsRightToLeft() const + { + return GetTextRun()->IsRightToLeft(); + } + + /** + * Returns whether this rendered run is vertical. + */ + bool IsVertical() const + { + return GetTextRun()->IsVertical(); + } + + /** + * Returns the transform that converts from a <text> element's user space into + * the coordinate space that rendered runs can be painted directly in. + * + * The difference between this method and GetTransformFromRunUserSpaceToUserSpace + * is that when calling in to nsTextFrame::PaintText, it will already take + * into account any left clip edge (that is, it doesn't just apply a visual + * clip to the rendered text, it shifts the glyphs over so that they are + * painted with their left edge at the x coordinate passed in to it). + * Thus we need to account for this in our transform. + * + * + * Assume that we have <text x="100" y="100" rotate="0 0 1 0 0 1">abcdef</text>. + * This would result in four text rendered runs: + * + * - one for "ab" + * - one for "c" + * - one for "de" + * - one for "f" + * + * Assume now that we are painting the third TextRenderedRun. It will have + * a left clip edge that is the sum of the advances of "abc", and it will + * have a right clip edge that is the advance of "f". In + * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin) + * as the point at which to paint the text frame, and we pass in the + * clip edge values. The nsTextFrame will paint the substring of its + * text such that the top-left corner of the "d"'s glyph cell will be at + * (0, 0) in the current coordinate system. + * + * Thus, GetTransformFromUserSpaceForPainting must return a transform from + * whatever user space the <text> element is in to a coordinate space in + * device pixels (as that's what nsTextFrame works in) where the origin is at + * the same position as our user space mPositions[i].mPosition value for + * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100). + * The translation required to do this (ignoring the scale to get from + * user space to device pixels, and ignoring the + * (100 + userSpaceAdvance("abc"), 100) translation) is: + * + * (-leftEdge, -baseline) + * + * where baseline is the distance between the baseline of the text and the top + * edge of the nsTextFrame. We translate by -leftEdge horizontally because + * the nsTextFrame will already shift the glyphs over by that amount and start + * painting glyphs at x = 0. We translate by -baseline vertically so that + * painting the top edges of the glyphs at y = 0 will result in their + * baselines being at our desired y position. + * + * + * Now for an example with RTL text. Assume our content is now + * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>. We'd have + * the following text rendered runs: + * + * - one for "EH" + * - one for "B" + * - one for "ER" + * - one for "W" + * + * Again, we are painting the third TextRenderedRun. The left clip edge + * is the advance of the "W" and the right clip edge is the sum of the + * advances of "BEH". Our translation to get the rendered "ER" glyphs + * in the right place this time is: + * + * (-frameWidth + rightEdge, -baseline) + * + * which is equivalent to: + * + * (-(leftEdge + advance("ER")), -baseline) + * + * The reason we have to shift left additionally by the width of the run + * of glyphs we are painting is that although the nsTextFrame is RTL, + * we still supply the top-left corner to paint the frame at when calling + * nsTextFrame::PaintText, even though our user space positions for each + * glyph in mPositions specifies the origin of each glyph, which for RTL + * glyphs is at the right edge of the glyph cell. + * + * + * For any other use of an nsTextFrame in the context of a particular run + * (such as hit testing, or getting its rectangle), + * GetTransformFromRunUserSpaceToUserSpace should be used. + * + * @param aContext The context to use for unit conversions. + * @param aItem The nsCharClipDisplayItem that holds the amount of clipping + * from the left and right edges of the text frame for this rendered run. + * An appropriate nsCharClipDisplayItem can be obtained by constructing an + * SVGCharClipDisplayItem for the TextRenderedRun. + */ + gfxMatrix GetTransformFromUserSpaceForPainting( + nsPresContext* aContext, + const nsCharClipDisplayItem& aItem) const; + + /** + * Returns the transform that converts from "run user space" to a <text> + * element's user space. Run user space is a coordinate system that has the + * same size as the <text>'s user space but rotated and translated such that + * (0,0) is the top-left of the rectangle that bounds the text. + * + * @param aContext The context to use for unit conversions. + */ + gfxMatrix GetTransformFromRunUserSpaceToUserSpace(nsPresContext* aContext) const; + + /** + * Returns the transform that converts from "run user space" to float pixels + * relative to the nsTextFrame that this rendered run is a part of. + * + * @param aContext The context to use for unit conversions. + */ + gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(nsPresContext* aContext) const; + + /** + * Flag values used for the aFlags arguments of GetRunUserSpaceRect, + * GetFrameUserSpaceRect and GetUserSpaceRect. + */ + enum { + // Includes the fill geometry of the text in the returned rectangle. + eIncludeFill = 1, + // Includes the stroke geometry of the text in the returned rectangle. + eIncludeStroke = 2, + // Includes any text shadow in the returned rectangle. + eIncludeTextShadow = 4, + // Don't include any horizontal glyph overflow in the returned rectangle. + eNoHorizontalOverflow = 8 + }; + + /** + * Returns a rectangle that bounds the fill and/or stroke of the rendered run + * in run user space. + * + * @param aContext The context to use for unit conversions. + * @param aFlags A combination of the flags above (eIncludeFill and + * eIncludeStroke) indicating what parts of the text to include in + * the rectangle. + */ + SVGBBox GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; + + /** + * Returns a rectangle that covers the fill and/or stroke of the rendered run + * in "frame user space". + * + * Frame user space is a coordinate space of the same scale as the <text> + * element's user space, but with its rotation set to the rotation of + * the glyphs within this rendered run and its origin set to the position + * such that placing the nsTextFrame there would result in the glyphs in + * this rendered run being at their correct positions. + * + * For example, say we have <text x="100 150" y="100">ab</text>. Assume + * the advance of both the "a" and the "b" is 12 user units, and the + * ascent of the text is 8 user units and its descent is 6 user units, + * and that we are not measuing the stroke of the text, so that we stay + * entirely within the glyph cells. + * + * There will be two text rendered runs, one for "a" and one for "b". + * + * The frame user space for the "a" run will have its origin at + * (100, 100 - 8) in the <text> element's user space and will have its + * axes aligned with the user space (since there is no rotate="" or + * text path involve) and with its scale the same as the user space. + * The rect returned by this method will be (0, 0, 12, 14), since the "a" + * glyph is right at the left of the nsTextFrame. + * + * The frame user space for the "b" run will have its origin at + * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect + * returned by this method will be (12, 0, 12, 14), since we are + * advance("a") horizontally in to the text frame. + * + * @param aContext The context to use for unit conversions. + * @param aFlags A combination of the flags above (eIncludeFill and + * eIncludeStroke) indicating what parts of the text to include in + * the rectangle. + */ + SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; + + /** + * Returns a rectangle that covers the fill and/or stroke of the rendered run + * in the <text> element's user space. + * + * @param aContext The context to use for unit conversions. + * @param aFlags A combination of the flags above indicating what parts of the + * text to include in the rectangle. + * @param aAdditionalTransform An additional transform to apply to the + * frame user space rectangle before its bounds are transformed into + * user space. + */ + SVGBBox GetUserSpaceRect(nsPresContext* aContext, uint32_t aFlags, + const gfxMatrix* aAdditionalTransform = nullptr) const; + + /** + * Gets the app unit amounts to clip from the left and right edges of + * the nsTextFrame in order to paint just this rendered run. + * + * Note that if clip edge amounts land in the middle of a glyph, the + * glyph won't be painted at all. The clip edges are thus more of + * a selection mechanism for which glyphs will be painted, rather + * than a geometric clip. + */ + void GetClipEdges(nscoord& aVisIStartEdge, nscoord& aVisIEndEdge) const; + + /** + * Returns the advance width of the whole rendered run. + */ + nscoord GetAdvanceWidth() const; + + /** + * Returns the index of the character into this rendered run whose + * glyph cell contains the given point, or -1 if there is no such + * character. This does not hit test against any overflow. + * + * @param aContext The context to use for unit conversions. + * @param aPoint The point in the user space of the <text> element. + */ + int32_t GetCharNumAtPosition(nsPresContext* aContext, + const gfxPoint& aPoint) const; + + /** + * The text frame that this rendered run lies within. + */ + nsTextFrame* mFrame; + + /** + * The point in user space that the text is positioned at. + * + * For a horizontal run: + * The x coordinate is the left edge of a LTR run of text or the right edge of + * an RTL run. The y coordinate is the baseline of the text. + * For a vertical run: + * The x coordinate is the baseline of the text. + * The y coordinate is the top edge of a LTR run, or bottom of RTL. + */ + gfxPoint mPosition; + + /** + * The horizontal scale factor to apply when painting glyphs to take + * into account textLength="". + */ + float mLengthAdjustScaleFactor; + + /** + * The rotation in radians in the user coordinate system that the text has. + */ + float mRotate; + + /** + * The scale factor that was used to transform the text run's original font + * size into a sane range for painting and measurement. + */ + double mFontSizeScaleFactor; + + /** + * The baseline in app units of this text run. The measurement is from the + * top of the text frame. (From the left edge if vertical.) + */ + nscoord mBaseline; + + /** + * The offset and length in mFrame's content nsTextNode that corresponds to + * this text rendered run. These are original char indexes. + */ + uint32_t mTextFrameContentOffset; + uint32_t mTextFrameContentLength; + + /** + * The character index in the whole SVG <text> element that this text rendered + * run begins at. + */ + uint32_t mTextElementCharIndex; +}; + +gfxMatrix +TextRenderedRun::GetTransformFromUserSpaceForPainting( + nsPresContext* aContext, + const nsCharClipDisplayItem& aItem) const +{ + // We transform to device pixels positioned such that painting the text frame + // at (0,0) with aItem will result in the text being in the right place. + + gfxMatrix m; + if (!mFrame) { + return m; + } + + float cssPxPerDevPx = aContext-> + AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + // Glyph position in user space. + m.Translate(mPosition / cssPxPerDevPx); + + // Take into account any font size scaling and scaling due to textLength="". + m.Scale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor); + + // Rotation due to rotate="" or a <textPath>. + m.Rotate(mRotate); + + m.Scale(mLengthAdjustScaleFactor, 1.0); + + // Translation to get the text frame in the right place. + nsPoint t; + if (IsVertical()) { + t = nsPoint(-mBaseline, + IsRightToLeft() + ? -mFrame->GetRect().height + aItem.mVisIEndEdge + : -aItem.mVisIStartEdge); + } else { + t = nsPoint(IsRightToLeft() + ? -mFrame->GetRect().width + aItem.mVisIEndEdge + : -aItem.mVisIStartEdge, + -mBaseline); + } + m.Translate(AppUnitsToGfxUnits(t, aContext)); + + return m; +} + +gfxMatrix +TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace( + nsPresContext* aContext) const +{ + gfxMatrix m; + if (!mFrame) { + return m; + } + + float cssPxPerDevPx = aContext-> + AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + nscoord start, end; + GetClipEdges(start, end); + + // Glyph position in user space. + m.Translate(mPosition); + + // Rotation due to rotate="" or a <textPath>. + m.Rotate(mRotate); + + // Scale due to textLength="". + m.Scale(mLengthAdjustScaleFactor, 1.0); + + // Translation to get the text frame in the right place. + nsPoint t; + if (IsVertical()) { + t = nsPoint(-mBaseline, + IsRightToLeft() + ? -mFrame->GetRect().height + start + end + : 0); + } else { + t = nsPoint(IsRightToLeft() + ? -mFrame->GetRect().width + start + end + : 0, + -mBaseline); + } + m.Translate(AppUnitsToGfxUnits(t, aContext) * + cssPxPerDevPx / mFontSizeScaleFactor); + + return m; +} + +gfxMatrix +TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace( + nsPresContext* aContext) const +{ + gfxMatrix m; + if (!mFrame) { + return m; + } + + nscoord start, end; + GetClipEdges(start, end); + + // Translate by the horizontal distance into the text frame this + // rendered run is. + gfxFloat appPerCssPx = aContext->AppUnitsPerCSSPixel(); + gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx) + : gfxPoint(start / appPerCssPx, 0); + return m.Translate(t); +} + +SVGBBox +TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext, + uint32_t aFlags) const +{ + SVGBBox r; + if (!mFrame) { + return r; + } + + // Determine the amount of overflow above and below the frame's mRect. + // + // We need to call GetVisualOverflowRectRelativeToSelf because this includes + // overflowing decorations, which the MeasureText call below does not. We + // assume here the decorations only overflow above and below the frame, never + // horizontally. + nsRect self = mFrame->GetVisualOverflowRectRelativeToSelf(); + nsRect rect = mFrame->GetRect(); + bool vertical = IsVertical(); + nscoord above = vertical ? -self.x : -self.y; + nscoord below = vertical ? self.XMost() - rect.width + : self.YMost() - rect.height; + + gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); + + // Get the content range for this rendered run. + Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, + mTextFrameContentLength); + if (range.Length() == 0) { + return r; + } + + // Measure that range. + gfxTextRun::Metrics metrics = + textRun->MeasureText(range, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr); + // Make sure it includes the font-box. + gfxRect fontBox(0, -metrics.mAscent, + metrics.mAdvanceWidth, metrics.mAscent + metrics.mDescent); + metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox); + + // Determine the rectangle that covers the rendered run's fill, + // taking into account the measured vertical overflow due to + // decorations. + nscoord baseline = metrics.mBoundingBox.y + metrics.mAscent; + gfxFloat x, width; + if (aFlags & eNoHorizontalOverflow) { + x = 0.0; + width = textRun->GetAdvanceWidth(range, nullptr); + } else { + x = metrics.mBoundingBox.x; + width = metrics.mBoundingBox.width; + } + nsRect fillInAppUnits(x, baseline - above, + width, metrics.mBoundingBox.height + above + below); + if (textRun->IsVertical()) { + // Swap line-relative textMetrics dimensions to physical coordinates. + Swap(fillInAppUnits.x, fillInAppUnits.y); + Swap(fillInAppUnits.width, fillInAppUnits.height); + } + + // Account for text-shadow. + if (aFlags & eIncludeTextShadow) { + fillInAppUnits = + nsLayoutUtils::GetTextShadowRectsUnion(fillInAppUnits, mFrame); + } + + // Convert the app units rectangle to user units. + gfxRect fill = AppUnitsToFloatCSSPixels(gfxRect(fillInAppUnits.x, + fillInAppUnits.y, + fillInAppUnits.width, + fillInAppUnits.height), + aContext); + + // Scale the rectangle up due to any mFontSizeScaleFactor. We scale + // it around the text's origin. + ScaleAround(fill, + textRun->IsVertical() + ? gfxPoint(aContext->AppUnitsToFloatCSSPixels(baseline), 0.0) + : gfxPoint(0.0, aContext->AppUnitsToFloatCSSPixels(baseline)), + 1.0 / mFontSizeScaleFactor); + + // Include the fill if requested. + if (aFlags & eIncludeFill) { + r = fill; + } + + // Include the stroke if requested. + if ((aFlags & eIncludeStroke) && + !fill.IsEmpty() && + nsSVGUtils::GetStrokeWidth(mFrame) > 0) { + r.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, + gfxMatrix())); + } + + return r; +} + +SVGBBox +TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext, + uint32_t aFlags) const +{ + SVGBBox r = GetRunUserSpaceRect(aContext, aFlags); + if (r.IsEmpty()) { + return r; + } + gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext); + return m.TransformBounds(r.ToThebesRect()); +} + +SVGBBox +TextRenderedRun::GetUserSpaceRect(nsPresContext* aContext, + uint32_t aFlags, + const gfxMatrix* aAdditionalTransform) const +{ + SVGBBox r = GetRunUserSpaceRect(aContext, aFlags); + if (r.IsEmpty()) { + return r; + } + gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext); + if (aAdditionalTransform) { + m *= *aAdditionalTransform; + } + return m.TransformBounds(r.ToThebesRect()); +} + +void +TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge, + nscoord& aVisIEndEdge) const +{ + uint32_t contentLength = mFrame->GetContentLength(); + if (mTextFrameContentOffset == 0 && + mTextFrameContentLength == contentLength) { + // If the rendered run covers the entire content, we know we don't need + // to clip without having to measure anything. + aVisIStartEdge = 0; + aVisIEndEdge = 0; + return; + } + + gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); + + // Get the covered content offset/length for this rendered run in skipped + // characters, since that is what GetAdvanceWidth expects. + Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset, + mTextFrameContentLength); + + // Get the offset/length of the whole nsTextFrame. + uint32_t frameOffset = mFrame->GetContentOffset(); + uint32_t frameLength = mFrame->GetContentLength(); + + // Trim the whole-nsTextFrame offset/length to remove any leading/trailing + // white space, as the nsTextFrame when painting does not include them when + // interpreting clip edges. + nsTextFrame::TrimmedOffsets trimmedOffsets = + mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText(), true); + TrimOffsets(frameOffset, frameLength, trimmedOffsets); + + // Convert the trimmed whole-nsTextFrame offset/length into skipped + // characters. + Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength); + + // Measure the advance width in the text run between the start of + // frame's content and the start of the rendered run's content, + nscoord startEdge = textRun-> + GetAdvanceWidth(Range(frameRange.start, runRange.start), nullptr); + + // and between the end of the rendered run's content and the end + // of the frame's content. + nscoord endEdge = textRun-> + GetAdvanceWidth(Range(runRange.end, frameRange.end), nullptr); + + if (textRun->IsRightToLeft()) { + aVisIStartEdge = endEdge; + aVisIEndEdge = startEdge; + } else { + aVisIStartEdge = startEdge; + aVisIEndEdge = endEdge; + } +} + +nscoord +TextRenderedRun::GetAdvanceWidth() const +{ + gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); + + Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, + mTextFrameContentLength); + + return textRun->GetAdvanceWidth(range, nullptr); +} + +int32_t +TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext, + const gfxPoint& aPoint) const +{ + if (mTextFrameContentLength == 0) { + return -1; + } + + float cssPxPerDevPx = aContext-> + AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + // Convert the point from user space into run user space, and take + // into account any mFontSizeScaleFactor. + gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext); + if (!m.Invert()) { + return -1; + } + gfxPoint p = m.Transform(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor; + + // First check that the point lies vertically between the top and bottom + // edges of the text. + gfxFloat ascent, descent; + GetAscentAndDescentInAppUnits(mFrame, ascent, descent); + + WritingMode writingMode = mFrame->GetWritingMode(); + if (writingMode.IsVertical()) { + gfxFloat leftEdge = + mFrame->GetLogicalBaseline(writingMode) - + (writingMode.IsVerticalRL() ? ascent : descent); + gfxFloat rightEdge = leftEdge + ascent + descent; + if (p.x < aContext->AppUnitsToGfxUnits(leftEdge) || + p.x > aContext->AppUnitsToGfxUnits(rightEdge)) { + return -1; + } + } else { + gfxFloat topEdge = mFrame->GetLogicalBaseline(writingMode) - ascent; + gfxFloat bottomEdge = topEdge + ascent + descent; + if (p.y < aContext->AppUnitsToGfxUnits(topEdge) || + p.y > aContext->AppUnitsToGfxUnits(bottomEdge)) { + return -1; + } + } + + gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); + + // Next check that the point lies horizontally within the left and right + // edges of the text. + Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, + mTextFrameContentLength); + gfxFloat runAdvance = + aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, nullptr)); + + gfxFloat pos = writingMode.IsVertical() ? p.y : p.x; + if (pos < 0 || pos >= runAdvance) { + return -1; + } + + // Finally, measure progressively smaller portions of the rendered run to + // find which glyph it lies within. This will need to change once we + // support letter-spacing and word-spacing. + bool rtl = textRun->IsRightToLeft(); + for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) { + range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i); + gfxFloat advance = + aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, nullptr)); + if ((rtl && pos < runAdvance - advance) || + (!rtl && pos >= advance)) { + return i; + } + } + return -1; +} + +// ---------------------------------------------------------------------------- +// TextNodeIterator + +enum SubtreePosition +{ + eBeforeSubtree, + eWithinSubtree, + eAfterSubtree +}; + +/** + * An iterator class for nsTextNodes that are descendants of a given node, the + * root. Nodes are iterated in document order. An optional subtree can be + * specified, in which case the iterator will track whether the current state of + * the traversal over the tree is within that subtree or is past that subtree. + */ +class TextNodeIterator +{ +public: + /** + * Constructs a TextNodeIterator with the specified root node and optional + * subtree. + */ + explicit TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr) + : mRoot(aRoot), + mSubtree(aSubtree == aRoot ? nullptr : aSubtree), + mCurrent(aRoot), + mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) + { + NS_ASSERTION(aRoot, "expected non-null root"); + if (!aRoot->IsNodeOfType(nsINode::eTEXT)) { + Next(); + } + } + + /** + * Returns the current nsTextNode, or null if the iterator has finished. + */ + nsTextNode* Current() const + { + return static_cast<nsTextNode*>(mCurrent); + } + + /** + * Advances to the next nsTextNode and returns it, or null if the end of + * iteration has been reached. + */ + nsTextNode* Next(); + + /** + * Returns whether the iterator is currently within the subtree rooted + * at mSubtree. Returns true if we are not tracking a subtree (we consider + * that we're always within the subtree). + */ + bool IsWithinSubtree() const + { + return mSubtreePosition == eWithinSubtree; + } + + /** + * Returns whether the iterator is past the subtree rooted at mSubtree. + * Returns false if we are not tracking a subtree. + */ + bool IsAfterSubtree() const + { + return mSubtreePosition == eAfterSubtree; + } + +private: + /** + * The root under which all nsTextNodes will be iterated over. + */ + nsIContent* mRoot; + + /** + * The node rooting the subtree to track. + */ + nsIContent* mSubtree; + + /** + * The current node during iteration. + */ + nsIContent* mCurrent; + + /** + * The current iterator position relative to mSubtree. + */ + SubtreePosition mSubtreePosition; +}; + +nsTextNode* +TextNodeIterator::Next() +{ + // Starting from mCurrent, we do a non-recursive traversal to the next + // nsTextNode beneath mRoot, updating mSubtreePosition appropriately if we + // encounter mSubtree. + if (mCurrent) { + do { + nsIContent* next = IsTextContentElement(mCurrent) ? + mCurrent->GetFirstChild() : + nullptr; + if (next) { + mCurrent = next; + if (mCurrent == mSubtree) { + mSubtreePosition = eWithinSubtree; + } + } else { + for (;;) { + if (mCurrent == mRoot) { + mCurrent = nullptr; + break; + } + if (mCurrent == mSubtree) { + mSubtreePosition = eAfterSubtree; + } + next = mCurrent->GetNextSibling(); + if (next) { + mCurrent = next; + if (mCurrent == mSubtree) { + mSubtreePosition = eWithinSubtree; + } + break; + } + if (mCurrent == mSubtree) { + mSubtreePosition = eAfterSubtree; + } + mCurrent = mCurrent->GetParent(); + } + } + } while (mCurrent && !mCurrent->IsNodeOfType(nsINode::eTEXT)); + } + + return static_cast<nsTextNode*>(mCurrent); +} + +// ---------------------------------------------------------------------------- +// TextNodeCorrespondenceRecorder + +/** + * TextNodeCorrespondence is used as the value of a frame property that + * is stored on all its descendant nsTextFrames. It stores the number of DOM + * characters between it and the previous nsTextFrame that did not have an + * nsTextFrame created for them, due to either not being in a correctly + * parented text content element, or because they were display:none. + * These are called "undisplayed characters". + * + * See also TextNodeCorrespondenceRecorder below, which is what sets the + * frame property. + */ +struct TextNodeCorrespondence +{ + explicit TextNodeCorrespondence(uint32_t aUndisplayedCharacters) + : mUndisplayedCharacters(aUndisplayedCharacters) + { + } + + uint32_t mUndisplayedCharacters; +}; + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextNodeCorrespondenceProperty, + TextNodeCorrespondence) + +/** + * Returns the number of undisplayed characters before the specified + * nsTextFrame. + */ +static uint32_t +GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) +{ + void* value = aFrame->Properties().Get(TextNodeCorrespondenceProperty()); + TextNodeCorrespondence* correspondence = + static_cast<TextNodeCorrespondence*>(value); + if (!correspondence) { + NS_NOTREACHED("expected a TextNodeCorrespondenceProperty on nsTextFrame " + "used for SVG text"); + return 0; + } + return correspondence->mUndisplayedCharacters; +} + +/** + * Traverses the nsTextFrames for an SVGTextFrame and records a + * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM + * characters between each frame. This is done by iterating simultaneously + * over the nsTextNodes and nsTextFrames and noting when nsTextNodes (or + * parts of them) are skipped when finding the next nsTextFrame. + */ +class TextNodeCorrespondenceRecorder +{ +public: + /** + * Entry point for the TextNodeCorrespondenceProperty recording. + */ + static void RecordCorrespondence(SVGTextFrame* aRoot); + +private: + explicit TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot) + : mNodeIterator(aRoot->GetContent()), + mPreviousNode(nullptr), + mNodeCharIndex(0) + { + } + + void Record(SVGTextFrame* aRoot); + void TraverseAndRecord(nsIFrame* aFrame); + + /** + * Returns the next non-empty nsTextNode. + */ + nsTextNode* NextNode(); + + /** + * The iterator over the nsTextNodes that we use as we simultaneously + * iterate over the nsTextFrames. + */ + TextNodeIterator mNodeIterator; + + /** + * The previous nsTextNode we iterated over. + */ + nsTextNode* mPreviousNode; + + /** + * The index into the current nsTextNode's character content. + */ + uint32_t mNodeCharIndex; +}; + +/* static */ void +TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) +{ + TextNodeCorrespondenceRecorder recorder(aRoot); + recorder.Record(aRoot); +} + +void +TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) +{ + if (!mNodeIterator.Current()) { + // If there are no nsTextNodes then there is nothing to do. + return; + } + + // Traverse over all the nsTextFrames and record the number of undisplayed + // characters. + TraverseAndRecord(aRoot); + + // Find how many undisplayed characters there are after the final nsTextFrame. + uint32_t undisplayed = 0; + if (mNodeIterator.Current()) { + if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) { + // The last nsTextFrame ended part way through an nsTextNode. The + // remaining characters count as undisplayed. + NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), + "incorrect tracking of undisplayed characters in " + "text nodes"); + undisplayed += mPreviousNode->TextLength() - mNodeCharIndex; + } + // All the remaining nsTextNodes that we iterate must also be undisplayed. + for (nsTextNode* textNode = mNodeIterator.Current(); + textNode; + textNode = NextNode()) { + undisplayed += textNode->TextLength(); + } + } + + // Record the trailing number of undisplayed characters on the + // SVGTextFrame. + aRoot->mTrailingUndisplayedCharacters = undisplayed; +} + +nsTextNode* +TextNodeCorrespondenceRecorder::NextNode() +{ + mPreviousNode = mNodeIterator.Current(); + nsTextNode* next; + do { + next = mNodeIterator.Next(); + } while (next && next->TextLength() == 0); + return next; +} + +void +TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) +{ + // Recursively iterate over the frame tree, for frames that correspond + // to text content elements. + if (IsTextContentElement(aFrame->GetContent())) { + for (nsIFrame* f : aFrame->PrincipalChildList()) { + TraverseAndRecord(f); + } + return; + } + + nsTextFrame* frame; // The current text frame. + nsTextNode* node; // The text node for the current text frame. + if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) { + // If this isn't an nsTextFrame, or is empty, nothing to do. + return; + } + + NS_ASSERTION(frame->GetContentOffset() >= 0, + "don't know how to handle negative content indexes"); + + uint32_t undisplayed = 0; + if (!mPreviousNode) { + // Must be the very first text frame. + NS_ASSERTION(mNodeCharIndex == 0, "incorrect tracking of undisplayed " + "characters in text nodes"); + if (!mNodeIterator.Current()) { + NS_NOTREACHED("incorrect tracking of correspondence between text frames " + "and text nodes"); + } else { + // Each whole nsTextNode we find before we get to the text node for the + // first text frame must be undisplayed. + while (mNodeIterator.Current() != node) { + undisplayed += mNodeIterator.Current()->TextLength(); + NextNode(); + } + // If the first text frame starts at a non-zero content offset, then those + // earlier characters are also undisplayed. + undisplayed += frame->GetContentOffset(); + NextNode(); + } + } else if (mPreviousNode == node) { + // Same text node as last time. + if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) { + // We have some characters in the middle of the text node + // that are undisplayed. + NS_ASSERTION(mNodeCharIndex < + static_cast<uint32_t>(frame->GetContentOffset()), + "incorrect tracking of undisplayed characters in " + "text nodes"); + undisplayed = frame->GetContentOffset() - mNodeCharIndex; + } + } else { + // Different text node from last time. + if (mPreviousNode->TextLength() != mNodeCharIndex) { + NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), + "incorrect tracking of undisplayed characters in " + "text nodes"); + // Any trailing characters at the end of the previous nsTextNode are + // undisplayed. + undisplayed = mPreviousNode->TextLength() - mNodeCharIndex; + } + // Each whole nsTextNode we find before we get to the text node for + // the current text frame must be undisplayed. + while (mNodeIterator.Current() != node) { + undisplayed += mNodeIterator.Current()->TextLength(); + NextNode(); + } + // If the current text frame starts at a non-zero content offset, then those + // earlier characters are also undisplayed. + undisplayed += frame->GetContentOffset(); + NextNode(); + } + + // Set the frame property. + frame->Properties().Set(TextNodeCorrespondenceProperty(), + new TextNodeCorrespondence(undisplayed)); + + // Remember how far into the current nsTextNode we are. + mNodeCharIndex = frame->GetContentEnd(); +} + +// ---------------------------------------------------------------------------- +// TextFrameIterator + +/** + * An iterator class for nsTextFrames that are descendants of an + * SVGTextFrame. The iterator can optionally track whether the + * current nsTextFrame is for a descendant of, or past, a given subtree + * content node or frame. (This functionality is used for example by the SVG + * DOM text methods to get only the nsTextFrames for a particular <tspan>.) + * + * TextFrameIterator also tracks and exposes other information about the + * current nsTextFrame: + * + * * how many undisplayed characters came just before it + * * its position (in app units) relative to the SVGTextFrame's anonymous + * block frame + * * what nsInlineFrame corresponding to a <textPath> element it is a + * descendant of + * * what computed dominant-baseline value applies to it + * + * Note that any text frames that are empty -- whose ContentLength() is 0 -- + * will be skipped over. + */ +class TextFrameIterator +{ +public: + /** + * Constructs a TextFrameIterator for the specified SVGTextFrame + * with an optional frame subtree to restrict iterated text frames to. + */ + explicit TextFrameIterator(SVGTextFrame* aRoot, nsIFrame* aSubtree = nullptr) + : mRootFrame(aRoot), + mSubtree(aSubtree), + mCurrentFrame(aRoot), + mCurrentPosition(), + mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) + { + Init(); + } + + /** + * Constructs a TextFrameIterator for the specified SVGTextFrame + * with an optional frame content subtree to restrict iterated text frames to. + */ + TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree) + : mRootFrame(aRoot), + mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent() ? + aSubtree->GetPrimaryFrame() : + nullptr), + mCurrentFrame(aRoot), + mCurrentPosition(), + mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) + { + Init(); + } + + /** + * Returns the root SVGTextFrame this TextFrameIterator is iterating over. + */ + SVGTextFrame* Root() const + { + return mRootFrame; + } + + /** + * Returns the current nsTextFrame. + */ + nsTextFrame* Current() const + { + return do_QueryFrame(mCurrentFrame); + } + + /** + * Returns the number of undisplayed characters in the DOM just before the + * current frame. + */ + uint32_t UndisplayedCharacters() const; + + /** + * Returns the current frame's position, in app units, relative to the + * root SVGTextFrame's anonymous block frame. + */ + nsPoint Position() const + { + return mCurrentPosition; + } + + /** + * Advances to the next nsTextFrame and returns it. + */ + nsTextFrame* Next(); + + /** + * Returns whether the iterator is within the subtree. + */ + bool IsWithinSubtree() const + { + return mSubtreePosition == eWithinSubtree; + } + + /** + * Returns whether the iterator is past the subtree. + */ + bool IsAfterSubtree() const + { + return mSubtreePosition == eAfterSubtree; + } + + /** + * Returns the frame corresponding to the <textPath> element, if we + * are inside one. + */ + nsIFrame* TextPathFrame() const + { + return mTextPathFrames.IsEmpty() ? + nullptr : + mTextPathFrames.ElementAt(mTextPathFrames.Length() - 1); + } + + /** + * Returns the current frame's computed dominant-baseline value. + */ + uint8_t DominantBaseline() const + { + return mBaselines.ElementAt(mBaselines.Length() - 1); + } + + /** + * Finishes the iterator. + */ + void Close() + { + mCurrentFrame = nullptr; + } + +private: + /** + * Initializes the iterator and advances to the first item. + */ + void Init() + { + if (!mRootFrame) { + return; + } + + mBaselines.AppendElement(mRootFrame->StyleSVGReset()->mDominantBaseline); + Next(); + } + + /** + * Pushes the specified frame's computed dominant-baseline value. + * If the value of the property is "auto", then the parent frame's + * computed value is used. + */ + void PushBaseline(nsIFrame* aNextFrame); + + /** + * Pops the current dominant-baseline off the stack. + */ + void PopBaseline(); + + /** + * The root frame we are iterating through. + */ + SVGTextFrame* mRootFrame; + + /** + * The frame for the subtree we are also interested in tracking. + */ + nsIFrame* mSubtree; + + /** + * The current value of the iterator. + */ + nsIFrame* mCurrentFrame; + + /** + * The position, in app units, of the current frame relative to mRootFrame. + */ + nsPoint mCurrentPosition; + + /** + * Stack of frames corresponding to <textPath> elements that are in scope + * for the current frame. + */ + AutoTArray<nsIFrame*, 1> mTextPathFrames; + + /** + * Stack of dominant-baseline values to record as we traverse through the + * frame tree. + */ + AutoTArray<uint8_t, 8> mBaselines; + + /** + * The iterator's current position relative to mSubtree. + */ + SubtreePosition mSubtreePosition; +}; + +uint32_t +TextFrameIterator::UndisplayedCharacters() const +{ + MOZ_ASSERT(!(mRootFrame->PrincipalChildList().FirstChild() && + NS_SUBTREE_DIRTY(mRootFrame->PrincipalChildList().FirstChild())), + "should have already reflowed the anonymous block child"); + + if (!mCurrentFrame) { + return mRootFrame->mTrailingUndisplayedCharacters; + } + + nsTextFrame* frame = do_QueryFrame(mCurrentFrame); + return GetUndisplayedCharactersBeforeFrame(frame); +} + +nsTextFrame* +TextFrameIterator::Next() +{ + // Starting from mCurrentFrame, we do a non-recursive traversal to the next + // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we + // encounter mSubtree. + if (mCurrentFrame) { + do { + nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent()) ? + mCurrentFrame->PrincipalChildList().FirstChild() : + nullptr; + if (next) { + // Descend into this frame, and accumulate its position. + mCurrentPosition += next->GetPosition(); + if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) { + // Record this <textPath> frame. + mTextPathFrames.AppendElement(next); + } + // Record the frame's baseline. + PushBaseline(next); + mCurrentFrame = next; + if (mCurrentFrame == mSubtree) { + // If the current frame is mSubtree, we have now moved into it. + mSubtreePosition = eWithinSubtree; + } + } else { + for (;;) { + // We want to move past the current frame. + if (mCurrentFrame == mRootFrame) { + // If we've reached the root frame, we're finished. + mCurrentFrame = nullptr; + break; + } + // Remove the current frame's position. + mCurrentPosition -= mCurrentFrame->GetPosition(); + if (mCurrentFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath)) { + // Pop off the <textPath> frame if this is a <textPath>. + mTextPathFrames.TruncateLength(mTextPathFrames.Length() - 1); + } + // Pop off the current baseline. + PopBaseline(); + if (mCurrentFrame == mSubtree) { + // If this was mSubtree, we have now moved past it. + mSubtreePosition = eAfterSubtree; + } + next = mCurrentFrame->GetNextSibling(); + if (next) { + // Moving to the next sibling. + mCurrentPosition += next->GetPosition(); + if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) { + // Record this <textPath> frame. + mTextPathFrames.AppendElement(next); + } + // Record the frame's baseline. + PushBaseline(next); + mCurrentFrame = next; + if (mCurrentFrame == mSubtree) { + // If the current frame is mSubtree, we have now moved into it. + mSubtreePosition = eWithinSubtree; + } + break; + } + if (mCurrentFrame == mSubtree) { + // If there is no next sibling frame, and the current frame is + // mSubtree, we have now moved past it. + mSubtreePosition = eAfterSubtree; + } + // Ascend out of this frame. + mCurrentFrame = mCurrentFrame->GetParent(); + } + } + } while (mCurrentFrame && + !IsNonEmptyTextFrame(mCurrentFrame)); + } + + return Current(); +} + +void +TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) +{ + uint8_t baseline = aNextFrame->StyleSVGReset()->mDominantBaseline; + if (baseline == NS_STYLE_DOMINANT_BASELINE_AUTO) { + baseline = mBaselines.LastElement(); + } + mBaselines.AppendElement(baseline); +} + +void +TextFrameIterator::PopBaseline() +{ + NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines"); + mBaselines.TruncateLength(mBaselines.Length() - 1); +} + +// ----------------------------------------------------------------------------- +// TextRenderedRunIterator + +/** + * Iterator for TextRenderedRun objects for the SVGTextFrame. + */ +class TextRenderedRunIterator +{ +public: + /** + * Values for the aFilter argument of the constructor, to indicate which frames + * we should be limited to iterating TextRenderedRun objects for. + */ + enum RenderedRunFilter { + // Iterate TextRenderedRuns for all nsTextFrames. + eAllFrames, + // Iterate only TextRenderedRuns for nsTextFrames that are + // visibility:visible. + eVisibleFrames + }; + + /** + * Constructs a TextRenderedRunIterator with an optional frame subtree to + * restrict iterated rendered runs to. + * + * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate + * through. + * @param aFilter Indicates whether to iterate rendered runs for non-visible + * nsTextFrames. + * @param aSubtree An optional frame subtree to restrict iterated rendered + * runs to. + */ + explicit TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, + RenderedRunFilter aFilter = eAllFrames, + nsIFrame* aSubtree = nullptr) + : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), + mFilter(aFilter), + mTextElementCharIndex(0), + mFrameStartTextElementCharIndex(0), + mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), + mCurrent(First()) + { + } + + /** + * Constructs a TextRenderedRunIterator with a content subtree to restrict + * iterated rendered runs to. + * + * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate + * through. + * @param aFilter Indicates whether to iterate rendered runs for non-visible + * nsTextFrames. + * @param aSubtree A content subtree to restrict iterated rendered runs to. + */ + TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, + RenderedRunFilter aFilter, + nsIContent* aSubtree) + : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), + mFilter(aFilter), + mTextElementCharIndex(0), + mFrameStartTextElementCharIndex(0), + mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), + mCurrent(First()) + { + } + + /** + * Returns the current TextRenderedRun. + */ + TextRenderedRun Current() const + { + return mCurrent; + } + + /** + * Advances to the next TextRenderedRun and returns it. + */ + TextRenderedRun Next(); + +private: + /** + * Returns the root SVGTextFrame this iterator is for. + */ + SVGTextFrame* Root() const + { + return mFrameIterator.Root(); + } + + /** + * Advances to the first TextRenderedRun and returns it. + */ + TextRenderedRun First(); + + /** + * The frame iterator to use. + */ + TextFrameIterator mFrameIterator; + + /** + * The filter indicating which TextRenderedRuns to return. + */ + RenderedRunFilter mFilter; + + /** + * The character index across the entire <text> element we are currently + * up to. + */ + uint32_t mTextElementCharIndex; + + /** + * The character index across the entire <text> for the start of the current + * frame. + */ + uint32_t mFrameStartTextElementCharIndex; + + /** + * The font-size scale factor we used when constructing the nsTextFrames. + */ + double mFontSizeScaleFactor; + + /** + * The current TextRenderedRun. + */ + TextRenderedRun mCurrent; +}; + +TextRenderedRun +TextRenderedRunIterator::Next() +{ + if (!mFrameIterator.Current()) { + // If there are no more frames, then there are no more rendered runs to + // return. + mCurrent = TextRenderedRun(); + return mCurrent; + } + + // The values we will use to initialize the TextRenderedRun with. + nsTextFrame* frame; + gfxPoint pt; + double rotate; + nscoord baseline; + uint32_t offset, length; + uint32_t charIndex; + + // We loop, because we want to skip over rendered runs that either aren't + // within our subtree of interest, because they don't match the filter, + // or because they are hidden due to having fallen off the end of a + // <textPath>. + for (;;) { + if (mFrameIterator.IsAfterSubtree()) { + mCurrent = TextRenderedRun(); + return mCurrent; + } + + frame = mFrameIterator.Current(); + + charIndex = mTextElementCharIndex; + + // Find the end of the rendered run, by looking through the + // SVGTextFrame's positions array until we find one that is recorded + // as a run boundary. + uint32_t runStart, runEnd; // XXX Replace runStart with mTextElementCharIndex. + runStart = mTextElementCharIndex; + runEnd = runStart + 1; + while (runEnd < Root()->mPositions.Length() && + !Root()->mPositions[runEnd].mRunBoundary) { + runEnd++; + } + + // Convert the global run start/end indexes into an offset/length into the + // current frame's nsTextNode. + offset = frame->GetContentOffset() + runStart - + mFrameStartTextElementCharIndex; + length = runEnd - runStart; + + // If the end of the frame's content comes before the run boundary we found + // in SVGTextFrame's position array, we need to shorten the rendered run. + uint32_t contentEnd = frame->GetContentEnd(); + if (offset + length > contentEnd) { + length = contentEnd - offset; + } + + NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()), "invalid offset"); + NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length"); + + // Get the frame's baseline position. + frame->EnsureTextRun(nsTextFrame::eInflated); + baseline = GetBaselinePosition(frame, + frame->GetTextRun(nsTextFrame::eInflated), + mFrameIterator.DominantBaseline(), + mFontSizeScaleFactor); + + // Trim the offset/length to remove any leading/trailing white space. + uint32_t untrimmedOffset = offset; + uint32_t untrimmedLength = length; + nsTextFrame::TrimmedOffsets trimmedOffsets = + frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true); + TrimOffsets(offset, length, trimmedOffsets); + charIndex += offset - untrimmedOffset; + + // Get the position and rotation of the character that begins this + // rendered run. + pt = Root()->mPositions[charIndex].mPosition; + rotate = Root()->mPositions[charIndex].mAngle; + + // Determine if we should skip this rendered run. + bool skip = !mFrameIterator.IsWithinSubtree() || + Root()->mPositions[mTextElementCharIndex].mHidden; + if (mFilter == eVisibleFrames) { + skip = skip || !frame->StyleVisibility()->IsVisible(); + } + + // Update our global character index to move past the characters + // corresponding to this rendered run. + mTextElementCharIndex += untrimmedLength; + + // If we have moved past the end of the current frame's content, we need to + // advance to the next frame. + if (offset + untrimmedLength >= contentEnd) { + mFrameIterator.Next(); + mTextElementCharIndex += mFrameIterator.UndisplayedCharacters(); + mFrameStartTextElementCharIndex = mTextElementCharIndex; + } + + if (!mFrameIterator.Current()) { + if (skip) { + // That was the last frame, and we skipped this rendered run. So we + // have no rendered run to return. + mCurrent = TextRenderedRun(); + return mCurrent; + } + break; + } + + if (length && !skip) { + // Only return a rendered run if it didn't get collapsed away entirely + // (due to it being all white space) and if we don't want to skip it. + break; + } + } + + mCurrent = TextRenderedRun(frame, pt, Root()->mLengthAdjustScaleFactor, + rotate, mFontSizeScaleFactor, baseline, + offset, length, charIndex); + return mCurrent; +} + +TextRenderedRun +TextRenderedRunIterator::First() +{ + if (!mFrameIterator.Current()) { + return TextRenderedRun(); + } + + if (Root()->mPositions.IsEmpty()) { + mFrameIterator.Close(); + return TextRenderedRun(); + } + + // Get the character index for the start of this rendered run, by skipping + // any undisplayed characters. + mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); + mFrameStartTextElementCharIndex = mTextElementCharIndex; + + return Next(); +} + +// ----------------------------------------------------------------------------- +// CharIterator + +/** + * Iterator for characters within an SVGTextFrame. + */ +class CharIterator +{ + typedef gfxTextRun::Range Range; + +public: + /** + * Values for the aFilter argument of the constructor, to indicate which + * characters we should be iterating over. + */ + enum CharacterFilter { + // Iterate over all original characters from the DOM that are within valid + // text content elements. + eOriginal, + // Iterate only over characters that are addressable by the positioning + // attributes x="", y="", etc. This includes all characters after + // collapsing white space as required by the value of 'white-space'. + eAddressable, + // Iterate only over characters that are the first of clusters or ligature + // groups. + eClusterAndLigatureGroupStart, + // Iterate only over characters that are part of a cluster or ligature + // group but not the first character. + eClusterOrLigatureGroupMiddle + }; + + /** + * Constructs a CharIterator. + * + * @param aSVGTextFrame The SVGTextFrame whose characters to iterate + * through. + * @param aFilter Indicates which characters to iterate over. + * @param aSubtree A content subtree to track whether the current character + * is within. + */ + CharIterator(SVGTextFrame* aSVGTextFrame, + CharacterFilter aFilter, + nsIContent* aSubtree = nullptr); + + /** + * Returns whether the iterator is finished. + */ + bool AtEnd() const + { + return !mFrameIterator.Current(); + } + + /** + * Advances to the next matching character. Returns true if there was a + * character to advance to, and false otherwise. + */ + bool Next(); + + /** + * Advances ahead aCount matching characters. Returns true if there were + * enough characters to advance past, and false otherwise. + */ + bool Next(uint32_t aCount); + + /** + * Advances ahead up to aCount matching characters. + */ + void NextWithinSubtree(uint32_t aCount); + + /** + * Advances to the character with the specified index. The index is in the + * space of original characters (i.e., all DOM characters under the <text> + * that are within valid text content elements). + */ + bool AdvanceToCharacter(uint32_t aTextElementCharIndex); + + /** + * Advances to the first matching character after the current nsTextFrame. + */ + bool AdvancePastCurrentFrame(); + + /** + * Advances to the first matching character after the frames within + * the current <textPath>. + */ + bool AdvancePastCurrentTextPathFrame(); + + /** + * Advances to the first matching character of the subtree. Returns true + * if we successfully advance to the subtree, or if we are already within + * the subtree. Returns false if we are past the subtree. + */ + bool AdvanceToSubtree(); + + /** + * Returns the nsTextFrame for the current character. + */ + nsTextFrame* TextFrame() const + { + return mFrameIterator.Current(); + } + + /** + * Returns whether the iterator is within the subtree. + */ + bool IsWithinSubtree() const + { + return mFrameIterator.IsWithinSubtree(); + } + + /** + * Returns whether the iterator is past the subtree. + */ + bool IsAfterSubtree() const + { + return mFrameIterator.IsAfterSubtree(); + } + + /** + * Returns whether the current character is a skipped character. + */ + bool IsOriginalCharSkipped() const + { + return mSkipCharsIterator.IsOriginalCharSkipped(); + } + + /** + * Returns whether the current character is the start of a cluster and + * ligature group. + */ + bool IsClusterAndLigatureGroupStart() const; + + /** + * Returns whether the current character is trimmed away when painting, + * due to it being leading/trailing white space. + */ + bool IsOriginalCharTrimmed() const; + + /** + * Returns whether the current character is unaddressable from the SVG glyph + * positioning attributes. + */ + bool IsOriginalCharUnaddressable() const + { + return IsOriginalCharSkipped() || IsOriginalCharTrimmed(); + } + + /** + * Returns the text run for the current character. + */ + gfxTextRun* TextRun() const + { + return mTextRun; + } + + /** + * Returns the current character index. + */ + uint32_t TextElementCharIndex() const + { + return mTextElementCharIndex; + } + + /** + * Returns the character index for the start of the cluster/ligature group it + * is part of. + */ + uint32_t GlyphStartTextElementCharIndex() const + { + return mGlyphStartTextElementCharIndex; + } + + /** + * Returns the number of undisplayed characters between the beginning of + * the glyph and the current character. + */ + uint32_t GlyphUndisplayedCharacters() const + { + return mGlyphUndisplayedCharacters; + } + + /** + * Gets the original character offsets within the nsTextNode for the + * cluster/ligature group the current character is a part of. + * + * @param aOriginalOffset The offset of the start of the cluster/ligature + * group (output). + * @param aOriginalLength The length of cluster/ligature group (output). + */ + void GetOriginalGlyphOffsets(uint32_t& aOriginalOffset, + uint32_t& aOriginalLength) const; + + /** + * Gets the advance, in user units, of the glyph the current character is + * part of. + * + * @param aContext The context to use for unit conversions. + */ + gfxFloat GetGlyphAdvance(nsPresContext* aContext) const; + + /** + * Gets the advance, in user units, of the current character. If the + * character is a part of ligature, then the advance returned will be + * a fraction of the ligature glyph's advance. + * + * @param aContext The context to use for unit conversions. + */ + gfxFloat GetAdvance(nsPresContext* aContext) const; + + /** + * Gets the specified partial advance of the glyph the current character is + * part of. The partial advance is measured from the first character + * corresponding to the glyph until the specified part length. + * + * The part length value does not include any undisplayed characters in the + * middle of the cluster/ligature group. For example, if you have: + * + * <text>f<tspan display="none">x</tspan>i</text> + * + * and the "f" and "i" are ligaturized, then calling GetGlyphPartialAdvance + * with aPartLength values will have the following results: + * + * 0 => 0 + * 1 => adv("fi") / 2 + * 2 => adv("fi") + * + * @param aPartLength The number of characters in the cluster/ligature group + * to measure. + * @param aContext The context to use for unit conversions. + */ + gfxFloat GetGlyphPartialAdvance(uint32_t aPartLength, + nsPresContext* aContext) const; + + /** + * Returns the frame corresponding to the <textPath> that the current + * character is within. + */ + nsIFrame* TextPathFrame() const + { + return mFrameIterator.TextPathFrame(); + } + +private: + /** + * Advances to the next character without checking it against the filter. + * Returns true if there was a next character to advance to, or false + * otherwise. + */ + bool NextCharacter(); + + /** + * Returns whether the current character matches the filter. + */ + bool MatchesFilter() const; + + /** + * If this is the start of a glyph, record it. + */ + void UpdateGlyphStartTextElementCharIndex() { + if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) { + mGlyphStartTextElementCharIndex = mTextElementCharIndex; + mGlyphUndisplayedCharacters = 0; + } + } + + /** + * The filter to use. + */ + CharacterFilter mFilter; + + /** + * The iterator for text frames. + */ + TextFrameIterator mFrameIterator; + + /** + * A gfxSkipCharsIterator for the text frame the current character is + * a part of. + */ + gfxSkipCharsIterator mSkipCharsIterator; + + // Cache for information computed by IsOriginalCharTrimmed. + mutable nsTextFrame* mFrameForTrimCheck; + mutable uint32_t mTrimmedOffset; + mutable uint32_t mTrimmedLength; + + /** + * The text run the current character is a part of. + */ + gfxTextRun* mTextRun; + + /** + * The current character's index. + */ + uint32_t mTextElementCharIndex; + + /** + * The index of the character that starts the cluster/ligature group the + * current character is a part of. + */ + uint32_t mGlyphStartTextElementCharIndex; + + /** + * If we are iterating in mode eClusterOrLigatureGroupMiddle, then + * this tracks how many undisplayed characters were encountered + * between the start of this glyph (at mGlyphStartTextElementCharIndex) + * and the current character (at mTextElementCharIndex). + */ + uint32_t mGlyphUndisplayedCharacters; + + /** + * The scale factor to apply to glyph advances returned by + * GetGlyphAdvance etc. to take into account textLength="". + */ + float mLengthAdjustScaleFactor; +}; + +CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame, + CharIterator::CharacterFilter aFilter, + nsIContent* aSubtree) + : mFilter(aFilter), + mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), + mFrameForTrimCheck(nullptr), + mTrimmedOffset(0), + mTrimmedLength(0), + mTextElementCharIndex(0), + mGlyphStartTextElementCharIndex(0), + mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor) +{ + if (!AtEnd()) { + mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); + mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated); + mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); + UpdateGlyphStartTextElementCharIndex(); + if (!MatchesFilter()) { + Next(); + } + } +} + +bool +CharIterator::Next() +{ + while (NextCharacter()) { + if (MatchesFilter()) { + return true; + } + } + return false; +} + +bool +CharIterator::Next(uint32_t aCount) +{ + if (aCount == 0 && AtEnd()) { + return false; + } + while (aCount) { + if (!Next()) { + return false; + } + aCount--; + } + return true; +} + +void +CharIterator::NextWithinSubtree(uint32_t aCount) +{ + while (IsWithinSubtree() && aCount) { + --aCount; + if (!Next()) { + return; + } + } +} + +bool +CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex) +{ + while (mTextElementCharIndex < aTextElementCharIndex) { + if (!Next()) { + return false; + } + } + return true; +} + +bool +CharIterator::AdvancePastCurrentFrame() +{ + // XXX Can do this better than one character at a time if it matters. + nsTextFrame* currentFrame = TextFrame(); + do { + if (!Next()) { + return false; + } + } while (TextFrame() == currentFrame); + return true; +} + +bool +CharIterator::AdvancePastCurrentTextPathFrame() +{ + nsIFrame* currentTextPathFrame = TextPathFrame(); + NS_ASSERTION(currentTextPathFrame, + "expected AdvancePastCurrentTextPathFrame to be called only " + "within a text path frame"); + do { + if (!AdvancePastCurrentFrame()) { + return false; + } + } while (TextPathFrame() == currentTextPathFrame); + return true; +} + +bool +CharIterator::AdvanceToSubtree() +{ + while (!IsWithinSubtree()) { + if (IsAfterSubtree()) { + return false; + } + if (!AdvancePastCurrentFrame()) { + return false; + } + } + return true; +} + +bool +CharIterator::IsClusterAndLigatureGroupStart() const +{ + return mTextRun->IsLigatureGroupStart(mSkipCharsIterator.GetSkippedOffset()) && + mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset()); +} + +bool +CharIterator::IsOriginalCharTrimmed() const +{ + if (mFrameForTrimCheck != TextFrame()) { + // Since we do a lot of trim checking, we cache the trimmed offsets and + // lengths while we are in the same frame. + mFrameForTrimCheck = TextFrame(); + uint32_t offset = mFrameForTrimCheck->GetContentOffset(); + uint32_t length = mFrameForTrimCheck->GetContentLength(); + nsIContent* content = mFrameForTrimCheck->GetContent(); + nsTextFrame::TrimmedOffsets trim = + mFrameForTrimCheck->GetTrimmedOffsets(content->GetText(), true); + TrimOffsets(offset, length, trim); + mTrimmedOffset = offset; + mTrimmedLength = length; + } + + // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength + // range and it is not a significant newline character. + uint32_t index = mSkipCharsIterator.GetOriginalOffset(); + return !((index >= mTrimmedOffset && + index < mTrimmedOffset + mTrimmedLength) || + (index >= mTrimmedOffset + mTrimmedLength && + mFrameForTrimCheck->StyleText()-> + NewlineIsSignificant(mFrameForTrimCheck) && + mFrameForTrimCheck->GetContent()->GetText()->CharAt(index) == '\n')); +} + +void +CharIterator::GetOriginalGlyphOffsets(uint32_t& aOriginalOffset, + uint32_t& aOriginalLength) const +{ + gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); + it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset() - + (mTextElementCharIndex - + mGlyphStartTextElementCharIndex - + mGlyphUndisplayedCharacters)); + + while (it.GetSkippedOffset() > 0 && + (!mTextRun->IsClusterStart(it.GetSkippedOffset()) || + !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))) { + it.AdvanceSkipped(-1); + } + + aOriginalOffset = it.GetOriginalOffset(); + + // Find the end of the cluster/ligature group. + it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset()); + do { + it.AdvanceSkipped(1); + } while (it.GetSkippedOffset() < mTextRun->GetLength() && + (!mTextRun->IsClusterStart(it.GetSkippedOffset()) || + !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))); + + aOriginalLength = it.GetOriginalOffset() - aOriginalOffset; +} + +gfxFloat +CharIterator::GetGlyphAdvance(nsPresContext* aContext) const +{ + uint32_t offset, length; + GetOriginalGlyphOffsets(offset, length); + + gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); + Range range = ConvertOriginalToSkipped(it, offset, length); + + float cssPxPerDevPx = aContext-> + AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + gfxFloat advance = mTextRun->GetAdvanceWidth(range, nullptr); + return aContext->AppUnitsToGfxUnits(advance) * + mLengthAdjustScaleFactor * cssPxPerDevPx; +} + +gfxFloat +CharIterator::GetAdvance(nsPresContext* aContext) const +{ + float cssPxPerDevPx = aContext-> + AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + uint32_t offset = mSkipCharsIterator.GetSkippedOffset(); + gfxFloat advance = mTextRun-> + GetAdvanceWidth(Range(offset, offset + 1), nullptr); + return aContext->AppUnitsToGfxUnits(advance) * + mLengthAdjustScaleFactor * cssPxPerDevPx; +} + +gfxFloat +CharIterator::GetGlyphPartialAdvance(uint32_t aPartLength, + nsPresContext* aContext) const +{ + uint32_t offset, length; + GetOriginalGlyphOffsets(offset, length); + + NS_ASSERTION(aPartLength <= length, "invalid aPartLength value"); + length = aPartLength; + + gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); + Range range = ConvertOriginalToSkipped(it, offset, length); + + float cssPxPerDevPx = aContext-> + AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + gfxFloat advance = mTextRun->GetAdvanceWidth(range, nullptr); + return aContext->AppUnitsToGfxUnits(advance) * + mLengthAdjustScaleFactor * cssPxPerDevPx; +} + +bool +CharIterator::NextCharacter() +{ + if (AtEnd()) { + return false; + } + + mTextElementCharIndex++; + + // Advance within the current text run. + mSkipCharsIterator.AdvanceOriginal(1); + if (mSkipCharsIterator.GetOriginalOffset() < TextFrame()->GetContentEnd()) { + // We're still within the part of the text run for the current text frame. + UpdateGlyphStartTextElementCharIndex(); + return true; + } + + // Advance to the next frame. + mFrameIterator.Next(); + + // Skip any undisplayed characters. + uint32_t undisplayed = mFrameIterator.UndisplayedCharacters(); + mGlyphUndisplayedCharacters += undisplayed; + mTextElementCharIndex += undisplayed; + if (!TextFrame()) { + // We're at the end. + mSkipCharsIterator = gfxSkipCharsIterator(); + return false; + } + + mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); + mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated); + UpdateGlyphStartTextElementCharIndex(); + return true; +} + +bool +CharIterator::MatchesFilter() const +{ + if (mFilter == eOriginal) { + return true; + } + + if (IsOriginalCharSkipped()) { + return false; + } + + if (mFilter == eAddressable) { + return !IsOriginalCharUnaddressable(); + } + + return (mFilter == eClusterAndLigatureGroupStart) == + IsClusterAndLigatureGroupStart(); +} + +// ----------------------------------------------------------------------------- +// nsCharClipDisplayItem + +/** + * An nsCharClipDisplayItem that obtains its left and right clip edges from a + * TextRenderedRun object. + */ +class SVGCharClipDisplayItem : public nsCharClipDisplayItem { +public: + explicit SVGCharClipDisplayItem(const TextRenderedRun& aRun) + : nsCharClipDisplayItem(aRun.mFrame) + { + aRun.GetClipEdges(mVisIStartEdge, mVisIEndEdge); + } + + NS_DISPLAY_DECL_NAME("SVGText", TYPE_TEXT) +}; + +// ----------------------------------------------------------------------------- +// SVGTextDrawPathCallbacks + +/** + * Text frame draw callback class that paints the text and text decoration parts + * of an nsTextFrame using SVG painting properties, and selection backgrounds + * and decorations as they would normally. + * + * An instance of this class is passed to nsTextFrame::PaintText if painting + * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking + * the text, etc.). + */ +class SVGTextDrawPathCallbacks : public nsTextFrame::DrawPathCallbacks +{ +public: + /** + * Constructs an SVGTextDrawPathCallbacks. + * + * @param aContext The context to use for painting. + * @param aFrame The nsTextFrame to paint. + * @param aCanvasTM The transformation matrix to set when painting; this + * should be the FOR_OUTERSVG_TM canvas TM of the text, so that + * paint servers are painted correctly. + * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted. + */ + SVGTextDrawPathCallbacks(nsRenderingContext* aContext, + nsTextFrame* aFrame, + const gfxMatrix& aCanvasTM, + bool aShouldPaintSVGGlyphs) + : DrawPathCallbacks(aShouldPaintSVGGlyphs), + gfx(aContext->ThebesContext()), + mFrame(aFrame), + mCanvasTM(aCanvasTM) + { + } + + void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect, + nscolor aColor, + DrawTarget& aDrawTarget) override; + void PaintDecorationLine(Rect aPath, nscolor aColor) override; + void PaintSelectionDecorationLine(Rect aPath, nscolor aColor) override; + void NotifyBeforeText(nscolor aColor) override; + void NotifyGlyphPathEmitted() override; + void NotifyAfterText() override; + +private: + void SetupContext(); + + bool IsClipPathChild() const { + return nsLayoutUtils::GetClosestFrameOfType + (mFrame->GetParent(), nsGkAtoms::svgTextFrame)->GetStateBits() & + NS_STATE_SVG_CLIPPATH_CHILD; + } + + /** + * Paints a piece of text geometry. This is called when glyphs + * or text decorations have been emitted to the gfxContext. + */ + void HandleTextGeometry(); + + /** + * Sets the gfxContext paint to the appropriate color or pattern + * for filling text geometry. + */ + void MakeFillPattern(GeneralPattern* aOutPattern); + + /** + * Fills and strokes a piece of text geometry, using group opacity + * if the selection style requires it. + */ + void FillAndStrokeGeometry(); + + /** + * Fills a piece of text geometry. + */ + void FillGeometry(); + + /** + * Strokes a piece of text geometry. + */ + void StrokeGeometry(); + + gfxContext* gfx; + nsTextFrame* mFrame; + const gfxMatrix& mCanvasTM; + + /** + * The color that we were last told from one of the path callback functions. + * This color can be the special NS_SAME_AS_FOREGROUND_COLOR, + * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are + * painting selections or IME decorations. + */ + nscolor mColor; +}; + +void +SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill( + const Rect& aBackgroundRect, + nscolor aColor, + DrawTarget& aDrawTarget) +{ + if (IsClipPathChild()) { + // Don't paint selection backgrounds when in a clip path. + return; + } + + mColor = aColor; // currently needed by MakeFillPattern + + GeneralPattern fillPattern; + MakeFillPattern(&fillPattern); + if (fillPattern.GetPattern()) { + DrawOptions drawOptions(aColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4 : 1.0); + aDrawTarget.FillRect(aBackgroundRect, fillPattern, drawOptions); + } +} + +void +SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor) +{ + mColor = aColor; + SetupContext(); + gfx->NewPath(); +} + +void +SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() +{ + HandleTextGeometry(); + gfx->NewPath(); +} + +void +SVGTextDrawPathCallbacks::NotifyAfterText() +{ + gfx->Restore(); +} + +void +SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, nscolor aColor) +{ + mColor = aColor; + AntialiasMode aaMode = + nsSVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); + + gfx->Save(); + gfx->NewPath(); + gfx->SetAntialiasMode(aaMode); + gfx->Rectangle(ThebesRect(aPath)); + HandleTextGeometry(); + gfx->NewPath(); + gfx->Restore(); +} + +void +SVGTextDrawPathCallbacks::PaintSelectionDecorationLine(Rect aPath, + nscolor aColor) +{ + if (IsClipPathChild()) { + // Don't paint selection decorations when in a clip path. + return; + } + + mColor = aColor; + + gfx->Save(); + gfx->NewPath(); + gfx->Rectangle(ThebesRect(aPath)); + FillAndStrokeGeometry(); + gfx->Restore(); +} + +void +SVGTextDrawPathCallbacks::SetupContext() +{ + gfx->Save(); + + // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually + // seem to do anything with the antialias mode. So we can perhaps remove it, + // or make SetAntialiasMode set cairo text antialiasing too. + switch (mFrame->StyleText()->mTextRendering) { + case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED: + gfx->SetAntialiasMode(AntialiasMode::NONE); + break; + default: + gfx->SetAntialiasMode(AntialiasMode::SUBPIXEL); + break; + } +} + +void +SVGTextDrawPathCallbacks::HandleTextGeometry() +{ + if (IsClipPathChild()) { + RefPtr<Path> path = gfx->GetPath(); + ColorPattern white(Color(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor + gfx->GetDrawTarget()->Fill(path, white); + } else { + // Normal painting. + gfxContextMatrixAutoSaveRestore saveMatrix(gfx); + gfx->SetMatrix(mCanvasTM); + + FillAndStrokeGeometry(); + } +} + +void +SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) +{ + if (mColor == NS_SAME_AS_FOREGROUND_COLOR || + mColor == NS_40PERCENT_FOREGROUND_COLOR) { + nsSVGUtils::MakeFillPatternFor(mFrame, gfx, aOutPattern); + return; + } + + if (mColor == NS_TRANSPARENT) { + return; + } + + aOutPattern->InitColorPattern(ToDeviceColor(mColor)); +} + +void +SVGTextDrawPathCallbacks::FillAndStrokeGeometry() +{ + bool pushedGroup = false; + if (mColor == NS_40PERCENT_FOREGROUND_COLOR) { + pushedGroup = true; + gfx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f); + } + + uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder; + if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) { + FillGeometry(); + StrokeGeometry(); + } else { + while (paintOrder) { + uint32_t component = + paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1); + switch (component) { + case NS_STYLE_PAINT_ORDER_FILL: + FillGeometry(); + break; + case NS_STYLE_PAINT_ORDER_STROKE: + StrokeGeometry(); + break; + } + paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH; + } + } + + if (pushedGroup) { + gfx->PopGroupAndBlend(); + } +} + +void +SVGTextDrawPathCallbacks::FillGeometry() +{ + GeneralPattern fillPattern; + MakeFillPattern(&fillPattern); + if (fillPattern.GetPattern()) { + RefPtr<Path> path = gfx->GetPath(); + FillRule fillRule = nsSVGUtils::ToFillRule(IsClipPathChild() ? + mFrame->StyleSVG()->mClipRule : + mFrame->StyleSVG()->mFillRule); + if (fillRule != path->GetFillRule()) { + RefPtr<PathBuilder> builder = path->CopyToBuilder(fillRule); + path = builder->Finish(); + } + gfx->GetDrawTarget()->Fill(path, fillPattern); + } +} + +void +SVGTextDrawPathCallbacks::StrokeGeometry() +{ + // We don't paint the stroke when we are filling with a selection color. + if (mColor == NS_SAME_AS_FOREGROUND_COLOR || + mColor == NS_40PERCENT_FOREGROUND_COLOR) { + if (nsSVGUtils::HasStroke(mFrame, /*aContextPaint*/ nullptr)) { + GeneralPattern strokePattern; + nsSVGUtils::MakeStrokePatternFor(mFrame, gfx, &strokePattern, /*aContextPaint*/ nullptr); + if (strokePattern.GetPattern()) { + if (!mFrame->GetParent()->GetContent()->IsSVGElement()) { + // The cast that follows would be unsafe + MOZ_ASSERT(false, "Our nsTextFrame's parent's content should be SVG"); + return; + } + nsSVGElement* svgOwner = + static_cast<nsSVGElement*>(mFrame->GetParent()->GetContent()); + + // Apply any stroke-specific transform + gfxMatrix outerSVGToUser; + if (nsSVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) && + outerSVGToUser.Invert()) { + gfx->Multiply(outerSVGToUser); + } + + RefPtr<Path> path = gfx->GetPath(); + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner, + mFrame->StyleContext(), + /*aContextPaint*/ nullptr); + DrawOptions drawOptions; + drawOptions.mAntialiasMode = + nsSVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); + gfx->GetDrawTarget()->Stroke(path, strokePattern, strokeOptions); + } + } + } +} + +} // namespace mozilla + + +// ============================================================================ +// SVGTextFrame + +// ---------------------------------------------------------------------------- +// Display list item + +class nsDisplaySVGText : public nsDisplayItem { +public: + nsDisplaySVGText(nsDisplayListBuilder* aBuilder, + SVGTextFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame), + mDisableSubpixelAA(false) + { + MOZ_COUNT_CTOR(nsDisplaySVGText); + MOZ_ASSERT(aFrame, "Must have a frame!"); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplaySVGText() { + MOZ_COUNT_DTOR(nsDisplaySVGText); + } +#endif + + NS_DISPLAY_DECL_NAME("nsDisplaySVGText", TYPE_SVG_TEXT) + + virtual void DisableComponentAlpha() override { + mDisableSubpixelAA = true; + } + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*> *aOutFrames) override; + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override + { + return new nsDisplayItemGenericImageGeometry(this, aBuilder); + } + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override { + bool snap; + return GetBounds(aBuilder, &snap); + } +private: + bool mDisableSubpixelAA; +}; + +void +nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) +{ + SVGTextFrame *frame = static_cast<SVGTextFrame*>(mFrame); + nsPoint pointRelativeToReferenceFrame = aRect.Center(); + // ToReferenceFrame() includes frame->GetPosition(), our user space position. + nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame - + (ToReferenceFrame() - frame->GetPosition()); + + gfxPoint userSpacePt = + gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) / + frame->PresContext()->AppUnitsPerCSSPixel(); + + nsIFrame* target = frame->GetFrameForPoint(userSpacePt); + if (target) { + aOutFrames->AppendElement(target); + } +} + +void +nsDisplaySVGText::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + DrawTargetAutoDisableSubpixelAntialiasing + disable(aCtx->GetDrawTarget(), mDisableSubpixelAA); + + uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + // ToReferenceFrame includes our mRect offset, but painting takes + // account of that too. To avoid double counting, we subtract that + // here. + nsPoint offset = ToReferenceFrame() - mFrame->GetPosition(); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel); + + gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) * + gfxMatrix::Translation(devPixelOffset); + + gfxContext* ctx = aCtx->ThebesContext(); + ctx->Save(); + DrawResult result = static_cast<SVGTextFrame*>(mFrame)->PaintSVG(*ctx, tm); + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); + ctx->Restore(); +} + +// --------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(SVGTextFrame) + NS_QUERYFRAME_ENTRY(SVGTextFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame) + +// --------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) SVGTextFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame) + +// --------------------------------------------------------------------- +// nsIFrame methods + +void +SVGTextFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::text), "Content is not an SVG text"); + + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); + AddStateBits((aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) | + NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT); + + mMutationObserver = new MutationObserver(this); +} + +void +SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (NS_SUBTREE_DIRTY(this)) { + // We can sometimes be asked to paint before reflow happens and we + // have updated mPositions, etc. In this case, we just avoid + // painting. + return; + } + if (!IsVisibleForPainting(aBuilder) && + aBuilder->IsForPainting()) { + return; + } + DisplayOutline(aBuilder, aLists); + aLists.Content()->AppendNewToTop( + new (aBuilder) nsDisplaySVGText(aBuilder, this)); +} + +nsresult +SVGTextFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID != kNameSpaceID_None) + return NS_OK; + + if (aAttribute == nsGkAtoms::transform) { + // We don't invalidate for transform changes (the layers code does that). + // Also note that SVGTransformableElement::GetAttributeChangeHint will + // return nsChangeHint_UpdateOverflow for "transform" attribute changes + // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. + + if (!(mState & NS_FRAME_FIRST_REFLOW) && + mCanvasTM && mCanvasTM->IsSingular()) { + // We won't have calculated the glyph positions correctly. + NotifyGlyphMetricsChange(); + } + mCanvasTM = nullptr; + } else if (IsGlyphPositioningAttribute(aAttribute) || + aAttribute == nsGkAtoms::textLength || + aAttribute == nsGkAtoms::lengthAdjust) { + NotifyGlyphMetricsChange(); + } + + return NS_OK; +} + +nsIAtom * +SVGTextFrame::GetType() const +{ + return nsGkAtoms::svgTextFrame; +} + +void +SVGTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + if (mState & NS_FRAME_IS_NONDISPLAY) { + // We need this DidSetStyleContext override to handle cases like this: + // + // <defs> + // <g> + // <mask> + // <text>...</text> + // </mask> + // </g> + // </defs> + // + // where the <text> is non-display, and a style change occurs on the <defs>, + // the <g>, the <mask>, or the <text> itself. If the style change happened + // on the parent of the <defs>, then in + // nsSVGDisplayContainerFrame::ReflowSVG, we would find the non-display + // <defs> container and then call ReflowSVGNonDisplayText on it. If we do + // not actually reflow the parent of the <defs>, then without this + // DidSetStyleContext we would (a) not cause the <text>'s anonymous block + // child to be reflowed when it is next painted, and (b) not cause the + // <text> to be repainted anyway since the user of the <mask> would not + // know it needs to be repainted. + ScheduleReflowSVGNonDisplayText(nsIPresShell::eStyleChange); + } +} + +void +SVGTextFrame::ReflowSVGNonDisplayText() +{ + MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this), + "only call ReflowSVGNonDisplayText when an outer SVG frame is " + "under ReflowSVG"); + MOZ_ASSERT(mState & NS_FRAME_IS_NONDISPLAY, + "only call ReflowSVGNonDisplayText if the frame is " + "NS_FRAME_IS_NONDISPLAY"); + + // We had a style change, so we mark this frame as dirty so that the next + // time it is painted, we reflow the anonymous block frame. + AddStateBits(NS_FRAME_IS_DIRTY); + + // We also need to call InvalidateRenderingObservers, so that if the <text> + // element is within a <mask>, say, the element referencing the <mask> will + // be updated, which will then cause this SVGTextFrame to be painted and + // in doing so cause the anonymous block frame to be reflowed. + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + + // Finally, we need to actually reflow the anonymous block frame and update + // mPositions, in case we are being reflowed immediately after a DOM + // mutation that needs frame reconstruction. + MaybeReflowAnonymousBlockChild(); + UpdateGlyphPositioning(); +} + +void +SVGTextFrame::ScheduleReflowSVGNonDisplayText(nsIPresShell::IntrinsicDirty aReason) +{ + MOZ_ASSERT(!nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "do not call ScheduleReflowSVGNonDisplayText when the outer SVG " + "frame is under ReflowSVG"); + MOZ_ASSERT(!(mState & NS_STATE_SVG_TEXT_IN_REFLOW), + "do not call ScheduleReflowSVGNonDisplayText while reflowing the " + "anonymous block child"); + + // We need to find an ancestor frame that we can call FrameNeedsReflow + // on that will cause the document to be marked as needing relayout, + // and for that ancestor (or some further ancestor) to be marked as + // a root to reflow. We choose the closest ancestor frame that is not + // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a + // non-SVG frame. (We don't consider displayed SVG frame ancestors toerh + // than nsSVGOuterSVGFrame, since calling FrameNeedsReflow on those other + // SVG frames would do a bunch of unnecessary work on the SVG frames up to + // the nsSVGOuterSVGFrame.) + + nsIFrame* f = this; + while (f) { + if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + if (NS_SUBTREE_DIRTY(f)) { + // This is a displayed frame, so if it is already dirty, we will be reflowed + // soon anyway. No need to call FrameNeedsReflow again, then. + return; + } + if (!f->IsFrameOfType(eSVG) || + (f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { + break; + } + f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + } + f = f->GetParent(); + } + + MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); + + PresContext()->PresShell()->FrameNeedsReflow(f, aReason, NS_FRAME_IS_DIRTY); +} + +NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver) + +void +SVGTextFrame::MutationObserver::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) +{ + mFrame->NotifyGlyphMetricsChange(); +} + +void +SVGTextFrame::MutationObserver::ContentInserted( + nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) +{ + mFrame->NotifyGlyphMetricsChange(); +} + +void +SVGTextFrame::MutationObserver::ContentRemoved( + nsIDocument *aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + mFrame->NotifyGlyphMetricsChange(); +} + +void +SVGTextFrame::MutationObserver::CharacterDataChanged( + nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + mFrame->NotifyGlyphMetricsChange(); +} + +void +SVGTextFrame::MutationObserver::AttributeChanged( + nsIDocument* aDocument, + mozilla::dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + if (!aElement->IsSVGElement()) { + return; + } + + // Attribute changes on this element will be handled by + // SVGTextFrame::AttributeChanged. + if (aElement == mFrame->GetContent()) { + return; + } + + mFrame->HandleAttributeChangeInDescendant(aElement, aNameSpaceID, aAttribute); +} + +void +SVGTextFrame::HandleAttributeChangeInDescendant(Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute) +{ + if (aElement->IsSVGElement(nsGkAtoms::textPath)) { + if (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::startOffset) { + NotifyGlyphMetricsChange(); + } else if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // Blow away our reference, if any + nsIFrame* childElementFrame = aElement->GetPrimaryFrame(); + if (childElementFrame) { + childElementFrame->Properties().Delete( + nsSVGEffects::HrefAsTextPathProperty()); + NotifyGlyphMetricsChange(); + } + } + } else { + if (aNameSpaceID == kNameSpaceID_None && + IsGlyphPositioningAttribute(aAttribute)) { + NotifyGlyphMetricsChange(); + } + } +} + +void +SVGTextFrame::FindCloserFrameForSelection( + nsPoint aPoint, + nsIFrame::FrameWithDistance* aCurrentBestFrame) +{ + if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + return; + } + + UpdateGlyphPositioning(); + + nsPresContext* presContext = PresContext(); + + // Find the frame that has the closest rendered run rect to aPoint. + TextRenderedRunIterator it(this); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke | + TextRenderedRun::eNoHorizontalOverflow; + SVGBBox userRect = run.GetUserSpaceRect(presContext, flags); + float devPxPerCSSPx = presContext->CSSPixelsToDevPixels(1.f); + userRect.Scale(devPxPerCSSPx); + + if (!userRect.IsEmpty()) { + gfxMatrix m; + if (!NS_SVGDisplayListHitTestingEnabled()) { + m = GetCanvasTM(); + } + nsRect rect = nsSVGUtils::ToCanvasBounds(userRect.ToThebesRect(), m, + presContext); + + if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect, + aCurrentBestFrame->mXDistance, + aCurrentBestFrame->mYDistance)) { + aCurrentBestFrame->mFrame = run.mFrame; + } + } + } +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +void +SVGTextFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + bool needNewBounds = false; + bool needGlyphMetricsUpdate = false; + bool needNewCanvasTM = false; + + if ((aFlags & COORD_CONTEXT_CHANGED) && + (mState & NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) { + needGlyphMetricsUpdate = true; + } + + if (aFlags & TRANSFORM_CHANGED) { + needNewCanvasTM = true; + if (mCanvasTM && mCanvasTM->IsSingular()) { + // We won't have calculated the glyph positions correctly. + needNewBounds = true; + needGlyphMetricsUpdate = true; + } + if (StyleSVGReset()->HasNonScalingStroke()) { + // Stroke currently contributes to our mRect, and our stroke depends on + // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|. + needNewBounds = true; + } + } + + // If the scale at which we computed our mFontSizeScaleFactor has changed by + // at least a factor of two, reflow the text. This avoids reflowing text + // at every tick of a transform animation, but ensures our glyph metrics + // do not get too far out of sync with the final font size on the screen. + if (needNewCanvasTM && mLastContextScale != 0.0f) { + mCanvasTM = nullptr; + // If we are a non-display frame, then we don't want to call + // GetCanvasTM(), since the context scale does not use it. + gfxMatrix newTM = + (mState & NS_FRAME_IS_NONDISPLAY) ? gfxMatrix() : + GetCanvasTM(); + // Compare the old and new context scales. + float scale = GetContextScale(newTM); + float change = scale / mLastContextScale; + if (change >= 2.0f || change <= 0.5f) { + needNewBounds = true; + needGlyphMetricsUpdate = true; + } + } + + if (needNewBounds) { + // Ancestor changes can't affect how we render from the perspective of + // any rendering observers that we may have, so we don't need to + // invalidate them. We also don't need to invalidate ourself, since our + // changed ancestor will have invalidated its entire area, which includes + // our area. + ScheduleReflowSVG(); + } + + if (needGlyphMetricsUpdate) { + // If we are positioned using percentage values we need to update our + // position whenever our viewport's dimensions change. But only do this if + // we have been reflowed once, otherwise the glyph positioning will be + // wrong. (We need to wait until bidi reordering has been done.) + if (!(mState & NS_FRAME_FIRST_REFLOW)) { + NotifyGlyphMetricsChange(); + } + } +} + +/** + * Gets the offset into a DOM node that the specified caret is positioned at. + */ +static int32_t +GetCaretOffset(nsCaret* aCaret) +{ + nsCOMPtr<nsISelection> selection = aCaret->GetSelection(); + if (!selection) { + return -1; + } + + int32_t offset = -1; + selection->GetAnchorOffset(&offset); + return offset; +} + +/** + * Returns whether the caret should be painted for a given TextRenderedRun + * by checking whether the caret is in the range covered by the rendered run. + * + * @param aThisRun The TextRenderedRun to be painted. + * @param aCaret The caret. + */ +static bool +ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret) +{ + int32_t caretOffset = GetCaretOffset(aCaret); + + if (caretOffset < 0) { + return false; + } + + if (uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset && + uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset + + aThisRun.mTextFrameContentLength) { + return true; + } + + return false; +} + +DrawResult +SVGTextFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) +{ + DrawTarget& aDrawTarget = *aContext.GetDrawTarget(); + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return DrawResult::SUCCESS; + + nsPresContext* presContext = PresContext(); + + gfxMatrix initialMatrix = aContext.CurrentMatrix(); + + if (mState & NS_FRAME_IS_NONDISPLAY) { + // If we are in a canvas DrawWindow call that used the + // DRAWWINDOW_DO_NOT_FLUSH flag, then we may still have out + // of date frames. Just don't paint anything if they are + // dirty. + if (presContext->PresShell()->InDrawWindowNotFlushing() && + NS_SUBTREE_DIRTY(this)) { + return DrawResult::SUCCESS; + } + // Text frames inside <clipPath>, <mask>, etc. will never have had + // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now. + UpdateGlyphPositioning(); + } else if (NS_SUBTREE_DIRTY(this)) { + // If we are asked to paint before reflow has recomputed mPositions etc. + // directly via PaintSVG, rather than via a display list, then we need + // to bail out here too. + return DrawResult::SUCCESS; + } + + if (aTransform.IsSingular()) { + NS_WARNING("Can't render text element!"); + return DrawResult::BAD_ARGS; + } + + gfxMatrix matrixForPaintServers = aTransform * initialMatrix; + + // Check if we need to draw anything. + if (aDirtyRect) { + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "Display lists handle dirty rect intersection test"); + nsRect dirtyRect(aDirtyRect->x, aDirtyRect->y, + aDirtyRect->width, aDirtyRect->height); + + gfxFloat appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + gfxRect frameRect(mRect.x / appUnitsPerDevPixel, + mRect.y / appUnitsPerDevPixel, + mRect.width / appUnitsPerDevPixel, + mRect.height / appUnitsPerDevPixel); + + nsRect canvasRect = nsLayoutUtils::RoundGfxRectToAppRect( + GetCanvasTM().TransformBounds(frameRect), 1); + if (!canvasRect.Intersects(dirtyRect)) { + return DrawResult::SUCCESS; + } + } + + // SVG frames' PaintSVG methods paint in CSS px, but normally frames paint in + // dev pixels. Here we multiply a CSS-px-to-dev-pixel factor onto aTransform + // so our non-SVG nsTextFrame children paint correctly. + auto auPerDevPx = presContext->AppUnitsPerDevPixel(); + float cssPxPerDevPx = presContext->AppUnitsToFloatCSSPixels(auPerDevPx); + gfxMatrix canvasTMForChildren = aTransform; + canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx); + initialMatrix.Scale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx); + + gfxContextAutoSaveRestore save(&aContext); + aContext.NewPath(); + aContext.Multiply(canvasTMForChildren); + gfxMatrix currentMatrix = aContext.CurrentMatrix(); + + RefPtr<nsCaret> caret = presContext->PresShell()->GetCaret(); + nsRect caretRect; + nsIFrame* caretFrame = caret->GetPaintGeometry(&caretRect); + + TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames); + TextRenderedRun run = it.Current(); + + SVGContextPaint* outerContextPaint = + SVGContextPaint::GetContextPaint(mContent); + + nsRenderingContext rendCtx(&aContext); + + while (run.mFrame) { + nsTextFrame* frame = run.mFrame; + + // Determine how much of the left and right edges of the text frame we + // need to ignore. + SVGCharClipDisplayItem item(run); + + // Set up the fill and stroke so that SVG glyphs can get painted correctly + // when they use context-fill etc. + aContext.SetMatrix(initialMatrix); + + SVGContextPaintImpl contextPaint; + DrawMode drawMode = contextPaint.Init(&aDrawTarget, + aContext.CurrentMatrix(), + frame, outerContextPaint); + + if (drawMode & DrawMode::GLYPH_STROKE) { + // This may change the gfxContext's transform (for non-scaling stroke), + // in which case this needs to happen before we call SetMatrix() below. + nsSVGUtils::SetupCairoStrokeGeometry(frame, &aContext, outerContextPaint); + } + + // Set up the transform for painting the text frame for the substring + // indicated by the run. + gfxMatrix runTransform = + run.GetTransformFromUserSpaceForPainting(presContext, item) * + currentMatrix; + aContext.SetMatrix(runTransform); + + if (drawMode != DrawMode(0)) { + bool paintSVGGlyphs; + nsTextFrame::PaintTextParams params(rendCtx.ThebesContext()); + params.framePt = gfxPoint(); + params.dirtyRect = LayoutDevicePixel:: + FromAppUnits(frame->GetVisualOverflowRect(), auPerDevPx); + params.contextPaint = &contextPaint; + if (ShouldRenderAsPath(frame, paintSVGGlyphs)) { + SVGTextDrawPathCallbacks callbacks(&rendCtx, frame, + matrixForPaintServers, + paintSVGGlyphs); + params.callbacks = &callbacks; + frame->PaintText(params, item); + } else { + frame->PaintText(params, item); + } + } + + if (frame == caretFrame && ShouldPaintCaret(run, caret)) { + // XXX Should we be looking at the fill/stroke colours to paint the + // caret with, rather than using the color property? + caret->PaintCaret(aDrawTarget, frame, nsPoint()); + aContext.NewPath(); + } + + run = it.Next(); + } + + return DrawResult::SUCCESS; +} + +nsIFrame* +SVGTextFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame"); + + if (mState & NS_FRAME_IS_NONDISPLAY) { + // Text frames inside <clipPath> will never have had ReflowSVG called on + // them, so call UpdateGlyphPositioning to do this now. (Text frames + // inside <mask> and other non-display containers will never need to + // be hit tested.) + UpdateGlyphPositioning(); + } else { + NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened"); + } + + // Hit-testing any clip-path will typically be a lot quicker than the + // hit-testing of our text frames in the loop below, so we do the former up + // front to avoid unnecessarily wasting cycles on the latter. + if (!nsSVGUtils::HitTestClip(this, aPoint)) { + return nullptr; + } + + nsPresContext* presContext = PresContext(); + + // Ideally we'd iterate backwards so that we can just return the first frame + // that is under aPoint. In practice this will rarely matter though since it + // is rare for text in/under an SVG <text> element to overlap (i.e. the first + // text frame that is hit will likely be the only text frame that is hit). + + TextRenderedRunIterator it(this); + nsIFrame* hit = nullptr; + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); + if (!(hitTestFlags & (SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE))) { + continue; + } + + gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); + if (!m.Invert()) { + return nullptr; + } + + gfxPoint pointInRunUserSpace = m.Transform(aPoint); + gfxRect frameRect = + run.GetRunUserSpaceRect(presContext, TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke).ToThebesRect(); + + if (Inside(frameRect, pointInRunUserSpace)) { + hit = run.mFrame; + } + } + return hit; +} + +nsRect +SVGTextFrame::GetCoveredRegion() +{ + return nsSVGUtils::TransformFrameRectToOuterSVG( + mRect, GetCanvasTM(), PresContext()); +} + +void +SVGTextFrame::ReflowSVG() +{ + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probaby a wasteful mistake"); + + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!nsSVGUtils::NeedsReflowSVG(this)) { + NS_ASSERTION(!(mState & NS_STATE_SVG_POSITIONING_DIRTY), "How did this happen?"); + return; + } + + MaybeReflowAnonymousBlockChild(); + UpdateGlyphPositioning(); + + nsPresContext* presContext = PresContext(); + + SVGBBox r; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint32_t runFlags = 0; + if (run.mFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) { + runFlags |= TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeTextShadow; + } + if (nsSVGUtils::HasStroke(run.mFrame)) { + runFlags |= TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeTextShadow; + } + // Our "visual" overflow rect needs to be valid for building display lists + // for hit testing, which means that for certain values of 'pointer-events' + // it needs to include the geometry of the fill or stroke even when the fill/ + // stroke don't actually render (e.g. when stroke="none" or + // stroke-opacity="0"). GetGeometryHitTestFlags accounts for 'pointer-events'. + // The text-shadow is not part of the hit-test area. + uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); + if (hitTestFlags & SVG_HIT_TEST_FILL) { + runFlags |= TextRenderedRun::eIncludeFill; + } + if (hitTestFlags & SVG_HIT_TEST_STROKE) { + runFlags |= TextRenderedRun::eIncludeStroke; + } + + if (runFlags) { + r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags)); + } + } + + if (r.IsEmpty()) { + mRect.SetEmpty(); + } else { + mRect = + nsLayoutUtils::RoundGfxRectToAppRect(r.ToThebesRect(), presContext->AppUnitsPerCSSPixel()); + + // Due to rounding issues when we have a transform applied, we sometimes + // don't include an additional row of pixels. For now, just inflate our + // covered region. + mRect.Inflate(presContext->AppUnitsPerDevPixel()); + } + + if (mState & NS_FRAME_FIRST_REFLOW) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + nsSVGEffects::UpdateEffects(this); + } + + // Now unset the various reflow bits. Do this before calling + // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph + // positions (to resolve transform-origin). + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); + + nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); + nsOverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + // XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame + // children, and calls ConsiderChildOverflow on them. Does it matter + // that ConsiderChildOverflow won't be called on our children? + nsSVGDisplayContainerFrame::ReflowSVG(); +} + +/** + * Converts nsSVGUtils::eBBox* flags into TextRenderedRun flags appropriate + * for the specified rendered run. + */ +static uint32_t +TextRenderedRunFlagsForBBoxContribution(const TextRenderedRun& aRun, + uint32_t aBBoxFlags) +{ + uint32_t flags = 0; + if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFillGeometry) || + ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFill) && + aRun.mFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None)) { + flags |= TextRenderedRun::eIncludeFill; + } + if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) || + ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStroke) && + nsSVGUtils::HasStroke(aRun.mFrame))) { + flags |= TextRenderedRun::eIncludeStroke; + } + return flags; +} + +SVGBBox +SVGTextFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace, + uint32_t aFlags) +{ + NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame"); + SVGBBox bbox; + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid && NS_SUBTREE_DIRTY(kid)) { + // Return an empty bbox if our kid's subtree is dirty. This may be called + // in that situation, e.g. when we're building a display list after an + // interrupted reflow. This can also be called during reflow before we've + // been reflowed, e.g. if an earlier sibling is calling FinishAndStoreOverflow and + // needs our parent's perspective matrix, which depends on the SVG bbox + // contribution of this frame. In the latter situation, when all siblings have + // been reflowed, the parent will compute its perspective and rerun + // FinishAndStoreOverflow for all its children. + return bbox; + } + + UpdateGlyphPositioning(); + + nsPresContext* presContext = PresContext(); + + TextRenderedRunIterator it(this); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags); + gfxMatrix m = ThebesMatrix(aToBBoxUserspace); + SVGBBox bboxForRun = + run.GetUserSpaceRect(presContext, flags, &m); + bbox.UnionEdges(bboxForRun); + } + + return bbox; +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods + +gfxMatrix +SVGTextFrame::GetCanvasTM() +{ + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "should not call GetCanvasTM() when we are non-display"); + + nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); + dom::SVGTextContentElement *content = static_cast<dom::SVGTextContentElement*>(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = new gfxMatrix(tm); + } + return *mCanvasTM; +} + +//---------------------------------------------------------------------- +// SVGTextFrame SVG DOM methods + +/** + * Returns whether the specified node has any non-empty nsTextNodes + * beneath it. + */ +static bool +HasTextContent(nsIContent* aContent) +{ + NS_ASSERTION(aContent, "expected non-null aContent"); + + TextNodeIterator it(aContent); + for (nsTextNode* text = it.Current(); text; text = it.Next()) { + if (text->TextLength() != 0) { + return true; + } + } + return false; +} + +/** + * Returns the number of DOM characters beneath the specified node. + */ +static uint32_t +GetTextContentLength(nsIContent* aContent) +{ + NS_ASSERTION(aContent, "expected non-null aContent"); + + uint32_t length = 0; + TextNodeIterator it(aContent); + for (nsTextNode* text = it.Current(); text; text = it.Next()) { + length += text->TextLength(); + } + return length; +} + +int32_t +SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex( + int32_t aIndex, + nsIContent* aContent) +{ + CharIterator it(this, CharIterator::eOriginal, aContent); + if (!it.AdvanceToSubtree()) { + return -1; + } + int32_t result = 0; + int32_t textElementCharIndex; + while (!it.AtEnd() && + it.IsWithinSubtree()) { + bool addressable = !it.IsOriginalCharUnaddressable(); + textElementCharIndex = it.TextElementCharIndex(); + it.Next(); + uint32_t delta = it.TextElementCharIndex() - textElementCharIndex; + aIndex -= delta; + if (addressable) { + if (aIndex < 0) { + return result; + } + result += delta; + } + } + return -1; +} + +/** + * Implements the SVG DOM GetNumberOfChars method for the specified + * text content element. + */ +uint32_t +SVGTextFrame::GetNumberOfChars(nsIContent* aContent) +{ + UpdateGlyphPositioning(); + + uint32_t n = 0; + CharIterator it(this, CharIterator::eAddressable, aContent); + if (it.AdvanceToSubtree()) { + while (!it.AtEnd() && it.IsWithinSubtree()) { + n++; + it.Next(); + } + } + return n; +} + +/** + * Implements the SVG DOM GetComputedTextLength method for the specified + * text child element. + */ +float +SVGTextFrame::GetComputedTextLength(nsIContent* aContent) +{ + UpdateGlyphPositioning(); + + float cssPxPerDevPx = PresContext()-> + AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel()); + + nscoord length = 0; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aContent); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + length += run.GetAdvanceWidth(); + } + + return PresContext()->AppUnitsToGfxUnits(length) * + cssPxPerDevPx * mLengthAdjustScaleFactor / mFontSizeScaleFactor; +} + +/** + * Implements the SVG DOM SelectSubString method for the specified + * text content element. + */ +nsresult +SVGTextFrame::SelectSubString(nsIContent* aContent, + uint32_t charnum, uint32_t nchars) +{ + UpdateGlyphPositioning(); + + // Convert charnum/nchars from addressable characters relative to + // aContent to global character indices. + CharIterator chit(this, CharIterator::eAddressable, aContent); + if (!chit.AdvanceToSubtree() || + !chit.Next(charnum) || + chit.IsAfterSubtree()) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + charnum = chit.TextElementCharIndex(); + nsIContent* content = chit.TextFrame()->GetContent(); + chit.NextWithinSubtree(nchars); + nchars = chit.TextElementCharIndex() - charnum; + + RefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); + + frameSelection->HandleClick(content, charnum, charnum + nchars, + false, false, CARET_ASSOCIATE_BEFORE); + return NS_OK; +} + +/** + * Implements the SVG DOM GetSubStringLength method for the specified + * text content element. + */ +nsresult +SVGTextFrame::GetSubStringLength(nsIContent* aContent, + uint32_t charnum, uint32_t nchars, + float* aResult) +{ + UpdateGlyphPositioning(); + + // Convert charnum/nchars from addressable characters relative to + // aContent to global character indices. + CharIterator chit(this, CharIterator::eAddressable, aContent); + if (!chit.AdvanceToSubtree() || + !chit.Next(charnum) || + chit.IsAfterSubtree()) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + if (nchars == 0) { + *aResult = 0.0f; + return NS_OK; + } + + charnum = chit.TextElementCharIndex(); + chit.NextWithinSubtree(nchars); + nchars = chit.TextElementCharIndex() - charnum; + + // Find each rendered run that intersects with the range defined + // by charnum/nchars. + nscoord textLength = 0; + TextRenderedRunIterator runIter(this, TextRenderedRunIterator::eAllFrames); + TextRenderedRun run = runIter.Current(); + while (run.mFrame) { + // If this rendered run is past the substring we are interested in, we + // are done. + uint32_t offset = run.mTextElementCharIndex; + if (offset >= charnum + nchars) { + break; + } + + // Intersect the substring we are interested in with the range covered by + // the rendered run. + uint32_t length = run.mTextFrameContentLength; + IntersectInterval(offset, length, charnum, nchars); + + if (length != 0) { + // Convert offset into an index into the frame. + offset += run.mTextFrameContentOffset - run.mTextElementCharIndex; + + gfxSkipCharsIterator skipCharsIter = + run.mFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated); + Range range = ConvertOriginalToSkipped(skipCharsIter, offset, length); + + // Accumulate the advance. + textLength += textRun->GetAdvanceWidth(range, nullptr); + } + + run = runIter.Next(); + } + + nsPresContext* presContext = PresContext(); + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + + *aResult = presContext->AppUnitsToGfxUnits(textLength) * + cssPxPerDevPx / mFontSizeScaleFactor; + return NS_OK; +} + +/** + * Implements the SVG DOM GetCharNumAtPosition method for the specified + * text content element. + */ +int32_t +SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent, + mozilla::nsISVGPoint* aPoint) +{ + UpdateGlyphPositioning(); + + nsPresContext* context = PresContext(); + + gfxPoint p(aPoint->X(), aPoint->Y()); + + int32_t result = -1; + + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, aContent); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + // Hit test this rendered run. Later runs will override earlier ones. + int32_t index = run.GetCharNumAtPosition(context, p); + if (index != -1) { + result = index + run.mTextElementCharIndex; + } + } + + if (result == -1) { + return result; + } + + return ConvertTextElementCharIndexToAddressableIndex(result, aContent); +} + +/** + * Implements the SVG DOM GetStartPositionOfChar method for the specified + * text content element. + */ +nsresult +SVGTextFrame::GetStartPositionOfChar(nsIContent* aContent, + uint32_t aCharNum, + mozilla::nsISVGPoint** aResult) +{ + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || + !it.Next(aCharNum)) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + // We need to return the start position of the whole glyph. + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + + NS_ADDREF(*aResult = + new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition))); + return NS_OK; +} + +/** + * Implements the SVG DOM GetEndPositionOfChar method for the specified + * text content element. + */ +nsresult +SVGTextFrame::GetEndPositionOfChar(nsIContent* aContent, + uint32_t aCharNum, + mozilla::nsISVGPoint** aResult) +{ + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || + !it.Next(aCharNum)) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + // We need to return the end position of the whole glyph. + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + + // Get the advance of the glyph. + gfxFloat advance = it.GetGlyphAdvance(PresContext()); + if (it.TextRun()->IsRightToLeft()) { + advance = -advance; + } + + // The end position is the start position plus the advance in the direction + // of the glyph's rotation. + Matrix m = + Matrix::Rotation(mPositions[startIndex].mAngle) * + Matrix::Translation(ToPoint(mPositions[startIndex].mPosition)); + Point p = m.TransformPoint(Point(advance / mFontSizeScaleFactor, 0)); + + NS_ADDREF(*aResult = new DOMSVGPoint(p)); + return NS_OK; +} + +/** + * Implements the SVG DOM GetExtentOfChar method for the specified + * text content element. + */ +nsresult +SVGTextFrame::GetExtentOfChar(nsIContent* aContent, + uint32_t aCharNum, + dom::SVGIRect** aResult) +{ + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || + !it.Next(aCharNum)) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + nsPresContext* presContext = PresContext(); + + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + + // We need to return the extent of the whole glyph. + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + + // The ascent and descent gives the height of the glyph. + gfxFloat ascent, descent; + GetAscentAndDescentInAppUnits(it.TextFrame(), ascent, descent); + + // Get the advance of the glyph. + gfxFloat advance = it.GetGlyphAdvance(presContext); + gfxFloat x = it.TextRun()->IsRightToLeft() ? -advance : 0.0; + + // The horizontal extent is the origin of the glyph plus the advance + // in the direction of the glyph's rotation. + gfxMatrix m; + m.Translate(mPositions[startIndex].mPosition); + m.Rotate(mPositions[startIndex].mAngle); + m.Scale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor); + + gfxRect glyphRect; + if (it.TextRun()->IsVertical()) { + glyphRect = + gfxRect(-presContext->AppUnitsToGfxUnits(descent) * cssPxPerDevPx, x, + presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx, + advance); + } else { + glyphRect = + gfxRect(x, -presContext->AppUnitsToGfxUnits(ascent) * cssPxPerDevPx, + advance, + presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx); + } + + // Transform the glyph's rect into user space. + gfxRect r = m.TransformBounds(glyphRect); + + NS_ADDREF(*aResult = new dom::SVGRect(aContent, r.x, r.y, r.width, r.height)); + return NS_OK; +} + +/** + * Implements the SVG DOM GetRotationOfChar method for the specified + * text content element. + */ +nsresult +SVGTextFrame::GetRotationOfChar(nsIContent* aContent, + uint32_t aCharNum, + float* aResult) +{ + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || + !it.Next(aCharNum)) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + *aResult = mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI; + return NS_OK; +} + +//---------------------------------------------------------------------- +// SVGTextFrame text layout methods + +/** + * Given the character position array before values have been filled in + * to any unspecified positions, and an array of dx/dy values, returns whether + * a character at a given index should start a new rendered run. + * + * @param aPositions The array of character positions before unspecified + * positions have been filled in and dx/dy values have been added to them. + * @param aDeltas The array of dx/dy values. + * @param aIndex The character index in question. + */ +static bool +ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions, + const nsTArray<gfxPoint>& aDeltas, + uint32_t aIndex) +{ + if (aIndex == 0) { + return true; + } + + if (aIndex < aPositions.Length()) { + // If an explicit x or y value was given, start a new run. + if (aPositions[aIndex].IsXSpecified() || + aPositions[aIndex].IsYSpecified()) { + return true; + } + + // If a non-zero rotation was given, or the previous character had a non- + // zero rotation, start a new run. + if ((aPositions[aIndex].IsAngleSpecified() && + aPositions[aIndex].mAngle != 0.0f) || + (aPositions[aIndex - 1].IsAngleSpecified() && + (aPositions[aIndex - 1].mAngle != 0.0f))) { + return true; + } + } + + if (aIndex < aDeltas.Length()) { + // If a non-zero dx or dy value was given, start a new run. + if (aDeltas[aIndex].x != 0.0 || + aDeltas[aIndex].y != 0.0) { + return true; + } + } + + return false; +} + +bool +SVGTextFrame::ResolvePositionsForNode(nsIContent* aContent, + uint32_t& aIndex, + bool aInTextPath, + bool& aForceStartOfChunk, + nsTArray<gfxPoint>& aDeltas) +{ + if (aContent->IsNodeOfType(nsINode::eTEXT)) { + // We found a text node. + uint32_t length = static_cast<nsTextNode*>(aContent)->TextLength(); + if (length) { + uint32_t end = aIndex + length; + if (MOZ_UNLIKELY(end > mPositions.Length())) { + MOZ_ASSERT_UNREACHABLE("length of mPositions does not match characters " + "found by iterating content"); + return false; + } + if (aForceStartOfChunk) { + // Note this character as starting a new anchored chunk. + mPositions[aIndex].mStartOfChunk = true; + aForceStartOfChunk = false; + } + while (aIndex < end) { + // Record whether each of these characters should start a new rendered + // run. That is always the case for characters on a text path. + // + // Run boundaries due to rotate="" values are handled in + // DoGlyphPositioning. + if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) { + mPositions[aIndex].mRunBoundary = true; + } + aIndex++; + } + } + return true; + } + + // Skip past elements that aren't text content elements. + if (!IsTextContentElement(aContent)) { + return true; + } + + if (aContent->IsSVGElement(nsGkAtoms::textPath)) { + // <textPath> elements are as if they are specified with x="0" y="0", but + // only if they actually have some text content. + if (HasTextContent(aContent)) { + if (MOZ_UNLIKELY(aIndex >= mPositions.Length())) { + MOZ_ASSERT_UNREACHABLE("length of mPositions does not match characters " + "found by iterating content"); + return false; + } + mPositions[aIndex].mPosition = gfxPoint(); + mPositions[aIndex].mStartOfChunk = true; + } + } else if (!aContent->IsSVGElement(nsGkAtoms::a)) { + // We have a text content element that can have x/y/dx/dy/rotate attributes. + nsSVGElement* element = static_cast<nsSVGElement*>(aContent); + + // Get x, y, dx, dy. + SVGUserUnitList x, y, dx, dy; + element->GetAnimatedLengthListValues(&x, &y, &dx, &dy, nullptr); + + // Get rotate. + const SVGNumberList* rotate = nullptr; + SVGAnimatedNumberList* animatedRotate = + element->GetAnimatedNumberList(nsGkAtoms::rotate); + if (animatedRotate) { + rotate = &animatedRotate->GetAnimValue(); + } + + bool percentages = false; + uint32_t count = GetTextContentLength(aContent); + + if (MOZ_UNLIKELY(aIndex + count > mPositions.Length())) { + MOZ_ASSERT_UNREACHABLE("length of mPositions does not match characters " + "found by iterating content"); + return false; + } + + // New text anchoring chunks start at each character assigned a position + // with x="" or y="", or if we forced one with aForceStartOfChunk due to + // being just after a <textPath>. + uint32_t newChunkCount = std::max(x.Length(), y.Length()); + if (!newChunkCount && aForceStartOfChunk) { + newChunkCount = 1; + } + for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) { + if (!mPositions[aIndex + j].mUnaddressable) { + mPositions[aIndex + j].mStartOfChunk = true; + i++; + } + } + + // Copy dx="" and dy="" values into aDeltas. + if (!dx.IsEmpty() || !dy.IsEmpty()) { + // Any unspecified deltas when we grow the array just get left as 0s. + aDeltas.EnsureLengthAtLeast(aIndex + count); + for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) { + if (!mPositions[aIndex + j].mUnaddressable) { + aDeltas[aIndex + j].x = dx[i]; + percentages = percentages || dx.HasPercentageValueAt(i); + i++; + } + } + for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) { + if (!mPositions[aIndex + j].mUnaddressable) { + aDeltas[aIndex + j].y = dy[i]; + percentages = percentages || dy.HasPercentageValueAt(i); + i++; + } + } + } + + // Copy x="" and y="" values. + for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) { + if (!mPositions[aIndex + j].mUnaddressable) { + mPositions[aIndex + j].mPosition.x = x[i]; + percentages = percentages || x.HasPercentageValueAt(i); + i++; + } + } + for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) { + if (!mPositions[aIndex + j].mUnaddressable) { + mPositions[aIndex + j].mPosition.y = y[i]; + percentages = percentages || y.HasPercentageValueAt(i); + i++; + } + } + + // Copy rotate="" values. + if (rotate && !rotate->IsEmpty()) { + uint32_t i = 0, j = 0; + while (i < rotate->Length() && j < count) { + if (!mPositions[aIndex + j].mUnaddressable) { + mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0; + i++; + } + j++; + } + // Propagate final rotate="" value to the end of this element. + while (j < count) { + mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle; + j++; + } + } + + if (percentages) { + AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); + } + } + + // Recurse to children. + bool inTextPath = aInTextPath || aContent->IsSVGElement(nsGkAtoms::textPath); + for (nsIContent* child = aContent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + bool ok = ResolvePositionsForNode(child, aIndex, inTextPath, + aForceStartOfChunk, aDeltas); + if (!ok) { + return false; + } + } + + if (aContent->IsSVGElement(nsGkAtoms::textPath)) { + // Force a new anchored chunk just after a <textPath>. + aForceStartOfChunk = true; + } + + return true; +} + +bool +SVGTextFrame::ResolvePositions(nsTArray<gfxPoint>& aDeltas, + bool aRunPerGlyph) +{ + NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty"); + RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); + + CharIterator it(this, CharIterator::eOriginal); + if (it.AtEnd()) { + return false; + } + + // We assume the first character position is (0,0) unless we later see + // otherwise, and note it as unaddressable if it is. + bool firstCharUnaddressable = it.IsOriginalCharUnaddressable(); + mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable)); + + // Fill in unspecified positions for all remaining characters, noting + // them as unaddressable if they are. + uint32_t index = 0; + while (it.Next()) { + while (++index < it.TextElementCharIndex()) { + mPositions.AppendElement(CharPosition::Unspecified(false)); + } + mPositions.AppendElement(CharPosition::Unspecified( + it.IsOriginalCharUnaddressable())); + } + while (++index < it.TextElementCharIndex()) { + mPositions.AppendElement(CharPosition::Unspecified(false)); + } + + // Recurse over the content and fill in character positions as we go. + bool forceStartOfChunk = false; + index = 0; + bool ok = ResolvePositionsForNode(mContent, index, aRunPerGlyph, + forceStartOfChunk, aDeltas); + return ok && index > 0; +} + +void +SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions) +{ + NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty"); + + nsPoint position, lastPosition; + + TextFrameIterator frit(this); + for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) { + gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); + + // Reset the position to the new frame's position. + position = frit.Position(); + if (textRun->IsVertical()) { + if (textRun->IsRightToLeft()) { + position.y += frame->GetRect().height; + } + position.x += GetBaselinePosition(frame, textRun, + frit.DominantBaseline(), + mFontSizeScaleFactor); + } else { + if (textRun->IsRightToLeft()) { + position.x += frame->GetRect().width; + } + position.y += GetBaselinePosition(frame, textRun, + frit.DominantBaseline(), + mFontSizeScaleFactor); + } + + // Any characters not in a frame, e.g. when display:none. + for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { + aPositions.AppendElement(position); + } + + // Any white space characters trimmed at the start of the line of text. + nsTextFrame::TrimmedOffsets trimmedOffsets = + frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true); + while (it.GetOriginalOffset() < trimmedOffsets.mStart) { + aPositions.AppendElement(position); + it.AdvanceOriginal(1); + } + + // If a ligature was started in the previous frame, we should record + // the ligature's start position, not any partial position. + while (it.GetOriginalOffset() < frame->GetContentEnd() && + !it.IsOriginalCharSkipped() && + (!textRun->IsLigatureGroupStart(it.GetSkippedOffset()) || + !textRun->IsClusterStart(it.GetSkippedOffset()))) { + uint32_t offset = it.GetSkippedOffset(); + nscoord advance = textRun-> + GetAdvanceWidth(Range(offset, offset + 1), nullptr); + (textRun->IsVertical() ? position.y : position.x) += + textRun->IsRightToLeft() ? -advance : advance; + aPositions.AppendElement(lastPosition); + it.AdvanceOriginal(1); + } + + // The meat of the text frame. + while (it.GetOriginalOffset() < frame->GetContentEnd()) { + aPositions.AppendElement(position); + if (!it.IsOriginalCharSkipped() && + textRun->IsLigatureGroupStart(it.GetSkippedOffset()) && + textRun->IsClusterStart(it.GetSkippedOffset())) { + // A real visible character. + nscoord advance = textRun-> + GetAdvanceWidth(ClusterRange(textRun, it), nullptr); + (textRun->IsVertical() ? position.y : position.x) += + textRun->IsRightToLeft() ? -advance : advance; + lastPosition = position; + } + it.AdvanceOriginal(1); + } + } + + // Finally any characters at the end that are not in a frame. + for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { + aPositions.AppendElement(position); + } +} + +/** + * Physical text-anchor values. + */ +enum TextAnchorSide { + eAnchorLeft, + eAnchorMiddle, + eAnchorRight +}; + +/** + * Converts a logical text-anchor value to its physical value, based on whether + * it is for an RTL frame. + */ +static TextAnchorSide +ConvertLogicalTextAnchorToPhysical(uint8_t aTextAnchor, bool aIsRightToLeft) +{ + NS_ASSERTION(aTextAnchor <= 3, "unexpected value for aTextAnchor"); + if (!aIsRightToLeft) + return TextAnchorSide(aTextAnchor); + return TextAnchorSide(2 - aTextAnchor); +} + +/** + * Shifts the recorded character positions for an anchored chunk. + * + * @param aCharPositions The recorded character positions. + * @param aChunkStart The character index the starts the anchored chunk. This + * character's initial position is the anchor point. + * @param aChunkEnd The character index just after the end of the anchored + * chunk. + * @param aVisIStartEdge The left/top-most edge of any of the glyphs within the + * anchored chunk. + * @param aVisIEndEdge The right/bottom-most edge of any of the glyphs within + * the anchored chunk. + * @param aAnchorSide The direction to anchor. + */ +static void +ShiftAnchoredChunk(nsTArray<mozilla::CharPosition>& aCharPositions, + uint32_t aChunkStart, + uint32_t aChunkEnd, + gfxFloat aVisIStartEdge, + gfxFloat aVisIEndEdge, + TextAnchorSide aAnchorSide, + bool aVertical) +{ + NS_ASSERTION(aVisIStartEdge <= aVisIEndEdge, + "unexpected anchored chunk edges"); + NS_ASSERTION(aChunkStart < aChunkEnd, + "unexpected values for aChunkStart and aChunkEnd"); + + gfxFloat shift = aVertical ? aCharPositions[aChunkStart].mPosition.y + : aCharPositions[aChunkStart].mPosition.x; + switch (aAnchorSide) { + case eAnchorLeft: + shift -= aVisIStartEdge; + break; + case eAnchorMiddle: + shift -= (aVisIStartEdge + aVisIEndEdge) / 2; + break; + case eAnchorRight: + shift -= aVisIEndEdge; + break; + default: + NS_NOTREACHED("unexpected value for aAnchorSide"); + } + + if (shift != 0.0) { + if (aVertical) { + for (uint32_t i = aChunkStart; i < aChunkEnd; i++) { + aCharPositions[i].mPosition.y += shift; + } + } else { + for (uint32_t i = aChunkStart; i < aChunkEnd; i++) { + aCharPositions[i].mPosition.x += shift; + } + } + } +} + +void +SVGTextFrame::AdjustChunksForLineBreaks() +{ + nsBlockFrame* block = nsLayoutUtils::GetAsBlock(PrincipalChildList().FirstChild()); + NS_ASSERTION(block, "expected block frame"); + + nsBlockFrame::LineIterator line = block->LinesBegin(); + + CharIterator it(this, CharIterator::eOriginal); + while (!it.AtEnd() && line != block->LinesEnd()) { + if (it.TextFrame() == line->mFirstChild) { + mPositions[it.TextElementCharIndex()].mStartOfChunk = true; + line++; + } + it.AdvancePastCurrentFrame(); + } +} + +void +SVGTextFrame::AdjustPositionsForClusters() +{ + nsPresContext* presContext = PresContext(); + + CharIterator it(this, CharIterator::eClusterOrLigatureGroupMiddle); + while (!it.AtEnd()) { + // Find the start of the cluster/ligature group. + uint32_t charIndex = it.TextElementCharIndex(); + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + + mPositions[charIndex].mClusterOrLigatureGroupMiddle = true; + + // Don't allow different rotations on ligature parts. + bool rotationAdjusted = false; + double angle = mPositions[startIndex].mAngle; + if (mPositions[charIndex].mAngle != angle) { + mPositions[charIndex].mAngle = angle; + rotationAdjusted = true; + } + + // Find out the partial glyph advance for this character and update + // the character position. + uint32_t partLength = + charIndex - startIndex - it.GlyphUndisplayedCharacters(); + gfxFloat advance = + it.GetGlyphPartialAdvance(partLength, presContext) / mFontSizeScaleFactor; + gfxPoint direction = gfxPoint(cos(angle), sin(angle)) * + (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0); + if (it.TextRun()->IsVertical()) { + Swap(direction.x, direction.y); + } + mPositions[charIndex].mPosition = mPositions[startIndex].mPosition + + direction * advance; + + // Ensure any runs that would end in the middle of a ligature now end just + // after the ligature. + if (mPositions[charIndex].mRunBoundary) { + mPositions[charIndex].mRunBoundary = false; + if (charIndex + 1 < mPositions.Length()) { + mPositions[charIndex + 1].mRunBoundary = true; + } + } else if (rotationAdjusted) { + if (charIndex + 1 < mPositions.Length()) { + mPositions[charIndex + 1].mRunBoundary = true; + } + } + + // Ensure any anchored chunks that would begin in the middle of a ligature + // now begin just after the ligature. + if (mPositions[charIndex].mStartOfChunk) { + mPositions[charIndex].mStartOfChunk = false; + if (charIndex + 1 < mPositions.Length()) { + mPositions[charIndex + 1].mStartOfChunk = true; + } + } + + it.Next(); + } +} + +SVGPathElement* +SVGTextFrame::GetTextPathPathElement(nsIFrame* aTextPathFrame) +{ + nsSVGTextPathProperty *property = + aTextPathFrame->Properties().Get(nsSVGEffects::HrefAsTextPathProperty()); + + if (!property) { + nsIContent* content = aTextPathFrame->GetContent(); + dom::SVGTextPathElement* tp = static_cast<dom::SVGTextPathElement*>(content); + nsAutoString href; + if (tp->mStringAttributes[dom::SVGTextPathElement::HREF].IsExplicitlySet()) { + tp->mStringAttributes[dom::SVGTextPathElement::HREF] + .GetAnimValue(href, tp); + } else { + tp->mStringAttributes[dom::SVGTextPathElement::XLINK_HREF] + .GetAnimValue(href, tp); + } + + if (href.IsEmpty()) { + return nullptr; // no URL + } + + nsCOMPtr<nsIURI> targetURI; + nsCOMPtr<nsIURI> base = content->GetBaseURI(); + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, + content->GetUncomposedDoc(), base); + + property = nsSVGEffects::GetTextPathProperty( + targetURI, + aTextPathFrame, + nsSVGEffects::HrefAsTextPathProperty()); + if (!property) + return nullptr; + } + + Element* element = property->GetReferencedElement(); + return (element && element->IsSVGElement(nsGkAtoms::path)) ? + static_cast<SVGPathElement*>(element) : nullptr; +} + +already_AddRefed<Path> +SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) +{ + SVGPathElement* element = GetTextPathPathElement(aTextPathFrame); + if (!element) { + return nullptr; + } + + RefPtr<Path> path = element->GetOrBuildPathForMeasuring(); + if (!path) { + return nullptr; + } + + gfxMatrix matrix = element->PrependLocalTransformsTo(gfxMatrix()); + if (!matrix.IsIdentity()) { + RefPtr<PathBuilder> builder = + path->TransformedCopyToBuilder(ToMatrix(matrix)); + path = builder->Finish(); + } + + return path.forget(); +} + +gfxFloat +SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) +{ + SVGPathElement* pathElement = GetTextPathPathElement(aTextPathFrame); + if (!pathElement) + return 1.0; + + return pathElement->GetPathLengthScale(dom::SVGPathElement::eForTextPath); +} + +gfxFloat +SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) +{ + dom::SVGTextPathElement *tp = + static_cast<dom::SVGTextPathElement*>(aTextPathFrame->GetContent()); + nsSVGLength2 *length = + &tp->mLengthAttributes[dom::SVGTextPathElement::STARTOFFSET]; + + if (length->IsPercentage()) { + RefPtr<Path> data = GetTextPath(aTextPathFrame); + return data ? + length->GetAnimValInSpecifiedUnits() * data->ComputeLength() / 100.0 : + 0.0; + } + return length->GetAnimValue(tp) * GetOffsetScale(aTextPathFrame); +} + +void +SVGTextFrame::DoTextPathLayout() +{ + nsPresContext* context = PresContext(); + + CharIterator it(this, CharIterator::eClusterAndLigatureGroupStart); + while (!it.AtEnd()) { + nsIFrame* textPathFrame = it.TextPathFrame(); + if (!textPathFrame) { + // Skip past this frame if we're not in a text path. + it.AdvancePastCurrentFrame(); + continue; + } + + // Get the path itself. + RefPtr<Path> path = GetTextPath(textPathFrame); + if (!path) { + it.AdvancePastCurrentTextPathFrame(); + continue; + } + + nsIContent* textPath = textPathFrame->GetContent(); + + gfxFloat offset = GetStartOffset(textPathFrame); + Float pathLength = path->ComputeLength(); + + // Loop for each text frame in the text path. + do { + uint32_t i = it.TextElementCharIndex(); + gfxFloat halfAdvance = + it.GetGlyphAdvance(context) / mFontSizeScaleFactor / 2.0; + gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0; + bool vertical = it.TextRun()->IsVertical(); + gfxFloat midx = (vertical ? mPositions[i].mPosition.y + : mPositions[i].mPosition.x) + + sign * halfAdvance + offset; + + // Hide the character if it falls off the end of the path. + mPositions[i].mHidden = midx < 0 || midx > pathLength; + + // Position the character on the path at the right angle. + Point tangent; // Unit vector tangent to the point we find. + Point pt = path->ComputePointAtLength(Float(midx), &tangent); + Float rotation = vertical ? atan2f(-tangent.x, tangent.y) + : atan2f(tangent.y, tangent.x); + Point normal(-tangent.y, tangent.x); // Unit vector normal to the point. + Point offsetFromPath = normal * (vertical ? -mPositions[i].mPosition.x + : mPositions[i].mPosition.y); + pt += offsetFromPath; + Point direction = tangent * sign; + mPositions[i].mPosition = ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance; + mPositions[i].mAngle += rotation; + + // Position any characters for a partial ligature. + for (uint32_t j = i + 1; + j < mPositions.Length() && mPositions[j].mClusterOrLigatureGroupMiddle; + j++) { + gfxPoint partialAdvance = + ThebesPoint(direction) * it.GetGlyphPartialAdvance(j - i, context) / + mFontSizeScaleFactor; + mPositions[j].mPosition = mPositions[i].mPosition + partialAdvance; + mPositions[j].mAngle = mPositions[i].mAngle; + mPositions[j].mHidden = mPositions[i].mHidden; + } + it.Next(); + } while (it.TextPathFrame() && + it.TextPathFrame()->GetContent() == textPath); + } +} + +void +SVGTextFrame::DoAnchoring() +{ + nsPresContext* presContext = PresContext(); + + CharIterator it(this, CharIterator::eOriginal); + + // Don't need to worry about skipped or trimmed characters. + while (!it.AtEnd() && + (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) { + it.Next(); + } + + bool vertical = GetWritingMode().IsVertical(); + uint32_t start = it.TextElementCharIndex(); + while (start < mPositions.Length()) { + it.AdvanceToCharacter(start); + nsTextFrame* chunkFrame = it.TextFrame(); + + // Measure characters in this chunk to find the left-most and right-most + // edges of all glyphs within the chunk. + uint32_t index = it.TextElementCharIndex(); + uint32_t end = start; + gfxFloat left = std::numeric_limits<gfxFloat>::infinity(); + gfxFloat right = -std::numeric_limits<gfxFloat>::infinity(); + do { + if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) { + gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor; + gfxFloat pos = + it.TextRun()->IsVertical() ? mPositions[index].mPosition.y + : mPositions[index].mPosition.x; + if (it.TextRun()->IsRightToLeft()) { + left = std::min(left, pos - advance); + right = std::max(right, pos); + } else { + left = std::min(left, pos); + right = std::max(right, pos + advance); + } + } + it.Next(); + index = end = it.TextElementCharIndex(); + } while (!it.AtEnd() && !mPositions[end].mStartOfChunk); + + if (left != std::numeric_limits<gfxFloat>::infinity()) { + bool isRTL = + chunkFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + TextAnchorSide anchor = + ConvertLogicalTextAnchorToPhysical(chunkFrame->StyleSVG()->mTextAnchor, + isRTL); + + ShiftAnchoredChunk(mPositions, start, end, left, right, anchor, + vertical); + } + + start = it.TextElementCharIndex(); + } +} + +void +SVGTextFrame::DoGlyphPositioning() +{ + mPositions.Clear(); + RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY); + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid && NS_SUBTREE_DIRTY(kid)) { + MOZ_ASSERT(false, "should have already reflowed the kid"); + return; + } + + // Determine the positions of each character in app units. + nsTArray<nsPoint> charPositions; + DetermineCharPositions(charPositions); + + if (charPositions.IsEmpty()) { + // No characters, so nothing to do. + return; + } + + // If the textLength="" attribute was specified, then we need ResolvePositions + // to record that a new run starts with each glyph. + SVGTextContentElement* element = static_cast<SVGTextContentElement*>(mContent); + nsSVGLength2* textLengthAttr = + element->GetAnimatedLength(nsGkAtoms::textLength); + bool adjustingTextLength = textLengthAttr->IsExplicitlySet(); + float expectedTextLength = textLengthAttr->GetAnimValue(element); + + if (adjustingTextLength && expectedTextLength < 0.0f) { + // If textLength="" is less than zero, ignore it. + adjustingTextLength = false; + } + + // Get the x, y, dx, dy, rotate values for the subtree. + nsTArray<gfxPoint> deltas; + if (!ResolvePositions(deltas, adjustingTextLength)) { + // If ResolvePositions returned false, it means either there were some + // characters in the DOM but none of them are displayed, or there was + // an error in processing mPositions. Clear out mPositions so that we don't + // attempt to do any painting later. + mPositions.Clear(); + return; + } + + // XXX We might be able to do less work when there is at most a single + // x/y/dx/dy position. + + // Truncate the positioning arrays to the actual number of characters present. + TruncateTo(deltas, charPositions); + TruncateTo(mPositions, charPositions); + + // Fill in an unspecified character position at index 0. + if (!mPositions[0].IsXSpecified()) { + mPositions[0].mPosition.x = 0.0; + } + if (!mPositions[0].IsYSpecified()) { + mPositions[0].mPosition.y = 0.0; + } + if (!mPositions[0].IsAngleSpecified()) { + mPositions[0].mAngle = 0.0; + } + + nsPresContext* presContext = PresContext(); + bool vertical = GetWritingMode().IsVertical(); + + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + double factor = cssPxPerDevPx / mFontSizeScaleFactor; + + // Determine how much to compress or expand glyph positions due to + // textLength="" and lengthAdjust="". + double adjustment = 0.0; + mLengthAdjustScaleFactor = 1.0f; + if (adjustingTextLength) { + nscoord frameLength = vertical ? PrincipalChildList().FirstChild()->GetRect().height + : PrincipalChildList().FirstChild()->GetRect().width; + float actualTextLength = + static_cast<float>(presContext->AppUnitsToGfxUnits(frameLength) * factor); + + RefPtr<SVGAnimatedEnumeration> lengthAdjustEnum = element->LengthAdjust(); + uint16_t lengthAdjust = lengthAdjustEnum->AnimVal(); + switch (lengthAdjust) { + case SVG_LENGTHADJUST_SPACINGANDGLYPHS: + // Scale the glyphs and their positions. + if (actualTextLength > 0) { + mLengthAdjustScaleFactor = expectedTextLength / actualTextLength; + } + break; + + default: + MOZ_ASSERT(lengthAdjust == SVG_LENGTHADJUST_SPACING); + // Just add space between each glyph. + int32_t adjustableSpaces = 0; + for (uint32_t i = 1; i < mPositions.Length(); i++) { + if (!mPositions[i].mUnaddressable) { + adjustableSpaces++; + } + } + if (adjustableSpaces) { + adjustment = (expectedTextLength - actualTextLength) / adjustableSpaces; + } + break; + } + } + + // Fill in any unspecified character positions based on the positions recorded + // in charPositions, and also add in the dx/dy values. + if (!deltas.IsEmpty()) { + mPositions[0].mPosition += deltas[0]; + } + + gfxFloat xLengthAdjustFactor = vertical ? 1.0 : mLengthAdjustScaleFactor; + gfxFloat yLengthAdjustFactor = vertical ? mLengthAdjustScaleFactor : 1.0; + for (uint32_t i = 1; i < mPositions.Length(); i++) { + // Fill in unspecified x position. + if (!mPositions[i].IsXSpecified()) { + nscoord d = charPositions[i].x - charPositions[i - 1].x; + mPositions[i].mPosition.x = + mPositions[i - 1].mPosition.x + + presContext->AppUnitsToGfxUnits(d) * factor * xLengthAdjustFactor; + if (!vertical && !mPositions[i].mUnaddressable) { + mPositions[i].mPosition.x += adjustment; + } + } + // Fill in unspecified y position. + if (!mPositions[i].IsYSpecified()) { + nscoord d = charPositions[i].y - charPositions[i - 1].y; + mPositions[i].mPosition.y = + mPositions[i - 1].mPosition.y + + presContext->AppUnitsToGfxUnits(d) * factor * yLengthAdjustFactor; + if (vertical && !mPositions[i].mUnaddressable) { + mPositions[i].mPosition.y += adjustment; + } + } + // Add in dx/dy. + if (i < deltas.Length()) { + mPositions[i].mPosition += deltas[i]; + } + // Fill in unspecified rotation values. + if (!mPositions[i].IsAngleSpecified()) { + mPositions[i].mAngle = 0.0f; + } + } + + MOZ_ASSERT(mPositions.Length() == charPositions.Length()); + + AdjustChunksForLineBreaks(); + AdjustPositionsForClusters(); + DoAnchoring(); + DoTextPathLayout(); +} + +bool +SVGTextFrame::ShouldRenderAsPath(nsTextFrame* aFrame, + bool& aShouldPaintSVGGlyphs) +{ + // Rendering to a clip path. + if (aFrame->GetParent()->GetParent()->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { + aShouldPaintSVGGlyphs = false; + return true; + } + + aShouldPaintSVGGlyphs = true; + + const nsStyleSVG* style = aFrame->StyleSVG(); + + // Fill is a non-solid paint, has a non-default fill-rule or has + // non-1 opacity. + if (!(style->mFill.Type() == eStyleSVGPaintType_None || + (style->mFill.Type() == eStyleSVGPaintType_Color && + style->mFillOpacity == 1))) { + return true; + } + + // Text has a stroke. + if (style->HasStroke() && + SVGContentUtils::CoordToFloat(static_cast<nsSVGElement*>(mContent), + style->mStrokeWidth) > 0) { + return true; + } + + return false; +} + +void +SVGTextFrame::ScheduleReflowSVG() +{ + if (mState & NS_FRAME_IS_NONDISPLAY) { + ScheduleReflowSVGNonDisplayText(nsIPresShell::eStyleChange); + } else { + nsSVGUtils::ScheduleReflowSVG(this); + } +} + +void +SVGTextFrame::NotifyGlyphMetricsChange() +{ + AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + ScheduleReflowSVG(); +} + +void +SVGTextFrame::UpdateGlyphPositioning() +{ + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + if (mState & NS_STATE_SVG_POSITIONING_DIRTY) { + DoGlyphPositioning(); + } +} + +void +SVGTextFrame::MaybeReflowAnonymousBlockChild() +{ + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return; + + NS_ASSERTION(!(kid->GetStateBits() & NS_FRAME_IN_REFLOW), + "should not be in reflow when about to reflow again"); + + if (NS_SUBTREE_DIRTY(this)) { + if (mState & NS_FRAME_IS_DIRTY) { + // If we require a full reflow, ensure our kid is marked fully dirty. + // (Note that our anonymous nsBlockFrame is not an nsISVGChildFrame, so + // even when we are called via our ReflowSVG this will not be done for us + // by nsSVGDisplayContainerFrame::ReflowSVG.) + kid->AddStateBits(NS_FRAME_IS_DIRTY); + } + MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this), + "should be under ReflowSVG"); + nsPresContext::InterruptPreventer noInterrupts(PresContext()); + DoReflow(); + } +} + +void +SVGTextFrame::DoReflow() +{ + // Since we are going to reflow the anonymous block frame, we will + // need to update mPositions. + AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); + + if (mState & NS_FRAME_IS_NONDISPLAY) { + // Normally, these dirty flags would be cleared in ReflowSVG(), but that + // doesn't get called for non-display frames. We don't want to reflow our + // descendants every time SVGTextFrame::PaintSVG makes sure that we have + // valid positions by calling UpdateGlyphPositioning(), so we need to clear + // these dirty bits. Note that this also breaks an invalidation loop where + // our descendants invalidate as they reflow, which invalidates rendering + // observers, which reschedules the frame that is currently painting by + // referencing us to paint again. See bug 839958 comment 7. Hopefully we + // will break that loop more convincingly at some point. + mState &= ~(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); + } + + nsPresContext *presContext = PresContext(); + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return; + + nsRenderingContext renderingContext( + presContext->PresShell()->CreateReferenceRenderingContext()); + + if (UpdateFontSizeScaleFactor()) { + // If the font size scale factor changed, we need the block to report + // an updated preferred width. + kid->MarkIntrinsicISizesDirty(); + } + + mState |= NS_STATE_SVG_TEXT_IN_REFLOW; + + nscoord inlineSize = kid->GetPrefISize(&renderingContext); + WritingMode wm = kid->GetWritingMode(); + ReflowInput reflowInput(presContext, kid, + &renderingContext, + LogicalSize(wm, inlineSize, + NS_UNCONSTRAINEDSIZE)); + ReflowOutput desiredSize(reflowInput); + nsReflowStatus status; + + NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && + reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), + "style system should ensure that :-moz-svg-text " + "does not get styled"); + + kid->Reflow(presContext, desiredSize, reflowInput, status); + kid->DidReflow(presContext, &reflowInput, nsDidReflowStatus::FINISHED); + kid->SetSize(wm, desiredSize.Size(wm)); + + mState &= ~NS_STATE_SVG_TEXT_IN_REFLOW; + + TextNodeCorrespondenceRecorder::RecordCorrespondence(this); +} + +// Usable font size range in devpixels / user-units +#define CLAMP_MIN_SIZE 8.0 +#define CLAMP_MAX_SIZE 200.0 +#define PRECISE_SIZE 200.0 + +bool +SVGTextFrame::UpdateFontSizeScaleFactor() +{ + double oldFontSizeScaleFactor = mFontSizeScaleFactor; + + nsPresContext* presContext = PresContext(); + + bool geometricPrecision = false; + nscoord min = nscoord_MAX, + max = nscoord_MIN; + + // Find the minimum and maximum font sizes used over all the + // nsTextFrames. + TextFrameIterator it(this); + nsTextFrame* f = it.Current(); + while (f) { + if (!geometricPrecision) { + // Unfortunately we can't treat text-rendering:geometricPrecision + // separately for each text frame. + geometricPrecision = f->StyleText()->mTextRendering == + NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION; + } + nscoord size = f->StyleFont()->mFont.size; + if (size) { + min = std::min(min, size); + max = std::max(max, size); + } + f = it.Next(); + } + + if (min == nscoord_MAX) { + // No text, so no need for scaling. + mFontSizeScaleFactor = 1.0; + return mFontSizeScaleFactor != oldFontSizeScaleFactor; + } + + double minSize = presContext->AppUnitsToFloatCSSPixels(min); + + if (geometricPrecision) { + // We want to ensure minSize is scaled to PRECISE_SIZE. + mFontSizeScaleFactor = PRECISE_SIZE / minSize; + return mFontSizeScaleFactor != oldFontSizeScaleFactor; + } + + // When we are non-display, we could be painted in different coordinate + // spaces, and we don't want to have to reflow for each of these. We + // just assume that the context scale is 1.0 for them all, so we don't + // get stuck with a font size scale factor based on whichever referencing + // frame happens to reflow first. + double contextScale = 1.0; + if (!(mState & NS_FRAME_IS_NONDISPLAY)) { + gfxMatrix m(GetCanvasTM()); + if (!m.IsSingular()) { + contextScale = GetContextScale(m); + } + } + mLastContextScale = contextScale; + + double maxSize = presContext->AppUnitsToFloatCSSPixels(max); + + // But we want to ignore any scaling required due to HiDPI displays, since + // regular CSS text frames will still create text runs using the font size + // in CSS pixels, and we want SVG text to have the same rendering as HTML + // text for regular font sizes. + float cssPxPerDevPx = + presContext->AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + contextScale *= cssPxPerDevPx; + + double minTextRunSize = minSize * contextScale; + double maxTextRunSize = maxSize * contextScale; + + if (minTextRunSize >= CLAMP_MIN_SIZE && + maxTextRunSize <= CLAMP_MAX_SIZE) { + // We are already in the ideal font size range for all text frames, + // so we only have to take into account the contextScale. + mFontSizeScaleFactor = contextScale; + } else if (maxSize / minSize > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) { + // We can't scale the font sizes so that all of the text frames lie + // within our ideal font size range, so we treat the minimum as more + // important and just scale so that minSize = CLAMP_MIN_SIZE. + mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize; + } else if (minTextRunSize < CLAMP_MIN_SIZE) { + mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize; + } else { + mFontSizeScaleFactor = CLAMP_MAX_SIZE / maxTextRunSize; + } + + return mFontSizeScaleFactor != oldFontSizeScaleFactor; +} + +double +SVGTextFrame::GetFontSizeScaleFactor() const +{ + return mFontSizeScaleFactor; +} + +/** + * Take aPoint, which is in the <text> element's user space, and convert + * it to the appropriate frame user space of aChildFrame according to + * which rendered run the point hits. + */ +Point +SVGTextFrame::TransformFramePointToTextChild(const Point& aPoint, + nsIFrame* aChildFrame) +{ + NS_ASSERTION(aChildFrame && + nsLayoutUtils::GetClosestFrameOfType + (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, + "aChildFrame must be a descendant of this frame"); + + UpdateGlyphPositioning(); + + nsPresContext* presContext = PresContext(); + + // Add in the mRect offset to aPoint, as that will have been taken into + // account when transforming the point from the ancestor frame down + // to this one. + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + float factor = presContext->AppUnitsPerCSSPixel(); + Point framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + Point pointInUserSpace = aPoint * cssPxPerDevPx + framePosition; + + // Find the closest rendered run for the text frames beneath aChildFrame. + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aChildFrame); + TextRenderedRun hit; + gfxPoint pointInRun; + nscoord dx = nscoord_MAX; + nscoord dy = nscoord_MAX; + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke | + TextRenderedRun::eNoHorizontalOverflow; + gfxRect runRect = run.GetRunUserSpaceRect(presContext, flags).ToThebesRect(); + + gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); + if (!m.Invert()) { + return aPoint; + } + gfxPoint pointInRunUserSpace = m.Transform(ThebesPoint(pointInUserSpace)); + + if (Inside(runRect, pointInRunUserSpace)) { + // The point was inside the rendered run's rect, so we choose it. + dx = 0; + dy = 0; + pointInRun = pointInRunUserSpace; + hit = run; + } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, + runRect, dx, dy)) { + // The point was closer to this rendered run's rect than any others + // we've seen so far. + pointInRun.x = clamped(pointInRunUserSpace.x, + runRect.X(), runRect.XMost()); + pointInRun.y = clamped(pointInRunUserSpace.y, + runRect.Y(), runRect.YMost()); + hit = run; + } + } + + if (!hit.mFrame) { + // We didn't find any rendered runs for the frame. + return aPoint; + } + + // Return the point in user units relative to the nsTextFrame, + // but taking into account mFontSizeScaleFactor. + gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext); + m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); + return ToPoint(m.Transform(pointInRun) / cssPxPerDevPx); +} + +/** + * For each rendered run for frames beneath aChildFrame, convert aRect + * into the run's frame user space and intersect it with the run's + * frame user space rectangle. For each of these intersections, + * then translate them up into aChildFrame's coordinate space + * and union them all together. + */ +gfxRect +SVGTextFrame::TransformFrameRectToTextChild(const gfxRect& aRect, + nsIFrame* aChildFrame) +{ + NS_ASSERTION(aChildFrame && + nsLayoutUtils::GetClosestFrameOfType + (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, + "aChildFrame must be a descendant of this frame"); + + UpdateGlyphPositioning(); + + nsPresContext* presContext = PresContext(); + + // Add in the mRect offset to aRect, as that will have been taken into + // account when transforming the rect from the ancestor frame down + // to this one. + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + float factor = presContext->AppUnitsPerCSSPixel(); + gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + gfxRect incomingRectInUserSpace(aRect.x * cssPxPerDevPx + framePosition.x, + aRect.y * cssPxPerDevPx + framePosition.y, + aRect.width * cssPxPerDevPx, + aRect.height * cssPxPerDevPx); + + // Find each rendered run for text frames beneath aChildFrame. + gfxRect result; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aChildFrame); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + // Convert the incoming rect into frame user space. + gfxMatrix userSpaceToRunUserSpace = + run.GetTransformFromRunUserSpaceToUserSpace(presContext); + if (!userSpaceToRunUserSpace.Invert()) { + return result; + } + gfxMatrix m = run.GetTransformFromRunUserSpaceToFrameUserSpace(presContext) * + userSpaceToRunUserSpace; + gfxRect incomingRectInFrameUserSpace = + m.TransformBounds(incomingRectInUserSpace); + + // Intersect it with this run's rectangle. + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke; + SVGBBox runRectInFrameUserSpace = run.GetFrameUserSpaceRect(presContext, flags); + if (runRectInFrameUserSpace.IsEmpty()) { + continue; + } + gfxRect runIntersectionInFrameUserSpace = + incomingRectInFrameUserSpace.Intersect(runRectInFrameUserSpace.ToThebesRect()); + + if (!runIntersectionInFrameUserSpace.IsEmpty()) { + // Take the font size scale into account. + runIntersectionInFrameUserSpace.x *= mFontSizeScaleFactor; + runIntersectionInFrameUserSpace.y *= mFontSizeScaleFactor; + runIntersectionInFrameUserSpace.width *= mFontSizeScaleFactor; + runIntersectionInFrameUserSpace.height *= mFontSizeScaleFactor; + + // Convert it into the coordinate space of aChildFrame. + nsPoint offset = run.mFrame->GetOffsetTo(aChildFrame); + gfxRect runIntersection = + runIntersectionInFrameUserSpace + + gfxPoint(NSAppUnitsToFloatPixels(offset.x, factor), + NSAppUnitsToFloatPixels(offset.y, factor)); + + // Union it into the result. + result.UnionRect(result, runIntersection); + } + } + + return result; +} + +/** + * For each rendered run beneath aChildFrame, translate aRect from + * aChildFrame to the run's text frame, transform it then into + * the run's frame user space, intersect it with the run's + * frame user space rect, then transform it up to user space. + * The result is the union of all of these. + */ +gfxRect +SVGTextFrame::TransformFrameRectFromTextChild(const nsRect& aRect, + nsIFrame* aChildFrame) +{ + NS_ASSERTION(aChildFrame && + nsLayoutUtils::GetClosestFrameOfType + (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, + "aChildFrame must be a descendant of this frame"); + + UpdateGlyphPositioning(); + + nsPresContext* presContext = PresContext(); + + gfxRect result; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aChildFrame); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + // First, translate aRect from aChildFrame to this run's frame. + nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame); + + // Scale it into frame user space. + gfxRect rectInFrameUserSpace = + AppUnitsToFloatCSSPixels(gfxRect(rectInTextFrame.x, + rectInTextFrame.y, + rectInTextFrame.width, + rectInTextFrame.height), presContext); + + // Intersect it with the run. + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke; + + if (rectInFrameUserSpace.IntersectRect(rectInFrameUserSpace, + run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect())) { + // Transform it up to user space of the <text>, also taking into + // account the font size scale. + gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); + m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); + gfxRect rectInUserSpace = m.Transform(rectInFrameUserSpace); + + // Union it into the result. + result.UnionRect(result, rectInUserSpace); + } + } + + // Subtract the mRect offset from the result, as our user space for + // this frame is relative to the top-left of mRect. + float factor = presContext->AppUnitsPerCSSPixel(); + gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + + return result - framePosition; +} diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h new file mode 100644 index 0000000000..9c672c6a53 --- /dev/null +++ b/layout/svg/SVGTextFrame.h @@ -0,0 +1,605 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_SVGTEXTFRAME_H +#define MOZILLA_SVGTEXTFRAME_H + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/2D.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "gfxTextRun.h" +#include "nsAutoPtr.h" +#include "nsIContent.h" // for GetContent +#include "nsStubMutationObserver.h" +#include "nsSVGContainerFrame.h" + +class gfxContext; +class nsDisplaySVGText; +class SVGTextFrame; +class nsTextFrame; + +namespace mozilla { + +class CharIterator; +class nsISVGPoint; +class TextFrameIterator; +class TextNodeCorrespondenceRecorder; +struct TextRenderedRun; +class TextRenderedRunIterator; + +namespace dom { +class SVGIRect; +class SVGPathElement; +} // namespace dom + +/** + * Information about the positioning for a single character in an SVG <text> + * element. + * + * During SVG text layout, we use infinity values to represent positions and + * rotations that are not explicitly specified with x/y/rotate attributes. + */ +struct CharPosition +{ + CharPosition() + : mAngle(0), + mHidden(false), + mUnaddressable(false), + mClusterOrLigatureGroupMiddle(false), + mRunBoundary(false), + mStartOfChunk(false) + { + } + + CharPosition(gfxPoint aPosition, double aAngle) + : mPosition(aPosition), + mAngle(aAngle), + mHidden(false), + mUnaddressable(false), + mClusterOrLigatureGroupMiddle(false), + mRunBoundary(false), + mStartOfChunk(false) + { + } + + static CharPosition Unspecified(bool aUnaddressable) + { + CharPosition cp(UnspecifiedPoint(), UnspecifiedAngle()); + cp.mUnaddressable = aUnaddressable; + return cp; + } + + bool IsAngleSpecified() const + { + return mAngle != UnspecifiedAngle(); + } + + bool IsXSpecified() const + { + return mPosition.x != UnspecifiedCoord(); + } + + bool IsYSpecified() const + { + return mPosition.y != UnspecifiedCoord(); + } + + gfxPoint mPosition; + double mAngle; + + // not displayed due to falling off the end of a <textPath> + bool mHidden; + + // skipped in positioning attributes due to being collapsed-away white space + bool mUnaddressable; + + // a preceding character is what positioning attributes address + bool mClusterOrLigatureGroupMiddle; + + // rendering is split here since an explicit position or rotation was given + bool mRunBoundary; + + // an anchored chunk begins here + bool mStartOfChunk; + +private: + static gfxFloat UnspecifiedCoord() + { + return std::numeric_limits<gfxFloat>::infinity(); + } + + static double UnspecifiedAngle() + { + return std::numeric_limits<double>::infinity(); + } + + static gfxPoint UnspecifiedPoint() + { + return gfxPoint(UnspecifiedCoord(), UnspecifiedCoord()); + } +}; + +/** + * A runnable to mark glyph positions as needing to be recomputed + * and to invalid the bounds of the SVGTextFrame frame. + */ +class GlyphMetricsUpdater : public Runnable { +public: + NS_DECL_NSIRUNNABLE + explicit GlyphMetricsUpdater(SVGTextFrame* aFrame) : mFrame(aFrame) { } + static void Run(SVGTextFrame* aFrame); + void Revoke() { mFrame = nullptr; } +private: + SVGTextFrame* mFrame; +}; + +} // namespace mozilla + +/** + * Frame class for SVG <text> elements. + * + * An SVGTextFrame manages SVG text layout, painting and interaction for + * all descendent text content elements. The frame tree will look like this: + * + * SVGTextFrame -- for <text> + * <anonymous block frame> + * ns{Block,Inline,Text}Frames -- for text nodes, <tspan>s, <a>s, etc. + * + * SVG text layout is done by: + * + * 1. Reflowing the anonymous block frame. + * 2. Inspecting the (app unit) positions of the glyph for each character in + * the nsTextFrames underneath the anonymous block frame. + * 3. Determining the (user unit) positions for each character in the <text> + * using the x/y/dx/dy/rotate attributes on all the text content elements, + * and using the step 2 results to fill in any gaps. + * 4. Applying any other SVG specific text layout (anchoring and text paths) + * to the positions computed in step 3. + * + * Rendering of the text is done by splitting up each nsTextFrame into ranges + * that can be contiguously painted. (For example <text x="10 20">abcd</text> + * would have two contiguous ranges: one for the "a" and one for the "bcd".) + * Each range is called a "text rendered run", represented by a TextRenderedRun + * object. The TextRenderedRunIterator class performs that splitting and + * returns a TextRenderedRun for each bit of text to be painted separately. + * + * Each rendered run is painted by calling nsTextFrame::PaintText. If the text + * formatting is simple enough (solid fill, no stroking, etc.), PaintText will + * itself do the painting. Otherwise, a DrawPathCallback is passed to + * PaintText so that we can fill the text geometry with SVG paint servers. + */ +class SVGTextFrame final : public nsSVGDisplayContainerFrame +{ + friend nsIFrame* + NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + friend class mozilla::CharIterator; + friend class mozilla::GlyphMetricsUpdater; + friend class mozilla::TextFrameIterator; + friend class mozilla::TextNodeCorrespondenceRecorder; + friend struct mozilla::TextRenderedRun; + friend class mozilla::TextRenderedRunIterator; + friend class MutationObserver; + friend class nsDisplaySVGText; + + typedef gfxTextRun::Range Range; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Path Path; + typedef mozilla::gfx::Point Point; + typedef mozilla::image::DrawResult DrawResult; + +protected: + explicit SVGTextFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) + , mFontSizeScaleFactor(1.0f) + , mLastContextScale(1.0f) + , mLengthAdjustScaleFactor(1.0f) + { + AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); + } + + ~SVGTextFrame() {} + +public: + NS_DECL_QUERYFRAME_TARGET(SVGTextFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame: + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual nsresult AttributeChanged(int32_t aNamespaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual nsContainerFrame* GetContentInsertionFrame() override + { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgTextFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGText"), aResult); + } +#endif + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + /** + * Finds the nsTextFrame for the closest rendered run to the specified point. + */ + virtual void FindCloserFrameForSelection(nsPoint aPoint, + FrameWithDistance* aCurrentBestFrame) override; + + + + // nsISVGChildFrame interface: + virtual void NotifySVGChanged(uint32_t aFlags) override; + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) override; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + virtual void ReflowSVG() override; + virtual nsRect GetCoveredRegion() override; + virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + // SVG DOM text methods: + uint32_t GetNumberOfChars(nsIContent* aContent); + float GetComputedTextLength(nsIContent* aContent); + nsresult SelectSubString(nsIContent* aContent, uint32_t charnum, uint32_t nchars); + nsresult GetSubStringLength(nsIContent* aContent, uint32_t charnum, + uint32_t nchars, float* aResult); + int32_t GetCharNumAtPosition(nsIContent* aContent, mozilla::nsISVGPoint* point); + + nsresult GetStartPositionOfChar(nsIContent* aContent, uint32_t aCharNum, + mozilla::nsISVGPoint** aResult); + nsresult GetEndPositionOfChar(nsIContent* aContent, uint32_t aCharNum, + mozilla::nsISVGPoint** aResult); + nsresult GetExtentOfChar(nsIContent* aContent, uint32_t aCharNum, + mozilla::dom::SVGIRect** aResult); + nsresult GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum, + float* aResult); + + // SVGTextFrame methods: + + /** + * Handles a base or animated attribute value change to a descendant + * text content element. + */ + void HandleAttributeChangeInDescendant(mozilla::dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute); + + /** + * Schedules mPositions to be recomputed and the covered region to be + * updated. + */ + void NotifyGlyphMetricsChange(); + + /** + * Calls ScheduleReflowSVGNonDisplayText if this is a non-display frame, + * and nsSVGUtils::ScheduleReflowSVG otherwise. + */ + void ScheduleReflowSVG(); + + /** + * Reflows the anonymous block frame of this non-display SVGTextFrame. + * + * When we are under nsSVGDisplayContainerFrame::ReflowSVG, we need to + * reflow any SVGTextFrame frames in the subtree in case they are + * being observed (by being for example in a <mask>) and the change + * that caused the reflow would not already have caused a reflow. + * + * Note that displayed SVGTextFrames are reflowed as needed, when PaintSVG + * is called or some SVG DOM method is called on the element. + */ + void ReflowSVGNonDisplayText(); + + /** + * This is a function that behaves similarly to nsSVGUtils::ScheduleReflowSVG, + * but which will skip over any ancestor non-display container frames on the + * way to the nsSVGOuterSVGFrame. It exists for the situation where a + * non-display <text> element has changed and needs to ensure ReflowSVG will + * be called on its closest display container frame, so that + * nsSVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on + * it. + * + * We have to do this in two cases: in response to a style change on a + * non-display <text>, where aReason will be eStyleChange (the common case), + * and also in response to glyphs changes on non-display <text> (i.e., + * animated SVG-in-OpenType glyphs), in which case aReason will be eResize, + * since layout doesn't need to be recomputed. + */ + void ScheduleReflowSVGNonDisplayText(nsIPresShell::IntrinsicDirty aReason); + + /** + * Updates the mFontSizeScaleFactor value by looking at the range of + * font-sizes used within the <text>. + * + * @return Whether mFontSizeScaleFactor changed. + */ + bool UpdateFontSizeScaleFactor(); + + double GetFontSizeScaleFactor() const; + + /** + * Takes a point from the <text> element's user space and + * converts it to the appropriate frame user space of aChildFrame, + * according to which rendered run the point hits. + */ + Point TransformFramePointToTextChild(const Point& aPoint, + nsIFrame* aChildFrame); + + /** + * Takes a rectangle, aRect, in the <text> element's user space, and + * returns a rectangle in aChildFrame's frame user space that + * covers intersections of aRect with each rendered run for text frames + * within aChildFrame. + */ + gfxRect TransformFrameRectToTextChild(const gfxRect& aRect, + nsIFrame* aChildFrame); + + /** + * Takes an app unit rectangle in the coordinate space of a given descendant + * frame of this frame, and returns a rectangle in the <text> element's user + * space that covers all parts of rendered runs that intersect with the + * rectangle. + */ + gfxRect TransformFrameRectFromTextChild(const nsRect& aRect, + nsIFrame* aChildFrame); + +private: + /** + * Mutation observer used to watch for text positioning attribute changes + * on descendent text content elements (like <tspan>s). + */ + class MutationObserver final : public nsStubMutationObserver { + public: + explicit MutationObserver(SVGTextFrame* aFrame) + : mFrame(aFrame) + { + MOZ_ASSERT(mFrame, "MutationObserver needs a non-null frame"); + mFrame->GetContent()->AddMutationObserver(this); + } + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + + private: + ~MutationObserver() + { + mFrame->GetContent()->RemoveMutationObserver(this); + } + + SVGTextFrame* const mFrame; + }; + + /** + * Reflows the anonymous block child if it is dirty or has dirty + * children, or if the SVGTextFrame itself is dirty. + */ + void MaybeReflowAnonymousBlockChild(); + + /** + * Performs the actual work of reflowing the anonymous block child. + */ + void DoReflow(); + + /** + * Recomputes mPositions by calling DoGlyphPositioning if this information + * is out of date. + */ + void UpdateGlyphPositioning(); + + /** + * Populates mPositions with positioning information for each character + * within the <text>. + */ + void DoGlyphPositioning(); + + /** + * Converts the specified index into mPositions to an addressable + * character index (as can be used with the SVG DOM text methods) + * relative to the specified text child content element. + * + * @param aIndex The global character index. + * @param aContent The descendant text child content element that + * the returned addressable index will be relative to; null + * means the same as the <text> element. + * @return The addressable index, or -1 if the index cannot be + * represented as an addressable index relative to aContent. + */ + int32_t + ConvertTextElementCharIndexToAddressableIndex(int32_t aIndex, + nsIContent* aContent); + + /** + * Recursive helper for ResolvePositions below. + * + * @param aContent The current node. + * @param aIndex (in/out) The current character index. + * @param aInTextPath Whether we are currently under a <textPath> element. + * @param aForceStartOfChunk (in/out) Whether the next character we find + * should start a new anchored chunk. + * @param aDeltas (in/out) Receives the resolved dx/dy values for each + * character. + * @return false if we discover that mPositions did not have enough + * elements; true otherwise. + */ + bool ResolvePositionsForNode(nsIContent* aContent, uint32_t& aIndex, + bool aInTextPath, bool& aForceStartOfChunk, + nsTArray<gfxPoint>& aDeltas); + + /** + * Initializes mPositions with character position information based on + * x/y/rotate attributes, leaving unspecified values in the array if a position + * was not given for that character. Also fills aDeltas with values based on + * dx/dy attributes. + * + * @param aDeltas (in/out) Receives the resolved dx/dy values for each + * character. + * @param aRunPerGlyph Whether mPositions should record that a new run begins + * at each glyph. + * @return false if we did not record any positions (due to having no + * displayed characters) or if we discover that mPositions did not have + * enough elements; true otherwise. + */ + bool ResolvePositions(nsTArray<gfxPoint>& aDeltas, bool aRunPerGlyph); + + /** + * Determines the position, in app units, of each character in the <text> as + * laid out by reflow, and appends them to aPositions. Any characters that + * are undisplayed or trimmed away just get the last position. + */ + void DetermineCharPositions(nsTArray<nsPoint>& aPositions); + + /** + * Sets mStartOfChunk to true for each character in mPositions that starts a + * line of text. + */ + void AdjustChunksForLineBreaks(); + + /** + * Adjusts recorded character positions in mPositions to account for glyph + * boundaries. Four things are done: + * + * 1. mClusterOrLigatureGroupMiddle is set to true for all such characters. + * + * 2. Any run and anchored chunk boundaries that begin in the middle of a + * cluster/ligature group get moved to the start of the next + * cluster/ligature group. + * + * 3. The position of any character in the middle of a cluster/ligature + * group is updated to take into account partial ligatures and any + * rotation the glyph as a whole has. (The values that come out of + * DetermineCharPositions which then get written into mPositions in + * ResolvePositions store the same position value for each part of the + * ligature.) + * + * 4. The rotation of any character in the middle of a cluster/ligature + * group is set to the rotation of the first character. + */ + void AdjustPositionsForClusters(); + + /** + * Updates the character positions stored in mPositions to account for + * text anchoring. + */ + void DoAnchoring(); + + /** + * Updates character positions in mPositions for those characters inside a + * <textPath>. + */ + void DoTextPathLayout(); + + /** + * Returns whether we need to render the text using + * nsTextFrame::DrawPathCallbacks rather than directly painting + * the text frames. + * + * @param aShouldPaintSVGGlyphs (out) Whether SVG glyphs in the text + * should be painted. + */ + bool ShouldRenderAsPath(nsTextFrame* aFrame, bool& aShouldPaintSVGGlyphs); + + // Methods to get information for a <textPath> frame. + mozilla::dom::SVGPathElement* + GetTextPathPathElement(nsIFrame* aTextPathFrame); + already_AddRefed<Path> GetTextPath(nsIFrame* aTextPathFrame); + gfxFloat GetOffsetScale(nsIFrame* aTextPathFrame); + gfxFloat GetStartOffset(nsIFrame* aTextPathFrame); + + /** + * The MutationObserver we have registered for the <text> element subtree. + */ + RefPtr<MutationObserver> mMutationObserver; + + /** + * Cached canvasTM value. + */ + nsAutoPtr<gfxMatrix> mCanvasTM; + + /** + * The number of characters in the DOM after the final nsTextFrame. For + * example, with + * + * <text>abcd<tspan display="none">ef</tspan></text> + * + * mTrailingUndisplayedCharacters would be 2. + */ + uint32_t mTrailingUndisplayedCharacters; + + /** + * Computed position information for each DOM character within the <text>. + */ + nsTArray<mozilla::CharPosition> mPositions; + + /** + * mFontSizeScaleFactor is used to cause the nsTextFrames to create text + * runs with a font size different from the actual font-size property value. + * This is used so that, for example with: + * + * <svg> + * <g transform="scale(2)"> + * <text font-size="10">abc</text> + * </g> + * </svg> + * + * a font size of 20 would be used. It's preferable to use a font size that + * is identical or close to the size that the text will appear on the screen, + * because at very small or large font sizes, text metrics will be computed + * differently due to the limited precision that text runs have. + * + * mFontSizeScaleFactor is the amount the actual font-size property value + * should be multiplied by to cause the text run font size to (a) be within a + * "reasonable" range, and (b) be close to the actual size to be painted on + * screen. (The "reasonable" range as determined by some #defines in + * SVGTextFrame.cpp is 8..200.) + */ + float mFontSizeScaleFactor; + + /** + * The scale of the context that we last used to compute mFontSizeScaleFactor. + * We record this so that we can tell when our scale transform has changed + * enough to warrant reflowing the text. + */ + float mLastContextScale; + + /** + * The amount that we need to scale each rendered run to account for + * lengthAdjust="spacingAndGlyphs". + */ + float mLengthAdjustScaleFactor; +}; + +#endif diff --git a/layout/svg/SVGViewFrame.cpp b/layout/svg/SVGViewFrame.cpp new file mode 100644 index 0000000000..7ffa4824f7 --- /dev/null +++ b/layout/svg/SVGViewFrame.cpp @@ -0,0 +1,127 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsFrame.h" +#include "nsGkAtoms.h" +#include "nsSVGOuterSVGFrame.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGViewElement.h" + +using namespace mozilla::dom; + +/** + * While views are not directly rendered in SVG they can be linked to + * and thereby override attributes of an <svg> element via a fragment + * identifier. The SVGViewFrame class passes on any attribute changes + * the view receives to the overridden <svg> element (if there is one). + **/ +class SVGViewFrame : public nsFrame +{ + friend nsIFrame* + NS_NewSVGViewFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit SVGViewFrame(nsStyleContext* aContext) + : nsFrame(aContext) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGView"), aResult); + } +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgFELeafFrame + */ + virtual nsIAtom* GetType() const override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override { + // We don't maintain a visual overflow rect + return false; + } +}; + +nsIFrame* +NS_NewSVGViewFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) SVGViewFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(SVGViewFrame) + +#ifdef DEBUG +void +SVGViewFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::view), + "Content is not an SVG view"); + + nsFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +SVGViewFrame::GetType() const +{ + return nsGkAtoms::svgViewFrame; +} + +nsresult +SVGViewFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + // Ignore zoomAndPan as it does not cause the <svg> element to re-render + + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::preserveAspectRatio || + aAttribute == nsGkAtoms::viewBox || + aAttribute == nsGkAtoms::viewTarget)) { + + nsSVGOuterSVGFrame *outerSVGFrame = nsSVGUtils::GetOuterSVGFrame(this); + NS_ASSERTION(outerSVGFrame->GetContent()->IsSVGElement(nsGkAtoms::svg), + "Expecting an <svg> element"); + + SVGSVGElement* svgElement = + static_cast<SVGSVGElement*>(outerSVGFrame->GetContent()); + + nsAutoString viewID; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, viewID); + + if (svgElement->IsOverriddenBy(viewID)) { + // We're the view that's providing overrides, so pretend that the frame + // we're overriding was updated. + outerSVGFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); + } + } + + return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} diff --git a/layout/svg/crashtests/1016145.svg b/layout/svg/crashtests/1016145.svg new file mode 100644 index 0000000000..5c362a17e1 --- /dev/null +++ b/layout/svg/crashtests/1016145.svg @@ -0,0 +1,5 @@ +<!-- svg mime type but html root --> +<html xmlns="http://www.w3.org/1999/xhtml"> + <body style="display: table-column-group;" /> +</html> + diff --git a/layout/svg/crashtests/1028512.svg b/layout/svg/crashtests/1028512.svg new file mode 100644 index 0000000000..0c9458b478 --- /dev/null +++ b/layout/svg/crashtests/1028512.svg @@ -0,0 +1,15 @@ +<!-- {lower,upper}-{roman,alpha} in svg --> +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ + +function boom() +{ + document.documentElement.style.transition = "2s"; + document.documentElement.style.listStyleType = "lower-roman"; +} + +window.addEventListener("load", boom, false); + +]]> +</script></svg> diff --git a/layout/svg/crashtests/1140080-1.svg b/layout/svg/crashtests/1140080-1.svg new file mode 100644 index 0000000000..d424562468 --- /dev/null +++ b/layout/svg/crashtests/1140080-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> + +window.addEventListener("load", function() { + var stop = document.getElementsByTagName("stop")[0]; + stop.setAttribute("offset", "0"); +}, false); + +</script> +<stop/> +</svg> diff --git a/layout/svg/crashtests/1149542-1.svg b/layout/svg/crashtests/1149542-1.svg new file mode 100644 index 0000000000..7353f12775 --- /dev/null +++ b/layout/svg/crashtests/1149542-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <style> + text { white-space: pre } + text::first-letter { color: red; } + tspan { display: none } + </style> + <text textLength="64"> +<tspan>a</tspan>b</text> +</svg> diff --git a/layout/svg/crashtests/1156581-1.svg b/layout/svg/crashtests/1156581-1.svg new file mode 100644 index 0000000000..97e5fb1ca9 --- /dev/null +++ b/layout/svg/crashtests/1156581-1.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="filter: url(#a); clip: rect(0px, 4rem, 2px, 2px);"> + <script> + function boom() + { + document.getElementById("a").style.overflow = "hidden"; + document.documentElement.style.fontSize = "10px"; + } + window.addEventListener("load", boom, false); + </script> + + <set id="a"/> +</svg> diff --git a/layout/svg/crashtests/1182496-1.html b/layout/svg/crashtests/1182496-1.html new file mode 100644 index 0000000000..1d95905a2d --- /dev/null +++ b/layout/svg/crashtests/1182496-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> + <script> + function tweak(){ + document.body.innerHTML="fuzz" + } + </script> +</head> +<body onload="tweak()"> + <svg xmlns="http://www.w3.org/2000/svg"> + <text> + <foreignObject requiredFeatures="foo"> + <svg style="position: absolute;"/> + </foreignObject> + </text> + </svg> +</body> +</html> + + diff --git a/layout/svg/crashtests/1209525-1.svg b/layout/svg/crashtests/1209525-1.svg new file mode 100644 index 0000000000..21134df33b --- /dev/null +++ b/layout/svg/crashtests/1209525-1.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" + width="0" height="0"> + + <rect width="10" height="10" stroke="black" + vector-effect="non-scaling-stroke" /> + +</svg> diff --git a/layout/svg/crashtests/1223281-1.svg b/layout/svg/crashtests/1223281-1.svg new file mode 100644 index 0000000000..b548a9a600 --- /dev/null +++ b/layout/svg/crashtests/1223281-1.svg @@ -0,0 +1,24 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ + +function forceFrameConstruction() +{ + document.documentElement.getBoundingClientRect(); +} + +function boom() +{ + document.documentElement.style.overflow = "scroll"; + forceFrameConstruction() + document.documentElement.style.visibility = "visible"; +} + +window.addEventListener("load", boom, false); + +]]> +</script> + +<rect style="perspective: 10em;" /> +</svg> + diff --git a/layout/svg/crashtests/220165-1.svg b/layout/svg/crashtests/220165-1.svg new file mode 100644 index 0000000000..0335f78d41 --- /dev/null +++ b/layout/svg/crashtests/220165-1.svg @@ -0,0 +1,21 @@ +<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml" height="500"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.documentElement.getBoundingClientRect();
+ document.getElementById('x').textContent = 'New text'">
+
+ <foreignObject x="200" y="180" width="100" height="50" >
+ <html:button id="x">Old long long long text</html:button>
+ </foreignObject>
+
+ <g transform="rotate(10) translate(-100) scale(0.8)">
+ <polygon style="fill:red; fill-opacity:0.5;"
+ points="350, 75 379,161 469,161 397,215
+ 423,301 350,250 277,301 303,215
+ 231,161 321,161" />
+
+ </g>
+
+</svg>
diff --git a/layout/svg/crashtests/267650-1.svg b/layout/svg/crashtests/267650-1.svg new file mode 100644 index 0000000000..3e9c7ecc01 --- /dev/null +++ b/layout/svg/crashtests/267650-1.svg @@ -0,0 +1,4 @@ +<?xml version='1.0'?>
+<svg xmlns='http://www.w3.org/2000/svg'>
+ <text fill='none' stroke='black'>TESTCASE</text>
+</svg>
diff --git a/layout/svg/crashtests/294022-1.svg b/layout/svg/crashtests/294022-1.svg new file mode 100644 index 0000000000..f30b484c83 --- /dev/null +++ b/layout/svg/crashtests/294022-1.svg @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + > + + <g> + <clipPath id="action_box_cp"> + <rect width="100" height="46"/> + </clipPath> + <text style="clip-path:url(#action_box_cp); " y="10" id="action_boxtext" pointer-events="none" class="TextBoxText"> + <tspan x="0" dy="0">Action</tspan> + </text> + </g> + +</svg> diff --git a/layout/svg/crashtests/307314-1.svg b/layout/svg/crashtests/307314-1.svg new file mode 100644 index 0000000000..5c538df88d --- /dev/null +++ b/layout/svg/crashtests/307314-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait" onload="setTimeout(function() { var g = document.getElementById('N1'), h = document.getElementById('N2'); g.appendChild(h); document.documentElement.removeAttribute("class"); }, 20);"> + + <text id="N1"/> + <text id="N2"> + <tspan> + <textPath/> + </tspan> + </text> +</svg> diff --git a/layout/svg/crashtests/308615-1.svg b/layout/svg/crashtests/308615-1.svg new file mode 100644 index 0000000000..fe5391de38 --- /dev/null +++ b/layout/svg/crashtests/308615-1.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + + <pattern id="pattern1"> + <rect style="fill:url(#pattern1);"/> + </pattern> + + <rect style="fill:url(#pattern1);" /> + +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/308917-1.svg b/layout/svg/crashtests/308917-1.svg new file mode 100644 index 0000000000..7d0ae44f74 --- /dev/null +++ b/layout/svg/crashtests/308917-1.svg @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<svg version="1.1" baseProfile="basic" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 480 360" class="reftest-wait" onload="first();"> + +<script><![CDATA[ + +function first() +{ + document.getElementById("z").appendChild(document.getElementById("pat1")); + setTimeout(second, 30); +} + +function second() +{ + document.getElementById("pat4").appendChild(document.getElementById("z")); + document.documentElement.removeAttribute("class"); +} + +]]></script> + + + <pattern patternUnits="userSpaceOnUse" id="pat1" x="10" y="10" width="20" height="20"> + <rect x="5" y="5" width="10" height="10" fill="red" /> + <rect x="10" y="10" width="10" height="10" fill="green" /> + </pattern> + <rect x="25" y="10" width="430" height="60" stroke="black" fill="url(#pat1)" /> + + <pattern patternUnits="userSpaceOnUse" id="pat4" x="0" y="0" width="20" height="10"> + <rect x="0" y="0" width="10" height="10" fill="red" /> + <rect x="10" y="0" width="10" height="10" fill="blue" /> + </pattern> + <text font-family="Arial" font-size="40" fill="none" stroke="url(#pat4)" stroke-width="2" x="25" y="275" id="z">Pattern on stroke</text> + +</svg> + diff --git a/layout/svg/crashtests/310436-1.svg b/layout/svg/crashtests/310436-1.svg new file mode 100644 index 0000000000..e6dd5680ce --- /dev/null +++ b/layout/svg/crashtests/310436-1.svg @@ -0,0 +1,28 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"><script><![CDATA[ + +function init() { + var docElt = document.documentElement; + var div1 = document.getElementById("div1"); + var div2 = document.getElementById("div2"); + var textNode = div2.childNodes[0]; + + function first() + { + docElt.appendChild(div2); + div2.appendChild(div1); + } + + function second() + { + div2.appendChild(div1); + div1.appendChild(textNode); + document.documentElement.removeAttribute("class"); + } + + first(); + setTimeout(second, 30); +} + +window.addEventListener("load", init, false); + +]]></script><div xmlns="http://www.w3.org/1999/xhtml" id="div1"><div id="div2">A Z</div></div></svg> diff --git a/layout/svg/crashtests/310638.svg b/layout/svg/crashtests/310638.svg new file mode 100644 index 0000000000..e5ee30fb2c --- /dev/null +++ b/layout/svg/crashtests/310638.svg @@ -0,0 +1,35 @@ +<svg xmlns="http://www.w3.org/2000/svg"><div xmlns='http://www.w3.org/1999/xhtml' id="div1">
+<div id="div2">bar</div> +</div>
+<script><![CDATA[ + +function init() +{ + var div2 = document.getElementById("div2"); + var div1 = document.getElementById("div1"); + var docElt = document.documentElement; + var titleText = document.createTextNode("foo baz"); + + docElt.appendChild(div2);
div2.appendChild(titleText); + + function second () + { + div2.appendChild(div1); + removeNode(titleText); + removeNode(div2); + } + + setTimeout(second, 0); +} + + +function removeNode(q1) { q1.parentNode.removeChild(q1); } + + +setTimeout(init, 0); + + +]]></script> + + +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/313737-1.xml b/layout/svg/crashtests/313737-1.xml new file mode 100644 index 0000000000..93421f6077 --- /dev/null +++ b/layout/svg/crashtests/313737-1.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?> +<!DOCTYPE xhtml PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd" [ +]> + +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xlink="http://www.w3.org/1999/xlink"> + <head> + <title>bug 313737</title> + </head> + <body> + + <svg:svg style="position:fixed;"> + <input type="text" style="position:absolute;" /> + </svg:svg> + + </body> +</html> diff --git a/layout/svg/crashtests/314244-1.xul b/layout/svg/crashtests/314244-1.xul new file mode 100644 index 0000000000..e801ca70e8 --- /dev/null +++ b/layout/svg/crashtests/314244-1.xul @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=314244 --> +<!-- Just checking for lack of crash, nothing more --> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="GMail" + width="300" height="300" + screenX="10" screenY="10"> + + <hbox> + <svg version="1.0" + xmlns="http://www.w3.org/2000/svg" + width="100px" height="100px" + id="back-button" + class="nav-button" + style="display: -moz-box;"> + <rect x="10" y="10" width="80" height="80" fill="blue" /> + </svg> + <spacer flex="1" /> + </hbox> + + <spacer flex="1" /> + +</window> + diff --git a/layout/svg/crashtests/322185-1.svg b/layout/svg/crashtests/322185-1.svg new file mode 100644 index 0000000000..38958c0b41 --- /dev/null +++ b/layout/svg/crashtests/322185-1.svg @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg"> + <g id="g" style="display: -moz-grid-line; overflow: hidden;"> + <circle /> + </g> +</svg> diff --git a/layout/svg/crashtests/322215-1.svg b/layout/svg/crashtests/322215-1.svg new file mode 100644 index 0000000000..f872fbcd8f --- /dev/null +++ b/layout/svg/crashtests/322215-1.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" +"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- 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/. --> +<!-- +Copyright Georgi Guninski +--> + + +<svg width="100%" height="100%" version="1.1" +xmlns="http://www.w3.org/2000/svg"> + +<defs> +<filter id="MyFilter" filterUnits="userSpaceOnUse" +x="0" y="0" width="32769" height="32769"> + +<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/> + +</filter> +</defs> + +<rect x="1" y="1" width="198" height="118" fill="#cccccc" /> + +<g filter="url(#MyFilter)"> +<text fill="#FFFFFF" stroke="black" font-size="45" +x="42" y="42">Feck b1ll</text> +</g> + +</svg> diff --git a/layout/svg/crashtests/323704-1.svg b/layout/svg/crashtests/323704-1.svg new file mode 100644 index 0000000000..13b8d52243 --- /dev/null +++ b/layout/svg/crashtests/323704-1.svg @@ -0,0 +1,12 @@ +<svg xmlns='http://www.w3.org/2000/svg' +xmlns:xlink='http://www.w3.org/1999/xlink'> + + <clipPath id='clipPath_0'> + <rect x='10' y='10' width='25' height='25' rx='5' ry='5' fill='none' +clip-path='url(#clipPath_0)'/> + </clipPath> + + <rect x='5' y='5' width='35' height='35' fill='red' +clip-path='url(#clipPath_0)'/> + +</svg> diff --git a/layout/svg/crashtests/325427-1.svg b/layout/svg/crashtests/325427-1.svg new file mode 100644 index 0000000000..1f1c645251 --- /dev/null +++ b/layout/svg/crashtests/325427-1.svg @@ -0,0 +1,20 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <g id="module">
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#baseModule" />
+ <use xlink:href="#baseModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ </g>
+ <use id="baseModule" xlink:href="#module" />
+ <use id="extendsModule" xlink:href="#module" />
+ </defs>
+</svg>
diff --git a/layout/svg/crashtests/326495-1.svg b/layout/svg/crashtests/326495-1.svg new file mode 100644 index 0000000000..4748e31578 --- /dev/null +++ b/layout/svg/crashtests/326495-1.svg @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<svg width="10cm" height="5cm" viewBox="0 0 1000 500" + xmlns="http://www.w3.org/2000/svg" + version="1.1"> + + <script> + function init() + { + document.getElementsByTagName("rect")[0].style.display = "-moz-inline-stack"; + } + + window.addEventListener("load", init, false); + </script> + + <rect requiredFeatures="http://www.w3.org/TR/SVG11/feature#SVG-nonexistent-feature"/> +</svg> diff --git a/layout/svg/crashtests/326974-1.svg b/layout/svg/crashtests/326974-1.svg new file mode 100644 index 0000000000..750165b730 --- /dev/null +++ b/layout/svg/crashtests/326974-1.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?>
+ +<svg xmlns="http://www.w3.org/2000/svg">
+ +<script> + +function init() {
var n2 = document.getElementById("n2"); + var n3 = document.getElementById("n3"); + + n2.appendChild(n3); +}
+ +window.addEventListener("load", init, false); + +</script> + +
+<g id="n2">
<text id="n3" /> +</g>
+ +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/327706-1.svg b/layout/svg/crashtests/327706-1.svg new file mode 100644 index 0000000000..9aae909250 --- /dev/null +++ b/layout/svg/crashtests/327706-1.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<svg xmlns="http://www.w3.org/2000/svg"> + +<script> + +document.documentElement.unsuspendRedraw(6) + +</script> +</svg> diff --git a/layout/svg/crashtests/327709-1.svg b/layout/svg/crashtests/327709-1.svg new file mode 100644 index 0000000000..ce07114272 --- /dev/null +++ b/layout/svg/crashtests/327709-1.svg @@ -0,0 +1,17 @@ +<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=327709 --> +<!-- Just checking for absence of assertion, nothing more --> +<svg xmlns="http://www.w3.org/2000/svg"> + +<script> +function init() +{ + var apsl = document.getElementById("n126").animatedPathSegList; + apsl.appendItem(apsl.__proto__); +} +window.addEventListener("load", init, false); +</script> + +<path id="n126" d="M 270 60 L 320 60 L 320 110 Z"/> + +</svg> + diff --git a/layout/svg/crashtests/327711-1.svg b/layout/svg/crashtests/327711-1.svg new file mode 100644 index 0000000000..d919b866f6 --- /dev/null +++ b/layout/svg/crashtests/327711-1.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?>
+ +<svg xmlns="http://www.w3.org/2000/svg"> + +<script>
+ +function init() +{
+ document.documentElement.unsuspendRedrawAll(); + document.getElementsByTagName("text")[0].firstChild.data = "Quux"; +}
+ +window.addEventListener("load", init, false); + +</script> + +<text x="125" y="30">Foo</text>
+ +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/328137-1.svg b/layout/svg/crashtests/328137-1.svg new file mode 100644 index 0000000000..26190b1eb0 --- /dev/null +++ b/layout/svg/crashtests/328137-1.svg @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<svg xmlns="http://www.w3.org/2000/svg"> + +<script> + + +function init() +{ + x = document.getElementsByTagName("stop"); + x[0].appendChild(x[1]); +} + + +window.addEventListener("load", init, false); + +</script> + +<radialGradient> + <stop/> + <stop/> +</radialGradient> + +</svg> diff --git a/layout/svg/crashtests/329848-1.svg b/layout/svg/crashtests/329848-1.svg new file mode 100644 index 0000000000..ac4f0022e1 --- /dev/null +++ b/layout/svg/crashtests/329848-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg">
<polygon transform="?" points="100,100 200,100 150,200"/>
</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/337408-1.xul b/layout/svg/crashtests/337408-1.xul new file mode 100644 index 0000000000..f56c06ec94 --- /dev/null +++ b/layout/svg/crashtests/337408-1.xul @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=337408 --> +<!-- Just checking for lack of crash, nothing more --> +<window id="svg-in-xul-stack" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:svg="http://www.w3.org/2000/svg" + style="background-color:white;" + screenX="20" + screenY="20" + width="600" + height="400"> + + <stack> + <box flex="1"> + <label value="foo"/> + </box> + <svg:svg> + <svg:rect width="100%" height="100%" fill="red" fill-opacity="0.5"/> + </svg:svg> + </stack> +</window> diff --git a/layout/svg/crashtests/338301-1.xhtml b/layout/svg/crashtests/338301-1.xhtml new file mode 100644 index 0000000000..3367eedebb --- /dev/null +++ b/layout/svg/crashtests/338301-1.xhtml @@ -0,0 +1,13 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de"> +<body> + + <svg xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient> + <path/> + </linearGradient> + </defs> + </svg> + +</body> +</html> diff --git a/layout/svg/crashtests/338312-1.xhtml b/layout/svg/crashtests/338312-1.xhtml new file mode 100644 index 0000000000..5a751a2ae0 --- /dev/null +++ b/layout/svg/crashtests/338312-1.xhtml @@ -0,0 +1,28 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script> + + +function boom() +{ + document.getElementById("foo").appendChild(document.getElementById("bar")); +} + +window.addEventListener("load", boom, false); + +</script> +</head> + +<body> + + <div id="foo"></div> + + <svg xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="grad1"/> + </defs> + <rect id="bar" style="fill:url(#grad1);" /> + </svg> + +</body> +</html> diff --git a/layout/svg/crashtests/340083-1.svg b/layout/svg/crashtests/340083-1.svg new file mode 100644 index 0000000000..7f015b6efe --- /dev/null +++ b/layout/svg/crashtests/340083-1.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%"> + <defs> + <title> + <image x="30" y="0" width="190" height="190" xlink:href="../../../testing/crashtest/images/tree.gif"/> + </title> + </defs> +</svg> diff --git a/layout/svg/crashtests/340945-1.svg b/layout/svg/crashtests/340945-1.svg new file mode 100644 index 0000000000..01ac66fb33 --- /dev/null +++ b/layout/svg/crashtests/340945-1.svg @@ -0,0 +1,2 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="display: table;"> +</svg> diff --git a/layout/svg/crashtests/342923-1.html b/layout/svg/crashtests/342923-1.html new file mode 100644 index 0000000000..bed6e89791 --- /dev/null +++ b/layout/svg/crashtests/342923-1.html @@ -0,0 +1,23 @@ +<html> +<head> +<script> + +function boo() +{ + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + rect.setAttribute("stroke", "blue"); + + document.body.appendChild(rect); +} + +</script> +</head> + +<body class="bodytext" onload="boo();"> + +<div id="c1"></div> + +<p>In a debug trunk build from 2006-006-27, loading this page triggers an assertion. (It also triggers a CSS error in the console, but I think that's a known, separate bug.)</p> + +</body> +</html> diff --git a/layout/svg/crashtests/343221-1.xhtml b/layout/svg/crashtests/343221-1.xhtml new file mode 100644 index 0000000000..890b161dfe --- /dev/null +++ b/layout/svg/crashtests/343221-1.xhtml @@ -0,0 +1,20 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> +<head> +<script> + +function boo() +{ + document.getElementById("c").style.overflow = "hidden"; + document.documentElement.removeAttribute("class"); +} + +</script> +</head> +<body onload="setTimeout(boo, 30);"> + +<svg xmlns="http://www.w3.org/2000/svg"> + <circle id="c" cx="50" cy="50" r="20" /> +</svg> + +</body> +</html> diff --git a/layout/svg/crashtests/344749-1.svg b/layout/svg/crashtests/344749-1.svg new file mode 100644 index 0000000000..1a02d7c180 --- /dev/null +++ b/layout/svg/crashtests/344749-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + +<circle cx="6cm" cy="2cm" r="100" fill="red" transform="translate(0,50)" /> +<circle cx="6cm" cy="2cm" r="100" fill="blue" transform="translate(70,150)" /> +<circle cx="6cm" cy="2cm" r="100" fill="green" transform="translate(-70,150)" /> + +<rect id="rect1" fill="url(#pat0)"/> + +<pattern patternUnits="userSpaceOnUse" id="pat0" x="10" y="10" width="20" height="20">
<rect x="5" y="5" width="10" height="10" fill="red" />
<rect x="10" y="10" width="10" height="10" fill="green" />
</pattern>
+
</svg> diff --git a/layout/svg/crashtests/344887-1.svg b/layout/svg/crashtests/344887-1.svg new file mode 100644 index 0000000000..f0bd21c592 --- /dev/null +++ b/layout/svg/crashtests/344887-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(foo, 30);" class="reftest-wait"> + +<script> + +var SVG_NS = "http://www.w3.org/2000/svg"; + +function foo() +{ + var rect = document.createElementNS(SVG_NS, 'rect'); + rect.setAttribute('opacity', ".3"); + document.documentElement.appendChild(rect); + document.documentElement.removeAttribute("class"); +} + +</script> + + +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/344892-1.svg b/layout/svg/crashtests/344892-1.svg new file mode 100644 index 0000000000..a38d7eb40f --- /dev/null +++ b/layout/svg/crashtests/344892-1.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<text stroke-width="50%">foo</text> + +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/344898-1.svg b/layout/svg/crashtests/344898-1.svg new file mode 100644 index 0000000000..34c3f45a4e --- /dev/null +++ b/layout/svg/crashtests/344898-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(removeText, 30);" class="reftest-wait"> + + +<script> + +function removeText() +{ + var x = document.getElementById("textPath"); + x.removeChild(x.firstChild); + document.documentElement.removeAttribute("class"); +} + +</script> + + +<text x="30" y="30"><textPath id="textPath">Foo</textPath></text> + + +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/344904-1.svg b/layout/svg/crashtests/344904-1.svg new file mode 100644 index 0000000000..a2c8d07647 --- /dev/null +++ b/layout/svg/crashtests/344904-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait"> + +<script> + +function boom() +{ + document.getElementById("m").setAttribute("stroke-miterlimit", 1); + document.documentElement.removeAttribute("class"); +} + +</script> + + +<marker> + <path id="m" /> +</marker> + + +</svg> diff --git a/layout/svg/crashtests/345418-1.svg b/layout/svg/crashtests/345418-1.svg new file mode 100644 index 0000000000..2cf8b331fa --- /dev/null +++ b/layout/svg/crashtests/345418-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<tspan>arg</tspan> +
</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/348982-1.xhtml b/layout/svg/crashtests/348982-1.xhtml new file mode 100644 index 0000000000..ad0340689a --- /dev/null +++ b/layout/svg/crashtests/348982-1.xhtml @@ -0,0 +1,20 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + +<head> +<style> +#div, #svg { display: table; } +#g { display: inline; } +</style> +</head> + +<body> + <div id="div"> + <svg id="svg" xmlns="http://www.w3.org/2000/svg"> + <g id="g"> + <circle cx="6.5cm" cy="2cm" r="100" style="fill: blue;" /> + </g> + </svg> + </div> +</body> + +</html> diff --git a/layout/svg/crashtests/354777-1.xhtml b/layout/svg/crashtests/354777-1.xhtml new file mode 100644 index 0000000000..e82baf34c9 --- /dev/null +++ b/layout/svg/crashtests/354777-1.xhtml @@ -0,0 +1,28 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + +<head> +<script> + +var SVG_NS = "http://www.w3.org/2000/svg"; + +function boom() +{ + var svgElem = document.createElementNS(SVG_NS, "svg"); + var ellipse = document.createElementNS(SVG_NS, "ellipse"); + + svgElem.setAttribute("viewBox", "0 0 30 40"); + document.body.appendChild(svgElem); + document.body.appendChild(ellipse); + ellipse.appendChild(svgElem); + svgElem.removeAttribute("viewBox"); +} + +</script> +</head> + +<body onload="boom()"> + +</body> + +</html> + diff --git a/layout/svg/crashtests/359516-1.svg b/layout/svg/crashtests/359516-1.svg new file mode 100644 index 0000000000..c997eb3a85 --- /dev/null +++ b/layout/svg/crashtests/359516-1.svg @@ -0,0 +1,36 @@ +<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ onload="setTimeout(doStuff, 30);">
+
+<html:script style="display: none;" type="text/javascript">
+
+function doStuff()
+{
+ var svg = document.documentElement;
+ var ellipse = document.getElementById("ellipse");
+ var filter = document.getElementById("filter");
+
+ document.addEventListener("DOMNodeRemoved", foopy, false);
+ filter.removeChild(filter.firstChild);
+ document.removeEventListener("DOMNodeRemoved", foopy, false);
+
+ function foopy()
+ {
+ document.removeEventListener("DOMNodeRemoved", foopy, false);
+ svg.appendChild(filter);
+ }
+
+ // Needed for the crash, but not for the assertion.
+ svg.appendChild(ellipse);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<ellipse id="ellipse" cx="200" cy="150" rx="70" ry="40" style="filter: url(#filter);"/>
+
+<filter id="filter"> </filter>
+
+</svg>
diff --git a/layout/svg/crashtests/361015-1.svg b/layout/svg/crashtests/361015-1.svg new file mode 100644 index 0000000000..8ac4bc56f2 --- /dev/null +++ b/layout/svg/crashtests/361015-1.svg @@ -0,0 +1,33 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:html="http://www.w3.org/1999/xhtml" + class="reftest-wait" + onload="setTimeout(boom, 30)"> + +<html:script> +<![CDATA[ + +function boom() +{ + var grad = document.getElementById("grad"); + var g = document.getElementById("g"); + grad.appendChild(g); + g.removeAttribute("transform"); + document.documentElement.removeAttribute("class"); +} + +]]> +</html:script> + + + <g id="g" transform="translate(500,0)"> + <text x="25" y="85">Foo</text> + </g> + + + <linearGradient id="grad" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="1"> + <stop stop-color="blue" offset="0.2"/> + <stop stop-color="lime" offset="0.4"/> + </linearGradient> + + +</svg> diff --git a/layout/svg/crashtests/361587-1.svg b/layout/svg/crashtests/361587-1.svg new file mode 100644 index 0000000000..52bce9eda7 --- /dev/null +++ b/layout/svg/crashtests/361587-1.svg @@ -0,0 +1,31 @@ +<svg xmlns="http://www.w3.org/2000/svg" + onload="setTimeout(boom, 30);" + class="reftest-wait"> + +<script style="display: none" type="text/javascript"> +<![CDATA[ + +function boom() +{ + var oldGrad = document.getElementById("grad"); + oldGrad.parentNode.removeChild(oldGrad); + + var newGrad = document.createElementNS("http://www.w3.org/2000/svg", "radialGradient"); + newGrad.setAttribute("gradientUnits", "userSpaceOnUse"); + newGrad.setAttribute("id", "grad"); + + document.documentElement.appendChild(newGrad); + + document.documentElement.removeAttribute("class"); +} + +]]> +</script> + + <radialGradient id="grad" gradientUnits="userSpaceOnUse" cx="240" cy="210" r="220" fx="240" fy="210"> + <stop stop-color="yellow" offset="0"/> + <stop stop-color="green" offset="1"/> + </radialGradient> + <rect x="20" y="150" width="440" height="80" fill="url(#grad)" stroke-width="40"/> + +</svg> diff --git a/layout/svg/crashtests/363611-1.xhtml b/layout/svg/crashtests/363611-1.xhtml new file mode 100644 index 0000000000..6bc386bcdf --- /dev/null +++ b/layout/svg/crashtests/363611-1.xhtml @@ -0,0 +1,21 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<script style="display: none" type="text/javascript"> + +function boom() +{ + var fo = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject'); + document.getElementById("innerSVG").appendChild(fo); +} + +</script> + +</head> + +<body onload="boom();"> + +<svg xmlns="http://www.w3.org/2000/svg" ><linearGradient><svg id="innerSVG"></svg></linearGradient></svg> + +</body> +</html> diff --git a/layout/svg/crashtests/364688-1.svg b/layout/svg/crashtests/364688-1.svg new file mode 100644 index 0000000000..045061cd2f --- /dev/null +++ b/layout/svg/crashtests/364688-1.svg @@ -0,0 +1,34 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="setTimeout(boom, 30);" + class="reftest-wait"> + +<script> +function boom() +{ + document.getElementById("sss").removeAttribute('value'); + + document.documentElement.removeAttribute("class"); +} +</script> + + +<foreignObject width="500" height="500" y="300"> + +<div xmlns="http://www.w3.org/1999/xhtml"> + +<table border="1"> + <tr> + <td>Foo</td> + </tr> + <tr> + <td><input type="text" value="Baz" id="sss" /></td> + </tr> +</table> + +</div> + +</foreignObject> + + +</svg> diff --git a/layout/svg/crashtests/366956-1.svg b/layout/svg/crashtests/366956-1.svg new file mode 100644 index 0000000000..9836c7ea3a --- /dev/null +++ b/layout/svg/crashtests/366956-1.svg @@ -0,0 +1,61 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom1, 50);" class="reftest-wait"> + +<html:script> + +function boom1() +{ + document.getElementsByTagName("mi")[0].setAttribute('id', "ffff"); + + document.getElementById("fo").appendChild(document.createTextNode(" ")); + + setTimeout(boom2, 50); +} + +function boom2() +{ + var fodiv = document.getElementById("fodiv"); + fodiv.parentNode.removeChild(fodiv); + + document.documentElement.removeAttribute("class"); +} + +</html:script> + + + <g> + <foreignObject width="500" height="500" transform="scale(.7,.7)" id="fo" y="300"> + +<div id="fodiv" xmlns="http://www.w3.org/1999/xhtml"> + + + +<p>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99</p> + + + +<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +<mrow> + <mi>A</mi> +</mrow> +</math></div> + + +<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewbox="0 0 250 250" width="100" height="100"> + <style type="text/css"> + circle:hover {fill-opacity:0.9;} + </style> + + <g style="fill-opacity:0.7;" transform="scale(.2)"> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" /> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" /> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/> + </g> +</svg> + +</div> +</foreignObject> +</g> + + + +</svg> diff --git a/layout/svg/crashtests/366956-2.svg b/layout/svg/crashtests/366956-2.svg new file mode 100644 index 0000000000..a2ab21ed55 --- /dev/null +++ b/layout/svg/crashtests/366956-2.svg @@ -0,0 +1,61 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom1, 30);" class="reftest-wait"> + +<html:script> + +function boom1() +{ + document.getElementsByTagName("mi")[0].setAttribute('id', "ffff"); + + document.getElementById("fo").appendChild(document.createTextNode(" ")); + + boom2(); +} + +function boom2() +{ + var fodiv = document.getElementById("fodiv"); + fodiv.parentNode.removeChild(fodiv); + + document.documentElement.removeAttribute("class"); +} + +</html:script> + + + <g> + <foreignObject width="500" height="500" transform="scale(.7,.7)" id="fo" y="300"> + +<div id="fodiv" xmlns="http://www.w3.org/1999/xhtml"> + + + +<p>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99</p> + + + +<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +<mrow> + <mi>A</mi> +</mrow> +</math></div> + + +<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewbox="0 0 250 250" width="100" height="100"> + <style type="text/css"> + circle:hover {fill-opacity:0.9;} + </style> + + <g style="fill-opacity:0.7;" transform="scale(.2)"> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" /> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" /> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/> + </g> +</svg> + +</div> +</foreignObject> +</g> + + + +</svg> diff --git a/layout/svg/crashtests/367111-1.svg b/layout/svg/crashtests/367111-1.svg new file mode 100644 index 0000000000..dcf6a39bf7 --- /dev/null +++ b/layout/svg/crashtests/367111-1.svg @@ -0,0 +1,29 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="setTimeout(boom, 30);" + class="reftest-wait"> + +<html:script> + +function boom() +{ + document.getElementById("text").appendChild(document.getElementById("fo")); + + document.documentElement.removeAttribute("class"); +} + +</html:script> + +<defs> + <marker> + <text id="text">svg:text</text> + </marker> +</defs> + +<foreignObject id="fo"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>HTML in a foreignObject</p> + </div> +</foreignObject> + +</svg> diff --git a/layout/svg/crashtests/367368-1.xhtml b/layout/svg/crashtests/367368-1.xhtml new file mode 100644 index 0000000000..b9bcd3241b --- /dev/null +++ b/layout/svg/crashtests/367368-1.xhtml @@ -0,0 +1,12 @@ +<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=367368 --> +<!-- Just checking for crash, nothing more --> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <body> + + <svg xmlns="http://www.w3.org/2000/svg"> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black;" transform="translate(0,50)" /> + </svg> + + </body> +</html> diff --git a/layout/svg/crashtests/369233-1.svg b/layout/svg/crashtests/369233-1.svg new file mode 100644 index 0000000000..22f4aacb37 --- /dev/null +++ b/layout/svg/crashtests/369233-1.svg @@ -0,0 +1,33 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="setTimeout(boom, 200);" + class="reftest-wait"> + +<html:script> + +function boom() +{ + try { + document.getElementById("grad2").gradientUnits.baseVal = "y"; + } catch (e) { + } + + document.documentElement.removeAttribute("class"); +} + +</html:script> + + + +<radialGradient id="grad2" gradientUnits="userSpaceOnUse" cx="240" cy="210" r="220" fx="240" fy="210"> + <stop stop-color="black" offset="0"/> + <stop stop-color="yellow" offset="0.2"/> + <stop stop-color="red" offset="0.4"/> + <stop stop-color="blue" offset="0.6"/> + <stop stop-color="white" offset="0.8"/> + <stop stop-color="green" offset="1"/> +</radialGradient> + +<rect x="20" y="150" width="440" height="80" fill="url(#grad2)" stroke-width="40"/> + +</svg> diff --git a/layout/svg/crashtests/369438-1.svg b/layout/svg/crashtests/369438-1.svg new file mode 100644 index 0000000000..78bcb6b54d --- /dev/null +++ b/layout/svg/crashtests/369438-1.svg @@ -0,0 +1,24 @@ +<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom, 30);" class="reftest-wait"><html:script src="data:text/javascript,"></html:script><html:script>
+
+function boom()
+{
+ var defs = document.getElementById("defs");
+ defs.parentNode.removeChild(defs);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs id="defs">
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/369438-2.svg b/layout/svg/crashtests/369438-2.svg new file mode 100644 index 0000000000..92eea9ee0f --- /dev/null +++ b/layout/svg/crashtests/369438-2.svg @@ -0,0 +1,27 @@ +<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom, 30);" class="reftest-wait"><html:script>
+
+function boom()
+{
+ var defs = document.getElementById("defs");
+ var gb = document.getElementById("Gaussian_Blur");
+
+ defs.parentNode.removeChild(defs);
+ gb.removeChild(gb.firstChild); // remove a whitespace text node (!)
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs id="defs">
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/371463-1.xhtml b/layout/svg/crashtests/371463-1.xhtml new file mode 100644 index 0000000000..461fb27ba3 --- /dev/null +++ b/layout/svg/crashtests/371463-1.xhtml @@ -0,0 +1,8 @@ +<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<body>
+
+<select><svg:svg><svg:foreignObject/></svg:svg></select>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/371563-1.xhtml b/layout/svg/crashtests/371563-1.xhtml new file mode 100644 index 0000000000..0ebdc9bfa1 --- /dev/null +++ b/layout/svg/crashtests/371563-1.xhtml @@ -0,0 +1,32 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> +<head> +<script> + +function boom() +{ + document.getElementById("sdiv").style.overflow = "scroll"; + + document.documentElement.removeAttribute("class"); +} + + +</script> +</head> + +<body onload="setTimeout(boom, 30);"> + + <div id="sdiv" style="float: left;"> + + <svg xmlns="http://www.w3.org/2000/svg" height="400px" width="400px" + y="0.0000000" x="0.0000000" version="1.0" > + <defs> + <marker id="Arrow"/> + </defs> + <path style="marker-end:url(#Arrow)" + d="M 12.500000,200.00000 L 387.50000,200.00000" /> + </svg> + + </div> + +</body> +</html> diff --git a/layout/svg/crashtests/375775-1.svg b/layout/svg/crashtests/375775-1.svg new file mode 100644 index 0000000000..cd17c85a94 --- /dev/null +++ b/layout/svg/crashtests/375775-1.svg @@ -0,0 +1,23 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait"> + +<script type="text/javascript"> + +function boom() +{ + document.getElementById("filter").style.display = "none"; + document.getElementById("path").style.display = "none"; + + document.documentElement.removeAttribute("class"); +} + +</script> + +<filter id="filter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120" /> + +<g filter="url(#filter)"> + <path id="path" + fill="black" + d="M60,80 C30,80 30,40 60,40 L140,40 C170,40 170,80 140,80 z" /> +</g> + +</svg> diff --git a/layout/svg/crashtests/378716.svg b/layout/svg/crashtests/378716.svg new file mode 100644 index 0000000000..b6faa00284 --- /dev/null +++ b/layout/svg/crashtests/378716.svg @@ -0,0 +1,4 @@ +<svg width="100%" height="100%" x="0" y="0" viewBox="0 0 1 1" + xmlns="http://www.w3.org/2000/svg"> + <text id="text_1" x="0.5" y="0.5" font-size="0.05" fill="green">Okay Text</text> +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/380691-1.svg b/layout/svg/crashtests/380691-1.svg new file mode 100644 index 0000000000..ed28552633 --- /dev/null +++ b/layout/svg/crashtests/380691-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <mask id="m"/> + <foreignObject mask="url(#m)"/> +</svg> diff --git a/layout/svg/crashtests/384391-1.xhtml b/layout/svg/crashtests/384391-1.xhtml new file mode 100644 index 0000000000..12c657a48c --- /dev/null +++ b/layout/svg/crashtests/384391-1.xhtml @@ -0,0 +1,20 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" > +<head> +<script> + +function boom() +{ + var circle = document.getElementById("circle"); + document.removeChild(document.documentElement); + document.appendChild(circle); +} + +</script> +</head> + +<body onload="boom()"> + +<svg:circle id="circle" /> + +</body> +</html> diff --git a/layout/svg/crashtests/384499-1.svg b/layout/svg/crashtests/384499-1.svg new file mode 100644 index 0000000000..f448910008 --- /dev/null +++ b/layout/svg/crashtests/384499-1.svg @@ -0,0 +1,20 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"> + +<html:style> + #mathy { display: table} +</html:style> + +<foreignObject width="500" height="500" y="50"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>Foo</p> + <div> + <math xmlns="http://www.w3.org/1998/Math/MathML" id="mathy" display="block"> + <mrow> + <mi>x</mi> + </mrow> + </math> + </div> + </div> +</foreignObject> + +</svg> diff --git a/layout/svg/crashtests/384637-1.svg b/layout/svg/crashtests/384637-1.svg new file mode 100644 index 0000000000..263a2d556a --- /dev/null +++ b/layout/svg/crashtests/384637-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" baseProfile="basic" width="100%" height="100%" viewBox="0 0 480 360">
+
+ <mask id="mask1" maskUnits="userSpaceOnUse" x="60" y="50" width="100" height="60">
+ <rect x="60" y="50" width="100" height="60" fill="yellow" mask="url(#mask1)"/>
+ </mask>
+
+ <rect x="60" y="50" width="100" height="60" fill="lime" mask="url(#mask1)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/384728-1.svg b/layout/svg/crashtests/384728-1.svg new file mode 100644 index 0000000000..ccafc83706 --- /dev/null +++ b/layout/svg/crashtests/384728-1.svg @@ -0,0 +1,21 @@ +<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script>
+
+function boom()
+{
+ document.getElementById("thhh").setAttributeNS("http://www.w3.org/1999/xlink", 'href', '');
+}
+
+</script>
+
+ <defs>
+ <g id="ch" style="counter-reset: c;">
+ <rect x="75" y="0" width="75" height="75" fill="lightgreen" style="counter-increment: c;"/>
+ </g>
+
+ </defs>
+
+ <use id="thhh" x="0" y="0"><use xlink:href="#ch" x="0" y="0"/></use>
+
+</svg>
diff --git a/layout/svg/crashtests/385246-1.svg b/layout/svg/crashtests/385246-1.svg new file mode 100644 index 0000000000..cddad0c5e4 --- /dev/null +++ b/layout/svg/crashtests/385246-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<foreignObject x="100" y="100" width="-2" height="500"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>Foo</p> + </div> +</foreignObject> + +</svg> diff --git a/layout/svg/crashtests/385246-2.svg b/layout/svg/crashtests/385246-2.svg new file mode 100644 index 0000000000..c392f2fc8a --- /dev/null +++ b/layout/svg/crashtests/385246-2.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + +<script type="text/javascript" xlink:href="data:text/javascript,"></script> + + +<foreignObject width="-2" height="500" id="fo" x="300"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>Hi!!!</p> + </div> +</foreignObject> + + + + +</svg> diff --git a/layout/svg/crashtests/385552-1.svg b/layout/svg/crashtests/385552-1.svg new file mode 100644 index 0000000000..019e249d77 --- /dev/null +++ b/layout/svg/crashtests/385552-1.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<script> +document.createElementNS("http://www.w3.org/2000/svg", "svg").unsuspendRedrawAll(); +</script> diff --git a/layout/svg/crashtests/385552-2.svg b/layout/svg/crashtests/385552-2.svg new file mode 100644 index 0000000000..9a93d657fb --- /dev/null +++ b/layout/svg/crashtests/385552-2.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<script> +document.createElementNS("http://www.w3.org/2000/svg", "svg").suspendRedraw(3); +</script> diff --git a/layout/svg/crashtests/385840-1.svg b/layout/svg/crashtests/385840-1.svg new file mode 100644 index 0000000000..cf7ff6949c --- /dev/null +++ b/layout/svg/crashtests/385840-1.svg @@ -0,0 +1,20 @@ +<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script>
+
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+
+ var svgCircle = document.createElementNS(SVG_NS, 'circle');
+ var svgText = document.createElementNS(SVG_NS, 'text');
+ svgText.appendChild(document.createTextNode("foo"));
+ svgCircle.appendChild(svgText);
+
+ document.removeChild(document.documentElement);
+ document.appendChild(svgCircle);
+}
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/385852-1.svg b/layout/svg/crashtests/385852-1.svg new file mode 100644 index 0000000000..17ad99ca8f --- /dev/null +++ b/layout/svg/crashtests/385852-1.svg @@ -0,0 +1,34 @@ +<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30)" class="reftest-wait">
+
+<script>
+
+var originalRoot = document.documentElement;
+var svgCircle;
+
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+
+ var svgPolyline = document.createElementNS(SVG_NS, 'polyline');
+ svgCircle = document.createElementNS(SVG_NS, 'circle');
+
+ svgCircle.appendChild(svgPolyline);
+
+ document.removeChild(originalRoot);
+ document.appendChild(svgCircle);
+
+ setTimeout(restore, 30);
+}
+
+function restore()
+{
+ // We have to put it the root element back in the document so that reftest.js
+ // sees the event for the removal of class="reftest-wait"!
+ document.removeChild(svgCircle);
+ document.appendChild(originalRoot);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/386475-1.xhtml b/layout/svg/crashtests/386475-1.xhtml new file mode 100644 index 0000000000..4d1b9a2808 --- /dev/null +++ b/layout/svg/crashtests/386475-1.xhtml @@ -0,0 +1,24 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> +<head> +<script> +function boom() +{ + document.body.style.display = "table-header-group"; + document.getElementById("svg").setAttribute('height', 1); +} +</script> +</head> + +<body onload="boom();"> + +<svg:svg width="100%" height="100%" id="svg"> + <svg:g> + <svg:foreignObject width="8205em" height="100%"> + <span>hello</span> <span>world</span> + </svg:foreignObject> + </svg:g> +</svg:svg> + +</body> +</html> diff --git a/layout/svg/crashtests/386566-1.svg b/layout/svg/crashtests/386566-1.svg new file mode 100644 index 0000000000..fd44879c4f --- /dev/null +++ b/layout/svg/crashtests/386566-1.svg @@ -0,0 +1,21 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="5" onload="setTimeout(boom, 30);" class="reftest-wait"> + +<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content></content></binding></bindings> + +<script> +function boom() +{ + document.getElementById("rr").style.MozBinding = "url('#foo')"; + document.documentElement.setAttribute('height', "5px"); + setTimeout(done, 30); +} + +function done() +{ + document.documentElement.removeAttribute("class"); +} +</script> + +<rect id="rr" x="50%" width="25%" height="10em" fill="red"/> + +</svg> diff --git a/layout/svg/crashtests/386690-1.svg b/layout/svg/crashtests/386690-1.svg new file mode 100644 index 0000000000..e206978134 --- /dev/null +++ b/layout/svg/crashtests/386690-1.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 40"> + <foreignObject width="-2" height="100" /> +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/387290-1.svg b/layout/svg/crashtests/387290-1.svg new file mode 100644 index 0000000000..4ac8463204 --- /dev/null +++ b/layout/svg/crashtests/387290-1.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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/. --> +<!-- +Copyright Georgi Guninski +--> + +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" +"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg version="1.1" +xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" +> +<defs> +<filter id="dafilter" filterUnits="userSpaceOnUse" +x="0" y="0" width="4194305" height="17"> +<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/> +</filter> +</defs> +<g> + +<rect fill="red" width="256" height="256" filter="url(#dafilter)" transform="scale(262145,9)" /> +</g> + +</svg> diff --git a/layout/svg/crashtests/402408-1.svg b/layout/svg/crashtests/402408-1.svg new file mode 100644 index 0000000000..f442b2171e --- /dev/null +++ b/layout/svg/crashtests/402408-1.svg @@ -0,0 +1,32 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + onload="boom();" + class="reftest-wait"> + +<script> + +function boom() +{ + var grad1 = document.getElementById("grad1"); + var grad2 = document.getElementById("grad2"); + + grad1.appendChild(grad2); + + setTimeout(function() { + grad1.removeChild(grad2); + document.documentElement.removeAttribute("class"); + }, 30); +} + +</script> + +<linearGradient id="grad1" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="0"> + <stop id="green" stop-color="#00dd00" offset="0"/> + <stop id="blue" stop-color="#0000dd" offset="1"/> +</linearGradient> + +<linearGradient id="grad2" xlink:href="#grad1"/> + +<rect x="20" y="20" width="440" height="80" fill="url(#grad2)" /> + +</svg> diff --git a/layout/svg/crashtests/404677-1.xhtml b/layout/svg/crashtests/404677-1.xhtml new file mode 100644 index 0000000000..c1df3869b9 --- /dev/null +++ b/layout/svg/crashtests/404677-1.xhtml @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg"> +<head> +</head> +<body> + +<svg:svg height="-2" width="5" /> + +</body> +</html> diff --git a/layout/svg/crashtests/409565-1.xhtml b/layout/svg/crashtests/409565-1.xhtml new file mode 100644 index 0000000000..33ab9cefbb --- /dev/null +++ b/layout/svg/crashtests/409565-1.xhtml @@ -0,0 +1,3 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre;"><body style="width: 24px; height: 24px; -moz-column-width: 200px;"> + + <svg xmlns="http://www.w3.org/2000/svg" style="float: left;"></svg></body></html> diff --git a/layout/svg/crashtests/409573-1.svg b/layout/svg/crashtests/409573-1.svg new file mode 100644 index 0000000000..bec1469a52 --- /dev/null +++ b/layout/svg/crashtests/409573-1.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="document.documentElement.style.MozBinding = 'url(#empty)';">
+
+ <bindings xmlns="http://www.mozilla.org/xbl"><binding id="empty"><content></content></binding></bindings>
+
+ <defs>
+
+ <filter id="filter1"/>
+
+ <filter id="filter2">
+ <use filter="url(#filter1)" />
+ </filter>
+
+ </defs>
+
+</svg>
diff --git a/layout/svg/crashtests/420697-1.svg b/layout/svg/crashtests/420697-1.svg new file mode 100644 index 0000000000..d8b7f38340 --- /dev/null +++ b/layout/svg/crashtests/420697-1.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text stroke="black" y="1em" + stroke-dashoffset="1%" + stroke-dasharray="1px"> + m + </text> +</svg> diff --git a/layout/svg/crashtests/420697-2.svg b/layout/svg/crashtests/420697-2.svg new file mode 100644 index 0000000000..8987693e50 --- /dev/null +++ b/layout/svg/crashtests/420697-2.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text stroke="black" y="1em" + stroke-dasharray="1%"> + m + </text> +</svg> diff --git a/layout/svg/crashtests/429774-1.svg b/layout/svg/crashtests/429774-1.svg new file mode 100644 index 0000000000..00b726de6a --- /dev/null +++ b/layout/svg/crashtests/429774-1.svg @@ -0,0 +1,29 @@ +<?xml version="1.0"?> + +<svg width="7.5cm" height="5cm" viewBox="0 0 200 120" + xmlns="http://www.w3.org/2000/svg"> + + <defs> + <filter id="MyFilter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120"> + + <feOffset in="SourceAlpha" result="offset" dx="4" dy="4" y="76"/> + + <feSpecularLighting in="offset" result="specOut" + surfaceScale="5" specularConstant=".75" specularExponent="20"> + <fePointLight x="-5000" y="-10000" z="20000"/> + </feSpecularLighting> + + <feComposite in="SourceAlpha" in2="SourceAlpha" result="litPaint" + operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/> + + <feMerge> + <feMergeNode in="offset"/> + <feMergeNode in="litPaint"/> + </feMerge> + + </filter> + </defs> + + <g filter="url(#MyFilter)"/> + +</svg> diff --git a/layout/svg/crashtests/441368-1.svg b/layout/svg/crashtests/441368-1.svg new file mode 100644 index 0000000000..d0fee7478b --- /dev/null +++ b/layout/svg/crashtests/441368-1.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" +"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- 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/. --> +<!-- +Copyright Georgi Guninski +--> + + +<svg width="100%" height="100%" version="1.1" +xmlns="http://www.w3.org/2000/svg"> + +<defs> +<filter id="MyFilter" filterUnits="userSpaceOnUse" +x="0" y="0" width="32769" height="32769"> + +<feGaussianBlur in="SourceAlpha" stdDeviation="2147483648" result="blur"/> + +</filter> +</defs> + +<rect x="1" y="1" width="198" height="118" fill="#cccccc" /> + +<g filter="url(#MyFilter)"> +<text fill="#FFFFFF" stroke="black" font-size="45" +x="42" y="42">Feck b1ll</text> +</g> + +</svg> diff --git a/layout/svg/crashtests/453754-1.svg b/layout/svg/crashtests/453754-1.svg new file mode 100644 index 0000000000..a32d819281 --- /dev/null +++ b/layout/svg/crashtests/453754-1.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <filter id="f" height="-1"/> + + <rect filter="url(#f)" /> + +</svg> diff --git a/layout/svg/crashtests/455314-1.xhtml b/layout/svg/crashtests/455314-1.xhtml new file mode 100644 index 0000000000..01bb33d653 --- /dev/null +++ b/layout/svg/crashtests/455314-1.xhtml @@ -0,0 +1,16 @@ +<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<script>
+function doe() {
+document.getElementById('a').appendChild(document.body);
+}
+setTimeout(doe, 100);
+</script>
+</head>
+<body>
+<div style="position: absolute; -moz-appearance: button; filter: url(#b); "></div>
+<pre style="position: absolute;">
+<table id="b"></table>
+</pre>
+</body>
+<div id="a"/>
+</html>
\ No newline at end of file diff --git a/layout/svg/crashtests/458453.html b/layout/svg/crashtests/458453.html new file mode 100644 index 0000000000..ab72d46dee --- /dev/null +++ b/layout/svg/crashtests/458453.html @@ -0,0 +1,24 @@ +<html class="reftest-wait"> +<head> +<script type="text/javascript"> + +var i = 0; + +function bouncy() +{ + var body = document.body; + document.documentElement.removeChild(body); + document.documentElement.appendChild(body); + + if (++i < 30) + setTimeout(bouncy, 1); + else + document.documentElement.removeAttribute("class"); +} + +</script> +</head> + +<body onload="bouncy();"><span id="a"></span><span style="filter: url(#a);"><span style="filter: url(#a);">B</span></span></body> + +</html> diff --git a/layout/svg/crashtests/459666-1.html b/layout/svg/crashtests/459666-1.html new file mode 100644 index 0000000000..69074b6028 --- /dev/null +++ b/layout/svg/crashtests/459666-1.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html style="filter: url(#e);"> +<head></head> +<body onload="document.documentElement.style.counterReset = 'a';"> +<div id="e"></div> +</body> +</html> diff --git a/layout/svg/crashtests/459883.xhtml b/layout/svg/crashtests/459883.xhtml new file mode 100644 index 0000000000..e125e71d8a --- /dev/null +++ b/layout/svg/crashtests/459883.xhtml @@ -0,0 +1,13 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="filter: url(#r);" class="reftest-wait"><head> +<script type="text/javascript"> + +function boom() +{ + document.getElementById("s").setAttribute("style", "display: -moz-box;"); + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("load", function() { setTimeout(boom, 0); }, false); + +</script> +</head><body><ms xmlns="http://www.w3.org/1998/Math/MathML" id="s"><maction id="r"/></ms></body></html> diff --git a/layout/svg/crashtests/461289-1.svg b/layout/svg/crashtests/461289-1.svg new file mode 100644 index 0000000000..82a57f81b0 --- /dev/null +++ b/layout/svg/crashtests/461289-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + +<script type="text/javascript"> + +function boom() +{ + var f = document.getElementById("filter1"); + f.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "feImage")); + f.appendChild(document.getElementById("rect")); +} + +window.addEventListener("load", boom, false); + +</script> + +<filter id="filter1"/><rect id="rect" filter="url(#filter1)"/> + +</svg> diff --git a/layout/svg/crashtests/464374-1.svg b/layout/svg/crashtests/464374-1.svg new file mode 100644 index 0000000000..9844e5187f --- /dev/null +++ b/layout/svg/crashtests/464374-1.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("b").appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ document.getElementById("defs").setAttribute("filter", "url(#a)");
+}
+
+</script>
+
+<defs id="defs"><filter id="a"/><g id="b"><rect/></g></defs>
+
+</svg>
diff --git a/layout/svg/crashtests/466585-1.svg b/layout/svg/crashtests/466585-1.svg new file mode 100644 index 0000000000..22ad862e15 --- /dev/null +++ b/layout/svg/crashtests/466585-1.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<script type="text/javascript"> + +window.addEventListener("load", boom, false); + +function boom() +{ + document.getElementById("rect").setAttribute("filter", "url(#filter)"); + document.getElementById("defs").setAttribute("fill", "red"); +} + +</script> + +<defs id="defs"><filter id="filter"/><rect id="rect"/></defs> + +</svg> diff --git a/layout/svg/crashtests/467323-1.svg b/layout/svg/crashtests/467323-1.svg new file mode 100644 index 0000000000..9d757c349d --- /dev/null +++ b/layout/svg/crashtests/467323-1.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"> + +<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse"> + <feFlood flood-color="#ff0000" result="flood" x="0" y="0" width="100" height="100"/> + <feDisplacementMap style="color-interpolation-filters:sRGB" + in="SourceGraphic" in2="flood" scale="100" xChannelSelector="R" yChannelSelector="G"/> +</filter> +<g filter="url(#f1)"></g> + +</svg> diff --git a/layout/svg/crashtests/467498-1.svg b/layout/svg/crashtests/467498-1.svg new file mode 100644 index 0000000000..9839e6c30d --- /dev/null +++ b/layout/svg/crashtests/467498-1.svg @@ -0,0 +1,12 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <use id="a" width="100" height="100" xlink:href="#b"/> + <use id="b" x="100" y="100" width="100" height="100" xlink:href="#a"/> + <script> + document.getElementById("a").setAttribute("width", "200"); + </script> +</svg> diff --git a/layout/svg/crashtests/470124-1.svg b/layout/svg/crashtests/470124-1.svg new file mode 100644 index 0000000000..ba3b8aff40 --- /dev/null +++ b/layout/svg/crashtests/470124-1.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<filter id="f7" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox"><feComposite/></filter> + +<g filter="url(#f7)"/> + +</svg> diff --git a/layout/svg/crashtests/472782-1.svg b/layout/svg/crashtests/472782-1.svg new file mode 100644 index 0000000000..7cfeb11a69 --- /dev/null +++ b/layout/svg/crashtests/472782-1.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="r"> +<text><textPath xlink:href="#r">S</textPath> </text> +</svg> diff --git a/layout/svg/crashtests/474700-1.svg b/layout/svg/crashtests/474700-1.svg new file mode 100644 index 0000000000..141a1b3903 --- /dev/null +++ b/layout/svg/crashtests/474700-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><filter id="f1" height="-2"/><rect width="50" height="100" filter="url(#f1)"/></svg> diff --git a/layout/svg/crashtests/475181-1.svg b/layout/svg/crashtests/475181-1.svg new file mode 100644 index 0000000000..ef5a638afb --- /dev/null +++ b/layout/svg/crashtests/475181-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><filter id="filter1"><feImage y="4095"/><feTile/></filter><rect width="100%" height="100%" filter="url(#filter1)"/></svg> diff --git a/layout/svg/crashtests/475193-1.html b/layout/svg/crashtests/475193-1.html new file mode 100644 index 0000000000..edc08bcee4 --- /dev/null +++ b/layout/svg/crashtests/475193-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> + + +<style type="text/css"> + +.p { marker: url('#c'); } + +</style> +<script type="text/javascript"> + +function boom() +{ + document.getElementById("a").setAttribute("class", "p"); + document.documentElement.offsetHeight; + document.getElementById("b").setAttribute("id", "c"); +} + +</script> +</head><body onload="boom();"><div class="p" id="a">C</div><div id="c"></div></body></html>
\ No newline at end of file diff --git a/layout/svg/crashtests/475302-1.svg b/layout/svg/crashtests/475302-1.svg new file mode 100644 index 0000000000..4fdaa2213c --- /dev/null +++ b/layout/svg/crashtests/475302-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<style type="text/css"> + tref { filter: url(#filter1); } +</style> + +<filter id="filter1"><feFlood/></filter> + +<tref><polyline points="350,75 379,161 469,161 397,215 423,301 350,250 277,301 303,215 231,161 321,161"/></tref> + +</svg> diff --git a/layout/svg/crashtests/477935-1.html b/layout/svg/crashtests/477935-1.html new file mode 100644 index 0000000000..9c2ac5438a --- /dev/null +++ b/layout/svg/crashtests/477935-1.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<style type="text/css"> :root { filter: url('#g'); } </style> +<style type="text/css" id="ccs"> .cc { content: 'X'; } </style> +</head> +<body onload="document.getElementById('ccs').disabled = true;"> +<div id="g"></div> +<div class="cc">5</div> +</body> +</html> diff --git a/layout/svg/crashtests/478128-1.svg b/layout/svg/crashtests/478128-1.svg new file mode 100644 index 0000000000..a4100026b1 --- /dev/null +++ b/layout/svg/crashtests/478128-1.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <script type="text/javascript"> + + function boom() + { + document.documentElement.style.MozColumnCount = '15'; + } + window.onload = function() { setTimeout(boom, 20); }; + + </script> + + <foreignObject width="50" height="50" filter="url(#f1)"/> + + <filter id="f1"/> +</svg> diff --git a/layout/svg/crashtests/478511-1.svg b/layout/svg/crashtests/478511-1.svg new file mode 100644 index 0000000000..75a4aaa9b2 --- /dev/null +++ b/layout/svg/crashtests/478511-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.0"> + <defs> + <pattern id="pattern" + x="0" y="0" width="200" height="200"> + <circle fill="lime" r="100" cx="100" cy="100"/> + </pattern> + </defs> + <rect width="200" height="200" fill="url(#pattern)"/> +</svg> diff --git a/layout/svg/crashtests/483439-1.svg b/layout/svg/crashtests/483439-1.svg new file mode 100644 index 0000000000..c9e9ebae1c --- /dev/null +++ b/layout/svg/crashtests/483439-1.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + + <defs> + <path id="myTextPath" + d="M275,20 + a1,1 0 0,0 100,0 + " + /> + </defs> + + <svg y="15"> + <text x="10" y="100" style="stroke: #000000;"> + <textPath xlink:href="#myTextPath" >Text along a curved path...</textPath> + </text> + </svg> +</svg> diff --git a/layout/svg/crashtests/492186-1.svg b/layout/svg/crashtests/492186-1.svg new file mode 100644 index 0000000000..7f4b6650ab --- /dev/null +++ b/layout/svg/crashtests/492186-1.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<altGlyphDef/> +<script xmlns="http://www.w3.org/1999/xhtml"> +document.documentElement.getBBox(); +</script> +</svg> diff --git a/layout/svg/crashtests/508247-1.svg b/layout/svg/crashtests/508247-1.svg new file mode 100644 index 0000000000..c8b36b905f --- /dev/null +++ b/layout/svg/crashtests/508247-1.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<definition-src> +<path id="a"/> +</definition-src> + +<script id="script" xmlns="http://www.w3.org/1999/xhtml"> +setTimeout(function() {document.getElementById('a').getCTM()},10); +</script> +</svg> diff --git a/layout/svg/crashtests/512890-1.svg b/layout/svg/crashtests/512890-1.svg new file mode 100644 index 0000000000..044f693892 --- /dev/null +++ b/layout/svg/crashtests/512890-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <filter id="f" height="1em"/> + <rect width="50" height="50" filter="url(#f)"/> +</svg> diff --git a/layout/svg/crashtests/515288-1.html b/layout/svg/crashtests/515288-1.html new file mode 100644 index 0000000000..d78cbbfbec --- /dev/null +++ b/layout/svg/crashtests/515288-1.html @@ -0,0 +1,5 @@ +<script> +var svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg"); +document.createElement("script").appendChild(svgElem); +svgElem.getScreenCTM(); +</script>
\ No newline at end of file diff --git a/layout/svg/crashtests/522394-1.svg b/layout/svg/crashtests/522394-1.svg new file mode 100644 index 0000000000..f745c47dd2 --- /dev/null +++ b/layout/svg/crashtests/522394-1.svg @@ -0,0 +1,12 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<svg xmlns="http://www.w3.org/2000/svg"> +<defs> + <filter id="f1" x="0" y="0" width="-100" height="-100" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse"> + <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/> + </filter> +</defs> +<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/> +</svg> diff --git a/layout/svg/crashtests/522394-2.svg b/layout/svg/crashtests/522394-2.svg new file mode 100644 index 0000000000..1b6f1f0892 --- /dev/null +++ b/layout/svg/crashtests/522394-2.svg @@ -0,0 +1,12 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<svg xmlns="http://www.w3.org/2000/svg"> +<defs> + <filter id="f1" x="0" y="0" width="100000000" height="100000000" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse"> + <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/> + </filter> +</defs> +<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/> +</svg> diff --git a/layout/svg/crashtests/522394-3.svg b/layout/svg/crashtests/522394-3.svg new file mode 100644 index 0000000000..cf3483cfad --- /dev/null +++ b/layout/svg/crashtests/522394-3.svg @@ -0,0 +1,12 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<svg xmlns="http://www.w3.org/2000/svg"> +<defs> + <filter id="f1" x="0" y="0" width="4563402752" height="4563402752" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse"> + <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/> + </filter> +</defs> +<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/> +</svg> diff --git a/layout/svg/crashtests/566216-1.svg b/layout/svg/crashtests/566216-1.svg new file mode 100644 index 0000000000..999aaf4f08 --- /dev/null +++ b/layout/svg/crashtests/566216-1.svg @@ -0,0 +1,19 @@ +<?xml version="1.0"?> + +<svg xmlns="http://www.w3.org/2000/svg"><animate id="y"/><script> +<![CDATA[ + +function boom() +{ + var r = document.createRange(); + r.setEnd(document.getElementById('y'), 0); + r.extractContents(); +} + +window.addEventListener("load", boom, false); + +]]> +</script> +</svg> + + diff --git a/layout/svg/crashtests/587336-1.html b/layout/svg/crashtests/587336-1.html new file mode 100644 index 0000000000..811f483dd7 --- /dev/null +++ b/layout/svg/crashtests/587336-1.html @@ -0,0 +1,9 @@ +<html> +<head><script> +function boom() +{ + var b = document.getElementById("b"); + b.setAttributeNS(null, "style", "filter: url(#a);"); +} +</script></head> +<body onload="boom();" id="a"><span id="b">B</span></body></html> diff --git a/layout/svg/crashtests/590291-1.svg b/layout/svg/crashtests/590291-1.svg new file mode 100644 index 0000000000..db26fac3a8 --- /dev/null +++ b/layout/svg/crashtests/590291-1.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0cm" height="0cm">
+
+<text id="a">a</text>
+
+<script><![CDATA[
+var x=document.getElementById('a').getExtentOfChar(0);
+]]></script>
+</svg>
diff --git a/layout/svg/crashtests/601999-1.html b/layout/svg/crashtests/601999-1.html new file mode 100644 index 0000000000..7e8a3d39de --- /dev/null +++ b/layout/svg/crashtests/601999-1.html @@ -0,0 +1,5 @@ +<html class="reftest-wait"> + <body onload="document.getElementsByTagName('div')[0].id='b'; + document.documentElement.removeAttribute('class');" + ><div style="overflow-x: scroll; filter: url(#b)">abc</div></body> +</html> diff --git a/layout/svg/crashtests/605626-1.svg b/layout/svg/crashtests/605626-1.svg new file mode 100644 index 0000000000..678b011797 --- /dev/null +++ b/layout/svg/crashtests/605626-1.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<image width="10" height="10" xlink:href="data:text/plain,g"/> +</svg> diff --git a/layout/svg/crashtests/610594-1.html b/layout/svg/crashtests/610594-1.html new file mode 100644 index 0000000000..ee48e762cc --- /dev/null +++ b/layout/svg/crashtests/610594-1.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> +<svg><path d="M 0 5 a 2 5 -30 104 -5" marker-mid="url(#q)"></path></svg> +</body> +</html> diff --git a/layout/svg/crashtests/610954-1.html b/layout/svg/crashtests/610954-1.html new file mode 100644 index 0000000000..f5080df8b8 --- /dev/null +++ b/layout/svg/crashtests/610954-1.html @@ -0,0 +1 @@ +<!DOCTYPE html><html><body dir=rtl onload="document.getElementById('g').style.filter = 'url(#filter1)';"><span id="g">‎---</span></body></html> diff --git a/layout/svg/crashtests/612662-1.svg b/layout/svg/crashtests/612662-1.svg new file mode 100644 index 0000000000..73dc98ed19 --- /dev/null +++ b/layout/svg/crashtests/612662-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="background: url(#a); direction: rtl; margin: -32944px;"></svg> diff --git a/layout/svg/crashtests/612662-2.svg b/layout/svg/crashtests/612662-2.svg new file mode 100644 index 0000000000..b46841132f --- /dev/null +++ b/layout/svg/crashtests/612662-2.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" + style="direction: rtl; margin: -32944px; + background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E)"></svg> diff --git a/layout/svg/crashtests/612736-1.svg b/layout/svg/crashtests/612736-1.svg new file mode 100644 index 0000000000..cb3044efd0 --- /dev/null +++ b/layout/svg/crashtests/612736-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + class="reftest-wait"> + <path id="path"/> + <text> + <textPath xlink:href="#path">f</textPath> + <textPath xlink:href="#path">f</textPath> + </text> + + <script> + function boom() + { + var path = document.getElementById("path"); + path.parentNode.removeChild(path); + document.documentElement.removeAttribute("class"); + } + window.addEventListener("load", boom, false); + </script> +</svg> diff --git a/layout/svg/crashtests/612736-2.svg b/layout/svg/crashtests/612736-2.svg new file mode 100644 index 0000000000..30b8245a9f --- /dev/null +++ b/layout/svg/crashtests/612736-2.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <path id="path" d="M0 100 h50" stroke="black"/> + <text> + <textPath xlink:href="#path">abc</textPath> + <textPath xlink:href="#path">def</textPath> + </text> +</svg> diff --git a/layout/svg/crashtests/614367-1.svg b/layout/svg/crashtests/614367-1.svg new file mode 100644 index 0000000000..3af7b491da --- /dev/null +++ b/layout/svg/crashtests/614367-1.svg @@ -0,0 +1,8 @@ +<?xml version="1.0"?> + +<svg xmlns="http://www.w3.org/2000/svg"> + <polygon id="p" transform="?" /> + <script> + document.getElementById("p").transform.baseVal.removeItem(0); + </script> +</svg> diff --git a/layout/svg/crashtests/620034-1.html b/layout/svg/crashtests/620034-1.html new file mode 100644 index 0000000000..bfffd3ffac --- /dev/null +++ b/layout/svg/crashtests/620034-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<script> + +function boom() +{ + var f = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB"); + var tvb = f.tableValues.baseVal; + f.setAttribute("tableValues", "3 7 5"); + f.setAttribute("tableValues", "i"); + tvb.numberOfItems; +} + +boom(); + +</script> diff --git a/layout/svg/crashtests/621598-1.svg b/layout/svg/crashtests/621598-1.svg new file mode 100644 index 0000000000..dd47967d35 --- /dev/null +++ b/layout/svg/crashtests/621598-1.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <marker id="m1"> + <rect/> + <marker> + <line id="z" marker-end="url(#m1)"/> + </marker> + </marker> + <script> + function boom() + { + document.getElementById("z").getBoundingClientRect(); + } + window.addEventListener("load", boom, false); + </script> + +</svg> diff --git a/layout/svg/crashtests/648819-1.html b/layout/svg/crashtests/648819-1.html new file mode 100644 index 0000000000..727ca3e55f --- /dev/null +++ b/layout/svg/crashtests/648819-1.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> +var p = document.createElementNS("http://www.w3.org/2000/svg", "pattern"); +p.setAttribute("patternTransform", "i"); +p.patternTransform.baseVal.clear(); +</script> diff --git a/layout/svg/crashtests/655025-1.svg b/layout/svg/crashtests/655025-1.svg new file mode 100644 index 0000000000..4501bb57fa --- /dev/null +++ b/layout/svg/crashtests/655025-1.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text id="a">a</text> + <script> + document.getElementById("a").firstChild.nodeValue = ""; + </script> +</svg> diff --git a/layout/svg/crashtests/655025-2.svg b/layout/svg/crashtests/655025-2.svg new file mode 100644 index 0000000000..601006e831 --- /dev/null +++ b/layout/svg/crashtests/655025-2.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text id="a">a</text> + <script> + document.getElementById("a").appendChild(document.createTextNode("")); + </script> +</svg> diff --git a/layout/svg/crashtests/655025-3.svg b/layout/svg/crashtests/655025-3.svg new file mode 100644 index 0000000000..43e06b6fc3 --- /dev/null +++ b/layout/svg/crashtests/655025-3.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <script> + var a = document.createElementNS("http://www.w3.org/2000/svg", "text"); + a.appendChild(document.createTextNode("")); + document.documentElement.appendChild(a); + a.getNumberOfChars(); + document.documentElement.removeChild(a); + </script> +</svg> diff --git a/layout/svg/crashtests/657077-1.svg b/layout/svg/crashtests/657077-1.svg new file mode 100644 index 0000000000..b0165bd14a --- /dev/null +++ b/layout/svg/crashtests/657077-1.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<svg xmlns="http://www.w3.org/2000/svg"> + +<svg id="a" requiredExtensions="x"/> + +<script> +<![CDATA[ + +function boom() +{ + document.getElementById("a").unsuspendRedrawAll(); +} + +window.addEventListener("load", boom, false); + +]]> +</script> + +</svg> diff --git a/layout/svg/crashtests/669025-1.svg b/layout/svg/crashtests/669025-1.svg new file mode 100644 index 0000000000..eb529da4e0 --- /dev/null +++ b/layout/svg/crashtests/669025-1.svg @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg"> + <script type="text/javascript"> + + document.createElementNS("http://www.w3.org/2000/svg", "filter").filterResX; + + </script> +</svg> diff --git a/layout/svg/crashtests/669025-2.svg b/layout/svg/crashtests/669025-2.svg new file mode 100644 index 0000000000..ccecebef3c --- /dev/null +++ b/layout/svg/crashtests/669025-2.svg @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg"> + <script type="text/javascript"> + + document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur").stdDeviationX; + + </script> +</svg> diff --git a/layout/svg/crashtests/682411-1.svg b/layout/svg/crashtests/682411-1.svg new file mode 100644 index 0000000000..92d0c0a725 --- /dev/null +++ b/layout/svg/crashtests/682411-1.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<svg xmlns="http://www.w3.org/2000/svg" style="width: 0pt; padding: 100px;"> + <filter id="s"/> + <g filter="url(#s)"><text>z</text></g> +</svg> diff --git a/layout/svg/crashtests/692203-1.svg b/layout/svg/crashtests/692203-1.svg new file mode 100644 index 0000000000..8427b84268 --- /dev/null +++ b/layout/svg/crashtests/692203-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <marker id="marker" markerWidth="0"/> + <path d="M0,0 L100,100 200,200" marker-mid="url(#marker)"/> +</svg> diff --git a/layout/svg/crashtests/692203-2.svg b/layout/svg/crashtests/692203-2.svg new file mode 100644 index 0000000000..b59926dbba --- /dev/null +++ b/layout/svg/crashtests/692203-2.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <marker id="marker" markerHeight="0"/> + <path d="M0,0 L100,100 200,200" marker-mid="url(#marker)"/> +</svg> diff --git a/layout/svg/crashtests/693424-1.svg b/layout/svg/crashtests/693424-1.svg new file mode 100644 index 0000000000..8485f6b617 --- /dev/null +++ b/layout/svg/crashtests/693424-1.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <marker id="m"> + <foreignObject/> + </marker> + <line marker-end="url(#m)"/> +</svg> diff --git a/layout/svg/crashtests/709920-1.svg b/layout/svg/crashtests/709920-1.svg new file mode 100644 index 0000000000..5f25155307 --- /dev/null +++ b/layout/svg/crashtests/709920-1.svg @@ -0,0 +1,23 @@ +<svg xmlns="http://www.w3.org/2000/svg" + class="reftest-wait"> + <!-- Test to be sure that a zero-sized-in-one-dimension viewBox doesn't + make us fail assertions. --> + <script> + document.addEventListener("MozReftestInvalidate", waitAndFinish, false); + + function waitAndFinish() { + // Sadly, MozReftestInvalidate fires sooner than PaintPattern here, so + // we need to wait a little bit to give PaintPattern a chance to hit + // this bug. + setTimeout(finish, 100); + } + + function finish() { + document.documentElement.removeAttribute("class"); + } + </script> + <pattern id="test" viewBox="0 0 1 0"> + <rect/> + </pattern> + <rect width="200" height="200" fill="url(#test)"/> +</svg> diff --git a/layout/svg/crashtests/709920-2.svg b/layout/svg/crashtests/709920-2.svg new file mode 100644 index 0000000000..58c51111eb --- /dev/null +++ b/layout/svg/crashtests/709920-2.svg @@ -0,0 +1,23 @@ +<svg xmlns="http://www.w3.org/2000/svg" + class="reftest-wait"> + <!-- Test to be sure that a zero-sized-in-one-dimension viewBox doesn't + make us fail assertions. --> + <script> + document.addEventListener("MozReftestInvalidate", waitAndFinish, false); + + function waitAndFinish() { + // Sadly, MozReftestInvalidate fires sooner than PaintPattern here, so + // we need to wait a little bit to give PaintPattern a chance to hit + // this bug. + setTimeout(finish, 100); + } + + function finish() { + document.documentElement.removeAttribute("class"); + } + </script> + <pattern id="test" viewBox="0 0 0 1"> + <rect/> + </pattern> + <rect width="200" height="200" fill="url(#test)"/> +</svg> diff --git a/layout/svg/crashtests/713413-1.svg b/layout/svg/crashtests/713413-1.svg new file mode 100644 index 0000000000..7131202335 --- /dev/null +++ b/layout/svg/crashtests/713413-1.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<svg xmlns="http://www.w3.org/2000/svg"> + +<marker id="m"></marker> + +<script> +window.addEventListener("load", function() { + document.getElementById("m").appendChild(document.createElementNS("http://www.w3.org/2000/svg", "foreignObject")); +}, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/722003-1.svg b/layout/svg/crashtests/722003-1.svg new file mode 100644 index 0000000000..58e2d57734 --- /dev/null +++ b/layout/svg/crashtests/722003-1.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<marker><foreignObject><span id="x" xmlns="http://www.w3.org/1999/xhtml"></span></foreignObject></marker> + +<script> + +window.addEventListener("load", function() { + document.getElementById("x").getClientRects(); +}, false); + +</script> + +</svg> diff --git a/layout/svg/crashtests/725918-1.svg b/layout/svg/crashtests/725918-1.svg new file mode 100644 index 0000000000..5ebdf33f69 --- /dev/null +++ b/layout/svg/crashtests/725918-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text stroke="url(#p)">t</text> + <pattern id="p"/> +</svg> diff --git a/layout/svg/crashtests/732836-1.svg b/layout/svg/crashtests/732836-1.svg new file mode 100644 index 0000000000..a9abb46668 --- /dev/null +++ b/layout/svg/crashtests/732836-1.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" > + + <symbol id="z"> + <use xlink:href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' id='root' />#root" /> + </symbol> + + <use id="a" xlink:href="#z" width="20"/> + + <script> + window.addEventListener("load", function() { + window.scrollByPages(0); + document.getElementById("a").removeAttribute("width"); + document.elementFromPoint(0, 0); + }, false); + </script> + +</svg> diff --git a/layout/svg/crashtests/740627-1.svg b/layout/svg/crashtests/740627-1.svg new file mode 100644 index 0000000000..b74fcd2ddd --- /dev/null +++ b/layout/svg/crashtests/740627-1.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <pattern id="test" viewBox="0 0 10 10" height="-65%"> + <rect/> + </pattern> + <rect width="100" height="100" fill="url(#test)"/> +</svg> diff --git a/layout/svg/crashtests/740627-2.svg b/layout/svg/crashtests/740627-2.svg new file mode 100644 index 0000000000..6241ddaae5 --- /dev/null +++ b/layout/svg/crashtests/740627-2.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <pattern id="test" viewBox="0 0 10 10" width="-65%"> + <rect/> + </pattern> + <rect width="100" height="100" fill="url(#test)"/> +</svg> diff --git a/layout/svg/crashtests/757704-1.svg b/layout/svg/crashtests/757704-1.svg new file mode 100644 index 0000000000..b7e610e0e1 --- /dev/null +++ b/layout/svg/crashtests/757704-1.svg @@ -0,0 +1,17 @@ +<?xml version="1.0"?> + +<svg xmlns="http://www.w3.org/2000/svg"><script> +<![CDATA[ + +function boom() +{ + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + document.documentElement.appendChild(rect); + document.removeChild(document.documentElement); + rect.getScreenCTM(); +} + +window.addEventListener("load", boom, false); + +]]> +</script></svg> diff --git a/layout/svg/crashtests/757718-1.svg b/layout/svg/crashtests/757718-1.svg new file mode 100644 index 0000000000..fa948c6677 --- /dev/null +++ b/layout/svg/crashtests/757718-1.svg @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + var svgDoc = (new DOMParser).parseFromString("<svg xmlns='http://www.w3.org/2000/svg'></svg>", "image/svg+xml"); + var svgRoot = svgDoc.documentElement; + var rf = svgRoot.requiredFeatures; + document.adoptNode(svgRoot); + Object.getOwnPropertyNames(rf); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/layout/svg/crashtests/757751-1.svg b/layout/svg/crashtests/757751-1.svg new file mode 100644 index 0000000000..7ab51d0d19 --- /dev/null +++ b/layout/svg/crashtests/757751-1.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <defs> + <svg id="x" viewBox=" 0 0 10 10"/> + </defs> + <script> + window.addEventListener("load", function() { document.getElementById("x").setAttribute("width", "2"); }, false); + </script> +</svg> diff --git a/layout/svg/crashtests/767056-1.svg b/layout/svg/crashtests/767056-1.svg new file mode 100644 index 0000000000..b813d784b3 --- /dev/null +++ b/layout/svg/crashtests/767056-1.svg @@ -0,0 +1,21 @@ +<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%"
+ class="reftest-wait">
+ <script>
+
+function resize() {
+ // Set the viewBox to the same width as the content area, but slightly
+ // higher. This checks that we don't enter an infinite reflow loop. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=767056#c10
+ var viewBox = "0 0 " + window.innerWidth + " " + (window.innerHeight + 1);
+ document.documentElement.setAttribute("viewBox", viewBox);
+ document.documentElement.removeAttribute("class");
+}
+
+document.addEventListener("MozReftestInvalidate", resize, false);
+setTimeout(resize, 3000); // For non-gecko
+
+ </script>
+ <rect width="100%" height="100%"/>
+</svg>
diff --git a/layout/svg/crashtests/767535-1.xhtml b/layout/svg/crashtests/767535-1.xhtml new file mode 100644 index 0000000000..d92bd3b1a3 --- /dev/null +++ b/layout/svg/crashtests/767535-1.xhtml @@ -0,0 +1,22 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-columns: 2 auto;" class="reftest-wait"> + <head> + <script> + +function test() { + var r = document.documentElement; + document.removeChild(r); + document.appendChild(r); + document.documentElement.removeAttribute("class"); +} + + </script> + </head> + <body style="filter:url(#f);" onload="setTimeout(test, 0);"> + <div> + </div> + <svg xmlns="http://www.w3.org/2000/svg"> + <filter id="f"/> + </svg> + </body> +</html> + diff --git a/layout/svg/crashtests/768087-1.html b/layout/svg/crashtests/768087-1.html new file mode 100644 index 0000000000..9a7899f9d1 --- /dev/null +++ b/layout/svg/crashtests/768087-1.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<html> +<body onload="setTimeout(function() { document.body.innerHTML = '<span>x<svg viewbox=\'0 0 30 40\' ></svg></span>'; }, 0);"></body> +</html> diff --git a/layout/svg/crashtests/768351.svg b/layout/svg/crashtests/768351.svg new file mode 100644 index 0000000000..50a4b9b9c4 --- /dev/null +++ b/layout/svg/crashtests/768351.svg @@ -0,0 +1,2 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="mask: url('data:text/plain,1#f');" /> + diff --git a/layout/svg/crashtests/778492-1.svg b/layout/svg/crashtests/778492-1.svg new file mode 100644 index 0000000000..76deda594e --- /dev/null +++ b/layout/svg/crashtests/778492-1.svg @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<svg width="1cm" viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg"> +<line x2="100" y2="100"/> +</svg> diff --git a/layout/svg/crashtests/779971-1.svg b/layout/svg/crashtests/779971-1.svg new file mode 100644 index 0000000000..d57065a0ba --- /dev/null +++ b/layout/svg/crashtests/779971-1.svg @@ -0,0 +1,14 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="r" class="reftest-wait"> +<text id="t"><textPath xlink:href="#r">x</textPath>1</text> +<script> + +window.addEventListener("load", function() { + setTimeout(function() { + document.getElementById("t").lastChild.data = "2"; + + document.documentElement.removeAttribute("class"); + }, 200); +}, false); + +</script> +</svg> diff --git a/layout/svg/crashtests/780764-1.svg b/layout/svg/crashtests/780764-1.svg new file mode 100644 index 0000000000..6f4eb970bb --- /dev/null +++ b/layout/svg/crashtests/780764-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"> + +<marker id="marker"><path d="M100,0 l100,100 200,200" filter="url(#filter)"/></marker> + +<filter id="filter"/> + +<path d="M100,0 l100,100 200,200" marker-mid="url(#marker)"/> + +<script> +window.addEventListener("load", function() { + setTimeout(function() { + document.getElementById("filter").style.fontWeight = "bold"; + document.documentElement.removeAttribute("class"); + }, 200); +}, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/780963-1.html b/layout/svg/crashtests/780963-1.html new file mode 100644 index 0000000000..8cbeb1a37f --- /dev/null +++ b/layout/svg/crashtests/780963-1.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <script> + +function tweak() { + document.body.offsetTop; + + var feImage = document.getElementsByTagName("feImage")[0]; + feImage.setAttribute('filter', 'url(#f1)') + document.body.offsetTop; + + var child = document.createElementNS('http://www.w3.org/2000/svg', 'g') + feImage.appendChild(child); +} + + </script> + </head> + <body onload="tweak()"> + <svg xmlns="http://www.w3.org/2000/svg"> + <filter filterUnits="userSpaceOnUse" id="f1"> + <feImage/> + </filter> + <rect height="100" width="100"/> + </svg> + </body> +</html> diff --git a/layout/svg/crashtests/782141-1.svg b/layout/svg/crashtests/782141-1.svg new file mode 100644 index 0000000000..6f0af76ff4 --- /dev/null +++ b/layout/svg/crashtests/782141-1.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" + onload="go()"> + <script> + +function go() { + var f = document.getElementById('f'); + var fm = document.getElementById('fm'); + f.appendChild(fm.cloneNode(1)); +} + + </script> + <filter id="f"> + <feMorphology id="fm" radius="2147483500"/> + </filter> + <rect height="28" width="256" filter="url(#f)" /> +</svg> diff --git a/layout/svg/crashtests/784061-1.svg b/layout/svg/crashtests/784061-1.svg new file mode 100644 index 0000000000..6a9623154d --- /dev/null +++ b/layout/svg/crashtests/784061-1.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg" + class="reftest-wait"> + +<defs><path id="x"/></defs> + +<script> +function boom() +{ + document.getElementById("x").style.transform = "translate(0px)"; + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("load", boom, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/788831-1.svg b/layout/svg/crashtests/788831-1.svg new file mode 100644 index 0000000000..b6202a1324 --- /dev/null +++ b/layout/svg/crashtests/788831-1.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <pattern id="pattern" width="40" height="40"><stop/></pattern> + <rect id="rect" width="200" height="100"/> + <use xlink:href="#rect" stroke="url(#pattern)" /> +</svg> diff --git a/layout/svg/crashtests/789390-1.html b/layout/svg/crashtests/789390-1.html new file mode 100644 index 0000000000..542037f229 --- /dev/null +++ b/layout/svg/crashtests/789390-1.html @@ -0,0 +1 @@ +<html style="transition: 1s;"><body onload="document.documentElement.style.stroke = '-moz-objectStroke';"></body></html> diff --git a/layout/svg/crashtests/790072.svg b/layout/svg/crashtests/790072.svg new file mode 100644 index 0000000000..b288251a7e --- /dev/null +++ b/layout/svg/crashtests/790072.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><text style="stroke: -moz-objectfill none;">abc</text></svg> diff --git a/layout/svg/crashtests/791826-1.svg b/layout/svg/crashtests/791826-1.svg new file mode 100644 index 0000000000..f42261a3ad --- /dev/null +++ b/layout/svg/crashtests/791826-1.svg @@ -0,0 +1,14 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="position: fixed;"> +<script> +<![CDATA[ + +function boom() +{ + document.documentElement.setAttribute("preserveAspectRatio", "_"); +} + +window.addEventListener("load", boom, false); + +]]> +</script> +</svg> diff --git a/layout/svg/crashtests/803562-1.svg b/layout/svg/crashtests/803562-1.svg new file mode 100644 index 0000000000..e6fc91127e --- /dev/null +++ b/layout/svg/crashtests/803562-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<foreignObject width="500" height="500" style="-moz-appearance: checkbox;"> + <option xmlns="http://www.w3.org/1999/xhtml"></option> +</foreignObject> + +<script> +<![CDATA[ + +window.addEventListener("load", function() { + document.getElementsByTagName("option")[0].appendChild(document.createTextNode("Option 1")); +}, false); + +]]> +</script> + +</svg> + diff --git a/layout/svg/crashtests/808318-1.svg b/layout/svg/crashtests/808318-1.svg new file mode 100644 index 0000000000..48907225cc --- /dev/null +++ b/layout/svg/crashtests/808318-1.svg @@ -0,0 +1,2 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="-moz-transform-style: preserve-3d"></svg> + diff --git a/layout/svg/crashtests/813420-1.svg b/layout/svg/crashtests/813420-1.svg new file mode 100644 index 0000000000..d977c0e982 --- /dev/null +++ b/layout/svg/crashtests/813420-1.svg @@ -0,0 +1,14 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"> + <svg id="i" style="mask: url("#none");"> + <marker id="markerEnd"/><polygon marker-end="url(#markerEnd)" points="250,150 200,150"/> + </svg> + <script> + +window.addEventListener("MozReftestInvalidate", function() { + document.getElementById("i").style.mask = "url(#none)"; + document.documentElement.removeAttribute("class"); +}, false); + + </script> +</svg> + diff --git a/layout/svg/crashtests/841163-1.svg b/layout/svg/crashtests/841163-1.svg new file mode 100644 index 0000000000..b1bb5198ab --- /dev/null +++ b/layout/svg/crashtests/841163-1.svg @@ -0,0 +1,29 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"> + +<filter id="f"/> + +<g filter="url(#f)"> + <text>AB</text> +</g> + +<script> + +function forceFrameConstruction() +{ + document.documentElement.getBoundingClientRect(); +} + +function boom() +{ + document.getElementsByTagName("text")[0].firstChild.splitText(1); + forceFrameConstruction(); + document.normalize(); + forceFrameConstruction(); + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("load", boom, false); + +</script> + +</svg> diff --git a/layout/svg/crashtests/841812-1.svg b/layout/svg/crashtests/841812-1.svg new file mode 100644 index 0000000000..e5bcaa66ee --- /dev/null +++ b/layout/svg/crashtests/841812-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <text> + <circle> + <textPath id="t" xlink:href="data:text/html,1" /> + </circle> + </text> + + <script> + window.addEventListener("load", function() { document.getElementById("t").removeAttribute('xlink:href'); }, false); + </script> +</svg> diff --git a/layout/svg/crashtests/842009-1.svg b/layout/svg/crashtests/842009-1.svg new file mode 100644 index 0000000000..25656ba530 --- /dev/null +++ b/layout/svg/crashtests/842009-1.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <title id="hello">hello</title> + <text x="100" y="100"> <tref xlink:href="#hello"/></text> +</svg> diff --git a/layout/svg/crashtests/842630-1.svg b/layout/svg/crashtests/842630-1.svg new file mode 100644 index 0000000000..8d36998be6 --- /dev/null +++ b/layout/svg/crashtests/842630-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><text dy="20 20">A<tspan style="display: none;">B</tspan></text></svg> diff --git a/layout/svg/crashtests/842909-1.svg b/layout/svg/crashtests/842909-1.svg new file mode 100644 index 0000000000..9a1bc89eb4 --- /dev/null +++ b/layout/svg/crashtests/842909-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <defs> + <text id="t">X</text> + </defs> + + <script> + window.addEventListener("load", function() { + document.getElementById("t").getSubStringLength(0, 0); + }, false); + </script> +</svg> diff --git a/layout/svg/crashtests/843072-1.svg b/layout/svg/crashtests/843072-1.svg new file mode 100644 index 0000000000..590721f058 --- /dev/null +++ b/layout/svg/crashtests/843072-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <defs> + <text id="t"></text> + </defs> + + <script> + window.addEventListener("load", function() { + document.getElementById("t").getExtentOfChar(0); + }, false); + </script> +</svg> diff --git a/layout/svg/crashtests/843917-1.svg b/layout/svg/crashtests/843917-1.svg new file mode 100644 index 0000000000..55cf7ab186 --- /dev/null +++ b/layout/svg/crashtests/843917-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <filter id="f"/> + + <g filter="url(#f)"> + <text>a𞠯</text> + </g> + + <script> + + window.addEventListener("load", function() { + var text = document.getElementsByTagName("text")[0]; + text.firstChild.data = "d"; + text.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "g")); + }, false); + + </script> + +</svg> diff --git a/layout/svg/crashtests/847139-1.svg b/layout/svg/crashtests/847139-1.svg new file mode 100644 index 0000000000..81fffa4be8 --- /dev/null +++ b/layout/svg/crashtests/847139-1.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<marker><text>x</text></marker> + +<script> + +window.addEventListener("load", function() { + document.caretPositionFromPoint(0, 0); +}, false); + +</script> + +</svg> diff --git a/layout/svg/crashtests/849688-1.svg b/layout/svg/crashtests/849688-1.svg new file mode 100644 index 0000000000..142f04c933 --- /dev/null +++ b/layout/svg/crashtests/849688-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<text></text> + +<script> +window.addEventListener("load", function() { + document.getElementsByTagName('text')[0].getStartPositionOfChar(1); +}, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/849688-2.svg b/layout/svg/crashtests/849688-2.svg new file mode 100644 index 0000000000..4b71b20c7c --- /dev/null +++ b/layout/svg/crashtests/849688-2.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<text>X</text> + +<script> +window.addEventListener("load", function() { + document.getElementsByTagName('text')[0].getStartPositionOfChar(2); +}, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/860378-1.svg b/layout/svg/crashtests/860378-1.svg new file mode 100644 index 0000000000..f4ec09bc4c --- /dev/null +++ b/layout/svg/crashtests/860378-1.svg @@ -0,0 +1,24 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<filter id="f"/> +<g filter="url(#f)"><text>ab</text></g> + +<script> + +function boom() +{ + var svgtext = document.getElementsByTagName("text")[0]; + var text1 = svgtext.firstChild ; + var text2 = text1.splitText(1); + + setTimeout(function() { + text1.data = "c"; + svgtext.removeChild(text2); + }, 200); +} + +window.addEventListener("load", boom, false); + +</script> + +</svg> diff --git a/layout/svg/crashtests/868904-1.svg b/layout/svg/crashtests/868904-1.svg new file mode 100644 index 0000000000..c8d7e9437e --- /dev/null +++ b/layout/svg/crashtests/868904-1.svg @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<style> + +* { animation-name: a; animation-duration: 72ms } +@keyframes a { 60% { transform: skewx(30deg); } } + +</style> +</head> +<body> + +<svg></svg> + +</body> +</html> diff --git a/layout/svg/crashtests/873806-1.svg b/layout/svg/crashtests/873806-1.svg new file mode 100644 index 0000000000..e40aff201b --- /dev/null +++ b/layout/svg/crashtests/873806-1.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <foreignObject requiredFeatures="fail"> + <svg> + <text>a</text> + </svg> + </foreignObject> + <script> + document.querySelector("text").getBBox(); + </script> +</svg> diff --git a/layout/svg/crashtests/876831-1.svg b/layout/svg/crashtests/876831-1.svg new file mode 100644 index 0000000000..6b6c01f9e7 --- /dev/null +++ b/layout/svg/crashtests/876831-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<script> + +function boom() +{ + var x = document.getElementById("x").firstChild; + x.data = x.data.slice(1); + document.caretPositionFromPoint(0, 0); +} + +window.addEventListener("load", boom, false); + +</script> + +<text><tspan id="x">@ت</tspan></text> + +</svg> diff --git a/layout/svg/crashtests/877029-1.svg b/layout/svg/crashtests/877029-1.svg new file mode 100644 index 0000000000..1a7bad0f1b --- /dev/null +++ b/layout/svg/crashtests/877029-1.svg @@ -0,0 +1,10 @@ +<!-- + Check that we don't crash due to an nsSVGMarkerFrame having a null + mMarkedFrame and incorrectly calling GetCanvasTM() on the nsSVGMarkerFrame. + --> +<svg xmlns="http://www.w3.org/2000/svg"> + <marker><text>a</text></marker> + <script> + document.querySelector("text").getComputedTextLength(); + </script> +</svg> diff --git a/layout/svg/crashtests/880925-1.svg b/layout/svg/crashtests/880925-1.svg new file mode 100644 index 0000000000..77efd3c0a5 --- /dev/null +++ b/layout/svg/crashtests/880925-1.svg @@ -0,0 +1,26 @@ +<?xml version="1.0"?> + +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ + +function boom() +{ + var svgText = document.createElementNS("http://www.w3.org/2000/svg", "text"); + document.documentElement.appendChild(svgText); + var text1 = document.createTextNode("A"); + svgText.appendChild(text1); + var text2 = document.createTextNode(""); + svgText.appendChild(text2); + document.caretPositionFromPoint(0, 0); + setTimeout(function() { + text2.data = "B"; + document.caretPositionFromPoint(0, 0); + }, 0); +} + +window.addEventListener("load", boom, false); + +]]> +</script> +</svg> diff --git a/layout/svg/crashtests/881031-1.svg b/layout/svg/crashtests/881031-1.svg new file mode 100644 index 0000000000..0738e1299d --- /dev/null +++ b/layout/svg/crashtests/881031-1.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> + +function boom() +{ + var svgText = document.getElementById("t"); + svgText.firstChild.data = "C"; + svgText.appendChild(document.createTextNode("D")); + document.caretPositionFromPoint(0, 0); +} +window.addEventListener("load", boom, false); + +</script> +<text id="t">A</text> +</svg> diff --git a/layout/svg/crashtests/885608-1.svg b/layout/svg/crashtests/885608-1.svg new file mode 100644 index 0000000000..0c96777508 --- /dev/null +++ b/layout/svg/crashtests/885608-1.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<mask id="m"><text id="t">z</text></mask> + +<rect width="600" height="400" mask="url(#m)"/> + +<script> +window.addEventListener("load", function() { + document.getElementById("t").firstChild.data = "ab"; +}, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/890782-1.svg b/layout/svg/crashtests/890782-1.svg new file mode 100644 index 0000000000..686bc73a8f --- /dev/null +++ b/layout/svg/crashtests/890782-1.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <foreignObject requiredFeatures="foo" id="f"> + <svg> + <text id="t"/> + </svg> + </foreignObject> + + <script> + window.addEventListener("load", function() { + document.documentElement.appendChild(document.getElementById("f")) + document.getElementById("t").getNumberOfChars(); + }, false); + </script> + +</svg> diff --git a/layout/svg/crashtests/890783-1.svg b/layout/svg/crashtests/890783-1.svg new file mode 100644 index 0000000000..25f54ba2a3 --- /dev/null +++ b/layout/svg/crashtests/890783-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <mask id="m"> + <text> + <tspan id="tspan" /> + </text> + </mask> + + <rect width="600" height="400" mask="url(#m)"/> + + <script> + + window.addEventListener("load", function() { + document.getElementById("tspan").style.dominantBaseline = "alphabetic"; + }, false); + + </script> + +</svg> diff --git a/layout/svg/crashtests/893510-1.svg b/layout/svg/crashtests/893510-1.svg new file mode 100644 index 0000000000..bb58be0450 --- /dev/null +++ b/layout/svg/crashtests/893510-1.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <g requiredExtensions="foo"> + <text>تz</text> + </g> +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/895311-1.svg b/layout/svg/crashtests/895311-1.svg new file mode 100644 index 0000000000..7b0c728043 --- /dev/null +++ b/layout/svg/crashtests/895311-1.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <mask id="m"> + <text> + <tspan id="ts" /> + </text> + </mask> + + <rect width="600" height="400" mask="url(#m)"/> + + <script> + window.addEventListener("load", function() { + document.getElementById("ts").style.overflow = "hidden"; + }, false); + </script> + +</svg> diff --git a/layout/svg/crashtests/897342-1.svg b/layout/svg/crashtests/897342-1.svg new file mode 100644 index 0000000000..547e919b7d --- /dev/null +++ b/layout/svg/crashtests/897342-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><text textLength="50" lengthAdjust="spacingAndGlyphs">‍</text></svg> diff --git a/layout/svg/crashtests/898909-1.svg b/layout/svg/crashtests/898909-1.svg new file mode 100644 index 0000000000..8a70cd7b8d --- /dev/null +++ b/layout/svg/crashtests/898909-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" requiredFeatures="foo"> + + <text id="t" /> + + <script> + window.addEventListener("load", function() { + document.getElementById("t").getComputedTextLength(); + }, false); + </script> + +</svg> diff --git a/layout/svg/crashtests/898951-1.svg b/layout/svg/crashtests/898951-1.svg new file mode 100644 index 0000000000..f42dbf69f2 --- /dev/null +++ b/layout/svg/crashtests/898951-1.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text>X<tspan style="display: none;">2</tspan>́</text> +</svg> diff --git a/layout/svg/crashtests/913990.html b/layout/svg/crashtests/913990.html new file mode 100644 index 0000000000..21d8ef3cc3 --- /dev/null +++ b/layout/svg/crashtests/913990.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<body style="filter: url('feed:javascript:5');"> +</body> +</html> diff --git a/layout/svg/crashtests/919371-1.xhtml b/layout/svg/crashtests/919371-1.xhtml new file mode 100644 index 0000000000..b27ba3fa66 --- /dev/null +++ b/layout/svg/crashtests/919371-1.xhtml @@ -0,0 +1,5 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <svg xmlns="http://www.w3.org/2000/svg"> + <marker style="position: absolute;" /> + </svg> +</html> diff --git a/layout/svg/crashtests/950324-1.svg b/layout/svg/crashtests/950324-1.svg new file mode 100644 index 0000000000..a43d84f4d8 --- /dev/null +++ b/layout/svg/crashtests/950324-1.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text style="font-family: sans-serif;"> ́</text> +</svg> diff --git a/layout/svg/crashtests/952270-1.svg b/layout/svg/crashtests/952270-1.svg new file mode 100644 index 0000000000..69bac47d42 --- /dev/null +++ b/layout/svg/crashtests/952270-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <path id="path" transform="scale(2,1)" /> + + <text> + <textPath xlink:href="#path">F</textPath> + </text> + +</svg> diff --git a/layout/svg/crashtests/963086-1.svg b/layout/svg/crashtests/963086-1.svg new file mode 100644 index 0000000000..3805b46d75 --- /dev/null +++ b/layout/svg/crashtests/963086-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 64 64"> + <defs> + <filter id="dropShadow"> + <feGaussianBlur stdDeviation="2" /> + <feOffset + result="offsetBlur" + dy="1073741824"/> + <feMerge> + <feMergeNode + in="offsetBlur" /> + <feMergeNode + in="SourceGraphic" /> + </feMerge> + </filter> + </defs> + <rect height="64" width="64" style="filter:url(#dropShadow)" /> +</svg> diff --git a/layout/svg/crashtests/974746-1.svg b/layout/svg/crashtests/974746-1.svg new file mode 100644 index 0000000000..c619c25f79 --- /dev/null +++ b/layout/svg/crashtests/974746-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <pattern id="patternRotated" width="1" patternTransform="rotate(45 50 50)"> + <rect/> + </pattern> + + <rect width="100" height="100" fill="url(#patternRotated)"/> + +</svg> diff --git a/layout/svg/crashtests/975773-1.svg b/layout/svg/crashtests/975773-1.svg new file mode 100644 index 0000000000..dd225eb2ae --- /dev/null +++ b/layout/svg/crashtests/975773-1.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <filter id="f"> + <feSpecularLighting style="display: none;"/> + <feComposite in="SourceGraphic"/> + </filter> + + <path d="M0,0 h100 v100 h-100 z M20,20 v60 h60 v-60 z" filter="url(#f)"/> + +</svg> diff --git a/layout/svg/crashtests/979407-1.svg b/layout/svg/crashtests/979407-1.svg new file mode 100644 index 0000000000..b615f3bec2 --- /dev/null +++ b/layout/svg/crashtests/979407-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <marker id="marker" viewBox="0 0 10 10" markerHeight="-1px"/> + <path d="M0,0 h10" marker-start="url(#marker)"/> +</svg> diff --git a/layout/svg/crashtests/979407-2.svg b/layout/svg/crashtests/979407-2.svg new file mode 100644 index 0000000000..75aee06345 --- /dev/null +++ b/layout/svg/crashtests/979407-2.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <marker id="marker" viewBox="0 0 10 10" markerWidth="-1px"/> + <path d="M0,0 h10" marker-start="url(#marker)"/> +</svg> diff --git a/layout/svg/crashtests/993443.svg b/layout/svg/crashtests/993443.svg new file mode 100644 index 0000000000..30bd18543c --- /dev/null +++ b/layout/svg/crashtests/993443.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg"> +<image width="10" height="10" x="17592186044416pt"/> +</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list new file mode 100644 index 0000000000..adc2db66b6 --- /dev/null +++ b/layout/svg/crashtests/crashtests.list @@ -0,0 +1,199 @@ +load 220165-1.svg +load 267650-1.svg +load 294022-1.svg +load 307314-1.svg +load 308615-1.svg +load 308917-1.svg +load 310436-1.svg +load 310638.svg +load 313737-1.xml +load 314244-1.xul +load 322185-1.svg +load 322215-1.svg +load 323704-1.svg +load 325427-1.svg +load 326495-1.svg +load 326974-1.svg +load 327706-1.svg +load 327709-1.svg +load 327711-1.svg +load 328137-1.svg +load 329848-1.svg +load 337408-1.xul +load 338301-1.xhtml +load 338312-1.xhtml +load 340083-1.svg +load 340945-1.svg +load 342923-1.html +load 343221-1.xhtml +load 344749-1.svg +load 344887-1.svg +load 344892-1.svg +load 344898-1.svg +load 344904-1.svg +load 345418-1.svg +load 348982-1.xhtml +load 354777-1.xhtml +load 359516-1.svg +load 361015-1.svg +load 361587-1.svg +load 363611-1.xhtml +load 364688-1.svg +load 366956-1.svg +load 366956-2.svg +load 367111-1.svg +load 367368-1.xhtml +load 369233-1.svg +load 369438-1.svg +load 369438-2.svg +load 371463-1.xhtml +load 371563-1.xhtml +load 375775-1.svg +load 378716.svg +load 380691-1.svg +load 384391-1.xhtml +load 384499-1.svg +load 384637-1.svg +load 384728-1.svg +load 385246-1.svg +load 385246-2.svg +load 385552-1.svg +load 385552-2.svg +load 385840-1.svg +load 385852-1.svg +load 386475-1.xhtml +load 386566-1.svg +load 386690-1.svg +load 387290-1.svg +load 402408-1.svg +load 404677-1.xhtml +load 409565-1.xhtml +load 409573-1.svg +load 420697-1.svg +load 420697-2.svg +load 429774-1.svg +load 441368-1.svg +load 453754-1.svg +load 455314-1.xhtml +load 458453.html +load 459666-1.html +load 459883.xhtml +load 461289-1.svg +load 464374-1.svg +load 466585-1.svg +load 467323-1.svg +load 467498-1.svg +load 470124-1.svg +load 472782-1.svg +load 474700-1.svg +load 475181-1.svg +load 475193-1.html +load 475302-1.svg +load 477935-1.html +load 478128-1.svg +load 478511-1.svg +load 483439-1.svg +load 492186-1.svg +load 508247-1.svg +load 512890-1.svg +load 515288-1.html +load 522394-1.svg +load 522394-2.svg +load 522394-3.svg +load 566216-1.svg +load 587336-1.html +load 590291-1.svg +load 601999-1.html +load 605626-1.svg +load 610594-1.html +load 610954-1.html +load 612662-1.svg +load 612662-2.svg +load 612736-1.svg +load 612736-2.svg +load 614367-1.svg +load 620034-1.html +load 621598-1.svg +load 648819-1.html +load 655025-1.svg +load 655025-2.svg +load 655025-3.svg +load 657077-1.svg +load 669025-1.svg +load 669025-2.svg +load 682411-1.svg +load 692203-1.svg +load 692203-2.svg +load 693424-1.svg +load 709920-1.svg +load 709920-2.svg +load 713413-1.svg +load 722003-1.svg +load 725918-1.svg +load 732836-1.svg +load 740627-1.svg +load 740627-2.svg +load 757704-1.svg +load 757718-1.svg +load 757751-1.svg +load 767056-1.svg +load 767535-1.xhtml +load 768087-1.html +load 768351.svg +load 778492-1.svg +load 779971-1.svg +load 780764-1.svg +load 780963-1.html +load 782141-1.svg +load 784061-1.svg +load 788831-1.svg +load 789390-1.html +load 790072.svg +load 791826-1.svg +load 808318-1.svg +load 803562-1.svg +load 813420-1.svg +load 841163-1.svg +load 841812-1.svg +load 842009-1.svg +load 842630-1.svg +load 842909-1.svg +load 843072-1.svg +load 843917-1.svg +load 847139-1.svg +load 849688-1.svg +load 849688-2.svg +load 860378-1.svg +load 868904-1.svg +load 873806-1.svg +load 876831-1.svg +load 877029-1.svg +load 880925-1.svg +load 881031-1.svg +load 885608-1.svg +load 890782-1.svg +load 890783-1.svg +load 893510-1.svg +load 895311-1.svg +load 897342-1.svg +load 898909-1.svg +load 898951-1.svg +load 913990.html +load 919371-1.xhtml +load 950324-1.svg +load 952270-1.svg +load 963086-1.svg +load 974746-1.svg +load 975773-1.svg +load 979407-1.svg +load 979407-2.svg +load 993443.svg +load 1016145.svg +load 1028512.svg +load 1140080-1.svg +load 1149542-1.svg +load 1156581-1.svg +load 1182496-1.html +load 1209525-1.svg +load 1223281-1.svg +load extref-test-1.xhtml diff --git a/layout/svg/crashtests/extref-test-1-resource.xhtml b/layout/svg/crashtests/extref-test-1-resource.xhtml new file mode 100644 index 0000000000..cd47ddc2f5 --- /dev/null +++ b/layout/svg/crashtests/extref-test-1-resource.xhtml @@ -0,0 +1,24 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> +<body style="margin:0"> + <embed type="application/x-shockwave-flash" src="data:application/x-shockwave-flash,This is a test"></embed> + <iframe src="date:text/plain,aaa"></iframe> + <div style="mask: url(#m1); width:500px; height:500px; background:lime;"></div> + + <svg:svg height="0"> + <svg:mask id="m1" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox"> + <svg:linearGradient id="g" gradientUnits="objectBoundingBox" x2="0" y2="1"> + <svg:stop stop-color="white" offset="0"/> + <svg:stop stop-color="white" stop-opacity="0" offset="1"/> + </svg:linearGradient> + <svg:circle cx="0.25" cy="0.25" r="0.25" id="circle" fill="white"/> + <svg:rect x="0.5" y="0" width="0.5" height="1" fill="url(#g)"/> + </svg:mask> + </svg:svg> +</body> +</html> diff --git a/layout/svg/crashtests/extref-test-1.xhtml b/layout/svg/crashtests/extref-test-1.xhtml new file mode 100644 index 0000000000..932b679b1f --- /dev/null +++ b/layout/svg/crashtests/extref-test-1.xhtml @@ -0,0 +1,11 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> +<body style="margin:0"> + <div style="mask: url(extref-test-1-resource.xhtml#m1); width:500px; height:500px; background:lime;"></div> +</body> +</html> diff --git a/layout/svg/moz.build b/layout/svg/moz.build new file mode 100644 index 0000000000..b1481f4ef2 --- /dev/null +++ b/layout/svg/moz.build @@ -0,0 +1,76 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'SVG') + +EXPORTS += [ + 'nsFilterInstance.h', + 'nsSVGEffects.h', + 'nsSVGFilterInstance.h', + 'nsSVGForeignObjectFrame.h', + 'nsSVGIntegrationUtils.h', + 'nsSVGUtils.h', + 'SVGImageContext.h', +] + +EXPORTS.mozilla += [ + 'SVGContextPaint.h', +] + +UNIFIED_SOURCES += [ + 'nsCSSClipPathInstance.cpp', + 'nsCSSFilterInstance.cpp', + 'nsFilterInstance.cpp', + 'nsSVGAFrame.cpp', + 'nsSVGClipPathFrame.cpp', + 'nsSVGContainerFrame.cpp', + 'nsSVGEffects.cpp', + 'nsSVGFilterFrame.cpp', + 'nsSVGFilterInstance.cpp', + 'nsSVGForeignObjectFrame.cpp', + 'nsSVGGenericContainerFrame.cpp', + 'nsSVGGFrame.cpp', + 'nsSVGGradientFrame.cpp', + 'nsSVGImageFrame.cpp', + 'nsSVGInnerSVGFrame.cpp', + 'nsSVGIntegrationUtils.cpp', + 'nsSVGMarkerFrame.cpp', + 'nsSVGMaskFrame.cpp', + 'nsSVGOuterSVGFrame.cpp', + 'nsSVGPathGeometryFrame.cpp', + 'nsSVGPatternFrame.cpp', + 'nsSVGStopFrame.cpp', + 'nsSVGSwitchFrame.cpp', + 'nsSVGUseFrame.cpp', + 'nsSVGUtils.cpp', + 'SVGContextPaint.cpp', + 'SVGFEContainerFrame.cpp', + 'SVGFEImageFrame.cpp', + 'SVGFELeafFrame.cpp', + 'SVGFEUnstyledLeafFrame.cpp', + 'SVGTextFrame.cpp', + 'SVGViewFrame.cpp', +] + +if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']: + SOURCES += ['nsSVGMaskFrameNEON.cpp'] + SOURCES['nsSVGMaskFrameNEON.cpp'].flags += CONFIG['NEON_FLAGS'] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../../widget', + '../base', + '../generic', + '../style', + '../xul', + '/dom/base', + '/dom/svg', +] + +RESOURCE_FILES += [ + 'svg.css', +] diff --git a/layout/svg/nsCSSClipPathInstance.cpp b/layout/svg/nsCSSClipPathInstance.cpp new file mode 100644 index 0000000000..828b10eaca --- /dev/null +++ b/layout/svg/nsCSSClipPathInstance.cpp @@ -0,0 +1,377 @@ +/* -*- 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 "nsCSSClipPathInstance.h" + +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "nsCSSRendering.h" +#include "nsIFrame.h" +#include "nsRenderingContext.h" +#include "nsRuleNode.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +/* static*/ void +nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext, + nsIFrame* aFrame) +{ + auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; + +#ifdef DEBUG + StyleShapeSourceType type = clipPathStyle.GetType(); + MOZ_ASSERT(type == StyleShapeSourceType::Shape || + type == StyleShapeSourceType::Box, + "This function is used with basic-shape and geometry-box only."); +#endif + + nsCSSClipPathInstance instance(aFrame, clipPathStyle); + + aContext.NewPath(); + RefPtr<Path> path = instance.CreateClipPath(aContext.GetDrawTarget()); + aContext.SetPath(path); + aContext.Clip(); +} + +/* static*/ bool +nsCSSClipPathInstance::HitTestBasicShapeClip(nsIFrame* aFrame, + const gfxPoint& aPoint) +{ + auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; + StyleShapeSourceType type = clipPathStyle.GetType(); + MOZ_ASSERT(type != StyleShapeSourceType::None, "unexpected none value"); + // In the future nsCSSClipPathInstance may handle <clipPath> references as + // well. For the time being return early. + if (type == StyleShapeSourceType::URL) { + return false; + } + + nsCSSClipPathInstance instance(aFrame, clipPathStyle); + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<Path> path = instance.CreateClipPath(drawTarget); + float pixelRatio = float(nsPresContext::AppUnitsPerCSSPixel()) / + aFrame->PresContext()->AppUnitsPerDevPixel(); + return path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix()); +} + +nsRect +nsCSSClipPathInstance::ComputeSVGReferenceRect() +{ + MOZ_ASSERT(mTargetFrame->GetContent()->IsSVGElement()); + nsRect r; + + // For SVG elements without associated CSS layout box, the used value for + // content-box, padding-box, border-box and margin-box is fill-box. + switch (mClipPathStyle.GetReferenceBox()) { + case StyleClipPathGeometryBox::Stroke: { + // XXX Bug 1299876 + // The size of srtoke-box is not correct if this graphic element has + // specific stroke-linejoin or stroke-linecap. + gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame, + nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeStroke); + r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, + nsPresContext::AppUnitsPerCSSPixel()); + break; + } + case StyleClipPathGeometryBox::View: { + nsIContent* content = mTargetFrame->GetContent(); + nsSVGElement* element = static_cast<nsSVGElement*>(content); + SVGSVGElement* svgElement = element->GetCtx(); + MOZ_ASSERT(svgElement); + + if (svgElement && svgElement->HasViewBoxRect()) { + // If a ‘viewBox‘ attribute is specified for the SVG viewport creating + // element: + // 1. The reference box is positioned at the origin of the coordinate + // system established by the ‘viewBox‘ attribute. + // 2. The dimension of the reference box is set to the width and height + // values of the ‘viewBox‘ attribute. + nsSVGViewBox* viewBox = svgElement->GetViewBox(); + const nsSVGViewBoxRect& value = viewBox->GetAnimValue(); + r = nsRect(nsPresContext::CSSPixelsToAppUnits(value.x), + nsPresContext::CSSPixelsToAppUnits(value.y), + nsPresContext::CSSPixelsToAppUnits(value.width), + nsPresContext::CSSPixelsToAppUnits(value.height)); + } else { + // No viewBox is specified, uses the nearest SVG viewport as reference + // box. + svgFloatSize viewportSize = svgElement->GetViewportSize(); + r = nsRect(0, 0, + nsPresContext::CSSPixelsToAppUnits(viewportSize.width), + nsPresContext::CSSPixelsToAppUnits(viewportSize.height)); + } + + break; + } + case StyleClipPathGeometryBox::NoBox: + case StyleClipPathGeometryBox::Border: + case StyleClipPathGeometryBox::Content: + case StyleClipPathGeometryBox::Padding: + case StyleClipPathGeometryBox::Margin: + case StyleClipPathGeometryBox::Fill: { + gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame, + nsSVGUtils::eBBoxIncludeFill); + r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, + nsPresContext::AppUnitsPerCSSPixel()); + break; + } + default:{ + MOZ_ASSERT_UNREACHABLE("unknown StyleClipPathGeometryBox type"); + gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame, + nsSVGUtils::eBBoxIncludeFill); + r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, + nsPresContext::AppUnitsPerCSSPixel()); + break; + } + } + + return r; +} + +nsRect +nsCSSClipPathInstance::ComputeHTMLReferenceRect() +{ + nsRect r; + + // For elements with associated CSS layout box, the used value for fill-box, + // stroke-box and view-box is border-box. + switch (mClipPathStyle.GetReferenceBox()) { + case StyleClipPathGeometryBox::Content: + r = mTargetFrame->GetContentRectRelativeToSelf(); + break; + case StyleClipPathGeometryBox::Padding: + r = mTargetFrame->GetPaddingRectRelativeToSelf(); + break; + case StyleClipPathGeometryBox::Margin: + r = mTargetFrame->GetMarginRectRelativeToSelf(); + break; + case StyleClipPathGeometryBox::NoBox: + case StyleClipPathGeometryBox::Border: + case StyleClipPathGeometryBox::Fill: + case StyleClipPathGeometryBox::Stroke: + case StyleClipPathGeometryBox::View: + r = mTargetFrame->GetRectRelativeToSelf(); + break; + default: + MOZ_ASSERT_UNREACHABLE("unknown StyleClipPathGeometryBox type"); + r = mTargetFrame->GetRectRelativeToSelf(); + break; + } + + return r; +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPath(DrawTarget* aDrawTarget) +{ + // We use ComputeSVGReferenceRect for all SVG elements, except <svg> + // element, which does have an associated CSS layout box. In this case we + // should still use ComputeHTMLReferenceRect for region computing. + nsRect r = mTargetFrame->IsFrameOfType(nsIFrame::eSVG) && + (mTargetFrame->GetType() != nsGkAtoms::svgOuterSVGFrame) + ? ComputeSVGReferenceRect() : ComputeHTMLReferenceRect(); + + if (mClipPathStyle.GetType() != StyleShapeSourceType::Shape) { + // TODO Clip to border-radius/reference box if no shape + // was specified. + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + return builder->Finish(); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + r = ToAppUnits(r.ToNearestPixels(appUnitsPerDevPixel), appUnitsPerDevPixel); + + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + switch (basicShape->GetShapeType()) { + case StyleBasicShapeType::Circle: + return CreateClipPathCircle(aDrawTarget, r); + case StyleBasicShapeType::Ellipse: + return CreateClipPathEllipse(aDrawTarget, r); + case StyleBasicShapeType::Polygon: + return CreateClipPathPolygon(aDrawTarget, r); + case StyleBasicShapeType::Inset: + return CreateClipPathInset(aDrawTarget, r); + break; + default: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type"); + } + // Return an empty Path: + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + return builder->Finish(); +} + +static void +EnumerationToLength(nscoord& aCoord, int32_t aType, + nscoord aCenter, nscoord aPosMin, nscoord aPosMax) +{ + nscoord dist1 = abs(aPosMin - aCenter); + nscoord dist2 = abs(aPosMax - aCenter); + switch (aType) { + case NS_RADIUS_FARTHEST_SIDE: + aCoord = dist1 > dist2 ? dist1 : dist2; + break; + case NS_RADIUS_CLOSEST_SIDE: + aCoord = dist1 > dist2 ? dist2 : dist1; + break; + default: + NS_NOTREACHED("unknown keyword"); + break; + } +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathCircle(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nsPoint topLeft, anchor; + nsSize size = nsSize(aRefBox.width, aRefBox.height); + nsImageRenderer::ComputeObjectAnchorPoint(basicShape->GetPosition(), + size, size, + &topLeft, &anchor); + Point center = Point(anchor.x + aRefBox.x, anchor.y + aRefBox.y); + + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() == 1, "wrong number of arguments"); + float referenceLength = sqrt((aRefBox.width * aRefBox.width + + aRefBox.height * aRefBox.height) / 2.0); + nscoord r = 0; + if (coords[0].GetUnit() == eStyleUnit_Enumerated) { + nscoord horizontal, vertical; + EnumerationToLength(horizontal, coords[0].GetIntValue(), + center.x, aRefBox.x, aRefBox.x + aRefBox.width); + EnumerationToLength(vertical, coords[0].GetIntValue(), + center.y, aRefBox.y, aRefBox.y + aRefBox.height); + if (coords[0].GetIntValue() == NS_RADIUS_FARTHEST_SIDE) { + r = horizontal > vertical ? horizontal : vertical; + } else { + r = horizontal < vertical ? horizontal : vertical; + } + } else { + r = nsRuleNode::ComputeCoordPercentCalc(coords[0], referenceLength); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + builder->Arc(center / appUnitsPerDevPixel, r / appUnitsPerDevPixel, + 0, Float(2 * M_PI)); + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathEllipse(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nsPoint topLeft, anchor; + nsSize size = nsSize(aRefBox.width, aRefBox.height); + nsImageRenderer::ComputeObjectAnchorPoint(basicShape->GetPosition(), + size, size, + &topLeft, &anchor); + Point center = Point(anchor.x + aRefBox.x, anchor.y + aRefBox.y); + + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() == 2, "wrong number of arguments"); + nscoord rx = 0, ry = 0; + if (coords[0].GetUnit() == eStyleUnit_Enumerated) { + EnumerationToLength(rx, coords[0].GetIntValue(), + center.x, aRefBox.x, aRefBox.x + aRefBox.width); + } else { + rx = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width); + } + if (coords[1].GetUnit() == eStyleUnit_Enumerated) { + EnumerationToLength(ry, coords[1].GetIntValue(), + center.y, aRefBox.y, aRefBox.y + aRefBox.height); + } else { + ry = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + EllipseToBezier(builder.get(), + center / appUnitsPerDevPixel, + Size(rx, ry) / appUnitsPerDevPixel); + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathPolygon(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() % 2 == 0 && + coords.Length() >= 2, "wrong number of arguments"); + + FillRule fillRule = basicShape->GetFillRule() == StyleFillRule::Nonzero ? + FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD; + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(fillRule); + + nscoord x = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width); + nscoord y = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height); + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + builder->MoveTo(Point(aRefBox.x + x, aRefBox.y + y) / appUnitsPerDevPixel); + for (size_t i = 2; i < coords.Length(); i += 2) { + x = nsRuleNode::ComputeCoordPercentCalc(coords[i], aRefBox.width); + y = nsRuleNode::ComputeCoordPercentCalc(coords[i + 1], aRefBox.height); + builder->LineTo(Point(aRefBox.x + x, aRefBox.y + y) / appUnitsPerDevPixel); + } + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathInset(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() == 4, "wrong number of arguments"); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + + nsMargin inset(nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.height), + nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.width), + nsRuleNode::ComputeCoordPercentCalc(coords[2], aRefBox.height), + nsRuleNode::ComputeCoordPercentCalc(coords[3], aRefBox.width)); + + nsRect insetRect(aRefBox); + insetRect.Deflate(inset); + const Rect insetRectPixels = NSRectToRect(insetRect, appUnitsPerDevPixel); + const nsStyleCorners& radius = basicShape->GetRadius(); + + nscoord appUnitsRadii[8]; + + if (nsIFrame::ComputeBorderRadii(radius, insetRect.Size(), aRefBox.Size(), + Sides(), appUnitsRadii)) { + RectCornerRadii corners; + nsCSSRendering::ComputePixelRadii(appUnitsRadii, + appUnitsPerDevPixel, &corners); + + AppendRoundedRectToPath(builder, insetRectPixels, corners, true); + } else { + AppendRectToPath(builder, insetRectPixels, true); + } + return builder->Finish(); +} diff --git a/layout/svg/nsCSSClipPathInstance.h b/layout/svg/nsCSSClipPathInstance.h new file mode 100644 index 0000000000..3b0724dbd0 --- /dev/null +++ b/layout/svg/nsCSSClipPathInstance.h @@ -0,0 +1,64 @@ +/* -*- 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/. */ + +#ifndef __NS_CSSCLIPPATHINSTANCE_H__ +#define __NS_CSSCLIPPATHINSTANCE_H__ + +#include "nsStyleStruct.h" +#include "nsRect.h" +#include "mozilla/gfx/2D.h" + +class nsIFrame; +class nsRenderingContext; + +namespace mozilla { + +class nsCSSClipPathInstance +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Path Path; + +public: + static void ApplyBasicShapeClip(gfxContext& aContext, + nsIFrame* aFrame); + // aPoint is in CSS pixels. + static bool HitTestBasicShapeClip(nsIFrame* aFrame, + const gfxPoint& aPoint); +private: + explicit nsCSSClipPathInstance(nsIFrame* aFrame, + const StyleClipPath aClipPathStyle) + : mTargetFrame(aFrame) + , mClipPathStyle(aClipPathStyle) + { + } + + already_AddRefed<Path> CreateClipPath(DrawTarget* aDrawTarget); + + already_AddRefed<Path> CreateClipPathCircle(DrawTarget* aDrawTarget, + const nsRect& aRefBox); + + already_AddRefed<Path> CreateClipPathEllipse(DrawTarget* aDrawTarget, + const nsRect& aRefBox); + + already_AddRefed<Path> CreateClipPathPolygon(DrawTarget* aDrawTarget, + const nsRect& aRefBox); + + already_AddRefed<Path> CreateClipPathInset(DrawTarget* aDrawTarget, + const nsRect& aRefBox); + + + nsRect ComputeHTMLReferenceRect(); + nsRect ComputeSVGReferenceRect(); + + /** + * The frame for the element that is currently being clipped. + */ + nsIFrame* mTargetFrame; + StyleClipPath mClipPathStyle; +}; + +} // namespace mozilla + +#endif diff --git a/layout/svg/nsCSSFilterInstance.cpp b/layout/svg/nsCSSFilterInstance.cpp new file mode 100644 index 0000000000..79bf83b949 --- /dev/null +++ b/layout/svg/nsCSSFilterInstance.cpp @@ -0,0 +1,421 @@ +/* -*- 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 "nsCSSFilterInstance.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxUtils.h" +#include "nsIFrame.h" +#include "nsStyleStruct.h" +#include "nsTArray.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static float ClampFactor(float aFactor) +{ + if (aFactor > 1) { + return 1; + } else if (aFactor < 0) { + NS_NOTREACHED("A negative value should not have been parsed."); + return 0; + } + + return aFactor; +} + +nsCSSFilterInstance::nsCSSFilterInstance(const nsStyleFilter& aFilter, + nscolor aShadowFallbackColor, + const nsIntRect& aTargetBoundsInFilterSpace, + const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform) + : mFilter(aFilter) + , mShadowFallbackColor(aShadowFallbackColor) + , mTargetBoundsInFilterSpace(aTargetBoundsInFilterSpace) + , mFrameSpaceInCSSPxToFilterSpaceTransform(aFrameSpaceInCSSPxToFilterSpaceTransform) +{ +} + +nsresult +nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + bool aInputIsTainted) +{ + FilterPrimitiveDescription descr; + nsresult result; + + switch(mFilter.GetType()) { + case NS_STYLE_FILTER_BLUR: + descr = CreatePrimitiveDescription(PrimitiveType::GaussianBlur, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForBlur(descr); + break; + case NS_STYLE_FILTER_BRIGHTNESS: + descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForBrightness(descr); + break; + case NS_STYLE_FILTER_CONTRAST: + descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForContrast(descr); + break; + case NS_STYLE_FILTER_DROP_SHADOW: + descr = CreatePrimitiveDescription(PrimitiveType::DropShadow, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForDropShadow(descr); + break; + case NS_STYLE_FILTER_GRAYSCALE: + descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForGrayscale(descr); + break; + case NS_STYLE_FILTER_HUE_ROTATE: + descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForHueRotate(descr); + break; + case NS_STYLE_FILTER_INVERT: + descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForInvert(descr); + break; + case NS_STYLE_FILTER_OPACITY: + descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForOpacity(descr); + break; + case NS_STYLE_FILTER_SATURATE: + descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForSaturate(descr); + break; + case NS_STYLE_FILTER_SEPIA: + descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, + aPrimitiveDescrs, + aInputIsTainted); + result = SetAttributesForSepia(descr); + break; + default: + NS_NOTREACHED("not a valid CSS filter type"); + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(result)) { + return result; + } + + // Compute the primitive's bounds now that we've determined its attributes. + // Some attributes like blur radius can influence the bounds. + SetBounds(descr, aPrimitiveDescrs); + + // Add this primitive to the filter chain. + aPrimitiveDescrs.AppendElement(descr); + return NS_OK; +} + +FilterPrimitiveDescription +nsCSSFilterInstance::CreatePrimitiveDescription(PrimitiveType aType, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + bool aInputIsTainted) { + FilterPrimitiveDescription descr(aType); + int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs); + descr.SetInputPrimitive(0, inputIndex); + descr.SetIsTainted(inputIndex < 0 ? aInputIsTainted : aPrimitiveDescrs[inputIndex].IsTainted()); + descr.SetInputColorSpace(0, ColorSpace::SRGB); + descr.SetOutputColorSpace(ColorSpace::SRGB); + return descr; +} + +nsresult +nsCSSFilterInstance::SetAttributesForBlur(FilterPrimitiveDescription& aDescr) +{ + const nsStyleCoord& radiusInFrameSpace = mFilter.GetFilterParameter(); + if (radiusInFrameSpace.GetUnit() != eStyleUnit_Coord) { + NS_NOTREACHED("unexpected unit"); + return NS_ERROR_FAILURE; + } + + Size radiusInFilterSpace = BlurRadiusToFilterSpace(radiusInFrameSpace.GetCoordValue()); + aDescr.Attributes().Set(eGaussianBlurStdDeviation, radiusInFilterSpace); + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForBrightness(FilterPrimitiveDescription& aDescr) +{ + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = styleValue.GetFactorOrPercentValue(); + + // Set transfer functions for RGB. + AttributeMap brightnessAttrs; + brightnessAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR); + brightnessAttrs.Set(eComponentTransferFunctionSlope, value); + brightnessAttrs.Set(eComponentTransferFunctionIntercept, 0.0f); + aDescr.Attributes().Set(eComponentTransferFunctionR, brightnessAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionG, brightnessAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionB, brightnessAttrs); + + // Set identity transfer function for A. + AttributeMap identityAttrs; + identityAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY); + aDescr.Attributes().Set(eComponentTransferFunctionA, identityAttrs); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForContrast(FilterPrimitiveDescription& aDescr) +{ + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = styleValue.GetFactorOrPercentValue(); + float intercept = -(0.5 * value) + 0.5; + + // Set transfer functions for RGB. + AttributeMap contrastAttrs; + contrastAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR); + contrastAttrs.Set(eComponentTransferFunctionSlope, value); + contrastAttrs.Set(eComponentTransferFunctionIntercept, intercept); + aDescr.Attributes().Set(eComponentTransferFunctionR, contrastAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionG, contrastAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionB, contrastAttrs); + + // Set identity transfer function for A. + AttributeMap identityAttrs; + identityAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY); + aDescr.Attributes().Set(eComponentTransferFunctionA, identityAttrs); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForDropShadow(FilterPrimitiveDescription& aDescr) +{ + nsCSSShadowArray* shadows = mFilter.GetDropShadow(); + if (!shadows || shadows->Length() != 1) { + NS_NOTREACHED("Exactly one drop shadow should have been parsed."); + return NS_ERROR_FAILURE; + } + + nsCSSShadowItem* shadow = shadows->ShadowAt(0); + + // Set drop shadow blur radius. + Size radiusInFilterSpace = BlurRadiusToFilterSpace(shadow->mRadius); + aDescr.Attributes().Set(eDropShadowStdDeviation, radiusInFilterSpace); + + // Set offset. + IntPoint offsetInFilterSpace = OffsetToFilterSpace(shadow->mXOffset, shadow->mYOffset); + aDescr.Attributes().Set(eDropShadowOffset, offsetInFilterSpace); + + // Set color. If unspecified, use the CSS color property. + nscolor shadowColor = shadow->mHasColor ? shadow->mColor : mShadowFallbackColor; + aDescr.Attributes().Set(eDropShadowColor, ToAttributeColor(shadowColor)); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForGrayscale(FilterPrimitiveDescription& aDescr) +{ + // Set color matrix type. + aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE); + + // Set color matrix values. + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = 1 - ClampFactor(styleValue.GetFactorOrPercentValue()); + aDescr.Attributes().Set(eColorMatrixValues, &value, 1); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForHueRotate(FilterPrimitiveDescription& aDescr) +{ + // Set color matrix type. + aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_HUE_ROTATE); + + // Set color matrix values. + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = styleValue.GetAngleValueInDegrees(); + aDescr.Attributes().Set(eColorMatrixValues, &value, 1); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForInvert(FilterPrimitiveDescription& aDescr) +{ + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = ClampFactor(styleValue.GetFactorOrPercentValue()); + + // Set transfer functions for RGB. + AttributeMap invertAttrs; + float invertTableValues[2]; + invertTableValues[0] = value; + invertTableValues[1] = 1 - value; + invertAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE); + invertAttrs.Set(eComponentTransferFunctionTableValues, invertTableValues, 2); + aDescr.Attributes().Set(eComponentTransferFunctionR, invertAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionG, invertAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionB, invertAttrs); + + // Set identity transfer function for A. + AttributeMap identityAttrs; + identityAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY); + aDescr.Attributes().Set(eComponentTransferFunctionA, identityAttrs); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForOpacity(FilterPrimitiveDescription& aDescr) +{ + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = ClampFactor(styleValue.GetFactorOrPercentValue()); + + // Set identity transfer functions for RGB. + AttributeMap identityAttrs; + identityAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY); + aDescr.Attributes().Set(eComponentTransferFunctionR, identityAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionG, identityAttrs); + aDescr.Attributes().Set(eComponentTransferFunctionB, identityAttrs); + + // Set transfer function for A. + AttributeMap opacityAttrs; + float opacityTableValues[2]; + opacityTableValues[0] = 0; + opacityTableValues[1] = value; + opacityAttrs.Set(eComponentTransferFunctionType, + (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE); + opacityAttrs.Set(eComponentTransferFunctionTableValues, opacityTableValues, 2); + aDescr.Attributes().Set(eComponentTransferFunctionA, opacityAttrs); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForSaturate(FilterPrimitiveDescription& aDescr) +{ + // Set color matrix type. + aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE); + + // Set color matrix values. + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = styleValue.GetFactorOrPercentValue(); + aDescr.Attributes().Set(eColorMatrixValues, &value, 1); + + return NS_OK; +} + +nsresult +nsCSSFilterInstance::SetAttributesForSepia(FilterPrimitiveDescription& aDescr) +{ + // Set color matrix type. + aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_SEPIA); + + // Set color matrix values. + const nsStyleCoord& styleValue = mFilter.GetFilterParameter(); + float value = ClampFactor(styleValue.GetFactorOrPercentValue()); + aDescr.Attributes().Set(eColorMatrixValues, &value, 1); + + return NS_OK; +} + +Size +nsCSSFilterInstance::BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace) +{ + float radiusInFrameSpaceInCSSPx = + nsPresContext::AppUnitsToFloatCSSPixels(aRadiusInFrameSpace); + + // Convert the radius to filter space. + Size radiusInFilterSpace(radiusInFrameSpaceInCSSPx, + radiusInFrameSpaceInCSSPx); + gfxSize frameSpaceInCSSPxToFilterSpaceScale = + mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors(true); + radiusInFilterSpace.Scale(frameSpaceInCSSPxToFilterSpaceScale.width, + frameSpaceInCSSPxToFilterSpaceScale.height); + + // Check the radius limits. + if (radiusInFilterSpace.width < 0 || radiusInFilterSpace.height < 0) { + NS_NOTREACHED("we shouldn't have parsed a negative radius in the style"); + return Size(); + } + Float maxStdDeviation = (Float)kMaxStdDeviation; + radiusInFilterSpace.width = std::min(radiusInFilterSpace.width, maxStdDeviation); + radiusInFilterSpace.height = std::min(radiusInFilterSpace.height, maxStdDeviation); + + return radiusInFilterSpace; +} + +IntPoint +nsCSSFilterInstance::OffsetToFilterSpace(nscoord aXOffsetInFrameSpace, + nscoord aYOffsetInFrameSpace) +{ + gfxPoint offsetInFilterSpace(nsPresContext::AppUnitsToFloatCSSPixels(aXOffsetInFrameSpace), + nsPresContext::AppUnitsToFloatCSSPixels(aYOffsetInFrameSpace)); + + // Convert the radius to filter space. + gfxSize frameSpaceInCSSPxToFilterSpaceScale = + mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors(true); + offsetInFilterSpace.x *= frameSpaceInCSSPxToFilterSpaceScale.width; + offsetInFilterSpace.y *= frameSpaceInCSSPxToFilterSpaceScale.height; + + return IntPoint(int32_t(offsetInFilterSpace.x), int32_t(offsetInFilterSpace.y)); +} + +Color +nsCSSFilterInstance::ToAttributeColor(nscolor aColor) +{ + return Color( + NS_GET_R(aColor) / 255.0, + NS_GET_G(aColor) / 255.0, + NS_GET_B(aColor) / 255.0, + NS_GET_A(aColor) / 255.0 + ); +} + +int32_t +nsCSSFilterInstance::GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) +{ + uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); + return !numPrimitiveDescrs ? + FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic : + numPrimitiveDescrs - 1; +} + +void +nsCSSFilterInstance::SetBounds(FilterPrimitiveDescription& aDescr, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) +{ + int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs); + nsIntRect inputBounds = (inputIndex < 0) ? + mTargetBoundsInFilterSpace : aPrimitiveDescrs[inputIndex].PrimitiveSubregion(); + + nsTArray<nsIntRegion> inputExtents; + inputExtents.AppendElement(inputBounds); + + nsIntRegion outputExtents = + FilterSupport::PostFilterExtentsForPrimitive(aDescr, inputExtents); + IntRect outputBounds = outputExtents.GetBounds(); + + aDescr.SetPrimitiveSubregion(outputBounds); + aDescr.SetFilterSpaceBounds(outputBounds); +} diff --git a/layout/svg/nsCSSFilterInstance.h b/layout/svg/nsCSSFilterInstance.h new file mode 100644 index 0000000000..63e427440c --- /dev/null +++ b/layout/svg/nsCSSFilterInstance.h @@ -0,0 +1,144 @@ +/* -*- 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/. */ + +#ifndef __NS_CSSFILTERINSTANCE_H__ +#define __NS_CSSFILTERINSTANCE_H__ + +#include "FilterSupport.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" +#include "nsColor.h" +#include "nsTArrayForwardDeclare.h" + +struct nsStyleFilter; + +/** + * This class helps nsFilterInstance build its filter graph. It turns a CSS + * filter function (e.g. blur(3px)) from the style system into a + * FilterPrimitiveDescription connected to the filter graph. + */ +class nsCSSFilterInstance +{ + typedef mozilla::gfx::Color Color; + typedef mozilla::gfx::FilterPrimitiveDescription FilterPrimitiveDescription; + typedef mozilla::gfx::IntPoint IntPoint; + typedef mozilla::gfx::PrimitiveType PrimitiveType; + typedef mozilla::gfx::Size Size; + +public: + /** + * @param aFilter The CSS filter from the style system. This class stores + * aFilter by reference, so callers should avoid modifying or deleting + * aFilter during the lifetime of nsCSSFilterInstance. + * @param aShadowFallbackColor The color that should be used for + * drop-shadow() filters that don't specify a shadow color. + * @param aTargetBoundsInFilterSpace The pre-filter visual overflow rect of + * the frame being filtered, in filter space. + * @param aFrameSpaceInCSSPxToFilterSpaceTransform The transformation from + * the filtered element's frame space in CSS pixels to filter space. + */ + nsCSSFilterInstance(const nsStyleFilter& aFilter, + nscolor aShadowFallbackColor, + const nsIntRect& aTargetBoundsInFilterSpace, + const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform); + + /** + * Creates at least one new FilterPrimitiveDescription based on the filter + * from the style system. Appends the new FilterPrimitiveDescription(s) to the + * aPrimitiveDescrs list. + * aInputIsTainted describes whether the input to this filter is tainted, i.e. + * whether it contains security-sensitive content. This is needed to propagate + * taintedness to the FilterPrimitive that take tainted inputs. Something being + * tainted means that it contains security sensitive content. + * The input to this filter is the previous filter's output, i.e. the last + * element in aPrimitiveDescrs, or the SourceGraphic input if this is the first + * filter in the filter chain. + */ + nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + bool aInputIsTainted); + +private: + /** + * Returns a new FilterPrimitiveDescription with its basic properties set up. + * See the comment above BuildPrimitives for the meaning of aInputIsTainted. + */ + FilterPrimitiveDescription CreatePrimitiveDescription(PrimitiveType aType, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + bool aInputIsTainted); + + /** + * Sets aDescr's attributes using the style info in mFilter. + */ + nsresult SetAttributesForBlur(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForBrightness(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForContrast(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForDropShadow(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForGrayscale(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForHueRotate(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForInvert(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForOpacity(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForSaturate(FilterPrimitiveDescription& aDescr); + nsresult SetAttributesForSepia(FilterPrimitiveDescription& aDescr); + + /** + * Returns the index of the last result in the aPrimitiveDescrs, which we'll + * use as the input to this CSS filter. + */ + int32_t GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs); + + /** + * Sets aDescr's filter region and primitive subregion to appropriate values + * based on this CSS filter's input and its attributes. For example, a CSS + * blur filter will have bounds equal to its input bounds, inflated by the + * blur extents. + */ + void SetBounds(FilterPrimitiveDescription& aDescr, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs); + + /** + * Converts an nscolor to a Color, suitable for use as a + * FilterPrimitiveDescription attribute. + */ + Color ToAttributeColor(nscolor aColor); + + /** + * Converts a blur radius in frame space to filter space. + */ + Size BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace); + + /** + * Converts a point defined by a pair of nscoord x, y coordinates from frame + * space to filter space. + */ + IntPoint OffsetToFilterSpace(nscoord aXOffsetInFrameSpace, + nscoord aYOffsetInFrameSpace); + + /** + * The CSS filter originally from the style system. + */ + const nsStyleFilter& mFilter; + + /** + * The color that should be used for drop-shadow() filters that don't + * specify a shadow color. + */ + nscolor mShadowFallbackColor; + + /** + * The pre-filter overflow rect of the frame being filtered, in filter space. + * Used for input bounds if this CSS filter is the first in the filter chain. + */ + nsIntRect mTargetBoundsInFilterSpace; + + /** + * The transformation from the filtered element's frame space in CSS pixels to + * filter space. Used to transform style values to filter space. + */ + gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform; +}; + +#endif diff --git a/layout/svg/nsFilterInstance.cpp b/layout/svg/nsFilterInstance.cpp new file mode 100644 index 0000000000..fe52b8a8f8 --- /dev/null +++ b/layout/svg/nsFilterInstance.cpp @@ -0,0 +1,627 @@ +/* -*- 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 "nsFilterInstance.h" + +// MFBT headers next: +#include "mozilla/UniquePtr.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "nsISVGChildFrame.h" +#include "nsCSSFilterInstance.h" +#include "nsSVGFilterInstance.h" +#include "nsSVGFilterPaintCallback.h" +#include "nsSVGUtils.h" +#include "SVGContentUtils.h" +#include "FilterSupport.h" +#include "gfx2DGlue.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +FilterDescription +nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement, + const nsTArray<nsStyleFilter>& aFilterChain, + bool aFilterInputIsTainted, + const UserSpaceMetrics& aMetrics, + const gfxRect& aBBox, + nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) +{ + gfxMatrix unused; // aPaintTransform arg not used since we're not painting + nsFilterInstance instance(nullptr, aFilteredElement, aMetrics, + aFilterChain, aFilterInputIsTainted, nullptr, + unused, nullptr, nullptr, nullptr, &aBBox); + if (!instance.IsInitialized()) { + return FilterDescription(); + } + return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages); +} + +static UniquePtr<UserSpaceMetrics> +UserSpaceMetricsForFrame(nsIFrame* aFrame) +{ + if (aFrame->GetContent()->IsSVGElement()) { + nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent()); + return MakeUnique<SVGElementMetrics>(element); + } + return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame); +} + +nsresult +nsFilterInstance::PaintFilteredFrame(nsIFrame *aFilteredFrame, + DrawTarget* aDrawTarget, + const gfxMatrix& aTransform, + nsSVGFilterPaintCallback *aPaintCallback, + const nsRegion *aDirtyArea) +{ + auto& filterChain = aFilteredFrame->StyleEffects()->mFilters; + UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame); + // Hardcode InputIsTainted to true because we don't want JS to be able to + // read the rendered contents of aFilteredFrame. + nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics, + filterChain, /* InputIsTainted */ true, aPaintCallback, + aTransform, aDirtyArea, nullptr, nullptr, nullptr); + if (!instance.IsInitialized()) { + return NS_OK; + } + return instance.Render(aDrawTarget); +} + +nsRegion +nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame, + const nsRegion& aPreFilterDirtyRegion) +{ + if (aPreFilterDirtyRegion.IsEmpty()) { + return nsRegion(); + } + + gfxMatrix unused; // aPaintTransform arg not used since we're not painting + auto& filterChain = aFilteredFrame->StyleEffects()->mFilters; + UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame); + // Hardcode InputIsTainted to true because we don't want JS to be able to + // read the rendered contents of aFilteredFrame. + nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics, + filterChain, /* InputIsTainted */ true, nullptr, unused, + nullptr, &aPreFilterDirtyRegion); + if (!instance.IsInitialized()) { + return nsRegion(); + } + + // We've passed in the source's dirty area so the instance knows about it. + // Now we can ask the instance to compute the area of the filter output + // that's dirty. + return instance.ComputePostFilterDirtyRegion(); +} + +nsRegion +nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame, + const nsRegion& aPostFilterDirtyRegion) +{ + gfxMatrix unused; // aPaintTransform arg not used since we're not painting + auto& filterChain = aFilteredFrame->StyleEffects()->mFilters; + UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame); + // Hardcode InputIsTainted to true because we don't want JS to be able to + // read the rendered contents of aFilteredFrame. + nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics, + filterChain, /* InputIsTainted */ true, nullptr, unused, + &aPostFilterDirtyRegion); + if (!instance.IsInitialized()) { + return nsRect(); + } + + // Now we can ask the instance to compute the area of the source + // that's needed. + return instance.ComputeSourceNeededRect(); +} + +nsRect +nsFilterInstance::GetPostFilterBounds(nsIFrame *aFilteredFrame, + const gfxRect *aOverrideBBox, + const nsRect *aPreFilterBounds) +{ + MOZ_ASSERT(!(aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) || + !(aFilteredFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "Non-display SVG do not maintain visual overflow rects"); + + nsRegion preFilterRegion; + nsRegion* preFilterRegionPtr = nullptr; + if (aPreFilterBounds) { + preFilterRegion = *aPreFilterBounds; + preFilterRegionPtr = &preFilterRegion; + } + + gfxMatrix unused; // aPaintTransform arg not used since we're not painting + auto& filterChain = aFilteredFrame->StyleEffects()->mFilters; + UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame); + // Hardcode InputIsTainted to true because we don't want JS to be able to + // read the rendered contents of aFilteredFrame. + nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics, + filterChain, /* InputIsTainted */ true, nullptr, unused, + nullptr, preFilterRegionPtr, aPreFilterBounds, + aOverrideBBox); + if (!instance.IsInitialized()) { + return nsRect(); + } + + return instance.ComputePostFilterExtents(); +} + +nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame, + nsIContent* aTargetContent, + const UserSpaceMetrics& aMetrics, + const nsTArray<nsStyleFilter>& aFilterChain, + bool aFilterInputIsTainted, + nsSVGFilterPaintCallback *aPaintCallback, + const gfxMatrix& aPaintTransform, + const nsRegion *aPostFilterDirtyRegion, + const nsRegion *aPreFilterDirtyRegion, + const nsRect *aPreFilterVisualOverflowRectOverride, + const gfxRect *aOverrideBBox) + : mTargetFrame(aTargetFrame) + , mTargetContent(aTargetContent) + , mMetrics(aMetrics) + , mPaintCallback(aPaintCallback) + , mPaintTransform(aPaintTransform) + , mInitialized(false) +{ + if (aOverrideBBox) { + mTargetBBox = *aOverrideBBox; + } else { + MOZ_ASSERT(mTargetFrame, "Need to supply a frame when there's no aOverrideBBox"); + mTargetBBox = nsSVGUtils::GetBBox(mTargetFrame); + } + + // Compute user space to filter space transforms. + nsresult rv = ComputeUserSpaceToFilterSpaceScale(); + if (NS_FAILED(rv)) { + return; + } + + gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox); + targetBBoxInFilterSpace.RoundOut(); + if (!gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace, &mTargetBBoxInFilterSpace)) { + // The target's bbox is way too big if there is float->int overflow. + return; + } + + // Get various transforms: + + gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f, + 0.0f, mFilterSpaceToUserSpaceScale.height, + 0.0f, 0.0f); + + // Only used (so only set) when we paint: + if (mPaintCallback) { + mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform; + } + + mFilterSpaceToFrameSpaceInCSSPxTransform = + filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform(); + // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible + mFrameSpaceInCSSPxToFilterSpaceTransform = + mFilterSpaceToFrameSpaceInCSSPxTransform; + mFrameSpaceInCSSPxToFilterSpaceTransform.Invert(); + + nsIntRect targetBounds; + if (aPreFilterVisualOverflowRectOverride) { + targetBounds = + FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride); + } else if (mTargetFrame) { + nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect(); + targetBounds = FrameSpaceToFilterSpace(&preFilterVOR); + } + mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds); + + // Build the filter graph. + rv = BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted); + if (NS_FAILED(rv)) { + return; + } + + if (mPrimitiveDescriptions.IsEmpty()) { + // Nothing should be rendered. + return; + } + + // Convert the passed in rects from frame space to filter space: + mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion); + mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion); + + mInitialized = true; +} + +nsresult +nsFilterInstance::ComputeUserSpaceToFilterSpaceScale() +{ + gfxMatrix canvasTransform; + if (mTargetFrame) { + canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame); + if (canvasTransform.IsSingular()) { + // Nothing should be rendered. + return NS_ERROR_FAILURE; + } + } + + mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true); + if (mUserSpaceToFilterSpaceScale.width <= 0.0f || + mUserSpaceToFilterSpaceScale.height <= 0.0f) { + // Nothing should be rendered. + return NS_ERROR_FAILURE; + } + + mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width, + 1.0f / mUserSpaceToFilterSpaceScale.height); + return NS_OK; +} + +gfxRect +nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const +{ + gfxRect filterSpaceRect = aUserSpaceRect; + filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width, + mUserSpaceToFilterSpaceScale.height); + return filterSpaceRect; +} + +gfxRect +nsFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const +{ + gfxRect userSpaceRect = aFilterSpaceRect; + userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width, + mFilterSpaceToUserSpaceScale.height); + return userSpaceRect; +} + +nsresult +nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain, + nsIFrame* aTargetFrame, + bool aFilterInputIsTainted) +{ + NS_ASSERTION(!mPrimitiveDescriptions.Length(), + "expected to start building primitives from scratch"); + + for (uint32_t i = 0; i < aFilterChain.Length(); i++) { + bool inputIsTainted = + mPrimitiveDescriptions.IsEmpty() ? aFilterInputIsTainted : + mPrimitiveDescriptions.LastElement().IsTainted(); + nsresult rv = BuildPrimitivesForFilter(aFilterChain[i], aTargetFrame, inputIsTainted); + if (NS_FAILED(rv)) { + return rv; + } + } + + mFilterDescription = FilterDescription(mPrimitiveDescriptions); + + return NS_OK; +} + +nsresult +nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter, + nsIFrame* aTargetFrame, + bool aInputIsTainted) +{ + NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f && + mFilterSpaceToUserSpaceScale.height > 0.0f, + "scale factors between spaces should be positive values"); + + if (aFilter.GetType() == NS_STYLE_FILTER_URL) { + // Build primitives for an SVG filter. + nsSVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, + mTargetContent, + mMetrics, mTargetBBox, + mUserSpaceToFilterSpaceScale, + mFilterSpaceToUserSpaceScale); + if (!svgFilterInstance.IsInitialized()) { + return NS_ERROR_FAILURE; + } + + return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages, + aInputIsTainted); + } + + // Build primitives for a CSS filter. + + // If we don't have a frame, use opaque black for shadows with unspecified + // shadow colors. + nscolor shadowFallbackColor = + mTargetFrame ? mTargetFrame->StyleColor()->mColor : NS_RGB(0,0,0); + + nsCSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor, + mTargetBounds, + mFrameSpaceInCSSPxToFilterSpaceTransform); + return cssFilterInstance.BuildPrimitives(mPrimitiveDescriptions, aInputIsTainted); +} + +void +nsFilterInstance::ComputeNeededBoxes() +{ + if (mPrimitiveDescriptions.IsEmpty()) + return; + + nsIntRegion sourceGraphicNeededRegion; + nsIntRegion fillPaintNeededRegion; + nsIntRegion strokePaintNeededRegion; + + FilterSupport::ComputeSourceNeededRegions( + mFilterDescription, mPostFilterDirtyRegion, + sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion); + + sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds); + + mSourceGraphic.mNeededBounds = sourceGraphicNeededRegion.GetBounds(); + mFillPaint.mNeededBounds = fillPaintNeededRegion.GetBounds(); + mStrokePaint.mNeededBounds = strokePaintNeededRegion.GetBounds(); +} + +nsresult +nsFilterInstance::BuildSourcePaint(SourceInfo *aSource, + DrawTarget* aTargetDT) +{ + MOZ_ASSERT(mTargetFrame); + nsIntRect neededRect = aSource->mNeededBounds; + + RefPtr<DrawTarget> offscreenDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + neededRect.Size(), SurfaceFormat::B8G8R8A8); + if (!offscreenDT || !offscreenDT->IsValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform(); + if (!deviceToFilterSpace.Invert()) { + return NS_ERROR_FAILURE; + } + + if (!mPaintTransform.IsSingular()) { + RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(offscreenDT); + MOZ_ASSERT(gfx); // already checked the draw target above + gfx->Save(); + gfx->Multiply(mPaintTransform * + deviceToFilterSpace * + gfxMatrix::Translation(-neededRect.TopLeft())); + GeneralPattern pattern; + if (aSource == &mFillPaint) { + nsSVGUtils::MakeFillPatternFor(mTargetFrame, gfx, &pattern); + } else if (aSource == &mStrokePaint) { + nsSVGUtils::MakeStrokePatternFor(mTargetFrame, gfx, &pattern); + } + if (pattern.GetPattern()) { + offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), + pattern); + } + gfx->Restore(); + } + + aSource->mSourceSurface = offscreenDT->Snapshot(); + aSource->mSurfaceRect = neededRect; + + return NS_OK; +} + +nsresult +nsFilterInstance::BuildSourcePaints(DrawTarget* aTargetDT) +{ + nsresult rv = NS_OK; + + if (!mFillPaint.mNeededBounds.IsEmpty()) { + rv = BuildSourcePaint(&mFillPaint, aTargetDT); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!mStrokePaint.mNeededBounds.IsEmpty()) { + rv = BuildSourcePaint(&mStrokePaint, aTargetDT); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; +} + +nsresult +nsFilterInstance::BuildSourceImage(DrawTarget* aTargetDT) +{ + MOZ_ASSERT(mTargetFrame); + + nsIntRect neededRect = mSourceGraphic.mNeededBounds; + if (neededRect.IsEmpty()) { + return NS_OK; + } + + RefPtr<DrawTarget> offscreenDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + neededRect.Size(), SurfaceFormat::B8G8R8A8); + if (!offscreenDT || !offscreenDT->IsValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect)); + r.RoundOut(); + nsIntRect dirty; + if (!gfxUtils::GfxRectToIntRect(r, &dirty)) + return NS_ERROR_FAILURE; + + // SVG graphics paint to device space, so we need to set an initial device + // space to filter space transform on the gfxContext that SourceGraphic + // and SourceAlpha will paint to. + // + // (In theory it would be better to minimize error by having filtered SVG + // graphics temporarily paint to user space when painting the sources and + // only set a user space to filter space transform on the gfxContext + // (since that would eliminate the transform multiplications from user + // space to device space and back again). However, that would make the + // code more complex while being hard to get right without introducing + // subtle bugs, and in practice it probably makes no real difference.) + gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform(); + if (!deviceToFilterSpace.Invert()) { + return NS_ERROR_FAILURE; + } + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT); + MOZ_ASSERT(ctx); // already checked the draw target above + ctx->SetMatrix( + ctx->CurrentMatrix().Translate(-neededRect.TopLeft()). + PreMultiply(deviceToFilterSpace)); + + DrawResult result = + mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty); + + mSourceGraphic.mSourceSurface = offscreenDT->Snapshot(); + mSourceGraphic.mSurfaceRect = neededRect; + + return (result == DrawResult::SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +nsFilterInstance::Render(DrawTarget* aDrawTarget) +{ + MOZ_ASSERT(mTargetFrame, "Need a frame for rendering"); + + nsIntRect filterRect = + mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds()); + gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform(); + + if (filterRect.IsEmpty() || ctm.IsSingular()) { + return NS_OK; + } + + AutoRestoreTransform autoRestoreTransform(aDrawTarget); + Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) * + aDrawTarget->GetTransform(); + aDrawTarget->SetTransform(newTM); + + ComputeNeededBoxes(); + + nsresult rv = BuildSourceImage(aDrawTarget); + if (NS_FAILED(rv)) + return rv; + rv = BuildSourcePaints(aDrawTarget); + if (NS_FAILED(rv)) + return rv; + + FilterSupport::RenderFilterDescription( + aDrawTarget, mFilterDescription, IntRectToRect(filterRect), + mSourceGraphic.mSourceSurface, mSourceGraphic.mSurfaceRect, + mFillPaint.mSourceSurface, mFillPaint.mSurfaceRect, + mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect, + mInputImages, Point(0, 0)); + + return NS_OK; +} + +nsRegion +nsFilterInstance::ComputePostFilterDirtyRegion() +{ + if (mPreFilterDirtyRegion.IsEmpty()) { + return nsRegion(); + } + + nsIntRegion resultChangeRegion = + FilterSupport::ComputeResultChangeRegion(mFilterDescription, + mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion()); + return FilterSpaceToFrameSpace(resultChangeRegion); +} + +nsRect +nsFilterInstance::ComputePostFilterExtents() +{ + nsIntRegion postFilterExtents = + FilterSupport::ComputePostFilterExtents(mFilterDescription, mTargetBounds); + return FilterSpaceToFrameSpace(postFilterExtents.GetBounds()); +} + +nsRect +nsFilterInstance::ComputeSourceNeededRect() +{ + ComputeNeededBoxes(); + return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds); +} + +nsIntRect +nsFilterInstance::OutputFilterSpaceBounds() const +{ + uint32_t numPrimitives = mPrimitiveDescriptions.Length(); + if (numPrimitives <= 0) + return nsIntRect(); + + nsIntRect bounds = + mPrimitiveDescriptions[numPrimitives - 1].PrimitiveSubregion(); + bool overflow; + IntSize surfaceSize = + nsSVGUtils::ConvertToSurfaceSize(bounds.Size(), &overflow); + bounds.SizeTo(surfaceSize); + return bounds; +} + +nsIntRect +nsFilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const +{ + nsIntRect rect = OutputFilterSpaceBounds(); + if (aRect) { + if (aRect->IsEmpty()) { + return nsIntRect(); + } + gfxRect rectInCSSPx = + nsLayoutUtils::RectToGfxRect(*aRect, nsPresContext::AppUnitsPerCSSPixel()); + gfxRect rectInFilterSpace = + mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx); + rectInFilterSpace.RoundOut(); + nsIntRect intRect; + if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) { + rect = intRect; + } + } + return rect; +} + +nsRect +nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const +{ + if (aRect.IsEmpty()) { + return nsRect(); + } + gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); + r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r); + // nsLayoutUtils::RoundGfxRectToAppRect rounds out. + return nsLayoutUtils::RoundGfxRectToAppRect(r, nsPresContext::AppUnitsPerCSSPixel()); +} + +nsIntRegion +nsFilterInstance::FrameSpaceToFilterSpace(const nsRegion* aRegion) const +{ + if (!aRegion) { + return OutputFilterSpaceBounds(); + } + nsIntRegion result; + for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) { + // FrameSpaceToFilterSpace rounds out, so this works. + result.Or(result, FrameSpaceToFilterSpace(&iter.Get())); + } + return result; +} + +nsRegion +nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const +{ + nsRegion result; + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + // FilterSpaceToFrameSpace rounds out, so this works. + result.Or(result, FilterSpaceToFrameSpace(iter.Get())); + } + return result; +} + +gfxMatrix +nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const +{ + if (!mTargetFrame) { + return gfxMatrix(); + } + return gfxMatrix::Translation(-nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame)); +} diff --git a/layout/svg/nsFilterInstance.h b/layout/svg/nsFilterInstance.h new file mode 100644 index 0000000000..f4158f1c76 --- /dev/null +++ b/layout/svg/nsFilterInstance.h @@ -0,0 +1,390 @@ +/* -*- 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/. */ + +#ifndef __NS_FILTERINSTANCE_H__ +#define __NS_FILTERINSTANCE_H__ + +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsSVGFilters.h" +#include "nsSVGNumber2.h" +#include "nsSVGNumberPair.h" +#include "nsTArray.h" +#include "nsIFrame.h" +#include "mozilla/gfx/2D.h" + +class gfxContext; +class nsIFrame; +class nsSVGFilterPaintCallback; + +namespace mozilla { +namespace dom { +class UserSpaceMetrics; +} // namespace dom +} // namespace mozilla + +/** + * This class performs all filter processing. + * + * We build a graph of the filter image data flow, essentially + * converting the filter graph to SSA. This lets us easily propagate + * analysis data (such as bounding-boxes) over the filter primitive graph. + * + * Definition of "filter space": filter space is a coordinate system that is + * aligned with the user space of the filtered element, with its origin located + * at the top left of the filter region, and with one unit equal in size to one + * pixel of the offscreen surface into which the filter output would/will be + * painted. + * + * The definition of "filter region" can be found here: + * http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion + */ +class nsFilterInstance +{ + typedef mozilla::gfx::IntRect IntRect; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::FilterPrimitiveDescription FilterPrimitiveDescription; + typedef mozilla::gfx::FilterDescription FilterDescription; + typedef mozilla::dom::UserSpaceMetrics UserSpaceMetrics; + +public: + /** + * Create a FilterDescription for the supplied filter. All coordinates in + * the description are in filter space. + * @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha + * input is tainted. This affects whether feDisplacementMap will respect + * the filter input as its map input, and it affects the IsTainted() state + * on the filter primitives in the FilterDescription. "Tainted" is a term + * from the filters spec and means security-sensitive content, i.e. pixels + * that JS should not be able to read in any way. + * @param aOutAdditionalImages Will contain additional images needed to + * render the filter (from feImage primitives). + * @return A FilterDescription describing the filter. + */ + static FilterDescription GetFilterDescription(nsIContent* aFilteredElement, + const nsTArray<nsStyleFilter>& aFilterChain, + bool aFilterInputIsTainted, + const UserSpaceMetrics& aMetrics, + const gfxRect& aBBox, + nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages); + + /** + * Paint the given filtered frame. + * @param aDirtyArea The area than needs to be painted, in aFilteredFrame's + * frame space (i.e. relative to its origin, the top-left corner of its + * border box). + */ + static nsresult PaintFilteredFrame(nsIFrame *aFilteredFrame, + DrawTarget* aDrawTarget, + const gfxMatrix& aTransform, + nsSVGFilterPaintCallback *aPaintCallback, + const nsRegion* aDirtyArea); + + /** + * Returns the post-filter area that could be dirtied when the given + * pre-filter area of aFilteredFrame changes. + * @param aPreFilterDirtyRegion The pre-filter area of aFilteredFrame that has + * changed, relative to aFilteredFrame, in app units. + */ + static nsRegion GetPostFilterDirtyArea(nsIFrame *aFilteredFrame, + const nsRegion& aPreFilterDirtyRegion); + + /** + * Returns the pre-filter area that is needed from aFilteredFrame when the + * given post-filter area needs to be repainted. + * @param aPostFilterDirtyRegion The post-filter area that is dirty, relative + * to aFilteredFrame, in app units. + */ + static nsRegion GetPreFilterNeededArea(nsIFrame *aFilteredFrame, + const nsRegion& aPostFilterDirtyRegion); + + /** + * Returns the post-filter visual overflow rect (paint bounds) of + * aFilteredFrame. + * @param aOverrideBBox A user space rect, in user units, that should be used + * as aFilteredFrame's bbox ('bbox' is a specific SVG term), if non-null. + * @param aPreFilterBounds The pre-filter visual overflow rect of + * aFilteredFrame, if non-null. + */ + static nsRect GetPostFilterBounds(nsIFrame *aFilteredFrame, + const gfxRect *aOverrideBBox = nullptr, + const nsRect *aPreFilterBounds = nullptr); + + /** + * @param aTargetFrame The frame of the filtered element under consideration, + * may be null. + * @param aTargetContent The filtered element itself. + * @param aMetrics The metrics to resolve SVG lengths against. + * @param aFilterChain The list of filters to apply. + * @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha + * input is tainted. This affects whether feDisplacementMap will respect + * the filter input as its map input. + * @param aPaintCallback [optional] The callback that Render() should use to + * paint. Only required if you will call Render(). + * @param aPaintTransform The transform to apply to convert to + * aTargetFrame's SVG user space. Only used when painting. + * @param aPostFilterDirtyRegion [optional] The post-filter area + * that has to be repainted, in app units. Only required if you will + * call ComputeSourceNeededRect() or Render(). + * @param aPreFilterDirtyRegion [optional] The pre-filter area of + * the filtered element that changed, in app units. Only required if you + * will call ComputePostFilterDirtyRegion(). + * @param aOverridePreFilterVisualOverflowRect [optional] Use a different + * visual overflow rect for the target element. + * @param aOverrideBBox [optional] Use a different SVG bbox for the target + * element. Must be non-null if aTargetFrame is null. + */ + nsFilterInstance(nsIFrame *aTargetFrame, + nsIContent* aTargetContent, + const UserSpaceMetrics& aMetrics, + const nsTArray<nsStyleFilter>& aFilterChain, + bool aFilterInputIsTainted, + nsSVGFilterPaintCallback *aPaintCallback, + const gfxMatrix& aPaintTransform, + const nsRegion *aPostFilterDirtyRegion = nullptr, + const nsRegion *aPreFilterDirtyRegion = nullptr, + const nsRect *aOverridePreFilterVisualOverflowRect = nullptr, + const gfxRect *aOverrideBBox = nullptr); + + /** + * Returns true if the filter instance was created successfully. + */ + bool IsInitialized() const { return mInitialized; } + + /** + * Draws the filter output into aDrawTarget. The area that + * needs to be painted must have been specified before calling this method + * by passing it as the aPostFilterDirtyRegion argument to the + * nsFilterInstance constructor. + */ + nsresult Render(DrawTarget* aDrawTarget); + + const FilterDescription& ExtractDescriptionAndAdditionalImages(nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) + { + mInputImages.SwapElements(aOutAdditionalImages); + return mFilterDescription; + } + + /** + * Sets the aPostFilterDirtyRegion outparam to the post-filter area in frame + * space that would be dirtied by mTargetFrame when a given + * pre-filter area of mTargetFrame is dirtied. The pre-filter area must have + * been specified before calling this method by passing it as the + * aPreFilterDirtyRegion argument to the nsFilterInstance constructor. + */ + nsRegion ComputePostFilterDirtyRegion(); + + /** + * Sets the aPostFilterExtents outparam to the post-filter bounds in frame + * space for the whole filter output. This is not necessarily equivalent to + * the area that would be dirtied in the result when the entire pre-filter + * area is dirtied, because some filter primitives can generate output + * without any input. + */ + nsRect ComputePostFilterExtents(); + + /** + * Sets the aDirty outparam to the pre-filter bounds in frame space of the + * area of mTargetFrame that is needed in order to paint the filtered output + * for a given post-filter dirtied area. The post-filter area must have been + * specified before calling this method by passing it as the aPostFilterDirtyRegion + * argument to the nsFilterInstance constructor. + */ + nsRect ComputeSourceNeededRect(); + + + /** + * Returns the transform from filter space to outer-<svg> device space. + */ + gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const { + return mFilterSpaceToDeviceSpaceTransform; + } + +private: + struct SourceInfo { + // Specifies which parts of the source need to be rendered. + // Set by ComputeNeededBoxes(). + nsIntRect mNeededBounds; + + // The surface that contains the input rendering. + // Set by BuildSourceImage / BuildSourcePaint. + RefPtr<SourceSurface> mSourceSurface; + + // The position and size of mSourceSurface in filter space. + // Set by BuildSourceImage / BuildSourcePaint. + IntRect mSurfaceRect; + }; + + /** + * Creates a SourceSurface for either the FillPaint or StrokePaint graph + * nodes + */ + nsresult BuildSourcePaint(SourceInfo *aPrimitive, + DrawTarget* aTargetDT); + + /** + * Creates a SourceSurface for either the FillPaint and StrokePaint graph + * nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and + * mStrokePaint.mSourceSurface respectively. + */ + nsresult BuildSourcePaints(DrawTarget* aTargetDT); + + /** + * Creates the SourceSurface for the SourceGraphic graph node, paints its + * contents, and assigns it to mSourceGraphic.mSourceSurface. + */ + nsresult BuildSourceImage(DrawTarget* aTargetDT); + + /** + * Build the list of FilterPrimitiveDescriptions that describes the filter's + * filter primitives and their connections. This populates + * mPrimitiveDescriptions and mInputImages. aFilterInputIsTainted describes + * whether the SourceGraphic is tainted. + */ + nsresult BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain, + nsIFrame* aTargetFrame, + bool aFilterInputIsTainted); + + /** + * Add to the list of FilterPrimitiveDescriptions for a particular SVG + * reference filter or CSS filter. This populates mPrimitiveDescriptions and + * mInputImages. aInputIsTainted describes whether the input to aFilter is + * tainted. + */ + nsresult BuildPrimitivesForFilter(const nsStyleFilter& aFilter, + nsIFrame* aTargetFrame, + bool aInputIsTainted); + + /** + * Computes the filter space bounds of the areas that we actually *need* from + * the filter sources, based on the value of mPostFilterDirtyRegion. + * This sets mNeededBounds on the corresponding SourceInfo structs. + */ + void ComputeNeededBoxes(); + + /** + * Returns the output bounds of the final FilterPrimitiveDescription. + */ + nsIntRect OutputFilterSpaceBounds() const; + + /** + * Compute the scale factors between user space and filter space. + */ + nsresult ComputeUserSpaceToFilterSpaceScale(); + + /** + * Transform a rect between user space and filter space. + */ + gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const; + gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const; + + /** + * Converts an nsRect or an nsRegion that is relative to a filtered frame's + * origin (i.e. the top-left corner of its border box) into filter space, + * rounding out. + * Returns the entire filter region if aRect / aRegion is null, or if the + * result is too large to be stored in an nsIntRect. + */ + nsIntRect FrameSpaceToFilterSpace(const nsRect* aRect) const; + nsIntRegion FrameSpaceToFilterSpace(const nsRegion* aRegion) const; + + /** + * Converts an nsIntRect or an nsIntRegion from filter space into the space + * that is relative to a filtered frame's origin (i.e. the top-left corner + * of its border box) in app units, rounding out. + */ + nsRect FilterSpaceToFrameSpace(const nsIntRect& aRect) const; + nsRegion FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const; + + /** + * Returns the transform from frame space to the coordinate space that + * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the + * top-left corner of its border box, aka the top left corner of its mRect. + */ + gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const; + + /** + * The frame for the element that is currently being filtered. + */ + nsIFrame* mTargetFrame; + + /** + * The filtered element. + */ + nsIContent* mTargetContent; + + /** + * The user space metrics of the filtered frame. + */ + const UserSpaceMetrics& mMetrics; + + nsSVGFilterPaintCallback* mPaintCallback; + + /** + * The SVG bbox of the element that is being filtered, in user space. + */ + gfxRect mTargetBBox; + + /** + * The SVG bbox of the element that is being filtered, in filter space. + */ + nsIntRect mTargetBBoxInFilterSpace; + + /** + * The transform from filter space to outer-<svg> device space. + */ + gfxMatrix mFilterSpaceToDeviceSpaceTransform; + + /** + * Transform rects between filter space and frame space in CSS pixels. + */ + gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform; + gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform; + + /** + * The scale factors between user space and filter space. + */ + gfxSize mUserSpaceToFilterSpaceScale; + gfxSize mFilterSpaceToUserSpaceScale; + + /** + * Pre-filter paint bounds of the element that is being filtered, in filter + * space. + */ + nsIntRect mTargetBounds; + + /** + * The dirty area that needs to be repainted, in filter space. + */ + nsIntRegion mPostFilterDirtyRegion; + + /** + * The pre-filter area of the filtered element that changed, in filter space. + */ + nsIntRegion mPreFilterDirtyRegion; + + SourceInfo mSourceGraphic; + SourceInfo mFillPaint; + SourceInfo mStrokePaint; + + /** + * The transform to the SVG user space of mTargetFrame. + */ + gfxMatrix mPaintTransform; + + nsTArray<RefPtr<SourceSurface>> mInputImages; + nsTArray<FilterPrimitiveDescription> mPrimitiveDescriptions; + FilterDescription mFilterDescription; + bool mInitialized; +}; + +#endif diff --git a/layout/svg/nsISVGChildFrame.h b/layout/svg/nsISVGChildFrame.h new file mode 100644 index 0000000000..c9fd0f12ae --- /dev/null +++ b/layout/svg/nsISVGChildFrame.h @@ -0,0 +1,154 @@ +/* -*- 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/. */ + +#ifndef __NS_ISVGCHILDFRAME_H__ +#define __NS_ISVGCHILDFRAME_H__ + +#include "gfxRect.h" +#include "nsQueryFrame.h" + +class gfxContext; +class gfxMatrix; +class nsIFrame; +class SVGBBox; + +struct nsRect; + +namespace mozilla { +class SVGAnimatedLengthList; +class SVGAnimatedNumberList; +class SVGLengthList; +class SVGNumberList; +class SVGUserUnitList; + +namespace gfx { +class Matrix; +} // namespace gfx +} // namespace mozilla + +/** + * This class is not particularly well named. It is inherited by some, but + * not all SVG frame classes that can be descendants of an + * nsSVGOuterSVGFrame in the frame tree. Note specifically that SVG container + * frames that do not inherit nsSVGDisplayContainerFrame do not inherit this + * class (so that's classes that only inherit nsSVGContainerFrame). + */ +class nsISVGChildFrame : public nsQueryFrame +{ +public: + typedef mozilla::SVGAnimatedNumberList SVGAnimatedNumberList; + typedef mozilla::SVGNumberList SVGNumberList; + typedef mozilla::SVGAnimatedLengthList SVGAnimatedLengthList; + typedef mozilla::SVGLengthList SVGLengthList; + typedef mozilla::SVGUserUnitList SVGUserUnitList; + typedef mozilla::image::DrawResult DrawResult; + + NS_DECL_QUERYFRAME_TARGET(nsISVGChildFrame) + + /** + * Paint this frame. + * + * SVG is painted using a combination of display lists (trees of + * nsDisplayItem built by BuildDisplayList() implementations) and recursive + * PaintSVG calls. SVG frames with the NS_FRAME_IS_NONDISPLAY bit set are + * always painted using recursive PaintSVG calls since display list painting + * would provide no advantages (they wouldn't be retained for invalidation). + * Displayed SVG is normally painted via a display list tree created under + * nsSVGOuterSVGFrame::BuildDisplayList, unless the + * svg.display-lists.painting.enabled pref has been set to false by the user + * in which case it is done via an nsSVGOuterSVGFrame::PaintSVG() call that + * recurses over the entire SVG frame tree. In future we may use PaintSVG() + * calls on SVG container frames to avoid display list construction when it + * is expensive and unnecessary (see bug 934411). + * + * @param aTransform The transform that has to be multiplied onto the + * DrawTarget in order for drawing to be in this frame's SVG user space. + * Implementations of this method should avoid multiplying aTransform onto + * the DrawTarget when possible and instead just pass a transform down to + * their children. This is preferable because changing the transform is + * very expensive for certain DrawTarget backends so it is best to minimize + * the number of transform changes. + * + * @param aDirtyRect The area being redrawn, in frame offset pixel + * coordinates. + */ + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) = 0; + + /** + * Returns the frame that should handle pointer events at aPoint. aPoint is + * expected to be in the SVG user space of the frame on which this method is + * called. The frame returned may be the frame on which this method is + * called, any of its descendants or else nullptr. + */ + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) = 0; + + // Get bounds in our nsSVGOuterSVGFrame's coordinates space (in app units) + virtual nsRect GetCoveredRegion()=0; + + // Called on SVG child frames (except NS_FRAME_IS_NONDISPLAY frames) + // to update and then invalidate their cached bounds. This method is not + // called until after the nsSVGOuterSVGFrame has had its initial reflow + // (i.e. once the SVG viewport dimensions are known). It should also only + // be called by nsSVGOuterSVGFrame during its reflow. + virtual void ReflowSVG()=0; + + // Flags to pass to NotifySVGChange: + // + // DO_NOT_NOTIFY_RENDERING_OBSERVERS - this should only be used when + // updating the descendant frames of a clipPath, + // mask, pattern or marker frame (or other similar + // NS_FRAME_IS_NONDISPLAY frame) immediately + // prior to painting that frame's descendants. + // TRANSFORM_CHANGED - the current transform matrix for this frame has changed + // COORD_CONTEXT_CHANGED - the dimensions of this frame's coordinate context has + // changed (percentage lengths must be reevaluated) + enum SVGChangedFlags { + TRANSFORM_CHANGED = 0x01, + COORD_CONTEXT_CHANGED = 0x02, + FULL_ZOOM_CHANGED = 0x04 + }; + /** + * This is called on a frame when there has been a change to one of its + * ancestors that might affect the frame too. SVGChangedFlags are passed + * to indicate what changed. + * + * Implementations do not need to invalidate, since the caller will + * invalidate the entire area of the ancestor that changed. However, they + * may need to update their bounds. + */ + virtual void NotifySVGChanged(uint32_t aFlags)=0; + + /** + * Get this frame's contribution to the rect returned by a GetBBox() call + * that occurred either on this element, or on one of its ancestors. + * + * SVG defines an element's bbox to be the element's fill bounds in the + * userspace established by that element. By allowing callers to pass in the + * transform from the userspace established by this element to the userspace + * established by an an ancestor, this method allows callers to obtain this + * element's fill bounds in the userspace established by that ancestor + * instead. In that case, since we return the bounds in a different userspace + * (the ancestor's), the bounds we return are not this element's bbox, but + * rather this element's contribution to the bbox of the ancestor. + * + * @param aToBBoxUserspace The transform from the userspace established by + * this element to the userspace established by the ancestor on which + * getBBox was called. This will be the identity matrix if we are the + * element on which getBBox was called. + * + * @param aFlags Flags indicating whether, stroke, for example, should be + * included in the bbox calculation. + */ + virtual SVGBBox GetBBoxContribution(const mozilla::gfx::Matrix &aToBBoxUserspace, + uint32_t aFlags) = 0; + + // Are we a container frame? + virtual bool IsDisplayContainer()=0; +}; + +#endif // __NS_ISVGCHILDFRAME_H__ + diff --git a/layout/svg/nsISVGSVGFrame.h b/layout/svg/nsISVGSVGFrame.h new file mode 100644 index 0000000000..8a30cba8bc --- /dev/null +++ b/layout/svg/nsISVGSVGFrame.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef __NS_ISVGSVGFRAME_H__ +#define __NS_ISVGSVGFRAME_H__ + +#include "nsQueryFrame.h" + +class nsISVGSVGFrame +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsISVGSVGFrame) + + /** + * Called when non-attribute changes have caused the element's width/height + * or its for-children transform to change, and to get the element to notify + * its children appropriately. aFlags must be set to + * nsISVGChildFrame::COORD_CONTEXT_CHANGED and/or + * nsISVGChildFrame::TRANSFORM_CHANGED. + */ + virtual void NotifyViewportOrTransformChanged(uint32_t aFlags)=0; +}; + +#endif // __NS_ISVGSVGFRAME_H__ diff --git a/layout/svg/nsSVGAFrame.cpp b/layout/svg/nsSVGAFrame.cpp new file mode 100644 index 0000000000..54ec8958c6 --- /dev/null +++ b/layout/svg/nsSVGAFrame.cpp @@ -0,0 +1,147 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "gfxMatrix.h" +#include "mozilla/dom/SVGAElement.h" +#include "nsAutoPtr.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGUtils.h" +#include "SVGLengthList.h" + +using namespace mozilla; + +class nsSVGAFrame : public nsSVGDisplayContainerFrame +{ + friend nsIFrame* + NS_NewSVGAFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGAFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) {} + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + // nsIFrame: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgAFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGA"), aResult); + } +#endif + // nsISVGChildFrame interface: + virtual void NotifySVGChanged(uint32_t aFlags) override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + +private: + nsAutoPtr<gfxMatrix> mCanvasTM; +}; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGAFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGAFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGAFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods +#ifdef DEBUG +void +nsSVGAFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::a), + "Trying to construct an SVGAFrame for a " + "content element that doesn't support the right interfaces"); + + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult +nsSVGAFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::transform) { + // We don't invalidate for transform changes (the layers code does that). + // Also note that SVGTransformableElement::GetAttributeChangeHint will + // return nsChangeHint_UpdateOverflow for "transform" attribute changes + // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. + NotifySVGChanged(TRANSFORM_CHANGED); + } + + return NS_OK; +} + +nsIAtom * +nsSVGAFrame::GetType() const +{ + return nsGkAtoms::svgAFrame; +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +void +nsSVGAFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + if (aFlags & TRANSFORM_CHANGED) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + } + + nsSVGDisplayContainerFrame::NotifySVGChanged(aFlags); +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +gfxMatrix +nsSVGAFrame::GetCanvasTM() +{ + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + + nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); + dom::SVGAElement *content = static_cast<dom::SVGAElement*>(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = new gfxMatrix(tm); + } + + return *mCanvasTM; +} diff --git a/layout/svg/nsSVGClipPathFrame.cpp b/layout/svg/nsSVGClipPathFrame.cpp new file mode 100644 index 0000000000..a619ac9a4e --- /dev/null +++ b/layout/svg/nsSVGClipPathFrame.cpp @@ -0,0 +1,511 @@ +/* -*- 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 "nsSVGClipPathFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfxContext.h" +#include "mozilla/dom/SVGClipPathElement.h" +#include "nsGkAtoms.h" +#include "nsSVGEffects.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGPathGeometryFrame.h" +#include "nsSVGUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +// Arbitrary number +#define MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH int16_t(512) + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGClipPathFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame) + +void +nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext, + nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix) +{ + MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask"); + + DrawTarget& aDrawTarget = *aContext.GetDrawTarget(); + + // No need for AutoReferenceLimiter since simple clip paths can't create + // a reference loop (they don't reference other clip paths). + + // Restore current transform after applying clip path: + gfxContextMatrixAutoSaveRestore autoRestore(&aContext); + + RefPtr<Path> clipPath; + + nsISVGChildFrame* singleClipPathChild = nullptr; + IsTrivial(&singleClipPathChild); + + if (singleClipPathChild) { + nsSVGPathGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild); + if (pathFrame) { + nsSVGPathGeometryElement* pathElement = + static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent()); + gfxMatrix toChildsUserSpace = pathElement-> + PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix, + eUserSpaceToParent); + gfxMatrix newMatrix = + aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers(); + if (!newMatrix.IsSingular()) { + aContext.SetMatrix(newMatrix); + FillRule clipRule = + nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule); + clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule); + } + } + } + + if (clipPath) { + aContext.Clip(clipPath); + } else { + // The spec says clip away everything if we have no children or the + // clipping path otherwise can't be resolved: + aContext.Clip(Rect()); + } +} + +already_AddRefed<SourceSurface> +nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext, + nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, + Matrix* aMaskTransform, + SourceSurface* aExtraMask, + const Matrix& aExtraMasksTransform, + DrawResult* aResult) +{ + MOZ_ASSERT(!IsTrivial(), "Caller needs to use ApplyClipPath"); + + if (aResult) { + *aResult = DrawResult::SUCCESS; + } + DrawTarget& aReferenceDT = *aReferenceContext.GetDrawTarget(); + + // A clipPath can reference another clipPath. We re-enter this method for + // each clipPath in a reference chain, so here we limit chain length: + static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing; + AutoReferenceLimiter + refChainLengthLimiter(&sRefChainLengthCounter, + MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH); + if (!refChainLengthLimiter.Reference()) { + return nullptr; // Reference chain is too long! + } + + // And to prevent reference loops we check that this clipPath only appears + // once in the reference chain (if any) that we're currently processing: + AutoReferenceLimiter refLoopDetector(&mReferencing, 1); + if (!refLoopDetector.Reference()) { + return nullptr; // Reference loop! + } + + IntRect devSpaceClipExtents; + { + gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext); + + aReferenceContext.SetMatrix(gfxMatrix()); + gfxRect rect = aReferenceContext.GetClipExtents(); + devSpaceClipExtents = RoundedOut(ToRect(rect)); + if (devSpaceClipExtents.IsEmpty()) { + // We don't need to create a mask surface, all drawing is clipped anyway. + return nullptr; + } + } + + RefPtr<DrawTarget> maskDT = + aReferenceDT.CreateSimilarDrawTarget(devSpaceClipExtents.Size(), + SurfaceFormat::A8); + + gfxMatrix mat = aReferenceContext.CurrentMatrix() * + gfxMatrix::Translation(-devSpaceClipExtents.TopLeft()); + + // Paint this clipPath's contents into maskDT: + { + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(maskDT); + if (!ctx) { + gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT); + return nullptr; + } + ctx->SetMatrix(mat); + + // We need to set mMatrixForChildren here so that under the PaintSVG calls + // on our children (below) our GetCanvasTM() method will return the correct + // transform. + mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix; + + // Check if this clipPath is itself clipped by another clipPath: + nsSVGClipPathFrame* clipPathThatClipsClipPath = + nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); + bool clippingOfClipPathRequiredMasking; + if (clipPathThatClipsClipPath) { + ctx->Save(); + clippingOfClipPathRequiredMasking = !clipPathThatClipsClipPath->IsTrivial(); + if (!clippingOfClipPathRequiredMasking) { + clipPathThatClipsClipPath->ApplyClipPath(*ctx, aClippedFrame, aMatrix); + } else { + Matrix maskTransform; + RefPtr<SourceSurface> mask = + clipPathThatClipsClipPath->GetClipMask(*ctx, aClippedFrame, + aMatrix, &maskTransform); + ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, + mask, maskTransform); + // The corresponding PopGroupAndBlend call below will mask the + // blend using |mask|. + } + } + + // Paint our children into the mask: + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + // The CTM of each frame referencing us can be different. + SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); + + bool isOK = true; + // Children of this clipPath may themselves be clipped. + nsSVGClipPathFrame *clipPathThatClipsChild = + nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK); + if (!isOK) { + continue; + } + + bool childsClipPathRequiresMasking; + + if (clipPathThatClipsChild) { + childsClipPathRequiresMasking = !clipPathThatClipsChild->IsTrivial(); + ctx->Save(); + if (!childsClipPathRequiresMasking) { + clipPathThatClipsChild->ApplyClipPath(*ctx, aClippedFrame, aMatrix); + } else { + Matrix maskTransform; + RefPtr<SourceSurface> mask = + clipPathThatClipsChild->GetClipMask(*ctx, aClippedFrame, + aMatrix, &maskTransform); + ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, + mask, maskTransform); + // The corresponding PopGroupAndBlend call below will mask the + // blend using |mask|. + } + } + + gfxMatrix toChildsUserSpace = mMatrixForChildren; + nsIFrame* child = do_QueryFrame(SVGFrame); + nsIContent* childContent = child->GetContent(); + if (childContent->IsSVGElement()) { + toChildsUserSpace = + static_cast<const nsSVGElement*>(childContent)-> + PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent); + } + + // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and + // nsSVGPathGeometryFrame::Render checks for that state bit and paints + // only the geometry (opaque black) if set. + DrawResult result = SVGFrame->PaintSVG(*ctx, toChildsUserSpace); + if (aResult) { + *aResult &= result; + } + + if (clipPathThatClipsChild) { + if (childsClipPathRequiresMasking) { + ctx->PopGroupAndBlend(); + } + ctx->Restore(); + } + } + } + + + if (clipPathThatClipsClipPath) { + if (clippingOfClipPathRequiredMasking) { + ctx->PopGroupAndBlend(); + } + ctx->Restore(); + } + } + + // Moz2D transforms in the opposite direction to Thebes + mat.Invert(); + + if (aExtraMask) { + // We could potentially due this more efficiently with OPERATOR_IN + // but that operator does not work well on CG or D2D + RefPtr<SourceSurface> currentMask = maskDT->Snapshot(); + Matrix transform = maskDT->GetTransform(); + maskDT->SetTransform(Matrix()); + maskDT->ClearRect(Rect(0, 0, + devSpaceClipExtents.width, + devSpaceClipExtents.height)); + maskDT->SetTransform(aExtraMasksTransform * transform); + // draw currentMask with the inverse of the transform that we just so that + // it ends up in the same spot with aExtraMask transformed by aExtraMasksTransform + maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP, aExtraMasksTransform.Inverse() * ToMatrix(mat)), + aExtraMask, + Point(0, 0)); + } + + *aMaskTransform = ToMatrix(mat); + return maskDT->Snapshot(); +} + +bool +nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, + const gfxPoint &aPoint) +{ + // A clipPath can reference another clipPath. We re-enter this method for + // each clipPath in a reference chain, so here we limit chain length: + static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing; + AutoReferenceLimiter + refChainLengthLimiter(&sRefChainLengthCounter, + MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH); + if (!refChainLengthLimiter.Reference()) { + return false; // Reference chain is too long! + } + + // And to prevent reference loops we check that this clipPath only appears + // once in the reference chain (if any) that we're currently processing: + AutoReferenceLimiter refLoopDetector(&mReferencing, 1); + if (!refLoopDetector.Reference()) { + return true; // Reference loop! + } + + gfxMatrix matrix = GetClipPathTransform(aClippedFrame); + if (!matrix.Invert()) { + return false; + } + gfxPoint point = matrix.Transform(aPoint); + + // clipPath elements can themselves be clipped by a different clip path. In + // that case the other clip path further clips away the element that is being + // clipped by the original clipPath. If this clipPath is being clipped by a + // different clip path we need to check if it prevents the original element + // from recieving events at aPoint: + nsSVGClipPathFrame *clipPathFrame = + nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); + if (clipPathFrame && + !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) { + return false; + } + + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + gfxPoint pointForChild = point; + gfxMatrix m = static_cast<nsSVGElement*>(kid->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return false; + } + pointForChild = m.Transform(point); + } + if (SVGFrame->GetFrameForPoint(pointForChild)) { + return true; + } + } + } + return false; +} + +bool +nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild) +{ + // If the clip path is clipped then it's non-trivial + if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr)) + return false; + + if (aSingleChild) { + *aSingleChild = nullptr; + } + + nsISVGChildFrame *foundChild = nullptr; + + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + nsISVGChildFrame *svgChild = do_QueryFrame(kid); + if (svgChild) { + // We consider a non-trivial clipPath to be one containing + // either more than one svg child and/or a svg container + if (foundChild || svgChild->IsDisplayContainer()) + return false; + + // or where the child is itself clipped + if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(nullptr)) + return false; + + foundChild = svgChild; + } + } + if (aSingleChild) { + *aSingleChild = foundChild; + } + return true; +} + +bool +nsSVGClipPathFrame::IsValid() +{ + // A clipPath can reference another clipPath. We re-enter this method for + // each clipPath in a reference chain, so here we limit chain length: + static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing; + AutoReferenceLimiter + refChainLengthLimiter(&sRefChainLengthCounter, + MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH); + if (!refChainLengthLimiter.Reference()) { + return false; // Reference chain is too long! + } + + // And to prevent reference loops we check that this clipPath only appears + // once in the reference chain (if any) that we're currently processing: + AutoReferenceLimiter refLoopDetector(&mReferencing, 1); + if (!refLoopDetector.Reference()) { + return false; // Reference loop! + } + + bool isOK = true; + nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(&isOK); + if (!isOK) { + return false; + } + + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + + nsIAtom* kidType = kid->GetType(); + + if (kidType == nsGkAtoms::svgUseFrame) { + for (nsIFrame* grandKid : kid->PrincipalChildList()) { + + nsIAtom* grandKidType = grandKid->GetType(); + + if (grandKidType != nsGkAtoms::svgPathGeometryFrame && + grandKidType != nsGkAtoms::svgTextFrame) { + return false; + } + } + continue; + } + + if (kidType != nsGkAtoms::svgPathGeometryFrame && + kidType != nsGkAtoms::svgTextFrame) { + return false; + } + } + + return true; +} + +nsresult +nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::transform) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + nsSVGUtils::NotifyChildrenOfSVGChange(this, + nsISVGChildFrame::TRANSFORM_CHANGED); + } + if (aAttribute == nsGkAtoms::clipPathUnits) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + } + + return nsSVGContainerFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +void +nsSVGClipPathFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath), + "Content is not an SVG clipPath!"); + + AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD); + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} + +nsIAtom * +nsSVGClipPathFrame::GetType() const +{ + return nsGkAtoms::svgClipPathFrame; +} + +gfxMatrix +nsSVGClipPathFrame::GetCanvasTM() +{ + return mMatrixForChildren; +} + +gfxMatrix +nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) +{ + SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix()); + + nsSVGEnum* clipPathUnits = + &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS]; + + return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame); +} + +SVGBBox +nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox, + const gfxMatrix &aMatrix) +{ + nsIContent* node = GetContent()->GetFirstChild(); + SVGBBox unionBBox, tmpBBox; + for (; node; node = node->GetNextSibling()) { + nsIFrame *frame = + static_cast<nsSVGElement*>(node)->GetPrimaryFrame(); + if (frame) { + nsISVGChildFrame *svg = do_QueryFrame(frame); + if (svg) { + tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(aMatrix), + nsSVGUtils::eBBoxIncludeFill); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(frame); + bool isOK = true; + nsSVGClipPathFrame *clipPathFrame = + effectProperties.GetClipPathFrame(&isOK); + if (clipPathFrame && isOK) { + tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix); + } + tmpBBox.Intersect(aBBox); + unionBBox.UnionEdges(tmpBBox); + } + } + } + nsSVGEffects::EffectProperties props = + nsSVGEffects::GetEffectProperties(this); + if (props.mClipPath) { + bool isOK = true; + nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK); + if (clipPathFrame && isOK) { + tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix); + unionBBox.Intersect(tmpBBox); + } else if (!isOK) { + unionBBox = SVGBBox(); + } + } + return unionBBox; +} diff --git a/layout/svg/nsSVGClipPathFrame.h b/layout/svg/nsSVGClipPathFrame.h new file mode 100644 index 0000000000..42a8d16ffd --- /dev/null +++ b/layout/svg/nsSVGClipPathFrame.h @@ -0,0 +1,160 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGCLIPPATHFRAME_H__ +#define __NS_SVGCLIPPATHFRAME_H__ + +#include "AutoReferenceLimiter.h" +#include "gfxMatrix.h" +#include "mozilla/Attributes.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGUtils.h" + +class gfxContext; +class nsISVGChildFrame; + +class nsSVGClipPathFrame : public nsSVGContainerFrame +{ + friend nsIFrame* + NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::image::DrawResult DrawResult; + +protected: + explicit nsSVGClipPathFrame(nsStyleContext* aContext) + : nsSVGContainerFrame(aContext) + , mReferencing(mozilla::AutoReferenceLimiter::notReferencing) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame methods: + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + // nsSVGClipPathFrame methods: + + /** + * Applies the clipPath by pushing a clip path onto the DrawTarget. + * + * This method must only be used if IsTrivial() returns true, otherwise use + * GetClipMask. + * + * @param aContext The context that the clip path is to be applied to. + * @param aClippedFrame The/an nsIFrame of the element that references this + * clipPath that is currently being processed. + * @param aMatrix The transform from aClippedFrame's user space to aContext's + * current transform. + */ + void ApplyClipPath(gfxContext& aContext, + nsIFrame* aClippedFrame, + const gfxMatrix &aMatrix); + + /** + * Returns an alpha mask surface containing the clipping geometry. + * + * This method must only be used if IsTrivial() returns false, otherwise use + * ApplyClipPath. + * + * @param aReferenceContext Used to determine the backend for and size of the + * returned SourceSurface, the size being limited to the device space clip + * extents on the context. + * @param aClippedFrame The/an nsIFrame of the element that references this + * clipPath that is currently being processed. + * @param aMatrix The transform from aClippedFrame's user space to aContext's + * current transform. + * @param [out] aMaskTransform The transform to use with the returned + * surface. + * @param [in, optional] aExtraMask An extra surface that the returned + * surface should be masked with. + * @param [in, optional] aExtraMasksTransform The transform to use with + * aExtraMask. Should be passed when aExtraMask is passed. + * @param [out, optional] aResult returns the result of drawing action. + */ + already_AddRefed<SourceSurface> + GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, Matrix* aMaskTransform, + SourceSurface* aExtraMask = nullptr, + const Matrix& aExtraMasksTransform = Matrix(), + DrawResult* aResult = nullptr); + + /** + * aPoint is expected to be in aClippedFrame's SVG user space. + */ + bool PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint &aPoint); + + // Check if this clipPath is made up of more than one geometry object. + // If so, the clipping API in cairo isn't enough and we need to use + // mask based clipping. + bool IsTrivial(nsISVGChildFrame **aSingleChild = nullptr); + + bool IsValid(); + + // nsIFrame interface: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgClipPathFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGClipPath"), aResult); + } +#endif + + SVGBBox + GetBBoxForClipPathFrame(const SVGBBox &aBBox, const gfxMatrix &aMatrix); + + /** + * If the clipPath element transforms its children due to + * clipPathUnits="objectBoundingBox" being set on it and/or due to the + * 'transform' attribute being set on it, this function returns the resulting + * transform. + */ + gfxMatrix GetClipPathTransform(nsIFrame* aClippedFrame); + +private: + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + // Set, during a GetClipMask() call, to the transform that still needs to be + // concatenated to the transform of the DrawTarget that was passed to + // GetClipMask in order to establish the coordinate space that the clipPath + // establishes for its contents (i.e. including applying 'clipPathUnits' and + // any 'transform' attribute set on the clipPath) specifically for clipping + // the frame that was passed to GetClipMask at that moment in time. This is + // set so that if our GetCanvasTM method is called while GetClipMask is + // painting its children, the returned matrix will include the transforms + // that should be used when creating the mask for the frame passed to + // GetClipMask. + // + // Note: The removal of GetCanvasTM is nearly complete, so our GetCanvasTM + // may not even be called soon/any more. + gfxMatrix mMatrixForChildren; + + // Flag used by AutoReferenceLimiter while we're processing an instance of + // this class to protect against (break) reference loops. + int16_t mReferencing; +}; + +#endif diff --git a/layout/svg/nsSVGContainerFrame.cpp b/layout/svg/nsSVGContainerFrame.cpp new file mode 100644 index 0000000000..750dcc9da0 --- /dev/null +++ b/layout/svg/nsSVGContainerFrame.cpp @@ -0,0 +1,445 @@ +/* -*- 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 "nsSVGContainerFrame.h" + +// Keep others in (case-insensitive) order: +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" +#include "nsCSSFrameConstructor.h" +#include "nsSVGEffects.h" +#include "nsSVGElement.h" +#include "nsSVGUtils.h" +#include "nsSVGAnimatedTransformList.h" +#include "SVGTextFrame.h" + +using namespace mozilla; + +NS_QUERYFRAME_HEAD(nsSVGContainerFrame) + NS_QUERYFRAME_ENTRY(nsSVGContainerFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame) + NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame) + NS_QUERYFRAME_ENTRY(nsISVGChildFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame) + +nsIFrame* +NS_NewSVGContainerFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + nsIFrame *frame = new (aPresShell) nsSVGContainerFrame(aContext); + // If we were called directly, then the frame is for a <defs> or + // an unknown element type. In both cases we prevent the content + // from displaying directly. + frame->AddStateBits(NS_FRAME_IS_NONDISPLAY); + return frame; +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame) +NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame) + +void +nsSVGContainerFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + InsertFrames(aListID, mFrames.LastChild(), aFrameList); +} + +void +nsSVGContainerFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + + mFrames.InsertFrames(this, aPrevFrame, aFrameList); +} + +void +nsSVGContainerFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + mFrames.DestroyFrame(aOldFrame); +} + +bool +nsSVGContainerFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + if (mState & NS_FRAME_IS_NONDISPLAY) { + // We don't maintain overflow rects. + // XXX It would have be better if the restyle request hadn't even happened. + return false; + } + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +/** + * Traverses a frame tree, marking any SVGTextFrame frames as dirty + * and calling InvalidateRenderingObservers() on it. + * + * The reason that this helper exists is because SVGTextFrame is special. + * None of the other SVG frames ever need to be reflowed when they have the + * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods + * (and those of any containers that they can validly be contained within) do + * not make use of mRect or overflow rects. "em" lengths, etc., are resolved + * as those elements are painted. + * + * SVGTextFrame is different because its anonymous block and inline frames + * need to be reflowed in order to get the correct metrics when things like + * inherited font-size of an ancestor changes, or a delayed webfont loads and + * applies. + * + * We assume that any change that requires the anonymous kid of an + * SVGTextFrame to reflow will result in an NS_FRAME_IS_DIRTY reflow. When + * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally + * stop, but this helper looks for any SVGTextFrame descendants of such + * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they are + * painted their anonymous kid will first get the necessary reflow. + */ +/* static */ void +nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) +{ + NS_ASSERTION(aContainer->GetStateBits() & NS_FRAME_IS_DIRTY, + "expected aContainer to be NS_FRAME_IS_DIRTY"); + NS_ASSERTION((aContainer->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || + !aContainer->IsFrameOfType(nsIFrame::eSVG), + "it is wasteful to call ReflowSVGNonDisplayText on a container " + "frame that is not NS_FRAME_IS_NONDISPLAY"); + for (nsIFrame* kid : aContainer->PrincipalChildList()) { + nsIAtom* type = kid->GetType(); + if (type == nsGkAtoms::svgTextFrame) { + static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText(); + } else { + if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) || + type == nsGkAtoms::svgForeignObjectFrame || + !kid->IsFrameOfType(nsIFrame::eSVG)) { + ReflowSVGNonDisplayText(kid); + } + } + } +} + +void +nsSVGDisplayContainerFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG)) { + AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); + } + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} + +void +nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // mContent could be a XUL element so check for an SVG element before casting + if (mContent->IsSVGElement() && + !static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) { + return; + } + DisplayOutline(aBuilder, aLists); + return BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists); +} + +void +nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + // memorize first old frame after insertion point + // XXXbz once again, this would work a lot better if the nsIFrame + // methods returned framelist iterators.... + nsIFrame* nextFrame = aPrevFrame ? + aPrevFrame->GetNextSibling() : GetChildList(aListID).FirstChild(); + nsIFrame* firstNewFrame = aFrameList.FirstChild(); + + // Insert the new frames + nsSVGContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); + + // If we are not a non-display SVG frame and we do not have a bounds update + // pending, then we need to schedule one for our new children: + if (!(GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN | + NS_FRAME_IS_NONDISPLAY))) { + for (nsIFrame* kid = firstNewFrame; kid != nextFrame; + kid = kid->GetNextSibling()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + MOZ_ASSERT(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "Check for this explicitly in the |if|, then"); + bool isFirstReflow = (kid->GetStateBits() & NS_FRAME_FIRST_REFLOW); + // Remove bits so that ScheduleBoundsUpdate will work: + kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); + // No need to invalidate the new kid's old bounds, so we just use + // nsSVGUtils::ScheduleBoundsUpdate. + nsSVGUtils::ScheduleReflowSVG(kid); + if (isFirstReflow) { + // Add back the NS_FRAME_FIRST_REFLOW bit: + kid->AddStateBits(NS_FRAME_FIRST_REFLOW); + } + } + } + } +} + +void +nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + nsSVGEffects::InvalidateRenderingObservers(aOldFrame); + + // nsSVGContainerFrame::RemoveFrame doesn't call down into + // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We + // need to schedule a repaint and schedule an update to our overflow rects. + SchedulePaint(); + PresContext()->RestyleManager()->PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow); + + nsSVGContainerFrame::RemoveFrame(aListID, aOldFrame); + + if (!(GetStateBits() & (NS_FRAME_IS_NONDISPLAY | NS_STATE_IS_OUTER_SVG))) { + nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this); + } +} + +bool +nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, + gfx::Matrix *aFromParentTransform) const +{ + bool foundTransform = false; + + // Check if our parent has children-only transforms: + nsIFrame *parent = GetParent(); + if (parent && + parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { + foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> + HasChildrenOnlyTransform(aFromParentTransform); + } + + // mContent could be a XUL element so check for an SVG element before casting + if (mContent->IsSVGElement()) { + nsSVGElement *content = static_cast<nsSVGElement*>(mContent); + nsSVGAnimatedTransformList* transformList = + content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo( + gfxMatrix(), eUserSpaceToParent)); + } + foundTransform = true; + } + } + return foundTransform; +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +DrawResult +nsSVGDisplayContainerFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) +{ + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY) || + PresContext()->IsGlyph(), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + if (StyleEffects()->mOpacity == 0.0) { + return DrawResult::SUCCESS; + } + + gfxMatrix matrix = aTransform; + if (GetContent()->IsSVGElement()) { // must check before cast + matrix = static_cast<const nsSVGElement*>(GetContent())-> + PrependLocalTransformsTo(matrix, eChildToUserSpace); + if (matrix.IsSingular()) { + return DrawResult::SUCCESS; + } + } + + DrawResult result = DrawResult::SUCCESS; + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + gfxMatrix m = matrix; + // PaintFrameWithEffects() expects the transform that is passed to it to + // include the transform to the passed frame's user space, so add it: + const nsIContent* content = kid->GetContent(); + if (content->IsSVGElement()) { // must check before cast + const nsSVGElement* element = static_cast<const nsSVGElement*>(content); + if (!element->HasValidDimensions()) { + continue; // nothing to paint for kid + } + m = element->PrependLocalTransformsTo(m, eUserSpaceToParent); + if (m.IsSingular()) { + continue; + } + } + result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, m, aDirtyRect); + if (result != DrawResult::SUCCESS) { + return result; + } + } + + return result; +} + +nsIFrame* +nsSVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only hit-testing of a " + "clipPath's contents should take this code path"); + return nsSVGUtils::HitTestChildren(this, aPoint); +} + +nsRect +nsSVGDisplayContainerFrame::GetCoveredRegion() +{ + return nsSVGUtils::GetCoveredRegion(mFrames); +} + +void +nsSVGDisplayContainerFrame::ReflowSVG() +{ + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + MOZ_ASSERT(GetType() != nsGkAtoms::svgOuterSVGFrame, + "Do not call on outer-<svg>"); + + if (!nsSVGUtils::NeedsReflowSVG(this)) { + return; + } + + // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, + // then our outer-<svg> has previously had its initial reflow. In that case + // we need to make sure that that bit has been removed from ourself _before_ + // recursing over our children to ensure that they know too. Otherwise, we + // need to remove it _after_ recursing over our children so that they know + // the initial reflow is currently underway. + + bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW); + + bool outerSVGHasHadFirstReflow = + (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0; + + if (outerSVGHasHadFirstReflow) { + mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children + } + + nsOverflowAreas overflowRects; + + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + MOZ_ASSERT(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "Check for this explicitly in the |if|, then"); + kid->AddStateBits(mState & NS_FRAME_IS_DIRTY); + SVGFrame->ReflowSVG(); + + // We build up our child frame overflows here instead of using + // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same + // frame list, and we're iterating over that list now anyway. + ConsiderChildOverflow(overflowRects, kid); + } else { + // Inside a non-display container frame, we might have some + // SVGTextFrames. We need to cause those to get reflowed in + // case they are the target of a rendering observer. + NS_ASSERTION(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY, + "expected kid to be a NS_FRAME_IS_NONDISPLAY frame"); + if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) { + nsSVGContainerFrame* container = do_QueryFrame(kid); + if (container && container->GetContent()->IsSVGElement()) { + ReflowSVGNonDisplayText(container); + } + } + } + } + + // <svg> can create an SVG viewport with an offset due to its + // x/y/width/height attributes, and <use> can introduce an offset with an + // empty mRect (any width/height is copied to an anonymous <svg> child). + // Other than that containers should not set mRect since all other offsets + // come from transforms, which are accounted for by nsDisplayTransform. + // Note that we rely on |overflow:visible| to allow display list items to be + // created for our children. + MOZ_ASSERT(mContent->IsSVGElement(nsGkAtoms::svg) || + (mContent->IsSVGElement(nsGkAtoms::use) && + mRect.Size() == nsSize(0,0)) || + mRect.IsEqualEdges(nsRect()), + "Only inner-<svg>/<use> is expected to have mRect set"); + + if (isFirstReflow) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + nsSVGEffects::UpdateEffects(this); + } + + FinishAndStoreOverflow(overflowRects, mRect.Size()); + + // Remove state bits after FinishAndStoreOverflow so that it doesn't + // invalidate on first reflow: + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags); +} + +SVGBBox +nsSVGDisplayContainerFrame::GetBBoxContribution( + const Matrix &aToBBoxUserspace, + uint32_t aFlags) +{ + SVGBBox bboxUnion; + + nsIFrame* kid = mFrames.FirstChild(); + while (kid) { + nsIContent *content = kid->GetContent(); + nsISVGChildFrame* svgKid = do_QueryFrame(kid); + // content could be a XUL element so check for an SVG element before casting + if (svgKid && (!content->IsSVGElement() || + static_cast<const nsSVGElement*>(content)->HasValidDimensions())) { + + gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace); + if (content->IsSVGElement()) { + transform = static_cast<nsSVGElement*>(content)-> + PrependLocalTransformsTo(transform); + } + // We need to include zero width/height vertical/horizontal lines, so we have + // to use UnionEdges. + bboxUnion.UnionEdges(svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags)); + } + kid = kid->GetNextSibling(); + } + + return bboxUnion; +} diff --git a/layout/svg/nsSVGContainerFrame.h b/layout/svg/nsSVGContainerFrame.h new file mode 100644 index 0000000000..f4a01e1559 --- /dev/null +++ b/layout/svg/nsSVGContainerFrame.h @@ -0,0 +1,155 @@ +/* -*- 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/. */ + +#ifndef NS_SVGCONTAINERFRAME_H +#define NS_SVGCONTAINERFRAME_H + +#include "mozilla/Attributes.h" +#include "nsContainerFrame.h" +#include "nsFrame.h" +#include "nsIFrame.h" +#include "nsISVGChildFrame.h" +#include "nsQueryFrame.h" +#include "nsRect.h" +#include "nsSVGUtils.h" + +class gfxContext; +class nsFrameList; +class nsIContent; +class nsIPresShell; +class nsStyleContext; + +struct nsRect; + +/** + * Base class for SVG container frames. Frame sub-classes that do not + * display their contents directly (such as the frames for <marker> or + * <pattern>) just inherit this class. Frame sub-classes that do or can + * display their contents directly (such as the frames for inner-<svg> or + * <g>) inherit our nsDisplayContainerFrame sub-class. + * + * *** WARNING *** + * + * Do *not* blindly cast to SVG element types in this class's methods (see the + * warning comment for nsSVGDisplayContainerFrame below). + */ +class nsSVGContainerFrame : public nsContainerFrame +{ + friend nsIFrame* NS_NewSVGContainerFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); +protected: + explicit nsSVGContainerFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) + { + AddStateBits(NS_FRAME_SVG_LAYOUT); + } + +public: + NS_DECL_QUERYFRAME_TARGET(nsSVGContainerFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // Returns the transform to our gfxContext (to device pixels, not CSS px) + virtual gfxMatrix GetCanvasTM() { + return gfxMatrix(); + } + + /** + * Returns true if the frame's content has a transform that applies only to + * its children, and not to the frame itself. For example, an implicit + * transform introduced by a 'viewBox' attribute, or an explicit transform + * due to a root-<svg> having its currentScale/currentTransform properties + * set. If aTransform is non-null, then it will be set to the transform. + */ + virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const { + return false; + } + + // nsIFrame: + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType( + aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer)); + } + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override; + +protected: + /** + * Traverses a frame tree, marking any SVGTextFrame frames as dirty + * and calling InvalidateRenderingObservers() on it. + */ + static void ReflowSVGNonDisplayText(nsIFrame* aContainer); +}; + +/** + * Frame class or base-class for SVG containers that can or do display their + * contents directly. + * + * *** WARNING *** + * + * This class's methods can *not* assume that mContent points to an instance of + * an SVG element class since this class is inherited by + * nsSVGGenericContainerFrame which is used for unrecognized elements in the + * SVG namespace. Do *not* blindly cast to SVG element types. + */ +class nsSVGDisplayContainerFrame : public nsSVGContainerFrame, + public nsISVGChildFrame +{ +protected: + explicit nsSVGDisplayContainerFrame(nsStyleContext* aContext) + : nsSVGContainerFrame(aContext) + { + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + } + +public: + NS_DECL_QUERYFRAME_TARGET(nsSVGDisplayContainerFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame: + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual bool IsSVGTransformed(Matrix *aOwnTransform = nullptr, + Matrix *aFromParentTransform = nullptr) const override; + + // nsISVGChildFrame interface: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect = nullptr) override; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + virtual nsRect GetCoveredRegion() override; + virtual void ReflowSVG() override; + virtual void NotifySVGChanged(uint32_t aFlags) override; + virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) override; + virtual bool IsDisplayContainer() override { return true; } +}; + +#endif diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp new file mode 100644 index 0000000000..eac094a91f --- /dev/null +++ b/layout/svg/nsSVGEffects.cpp @@ -0,0 +1,1001 @@ +/* -*- 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 "nsSVGEffects.h" + +// Keep others in (case-insensitive) order: +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" +#include "nsCSSFrameConstructor.h" +#include "nsISupportsImpl.h" +#include "nsSVGClipPathFrame.h" +#include "nsSVGPaintServerFrame.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGFilterFrame.h" +#include "nsSVGMaskFrame.h" +#include "nsIReflowCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "SVGUseElement.h" + +using namespace mozilla; +using namespace mozilla::dom; + +void +nsSVGRenderingObserver::StartListening() +{ + Element* target = GetTarget(); + if (target) { + target->AddMutationObserver(this); + } +} + +void +nsSVGRenderingObserver::StopListening() +{ + Element* target = GetTarget(); + + if (target) { + target->RemoveMutationObserver(this); + if (mInObserverList) { + nsSVGEffects::RemoveRenderingObserver(target, this); + mInObserverList = false; + } + } + NS_ASSERTION(!mInObserverList, "still in an observer list?"); +} + +static nsSVGRenderingObserverList * +GetObserverList(Element *aElement) +{ + return static_cast<nsSVGRenderingObserverList*> + (aElement->GetProperty(nsGkAtoms::renderingobserverlist)); +} + +Element* +nsSVGRenderingObserver::GetReferencedElement() +{ + Element* target = GetTarget(); +#ifdef DEBUG + if (target) { + nsSVGRenderingObserverList *observerList = GetObserverList(target); + bool inObserverList = observerList && observerList->Contains(this); + NS_ASSERTION(inObserverList == mInObserverList, "failed to track whether we're in our referenced element's observer list!"); + } else { + NS_ASSERTION(!mInObserverList, "In whose observer list are we, then?"); + } +#endif + if (target && !mInObserverList) { + nsSVGEffects::AddRenderingObserver(target, this); + mInObserverList = true; + } + return target; +} + +nsIFrame* +nsSVGRenderingObserver::GetReferencedFrame() +{ + Element* referencedElement = GetReferencedElement(); + return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr; +} + +nsIFrame* +nsSVGRenderingObserver::GetReferencedFrame(nsIAtom* aFrameType, bool* aOK) +{ + nsIFrame* frame = GetReferencedFrame(); + if (frame) { + if (frame->GetType() == aFrameType) + return frame; + if (aOK) { + *aOK = false; + } + } + return nullptr; +} + +void +nsSVGRenderingObserver::InvalidateViaReferencedElement() +{ + mInObserverList = false; + DoUpdate(); +} + +void +nsSVGRenderingObserver::NotifyEvictedFromRenderingObserverList() +{ + mInObserverList = false; // We've been removed from rendering-obs. list. + StopListening(); // Remove ourselves from mutation-obs. list. +} + +void +nsSVGRenderingObserver::AttributeChanged(nsIDocument* aDocument, + dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + // An attribute belonging to the element that we are observing *or one of its + // descendants* has changed. + // + // In the case of observing a gradient element, say, we want to know if any + // of its 'stop' element children change, but we don't actually want to do + // anything for changes to SMIL element children, for example. Maybe it's not + // worth having logic to optimize for that, but in most cases it could be a + // small check? + // + // XXXjwatt: do we really want to blindly break the link between our + // observers and ourselves for all attribute changes? For non-ID changes + // surely that is unnecessary. + + DoUpdate(); +} + +void +nsSVGRenderingObserver::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t /* unused */) +{ + DoUpdate(); +} + +void +nsSVGRenderingObserver::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t /* unused */) +{ + DoUpdate(); +} + +void +nsSVGRenderingObserver::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + DoUpdate(); +} + +/** + * Note that in the current setup there are two separate observer lists. + * + * In nsSVGIDRenderingObserver's ctor, the new object adds itself to the + * mutation observer list maintained by the referenced element. In this way the + * nsSVGIDRenderingObserver is notified if there are any attribute or content + * tree changes to the element or any of its *descendants*. + * + * In nsSVGIDRenderingObserver::GetReferencedElement() the + * nsSVGIDRenderingObserver object also adds itself to an + * nsSVGRenderingObserverList object belonging to the referenced + * element. + * + * XXX: it would be nice to have a clear and concise executive summary of the + * benefits/necessity of maintaining a second observer list. + */ + +nsSVGIDRenderingObserver::nsSVGIDRenderingObserver(nsIURI* aURI, + nsIContent* aObservingContent, + bool aReferenceImage) + : mElement(this) +{ + // Start watching the target element + mElement.Reset(aObservingContent, aURI, true, aReferenceImage); + StartListening(); +} + +nsSVGIDRenderingObserver::~nsSVGIDRenderingObserver() +{ + StopListening(); +} + +void +nsSVGIDRenderingObserver::DoUpdate() +{ + if (mElement.get() && mInObserverList) { + nsSVGEffects::RemoveRenderingObserver(mElement.get(), this); + mInObserverList = false; + } +} + +void +nsSVGFrameReferenceFromProperty::Detach() +{ + mFrame = nullptr; + mFramePresShell = nullptr; +} + +nsIFrame* +nsSVGFrameReferenceFromProperty::Get() +{ + if (mFramePresShell && mFramePresShell->IsDestroying()) { + // mFrame is no longer valid. + Detach(); + } + return mFrame; +} + +NS_IMPL_ISUPPORTS(nsSVGRenderingObserverProperty, nsIMutationObserver) + +void +nsSVGRenderingObserverProperty::DoUpdate() +{ + nsSVGIDRenderingObserver::DoUpdate(); + + nsIFrame* frame = mFrameReference.Get(); + if (frame && frame->IsFrameOfType(nsIFrame::eSVG)) { + // Changes should propagate out to things that might be observing + // the referencing frame or its ancestors. + nsLayoutUtils::PostRestyleEvent( + frame->GetContent()->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + } +} + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGFilterReference) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGFilterReference) + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsSVGFilterReference) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsSVGFilterReference) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsSVGFilterReference) + tmp->StopListening(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGFilterReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsSVGIDRenderingObserver) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY(nsISVGFilterReference) +NS_INTERFACE_MAP_END + +nsSVGFilterFrame * +nsSVGFilterReference::GetFilterFrame() +{ + return static_cast<nsSVGFilterFrame *> + (GetReferencedFrame(nsGkAtoms::svgFilterFrame, nullptr)); +} + +void +nsSVGFilterReference::DoUpdate() +{ + nsSVGIDRenderingObserver::DoUpdate(); + + if (mFilterChainObserver) { + mFilterChainObserver->Invalidate(); + } +} + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGFilterChainObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGFilterChainObserver) + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsSVGFilterChainObserver) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsSVGFilterChainObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReferences) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsSVGFilterChainObserver) + tmp->DetachReferences(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferences); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGFilterChainObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +nsSVGFilterChainObserver::nsSVGFilterChainObserver(const nsTArray<nsStyleFilter>& aFilters, + nsIContent* aFilteredElement, + nsIFrame* aFilteredFrame) +{ + for (uint32_t i = 0; i < aFilters.Length(); i++) { + if (aFilters[i].GetType() != NS_STYLE_FILTER_URL) + continue; + + // aFilteredFrame can be null if this filter belongs to a + // CanvasRenderingContext2D. + nsCOMPtr<nsIURI> filterURL = aFilteredFrame + ? nsSVGEffects::GetFilterURI(aFilteredFrame, i) + : aFilters[i].GetURL()->ResolveLocalRef(aFilteredElement); + + RefPtr<nsSVGFilterReference> reference = + new nsSVGFilterReference(filterURL, aFilteredElement, this); + mReferences.AppendElement(reference); + } +} + +nsSVGFilterChainObserver::~nsSVGFilterChainObserver() +{ + DetachReferences(); +} + +bool +nsSVGFilterChainObserver::ReferencesValidResources() +{ + for (uint32_t i = 0; i < mReferences.Length(); i++) { + if (!mReferences[i]->ReferencesValidResource()) + return false; + } + return true; +} + +bool +nsSVGFilterChainObserver::IsInObserverLists() const +{ + for (uint32_t i = 0; i < mReferences.Length(); i++) { + if (!mReferences[i]->IsInObserverList()) + return false; + } + return true; +} + +void +nsSVGFilterProperty::DoUpdate() +{ + nsIFrame* frame = mFrameReference.Get(); + if (!frame) + return; + + // Repaint asynchronously in case the filter frame is being torn down + nsChangeHint changeHint = + nsChangeHint(nsChangeHint_RepaintFrame); + + if (frame && frame->IsFrameOfType(nsIFrame::eSVG)) { + // Changes should propagate out to things that might be observing + // the referencing frame or its ancestors. + changeHint |= nsChangeHint_InvalidateRenderingObservers; + } + + // Don't need to request UpdateOverflow if we're being reflowed. + if (!(frame->GetStateBits() & NS_FRAME_IN_REFLOW)) { + changeHint |= nsChangeHint_UpdateOverflow; + } + frame->PresContext()->RestyleManager()->PostRestyleEvent( + frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); +} + +void +nsSVGMarkerProperty::DoUpdate() +{ + nsSVGRenderingObserverProperty::DoUpdate(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) + return; + + NS_ASSERTION(frame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); + + // Repaint asynchronously in case the marker frame is being torn down + nsChangeHint changeHint = + nsChangeHint(nsChangeHint_RepaintFrame); + + // Don't need to request ReflowFrame if we're being reflowed. + if (!(frame->GetStateBits() & NS_FRAME_IN_REFLOW)) { + changeHint |= nsChangeHint_InvalidateRenderingObservers; + // XXXjwatt: We need to unify SVG into standard reflow so we can just use + // nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here. + // XXXSDL KILL THIS!!! + nsSVGUtils::ScheduleReflowSVG(frame); + } + frame->PresContext()->RestyleManager()->PostRestyleEvent( + frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); +} + +NS_IMPL_ISUPPORTS(nsSVGMaskProperty, nsISupports) + +nsSVGMaskProperty::nsSVGMaskProperty(nsIFrame* aFrame) +{ + const nsStyleSVGReset *svgReset = aFrame->StyleSVGReset(); + + for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) { + nsCOMPtr<nsIURI> maskUri = nsSVGEffects::GetMaskURI(aFrame, i); + nsSVGPaintingProperty* prop = new nsSVGPaintingProperty(maskUri, aFrame, + false); + mProperties.AppendElement(prop); + } +} + +bool +nsSVGTextPathProperty::TargetIsValid() +{ + Element* target = GetTarget(); + return target && target->IsSVGElement(nsGkAtoms::path); +} + +void +nsSVGTextPathProperty::DoUpdate() +{ + nsSVGRenderingObserverProperty::DoUpdate(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) + return; + + NS_ASSERTION(frame->IsFrameOfType(nsIFrame::eSVG) || frame->IsSVGText(), + "SVG frame expected"); + + // Avoid getting into an infinite loop of reflows if the <textPath> is + // pointing to one of its ancestors. TargetIsValid returns true iff + // the target element is a <path> element, and we would not have this + // nsSVGTextPathProperty if this <textPath> were a descendant of the + // target <path>. + // + // Note that we still have to post the restyle event when we + // change from being valid to invalid, so that mPositions on the + // SVGTextFrame gets updated, skipping the <textPath>, ensuring + // that nothing gets painted for that element. + bool nowValid = TargetIsValid(); + if (!mValid && !nowValid) { + // Just return if we were previously invalid, and are still invalid. + return; + } + mValid = nowValid; + + // Repaint asynchronously in case the path frame is being torn down + nsChangeHint changeHint = + nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath); + frame->PresContext()->RestyleManager()->PostRestyleEvent( + frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); +} + +static void +InvalidateAllContinuations(nsIFrame* aFrame) +{ + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + f->InvalidateFrame(); + } +} + +void +nsSVGPaintingProperty::DoUpdate() +{ + nsSVGRenderingObserverProperty::DoUpdate(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) + return; + + if (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) { + nsLayoutUtils::PostRestyleEvent( + frame->GetContent()->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + frame->InvalidateFrameSubtree(); + } else { + InvalidateAllContinuations(frame); + } +} + +static nsSVGFilterProperty* +GetOrCreateFilterProperty(nsIFrame* aFrame) +{ + const nsStyleEffects* effects = aFrame->StyleEffects(); + if (!effects->HasFilters()) + return nullptr; + + FrameProperties props = aFrame->Properties(); + nsSVGFilterProperty *prop = props.Get(nsSVGEffects::FilterProperty()); + if (prop) + return prop; + prop = new nsSVGFilterProperty(effects->mFilters, aFrame); + NS_ADDREF(prop); + props.Set(nsSVGEffects::FilterProperty(), prop); + return prop; +} + +static nsSVGMaskProperty* +GetOrCreateMaskProperty(nsIFrame* aFrame) +{ + FrameProperties props = aFrame->Properties(); + nsSVGMaskProperty *prop = props.Get(nsSVGEffects::MaskProperty()); + if (prop) + return prop; + + prop = new nsSVGMaskProperty(aFrame); + NS_ADDREF(prop); + props.Set(nsSVGEffects::MaskProperty(), prop); + return prop; +} + +template<class T> +static T* +GetEffectProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<T>* aProperty) +{ + if (!aURI) + return nullptr; + + FrameProperties props = aFrame->Properties(); + T* prop = props.Get(aProperty); + if (prop) + return prop; + prop = new T(aURI, aFrame, false); + NS_ADDREF(prop); + props.Set(aProperty, prop); + return prop; +} + +nsSVGMarkerProperty* +nsSVGEffects::GetMarkerProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<nsSVGMarkerProperty>* aProperty) +{ + MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame && + static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable(), + "Bad frame"); + return GetEffectProperty(aURI, aFrame, aProperty); +} + +nsSVGTextPathProperty* +nsSVGEffects::GetTextPathProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<nsSVGTextPathProperty>* aProperty) +{ + return GetEffectProperty(aURI, aFrame, aProperty); +} + +nsSVGPaintingProperty* +nsSVGEffects::GetPaintingProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>* aProperty) +{ + return GetEffectProperty(aURI, aFrame, aProperty); +} + +nsSVGPaintingProperty* +nsSVGEffects::GetPaintingPropertyForURI(nsIURI* aURI, nsIFrame* aFrame, + URIObserverHashtablePropertyDescriptor aProperty) +{ + if (!aURI) + return nullptr; + + FrameProperties props = aFrame->Properties(); + nsSVGEffects::URIObserverHashtable *hashtable = props.Get(aProperty); + if (!hashtable) { + hashtable = new nsSVGEffects::URIObserverHashtable(); + props.Set(aProperty, hashtable); + } + nsSVGPaintingProperty* prop = + static_cast<nsSVGPaintingProperty*>(hashtable->GetWeak(aURI)); + if (!prop) { + bool watchImage = aProperty == nsSVGEffects::BackgroundImageProperty(); + prop = new nsSVGPaintingProperty(aURI, aFrame, watchImage); + hashtable->Put(aURI, prop); + } + return prop; +} + +nsSVGEffects::EffectProperties +nsSVGEffects::GetEffectProperties(nsIFrame* aFrame) +{ + NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); + + EffectProperties result; + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + + result.mFilter = GetOrCreateFilterProperty(aFrame); + + if (style->mClipPath.GetType() == StyleShapeSourceType::URL) { + nsCOMPtr<nsIURI> pathURI = nsSVGEffects::GetClipPathURI(aFrame); + result.mClipPath = + GetPaintingProperty(pathURI, aFrame, ClipPathProperty()); + } else { + result.mClipPath = nullptr; + } + + MOZ_ASSERT(style->mMask.mImageCount > 0); + result.mMask = style->mMask.HasLayerWithImage() + ? GetOrCreateMaskProperty(aFrame) : nullptr; + + return result; +} + +nsSVGPaintServerFrame * +nsSVGEffects::GetPaintServer(nsIFrame* aTargetFrame, + nsStyleSVGPaint nsStyleSVG::* aPaint, + PaintingPropertyDescriptor aType) +{ + const nsStyleSVG* svgStyle = aTargetFrame->StyleSVG(); + if ((svgStyle->*aPaint).Type() != eStyleSVGPaintType_Server) + return nullptr; + + // If we're looking at a frame within SVG text, then we need to look up + // to find the right frame to get the painting property off. We should at + // least look up past a text frame, and if the text frame's parent is the + // anonymous block frame, then we look up to its parent (the SVGTextFrame). + nsIFrame* frame = aTargetFrame; + if (frame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { + frame = frame->GetParent(); + nsIFrame* grandparent = frame->GetParent(); + if (grandparent && grandparent->GetType() == nsGkAtoms::svgTextFrame) { + frame = grandparent; + } + } + + nsCOMPtr<nsIURI> paintServerURL = nsSVGEffects::GetPaintURI(frame, aPaint); + nsSVGPaintingProperty *property = + nsSVGEffects::GetPaintingProperty(paintServerURL, frame, aType); + if (!property) + return nullptr; + nsIFrame *result = property->GetReferencedFrame(); + if (!result) + return nullptr; + + nsIAtom *type = result->GetType(); + if (type != nsGkAtoms::svgLinearGradientFrame && + type != nsGkAtoms::svgRadialGradientFrame && + type != nsGkAtoms::svgPatternFrame) + return nullptr; + + return static_cast<nsSVGPaintServerFrame*>(result); +} + +nsSVGClipPathFrame * +nsSVGEffects::EffectProperties::GetClipPathFrame(bool* aOK) +{ + if (!mClipPath) + return nullptr; + nsSVGClipPathFrame *frame = static_cast<nsSVGClipPathFrame *> + (mClipPath->GetReferencedFrame(nsGkAtoms::svgClipPathFrame, aOK)); + if (frame && aOK && *aOK) { + *aOK = frame->IsValid(); + } + return frame; +} + +nsSVGMaskFrame * +nsSVGEffects::EffectProperties::GetFirstMaskFrame(bool* aOK) +{ + if (!mMask) { + return nullptr; + } + + const nsTArray<RefPtr<nsSVGPaintingProperty>>& props = mMask->GetProps(); + + if (props.IsEmpty()) { + return nullptr; + } + + return static_cast<nsSVGMaskFrame *> + (props[0]->GetReferencedFrame(nsGkAtoms::svgMaskFrame, aOK)); +} + +nsTArray<nsSVGMaskFrame *> +nsSVGEffects::EffectProperties::GetMaskFrames() +{ + nsTArray<nsSVGMaskFrame *> result; + if (!mMask) + return result; + + bool ok = false; + const nsTArray<RefPtr<nsSVGPaintingProperty>>& props = mMask->GetProps(); + for (size_t i = 0; i < props.Length(); i++) { + nsSVGMaskFrame* maskFrame = + static_cast<nsSVGMaskFrame *>(props[i]->GetReferencedFrame( + nsGkAtoms::svgMaskFrame, &ok)); + result.AppendElement(maskFrame); + } + + return result; +} + +void +nsSVGEffects::UpdateEffects(nsIFrame* aFrame) +{ + NS_ASSERTION(aFrame->GetContent()->IsElement(), + "aFrame's content should be an element"); + + FrameProperties props = aFrame->Properties(); + props.Delete(FilterProperty()); + props.Delete(MaskProperty()); + props.Delete(ClipPathProperty()); + props.Delete(MarkerBeginProperty()); + props.Delete(MarkerMiddleProperty()); + props.Delete(MarkerEndProperty()); + props.Delete(FillProperty()); + props.Delete(StrokeProperty()); + props.Delete(BackgroundImageProperty()); + + // Ensure that the filter is repainted correctly + // We can't do that in DoUpdate as the referenced frame may not be valid + GetOrCreateFilterProperty(aFrame); + + if (aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame && + static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable()) { + // Set marker properties here to avoid reference loops + nsCOMPtr<nsIURI> markerURL = + GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart); + GetMarkerProperty(markerURL, aFrame, MarkerBeginProperty()); + markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid); + GetMarkerProperty(markerURL, aFrame, MarkerMiddleProperty()); + markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd); + GetMarkerProperty(markerURL, aFrame, MarkerEndProperty()); + } +} + +nsSVGFilterProperty* +nsSVGEffects::GetFilterProperty(nsIFrame* aFrame) +{ + NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); + + if (!aFrame->StyleEffects()->HasFilters()) + return nullptr; + + return aFrame->Properties().Get(FilterProperty()); +} + +void +nsSVGRenderingObserverList::InvalidateAll() +{ + if (mObservers.Count() == 0) + return; + + AutoTArray<nsSVGRenderingObserver*,10> observers; + + for (auto it = mObservers.Iter(); !it.Done(); it.Next()) { + observers.AppendElement(it.Get()->GetKey()); + } + mObservers.Clear(); + + for (uint32_t i = 0; i < observers.Length(); ++i) { + observers[i]->InvalidateViaReferencedElement(); + } +} + +void +nsSVGRenderingObserverList::InvalidateAllForReflow() +{ + if (mObservers.Count() == 0) + return; + + AutoTArray<nsSVGRenderingObserver*,10> observers; + + for (auto it = mObservers.Iter(); !it.Done(); it.Next()) { + nsSVGRenderingObserver* obs = it.Get()->GetKey(); + if (obs->ObservesReflow()) { + observers.AppendElement(obs); + it.Remove(); + } + } + + for (uint32_t i = 0; i < observers.Length(); ++i) { + observers[i]->InvalidateViaReferencedElement(); + } +} + +void +nsSVGRenderingObserverList::RemoveAll() +{ + AutoTArray<nsSVGRenderingObserver*,10> observers; + + for (auto it = mObservers.Iter(); !it.Done(); it.Next()) { + observers.AppendElement(it.Get()->GetKey()); + } + mObservers.Clear(); + + // Our list is now cleared. We need to notify the observers we've removed, + // so they can update their state & remove themselves as mutation-observers. + for (uint32_t i = 0; i < observers.Length(); ++i) { + observers[i]->NotifyEvictedFromRenderingObserverList(); + } +} + +void +nsSVGEffects::AddRenderingObserver(Element* aElement, + nsSVGRenderingObserver* aObserver) +{ + nsSVGRenderingObserverList *observerList = GetObserverList(aElement); + if (!observerList) { + observerList = new nsSVGRenderingObserverList(); + if (!observerList) + return; + aElement->SetProperty(nsGkAtoms::renderingobserverlist, observerList, + nsINode::DeleteProperty<nsSVGRenderingObserverList>); + } + aElement->SetHasRenderingObservers(true); + observerList->Add(aObserver); +} + +void +nsSVGEffects::RemoveRenderingObserver(Element* aElement, + nsSVGRenderingObserver* aObserver) +{ + nsSVGRenderingObserverList *observerList = GetObserverList(aElement); + if (observerList) { + NS_ASSERTION(observerList->Contains(aObserver), + "removing observer from an element we're not observing?"); + observerList->Remove(aObserver); + if (observerList->IsEmpty()) { + aElement->SetHasRenderingObservers(false); + } + } +} + +void +nsSVGEffects::RemoveAllRenderingObservers(Element* aElement) +{ + nsSVGRenderingObserverList *observerList = GetObserverList(aElement); + if (observerList) { + observerList->RemoveAll(); + aElement->SetHasRenderingObservers(false); + } +} + +void +nsSVGEffects::InvalidateRenderingObservers(nsIFrame* aFrame) +{ + NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame must be first continuation"); + + nsIContent* content = aFrame->GetContent(); + if (!content || !content->IsElement()) + return; + + // If the rendering has changed, the bounds may well have changed too: + aFrame->Properties().Delete(nsSVGUtils::ObjectBoundingBoxProperty()); + + nsSVGRenderingObserverList *observerList = + GetObserverList(content->AsElement()); + if (observerList) { + observerList->InvalidateAll(); + return; + } + + // Check ancestor SVG containers. The root frame cannot be of type + // eSVGContainer so we don't have to check f for null here. + for (nsIFrame *f = aFrame->GetParent(); + f->IsFrameOfType(nsIFrame::eSVGContainer); f = f->GetParent()) { + if (f->GetContent()->IsElement()) { + observerList = GetObserverList(f->GetContent()->AsElement()); + if (observerList) { + observerList->InvalidateAll(); + return; + } + } + } +} + +void +nsSVGEffects::InvalidateDirectRenderingObservers(Element* aElement, uint32_t aFlags /* = 0 */) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + if (frame) { + // If the rendering has changed, the bounds may well have changed too: + frame->Properties().Delete(nsSVGUtils::ObjectBoundingBoxProperty()); + } + + if (aElement->HasRenderingObservers()) { + nsSVGRenderingObserverList *observerList = GetObserverList(aElement); + if (observerList) { + if (aFlags & INVALIDATE_REFLOW) { + observerList->InvalidateAllForReflow(); + } else { + observerList->InvalidateAll(); + } + } + } +} + +void +nsSVGEffects::InvalidateDirectRenderingObservers(nsIFrame* aFrame, uint32_t aFlags /* = 0 */) +{ + nsIContent* content = aFrame->GetContent(); + if (content && content->IsElement()) { + InvalidateDirectRenderingObservers(content->AsElement(), aFlags); + } +} + +static already_AddRefed<nsIURI> +ResolveURLUsingLocalRef(nsIFrame* aFrame, const css::URLValueData* aURL) +{ + MOZ_ASSERT(aFrame); + + if (!aURL) { + return nullptr; + } + + // Non-local-reference URL. + if (!aURL->IsLocalRef()) { + nsCOMPtr<nsIURI> result = aURL->GetURI(); + return result.forget(); + } + + // For a local-reference URL, resolve that fragment against the current + // document that relative URLs are resolved against. + nsIContent* content = aFrame->GetContent(); + nsCOMPtr<nsIURI> baseURI = content->OwnerDoc()->GetDocumentURI(); + + if (content->IsInAnonymousSubtree()) { + nsIContent* bindingParent = content->GetBindingParent(); + nsCOMPtr<nsIURI> originalURI; + + // content is in a shadow tree. If this URL was specified in the subtree + // referenced by the <use>(or -moz-binding) element, and that subtree came + // from a separate resource document, then we want the fragment-only URL + // to resolve to an element from the resource document. Otherwise, the + // URL was specified somewhere in the document with the <use> element, and + // we want the fragment-only URL to resolve to an element in that document. + if (bindingParent) { + if (content->IsAnonymousContentInSVGUseSubtree()) { + SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent); + originalURI = useElement->GetSourceDocURI(); + } else { + nsXBLBinding* binding = bindingParent->GetXBLBinding(); + if (binding) { + originalURI = binding->GetSourceDocURI(); + } else { + MOZ_ASSERT(content->IsInNativeAnonymousSubtree(), + "an non-native anonymous tree which is not from " + "an XBL binding?"); + } + } + + if (originalURI && aURL->EqualsExceptRef(originalURI)) { + baseURI = originalURI; + } + } + } + + return aURL->ResolveLocalRef(baseURI); +} + +already_AddRefed<nsIURI> +nsSVGEffects::GetMarkerURI(nsIFrame* aFrame, + RefPtr<css::URLValue> nsStyleSVG::* aMarker) +{ + return ResolveURLUsingLocalRef(aFrame, aFrame->StyleSVG()->*aMarker); +} + +already_AddRefed<nsIURI> +nsSVGEffects::GetClipPathURI(nsIFrame* aFrame) +{ + const nsStyleSVGReset* svgResetStyle = aFrame->StyleSVGReset(); + MOZ_ASSERT(svgResetStyle->mClipPath.GetType() == StyleShapeSourceType::URL); + + css::URLValue* url = svgResetStyle->mClipPath.GetURL(); + return ResolveURLUsingLocalRef(aFrame, url); +} + +already_AddRefed<nsIURI> +nsSVGEffects::GetFilterURI(nsIFrame* aFrame, uint32_t aIndex) +{ + const nsStyleEffects* effects = aFrame->StyleEffects(); + MOZ_ASSERT(effects->mFilters.Length() > aIndex); + MOZ_ASSERT(effects->mFilters[aIndex].GetType() == NS_STYLE_FILTER_URL); + + return ResolveURLUsingLocalRef(aFrame, effects->mFilters[aIndex].GetURL()); +} + +already_AddRefed<nsIURI> +nsSVGEffects::GetFilterURI(nsIFrame* aFrame, const nsStyleFilter& aFilter) +{ + MOZ_ASSERT(aFrame->StyleEffects()->mFilters.Length()); + MOZ_ASSERT(aFilter.GetType() == NS_STYLE_FILTER_URL); + + return ResolveURLUsingLocalRef(aFrame, aFilter.GetURL()); +} + +already_AddRefed<nsIURI> +nsSVGEffects::GetPaintURI(nsIFrame* aFrame, + nsStyleSVGPaint nsStyleSVG::* aPaint) +{ + const nsStyleSVG* svgStyle = aFrame->StyleSVG(); + MOZ_ASSERT((svgStyle->*aPaint).Type() == + nsStyleSVGPaintType::eStyleSVGPaintType_Server); + + return ResolveURLUsingLocalRef(aFrame, + (svgStyle->*aPaint).GetPaintServer()); +} + +already_AddRefed<nsIURI> +nsSVGEffects::GetMaskURI(nsIFrame* aFrame, uint32_t aIndex) +{ + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + MOZ_ASSERT(svgReset->mMask.mLayers.Length() > aIndex); + + return ResolveURLUsingLocalRef(aFrame, + svgReset->mMask.mLayers[aIndex].mSourceURI); +} diff --git a/layout/svg/nsSVGEffects.h b/layout/svg/nsSVGEffects.h new file mode 100644 index 0000000000..9dd92fd312 --- /dev/null +++ b/layout/svg/nsSVGEffects.h @@ -0,0 +1,636 @@ +/* -*- 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/. */ + +#ifndef NSSVGEFFECTS_H_ +#define NSSVGEFFECTS_H_ + +#include "mozilla/Attributes.h" +#include "FramePropertyTable.h" +#include "mozilla/dom/Element.h" +#include "nsHashKeys.h" +#include "nsID.h" +#include "nsIFrame.h" +#include "nsIMutationObserver.h" +#include "nsInterfaceHashtable.h" +#include "nsISupportsBase.h" +#include "nsISupportsImpl.h" +#include "nsReferencedElement.h" +#include "nsStubMutationObserver.h" +#include "nsSVGUtils.h" +#include "nsTHashtable.h" +#include "nsURIHashKey.h" +#include "nsCycleCollectionParticipant.h" + +class nsIAtom; +class nsIPresShell; +class nsIURI; +class nsSVGClipPathFrame; +class nsSVGPaintServerFrame; +class nsSVGFilterFrame; +class nsSVGMaskFrame; +class nsSVGFilterChainObserver; + +/* + * This interface allows us to be notified when a piece of SVG content is + * re-rendered. + * + * Concrete implementations of this interface need to implement + * "GetTarget()" to specify the piece of SVG content that they'd like to + * monitor, and they need to implement "DoUpdate" to specify how we'll react + * when that content gets re-rendered. They also need to implement a + * constructor and destructor, which should call StartListening and + * StopListening, respectively. + */ +class nsSVGRenderingObserver : public nsStubMutationObserver +{ + +protected: + virtual ~nsSVGRenderingObserver() + {} + +public: + typedef mozilla::dom::Element Element; + nsSVGRenderingObserver() + : mInObserverList(false) + {} + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + void InvalidateViaReferencedElement(); + + // When a nsSVGRenderingObserver list gets forcibly cleared, it uses this + // callback to notify every observer that's cleared from it, so they can + // react. + void NotifyEvictedFromRenderingObserverList(); + + bool IsInObserverList() const { return mInObserverList; } + + nsIFrame* GetReferencedFrame(); + /** + * @param aOK this is only for the convenience of callers. We set *aOK to false + * if the frame is the wrong type + */ + nsIFrame* GetReferencedFrame(nsIAtom* aFrameType, bool* aOK); + + Element* GetReferencedElement(); + + virtual bool ObservesReflow() { return true; } + +protected: + // Non-virtual protected methods + void StartListening(); + void StopListening(); + + // Virtual protected methods + virtual void DoUpdate() = 0; // called when the referenced resource changes. + + // This is an internally-used version of GetReferencedElement that doesn't + // forcibly add us as an observer. (whereas GetReferencedElement does) + virtual Element* GetTarget() = 0; + + // Whether we're in our referenced element's observer list at this time. + bool mInObserverList; +}; + + +/* + * SVG elements reference supporting resources by element ID. We need to + * track when those resources change and when the DOM changes in ways + * that affect which element is referenced by a given ID (e.g., when + * element IDs change). The code here is responsible for that. + * + * When a frame references a supporting resource, we create a property + * object derived from nsSVGIDRenderingObserver to manage the relationship. The + * property object is attached to the referencing frame. + */ +class nsSVGIDRenderingObserver : public nsSVGRenderingObserver +{ +public: + typedef mozilla::dom::Element Element; + nsSVGIDRenderingObserver(nsIURI* aURI, nsIContent* aObservingContent, + bool aReferenceImage); + virtual ~nsSVGIDRenderingObserver(); + +protected: + Element* GetTarget() override { return mElement.get(); } + + // This is called when the referenced resource changes. + virtual void DoUpdate() override; + + class SourceReference : public nsReferencedElement + { + public: + explicit SourceReference(nsSVGIDRenderingObserver* aContainer) : mContainer(aContainer) {} + protected: + virtual void ElementChanged(Element* aFrom, Element* aTo) override { + mContainer->StopListening(); + nsReferencedElement::ElementChanged(aFrom, aTo); + mContainer->StartListening(); + mContainer->DoUpdate(); + } + /** + * Override IsPersistent because we want to keep tracking the element + * for the ID even when it changes. + */ + virtual bool IsPersistent() override { return true; } + private: + nsSVGIDRenderingObserver* mContainer; + }; + + SourceReference mElement; +}; + +struct nsSVGFrameReferenceFromProperty +{ + explicit nsSVGFrameReferenceFromProperty(nsIFrame* aFrame) + : mFrame(aFrame) + , mFramePresShell(aFrame->PresContext()->PresShell()) + {} + + // Clear our reference to the frame. + void Detach(); + + // null if the frame has become invalid + nsIFrame* Get(); + +private: + // The frame that this property is attached to, may be null + nsIFrame *mFrame; + // When a presshell is torn down, we don't delete the properties for + // each frame until after the frames are destroyed. So here we remember + // the presshell for the frames we care about and, before we use the frame, + // we test the presshell to see if it's destroying itself. If it is, + // then the frame pointer is not valid and we know the frame has gone away. + // mFramePresShell may be null, but when mFrame is non-null, mFramePresShell + // is guaranteed to be non-null, too. + nsIPresShell *mFramePresShell; +}; + +class nsSVGRenderingObserverProperty : public nsSVGIDRenderingObserver +{ +public: + NS_DECL_ISUPPORTS + + nsSVGRenderingObserverProperty(nsIURI* aURI, nsIFrame *aFrame, + bool aReferenceImage) + : nsSVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage) + , mFrameReference(aFrame) + {} + +protected: + virtual ~nsSVGRenderingObserverProperty() {} + + virtual void DoUpdate() override; + + nsSVGFrameReferenceFromProperty mFrameReference; +}; + +/** + * In a filter chain, there can be multiple SVG reference filters. + * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); + * + * This class keeps track of one SVG reference filter in a filter chain. + * e.g. url(#svg-filter-1) + * + * It fires invalidations when the SVG filter element's id changes or when + * the SVG filter element's content changes. + * + * The nsSVGFilterChainObserver class manages a list of nsSVGFilterReferences. + */ +class nsSVGFilterReference final : public nsSVGIDRenderingObserver + , public nsISVGFilterReference +{ +public: + nsSVGFilterReference(nsIURI* aURI, + nsIContent* aObservingContent, + nsSVGFilterChainObserver* aFilterChainObserver) + : nsSVGIDRenderingObserver(aURI, aObservingContent, false) + , mFilterChainObserver(aFilterChainObserver) + { + } + + bool ReferencesValidResource() { return GetFilterFrame(); } + + void DetachFromChainObserver() { mFilterChainObserver = nullptr; } + + /** + * @return the filter frame, or null if there is no filter frame + */ + nsSVGFilterFrame *GetFilterFrame(); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsSVGFilterReference, nsSVGIDRenderingObserver) + + // nsISVGFilterReference + virtual void Invalidate() override { DoUpdate(); }; + +protected: + virtual ~nsSVGFilterReference() {} + + // nsSVGIDRenderingObserver + virtual void DoUpdate() override; + +private: + nsSVGFilterChainObserver* mFilterChainObserver; +}; + +/** + * This class manages a list of nsSVGFilterReferences, which represent SVG + * reference filters in a filter chain. + * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); + * + * In the above example, the nsSVGFilterChainObserver will manage two + * nsSVGFilterReferences, one for each SVG reference filter. CSS filters like + * "blur(10px)" don't reference filter elements, so they don't need an + * nsSVGFilterReference. The style system invalidates changes to CSS filters. + */ +class nsSVGFilterChainObserver : public nsISupports +{ +public: + nsSVGFilterChainObserver(const nsTArray<nsStyleFilter>& aFilters, + nsIContent* aFilteredElement, + nsIFrame* aFiltedFrame = nullptr); + + bool ReferencesValidResources(); + bool IsInObserverLists() const; + void Invalidate() { DoUpdate(); } + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsSVGFilterChainObserver) + +protected: + virtual ~nsSVGFilterChainObserver(); + + virtual void DoUpdate() = 0; + +private: + + void DetachReferences() + { + for (uint32_t i = 0; i < mReferences.Length(); i++) { + mReferences[i]->DetachFromChainObserver(); + } + } + + nsTArray<RefPtr<nsSVGFilterReference>> mReferences; +}; + +class nsSVGFilterProperty : public nsSVGFilterChainObserver +{ +public: + nsSVGFilterProperty(const nsTArray<nsStyleFilter>& aFilters, + nsIFrame* aFilteredFrame) + : nsSVGFilterChainObserver(aFilters, aFilteredFrame->GetContent(), + aFilteredFrame) + , mFrameReference(aFilteredFrame) + {} + + void DetachFromFrame() { mFrameReference.Detach(); } + +protected: + virtual void DoUpdate() override; + + nsSVGFrameReferenceFromProperty mFrameReference; +}; + +class nsSVGMarkerProperty final: public nsSVGRenderingObserverProperty +{ +public: + nsSVGMarkerProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage) + : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} + +protected: + virtual void DoUpdate() override; +}; + +class nsSVGTextPathProperty final : public nsSVGRenderingObserverProperty +{ +public: + nsSVGTextPathProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage) + : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) + , mValid(true) {} + + virtual bool ObservesReflow() override { return false; } + +protected: + virtual void DoUpdate() override; + +private: + /** + * Returns true if the target of the textPath is the frame of a 'path' element. + */ + bool TargetIsValid(); + + bool mValid; +}; + +class nsSVGPaintingProperty final : public nsSVGRenderingObserverProperty +{ +public: + nsSVGPaintingProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage) + : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} + +protected: + virtual void DoUpdate() override; +}; + +class nsSVGMaskProperty final : public nsISupports +{ +public: + explicit nsSVGMaskProperty(nsIFrame* aFrame); + + // nsISupports + NS_DECL_ISUPPORTS + + const nsTArray<RefPtr<nsSVGPaintingProperty>>& GetProps() const + { + return mProperties; + } + +private: + virtual ~nsSVGMaskProperty() {} + nsTArray<RefPtr<nsSVGPaintingProperty>> mProperties; +}; + +/** + * A manager for one-shot nsSVGRenderingObserver tracking. + * nsSVGRenderingObservers can be added or removed. They are not strongly + * referenced so an observer must be removed before it dies. + * When InvalidateAll is called, all outstanding references get + * InvalidateViaReferencedElement() + * called on them and the list is cleared. The intent is that + * the observer will force repainting of whatever part of the document + * is needed, and then at paint time the observer will do a clean lookup + * of the referenced element and [re-]add itself to the element's observer list. + * + * InvalidateAll must be called before this object is destroyed, i.e. + * before the referenced frame is destroyed. This should normally happen + * via nsSVGContainerFrame::RemoveFrame, since only frames in the frame + * tree should be referenced. + */ +class nsSVGRenderingObserverList +{ +public: + nsSVGRenderingObserverList() + : mObservers(4) + { + MOZ_COUNT_CTOR(nsSVGRenderingObserverList); + } + + ~nsSVGRenderingObserverList() { + InvalidateAll(); + MOZ_COUNT_DTOR(nsSVGRenderingObserverList); + } + + void Add(nsSVGRenderingObserver* aObserver) + { mObservers.PutEntry(aObserver); } + void Remove(nsSVGRenderingObserver* aObserver) + { mObservers.RemoveEntry(aObserver); } +#ifdef DEBUG + bool Contains(nsSVGRenderingObserver* aObserver) + { return (mObservers.GetEntry(aObserver) != nullptr); } +#endif + bool IsEmpty() + { return mObservers.Count() == 0; } + + /** + * Drop all our observers, and notify them that we have changed and dropped + * our reference to them. + */ + void InvalidateAll(); + + /** + * Drop all observers that observe reflow, and notify them that we have changed and dropped + * our reference to them. + */ + void InvalidateAllForReflow(); + + /** + * Drop all our observers, and notify them that we have dropped our reference + * to them. + */ + void RemoveAll(); + +private: + nsTHashtable<nsPtrHashKey<nsSVGRenderingObserver> > mObservers; +}; + +class nsSVGEffects +{ +public: + typedef mozilla::dom::Element Element; + typedef nsInterfaceHashtable<nsURIHashKey, nsIMutationObserver> + URIObserverHashtable; + + using PaintingPropertyDescriptor = + const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>*; + using URIObserverHashtablePropertyDescriptor = + const mozilla::FramePropertyDescriptor<URIObserverHashtable>*; + + static void DestroyFilterProperty(nsSVGFilterProperty* aProp) + { + // nsSVGFilterProperty is cycle-collected, so dropping the last reference + // doesn't necessarily destroy it. We need to tell it that the frame + // has now become invalid. + aProp->DetachFromFrame(); + + aProp->Release(); + } + + NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty, nsSVGFilterProperty, + DestroyFilterProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, nsSVGMaskProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, nsSVGPaintingProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerBeginProperty, nsSVGMarkerProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMiddleProperty, nsSVGMarkerProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, nsSVGMarkerProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, nsSVGPaintingProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, nsSVGPaintingProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty, + nsSVGTextPathProperty) + NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsPaintingProperty, + nsSVGPaintingProperty) + NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty, + URIObserverHashtable) + + /** + * Get the paint server for a aTargetFrame. + */ + static nsSVGPaintServerFrame *GetPaintServer(nsIFrame* aTargetFrame, + nsStyleSVGPaint nsStyleSVG::* aPaint, + PaintingPropertyDescriptor aProperty); + + struct EffectProperties { + nsSVGFilterProperty* mFilter; + nsSVGMaskProperty* mMask; + nsSVGPaintingProperty* mClipPath; + + /** + * @return the clip-path frame, or null if there is no clip-path frame + * @param aOK if a clip-path was specified and the designated element + * exists but is an element of the wrong type, *aOK is set to false. + * Otherwise *aOK is untouched. + */ + nsSVGClipPathFrame *GetClipPathFrame(bool* aOK); + /** + * @return the first mask frame, or null if there is no mask frame + * @param aOK if a mask was specified and the designated element + * exists but is an element of the wrong type, *aOK is set to false. + * Otherwise *aOK is untouched. + */ + nsSVGMaskFrame *GetFirstMaskFrame(bool* aOK = nullptr); + + /** + * @return an array which contains all SVG mask frames. + */ + nsTArray<nsSVGMaskFrame*> GetMaskFrames(); + + bool HasValidFilter() { + return mFilter && mFilter->ReferencesValidResources(); + } + + bool HasNoFilterOrHasValidFilter() { + return !mFilter || mFilter->ReferencesValidResources(); + } + }; + + /** + * @param aFrame should be the first continuation + */ + static EffectProperties GetEffectProperties(nsIFrame* aFrame); + + /** + * Called when changes to an element (e.g. CSS property changes) cause its + * frame to start/stop referencing (or reference different) SVG resource + * elements. (_Not_ called for changes to referenced resource elements.) + * + * This function handles such changes by discarding _all_ the frame's SVG + * effects frame properties (causing those properties to stop watching their + * target element). It also synchronously (re)creates the filter and marker + * frame properties (XXX why not the other properties?), which makes it + * useful for initializing those properties during first reflow. + * + * XXX rename to something more meaningful like RefreshResourceReferences? + */ + static void UpdateEffects(nsIFrame* aFrame); + + /** + * @param aFrame should be the first continuation + */ + static nsSVGFilterProperty *GetFilterProperty(nsIFrame* aFrame); + + /** + * @param aFrame must be a first-continuation. + */ + static void AddRenderingObserver(Element* aElement, nsSVGRenderingObserver *aObserver); + /** + * @param aFrame must be a first-continuation. + */ + static void RemoveRenderingObserver(Element* aElement, nsSVGRenderingObserver *aObserver); + + /** + * Removes all rendering observers from aElement. + */ + static void RemoveAllRenderingObservers(Element* aElement); + + /** + * This can be called on any frame. We invalidate the observers of aFrame's + * element, if any, or else walk up to the nearest observable SVG parent + * frame with observers and invalidate them instead. + * + * Note that this method is very different to e.g. + * nsNodeUtils::AttributeChanged which walks up the content node tree all the + * way to the root node (not stopping if it encounters a non-container SVG + * node) invalidating all mutation observers (not just + * nsSVGRenderingObservers) on all nodes along the way (not just the first + * node it finds with observers). In other words, by doing all the + * things in parentheses in the preceding sentence, this method uses + * knowledge about our implementation and what can be affected by SVG effects + * to make invalidation relatively lightweight when an SVG effect changes. + */ + static void InvalidateRenderingObservers(nsIFrame* aFrame); + + enum { + INVALIDATE_REFLOW = 1 + }; + + /** + * This can be called on any element or frame. Only direct observers of this + * (frame's) element, if any, are invalidated. + */ + static void InvalidateDirectRenderingObservers(Element* aElement, uint32_t aFlags = 0); + static void InvalidateDirectRenderingObservers(nsIFrame* aFrame, uint32_t aFlags = 0); + + /** + * Get an nsSVGMarkerProperty for the frame, creating a fresh one if necessary + */ + static nsSVGMarkerProperty * + GetMarkerProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<nsSVGMarkerProperty>* aProperty); + /** + * Get an nsSVGTextPathProperty for the frame, creating a fresh one if necessary + */ + static nsSVGTextPathProperty * + GetTextPathProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<nsSVGTextPathProperty>* aProperty); + /** + * Get an nsSVGPaintingProperty for the frame, creating a fresh one if necessary + */ + static nsSVGPaintingProperty* + GetPaintingProperty(nsIURI* aURI, nsIFrame* aFrame, + const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>* aProperty); + /** + * Get an nsSVGPaintingProperty for the frame for that URI, creating a fresh + * one if necessary + */ + static nsSVGPaintingProperty* + GetPaintingPropertyForURI(nsIURI* aURI, nsIFrame* aFrame, + URIObserverHashtablePropertyDescriptor aProp); + + /** + * A helper function to resolve marker's URL. + */ + static already_AddRefed<nsIURI> + GetMarkerURI(nsIFrame* aFrame, + RefPtr<mozilla::css::URLValue> nsStyleSVG::* aMarker); + + /** + * A helper function to resolve clip-path URL. + */ + static already_AddRefed<nsIURI> + GetClipPathURI(nsIFrame* aFrame); + + /** + * A helper function to resolve filter URL. + */ + static already_AddRefed<nsIURI> + GetFilterURI(nsIFrame* aFrame, uint32_t aIndex); + + /** + * A helper function to resolve filter URL. + */ + static already_AddRefed<nsIURI> + GetFilterURI(nsIFrame* aFrame, const nsStyleFilter& aFilter); + + /** + * A helper function to resolve paint-server URL. + */ + static already_AddRefed<nsIURI> + GetPaintURI(nsIFrame* aFrame, nsStyleSVGPaint nsStyleSVG::* aPaint); + + /** + * A helper function to resolve SVG mask URL. + */ + static already_AddRefed<nsIURI> + GetMaskURI(nsIFrame* aFrame, uint32_t aIndex); +}; + +#endif /*NSSVGEFFECTS_H_*/ diff --git a/layout/svg/nsSVGFilterFrame.cpp b/layout/svg/nsSVGFilterFrame.cpp new file mode 100644 index 0000000000..13ce16993d --- /dev/null +++ b/layout/svg/nsSVGFilterFrame.cpp @@ -0,0 +1,212 @@ +/* -*- 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 "nsSVGFilterFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfxUtils.h" +#include "nsGkAtoms.h" +#include "nsSVGEffects.h" +#include "nsSVGElement.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "nsSVGFilterInstance.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGUtils.h" +#include "nsContentUtils.h" + +using namespace mozilla::dom; + +nsIFrame* +NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGFilterFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGFilterFrame) + +class MOZ_RAII nsSVGFilterFrame::AutoFilterReferencer +{ +public: + explicit AutoFilterReferencer(nsSVGFilterFrame *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; + } + ~AutoFilterReferencer() { + mFrame->mLoopFlag = false; + } +private: + nsSVGFilterFrame *mFrame; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +uint16_t +nsSVGFilterFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault) +{ + nsSVGEnum& thisEnum = + static_cast<SVGFilterElement *>(mContent)->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) + return thisEnum.GetAnimValue(); + + AutoFilterReferencer filterRef(this); + + nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse(); + return next ? next->GetEnumValue(aIndex, aDefault) : + static_cast<SVGFilterElement *>(aDefault)-> + mEnumAttributes[aIndex].GetAnimValue(); +} + +const nsSVGLength2 * +nsSVGFilterFrame::GetLengthValue(uint32_t aIndex, nsIContent *aDefault) +{ + const nsSVGLength2 *thisLength = + &static_cast<SVGFilterElement *>(mContent)->mLengthAttributes[aIndex]; + + if (thisLength->IsExplicitlySet()) + return thisLength; + + AutoFilterReferencer filterRef(this); + + nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse(); + return next ? next->GetLengthValue(aIndex, aDefault) : + &static_cast<SVGFilterElement *>(aDefault)->mLengthAttributes[aIndex]; +} + +const SVGFilterElement * +nsSVGFilterFrame::GetFilterContent(nsIContent *aDefault) +{ + for (nsIContent* child = mContent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + RefPtr<nsSVGFE> primitive; + CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive)); + if (primitive) { + return static_cast<SVGFilterElement *>(mContent); + } + } + + AutoFilterReferencer filterRef(this); + + nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse(); + return next ? next->GetFilterContent(aDefault) : + static_cast<SVGFilterElement *>(aDefault); +} + +nsSVGFilterFrame * +nsSVGFilterFrame::GetReferencedFilter() +{ + if (mNoHRefURI) + return nullptr; + + nsSVGPaintingProperty *property = + Properties().Get(nsSVGEffects::HrefAsPaintingProperty()); + + if (!property) { + // Fetch our Filter element's href or xlink:href attribute + SVGFilterElement *filter = static_cast<SVGFilterElement *>(mContent); + nsAutoString href; + if (filter->mStringAttributes[SVGFilterElement::HREF].IsExplicitlySet()) { + filter->mStringAttributes[SVGFilterElement::HREF] + .GetAnimValue(href, filter); + } else { + filter->mStringAttributes[SVGFilterElement::XLINK_HREF] + .GetAnimValue(href, filter); + } + + 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::svgFilterFrame) + return nullptr; + + return static_cast<nsSVGFilterFrame*>(result); +} + +nsSVGFilterFrame * +nsSVGFilterFrame::GetReferencedFilterIfNotInUse() +{ + nsSVGFilterFrame *referenced = GetReferencedFilter(); + if (!referenced) + return nullptr; + + 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 nullptr; + } + + return referenced; +} + +nsresult +nsSVGFilterFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::filterUnits || + aAttribute == nsGkAtoms::primitiveUnits)) { + 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 nsSVGContainerFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +#ifdef DEBUG +void +nsSVGFilterFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::filter), + "Content is not an SVG filter"); + + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGFilterFrame::GetType() const +{ + return nsGkAtoms::svgFilterFrame; +} diff --git a/layout/svg/nsSVGFilterFrame.h b/layout/svg/nsSVGFilterFrame.h new file mode 100644 index 0000000000..223c787f6e --- /dev/null +++ b/layout/svg/nsSVGFilterFrame.h @@ -0,0 +1,99 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGFILTERFRAME_H__ +#define __NS_SVGFILTERFRAME_H__ + +#include "mozilla/Attributes.h" +#include "nsFrame.h" +#include "nsQueryFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGUtils.h" + +class nsIAtom; +class nsIContent; +class nsIFrame; +class nsIPresShell; +class nsStyleContext; +class nsSVGLength2; + +struct nsRect; + +namespace mozilla { +namespace dom { +class SVGFilterElement; +} // namespace dom +} // namespace mozilla + +class nsSVGFilterFrame : public nsSVGContainerFrame +{ + friend nsIFrame* + NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGFilterFrame(nsStyleContext* aContext) + : nsSVGContainerFrame(aContext) + , mLoopFlag(false) + , mNoHRefURI(false) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame methods: + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgFilterFrame + */ + virtual nsIAtom* GetType() const override; + +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 nsSVGFilterInstance; + nsSVGFilterFrame* GetReferencedFilter(); + nsSVGFilterFrame* GetReferencedFilterIfNotInUse(); + + // Accessors to lookup filter attributes + uint16_t GetEnumValue(uint32_t aIndex, nsIContent *aDefault); + uint16_t GetEnumValue(uint32_t aIndex) + { + return GetEnumValue(aIndex, mContent); + } + const nsSVGLength2 *GetLengthValue(uint32_t aIndex, nsIContent *aDefault); + const nsSVGLength2 *GetLengthValue(uint32_t aIndex) + { + return GetLengthValue(aIndex, mContent); + } + const mozilla::dom::SVGFilterElement *GetFilterContent(nsIContent *aDefault); + const mozilla::dom::SVGFilterElement *GetFilterContent() + { + return GetFilterContent(mContent); + } + + // This flag is used to detect loops in xlink:href processing + bool mLoopFlag; + bool mNoHRefURI; +}; + +#endif diff --git a/layout/svg/nsSVGFilterInstance.cpp b/layout/svg/nsSVGFilterInstance.cpp new file mode 100644 index 0000000000..59b5a27bab --- /dev/null +++ b/layout/svg/nsSVGFilterInstance.cpp @@ -0,0 +1,453 @@ +/* -*- 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 "nsSVGFilterInstance.h" + +// Keep others in (case-insensitive) order: +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "nsISVGChildFrame.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "nsReferencedElement.h" +#include "nsSVGFilterFrame.h" +#include "nsSVGUtils.h" +#include "SVGContentUtils.h" +#include "FilterSupport.h" +#include "gfx2DGlue.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter, + nsIFrame* aTargetFrame, + nsIContent* aTargetContent, + const UserSpaceMetrics& aMetrics, + const gfxRect& aTargetBBox, + const gfxSize& aUserSpaceToFilterSpaceScale, + const gfxSize& aFilterSpaceToUserSpaceScale) : + mFilter(aFilter), + mTargetContent(aTargetContent), + mMetrics(aMetrics), + mTargetBBox(aTargetBBox), + mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), + mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale), + mSourceAlphaAvailable(false), + mInitialized(false) { + + // Get the filter frame. + mFilterFrame = GetFilterFrame(aTargetFrame); + if (!mFilterFrame) { + return; + } + + // Get the filter element. + mFilterElement = mFilterFrame->GetFilterContent(); + if (!mFilterElement) { + NS_NOTREACHED("filter frame should have a related element"); + return; + } + + mPrimitiveUnits = + mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS); + + nsresult rv = ComputeBounds(); + if (NS_FAILED(rv)) { + return; + } + + mInitialized = true; +} + +nsresult +nsSVGFilterInstance::ComputeBounds() +{ + // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we + // should send a warning to the error console if the author has used lengths + // with units. This is a common mistake and can result in the filter region + // 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. + + // Set the user space bounds (i.e. the filter region in user space). + nsSVGLength2 XYWH[4]; + static_assert(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH), + "XYWH size incorrect"); + memcpy(XYWH, mFilterElement->mLengthAttributes, + sizeof(mFilterElement->mLengthAttributes)); + XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X); + XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y); + XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH); + XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT); + uint16_t filterUnits = + mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS); + gfxRect userSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits, + XYWH, mTargetBBox, mMetrics); + + // Transform the user space bounds to filter space, so we + // can align them with the pixel boundries of the offscreen surface. + // The offscreen surface has the same scale as filter space. + gfxRect filterSpaceBounds = UserSpaceToFilterSpace(userSpaceBounds); + filterSpaceBounds.RoundOut(); + if (filterSpaceBounds.width <= 0 || filterSpaceBounds.height <= 0) { + // 0 disables rendering, < 0 is error. dispatch error console warning + // or error as appropriate. + return NS_ERROR_FAILURE; + } + + // Set the filter space bounds. + if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds, &mFilterSpaceBounds)) { + // The filter region is way too big if there is float -> int overflow. + return NS_ERROR_FAILURE; + } + + mUserSpaceBounds = FilterSpaceToUserSpace(filterSpaceBounds); + + return NS_OK; +} + +nsSVGFilterFrame* +nsSVGFilterInstance::GetFilterFrame(nsIFrame* aTargetFrame) +{ + if (mFilter.GetType() != NS_STYLE_FILTER_URL) { + // The filter is not an SVG reference filter. + return nullptr; + } + + // Get the target element to use as a point of reference for looking up the + // filter element. + if (!mTargetContent) { + return nullptr; + } + + // aTargetFrame can be null if this filter belongs to a + // CanvasRenderingContext2D. + nsCOMPtr<nsIURI> url = aTargetFrame + ? nsSVGEffects::GetFilterURI(aTargetFrame, mFilter) + : mFilter.GetURL()->ResolveLocalRef(mTargetContent); + + if (!url) { + NS_NOTREACHED("an nsStyleFilter of type URL should have a non-null URL"); + return nullptr; + } + + // Look up the filter element by URL. + nsReferencedElement filterElement; + bool watch = false; + filterElement.Reset(mTargetContent, url, watch); + Element* element = filterElement.get(); + if (!element) { + // The URL points to no element. + return nullptr; + } + + // Get the frame of the filter element. + nsIFrame* frame = element->GetPrimaryFrame(); + if (!frame || frame->GetType() != nsGkAtoms::svgFilterFrame) { + // The URL points to an element that's not an SVG filter element, or to an + // element that hasn't had its frame constructed yet. + return nullptr; + } + + return static_cast<nsSVGFilterFrame*>(frame); +} + +float +nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const +{ + nsSVGLength2 val; + val.Init(aCtxType, 0xff, aValue, + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); + + float value; + if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + value = nsSVGUtils::ObjectSpace(mTargetBBox, &val); + } else { + value = nsSVGUtils::UserSpace(mMetrics, &val); + } + + switch (aCtxType) { + case SVGContentUtils::X: + return value * mUserSpaceToFilterSpaceScale.width; + case SVGContentUtils::Y: + return value * mUserSpaceToFilterSpaceScale.height; + case SVGContentUtils::XY: + default: + return value * SVGContentUtils::ComputeNormalizedHypotenuse( + mUserSpaceToFilterSpaceScale.width, + mUserSpaceToFilterSpaceScale.height); + } +} + +Point3D +nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const +{ + nsSVGLength2 val[4]; + val[0].Init(SVGContentUtils::X, 0xff, aPoint.x, + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); + val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y, + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); + // Dummy width/height values + val[2].Init(SVGContentUtils::X, 0xff, 0, + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); + val[3].Init(SVGContentUtils::Y, 0xff, 0, + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); + + gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, + val, mTargetBBox, mMetrics); + gfxRect r = UserSpaceToFilterSpace(feArea); + return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z)); +} + +gfxRect +nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const +{ + gfxRect filterSpaceRect = aUserSpaceRect; + filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width, + mUserSpaceToFilterSpaceScale.height); + return filterSpaceRect; +} + +gfxRect +nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const +{ + gfxRect userSpaceRect = aFilterSpaceRect; + userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width, + mFilterSpaceToUserSpaceScale.height); + return userSpaceRect; +} + +IntRect +nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTArray<int32_t>& aInputIndices) +{ + nsSVGFE* fE = aFilterElement; + + IntRect defaultFilterSubregion(0,0,0,0); + if (fE->SubregionIsUnionOfRegions()) { + for (uint32_t i = 0; i < aInputIndices.Length(); ++i) { + int32_t inputIndex = aInputIndices[i]; + bool isStandardInput = inputIndex < 0 || inputIndex == mSourceGraphicIndex; + IntRect inputSubregion = isStandardInput ? + mFilterSpaceBounds : + aPrimitiveDescrs[inputIndex].PrimitiveSubregion(); + + defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion); + } + } else { + defaultFilterSubregion = mFilterSpaceBounds; + } + + gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, + &fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mMetrics); + Rect region = ToRect(UserSpaceToFilterSpace(feArea)); + + if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet()) + region.x = defaultFilterSubregion.X(); + if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet()) + region.y = defaultFilterSubregion.Y(); + if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet()) + region.width = defaultFilterSubregion.Width(); + if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet()) + region.height = defaultFilterSubregion.Height(); + + // We currently require filter primitive subregions to be pixel-aligned. + // Following the spec, any pixel partially in the region is included + // in the region. + region.RoundOut(); + return RoundedToInt(region); +} + +void +nsSVGFilterInstance::GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTArray<int32_t>& aInputIndices, + bool aFilterInputIsTainted, + nsTArray<bool>& aOutInputsAreTainted) +{ + for (uint32_t i = 0; i < aInputIndices.Length(); i++) { + int32_t inputIndex = aInputIndices[i]; + if (inputIndex < 0) { + aOutInputsAreTainted.AppendElement(aFilterInputIsTainted); + } else { + aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted()); + } + } +} + +static int32_t +GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) +{ + uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); + return !numPrimitiveDescrs ? + FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic : + numPrimitiveDescrs - 1; +} + +int32_t +nsSVGFilterInstance::GetOrCreateSourceAlphaIndex(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) +{ + // If the SourceAlpha index has already been determined or created for this + // SVG filter, just return it. + if (mSourceAlphaAvailable) + return mSourceAlphaIndex; + + // If this is the first filter in the chain, we can just use the + // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the + // original image. + if (mSourceGraphicIndex < 0) { + mSourceAlphaIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha; + mSourceAlphaAvailable = true; + return mSourceAlphaIndex; + } + + // Otherwise, create a primitive description to turn the previous filter's + // output into a SourceAlpha input. + FilterPrimitiveDescription descr(PrimitiveType::ToAlpha); + descr.SetInputPrimitive(0, mSourceGraphicIndex); + + const FilterPrimitiveDescription& sourcePrimitiveDescr = + aPrimitiveDescrs[mSourceGraphicIndex]; + descr.SetPrimitiveSubregion(sourcePrimitiveDescr.PrimitiveSubregion()); + descr.SetIsTainted(sourcePrimitiveDescr.IsTainted()); + + ColorSpace colorSpace = sourcePrimitiveDescr.OutputColorSpace(); + descr.SetInputColorSpace(0, colorSpace); + descr.SetOutputColorSpace(colorSpace); + + aPrimitiveDescrs.AppendElement(descr); + mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1; + mSourceAlphaAvailable = true; + return mSourceAlphaIndex; +} + +nsresult +nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement, + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable, + nsTArray<int32_t>& aSourceIndices) +{ + AutoTArray<nsSVGStringInfo,2> sources; + aPrimitiveElement->GetSourceImageNames(sources); + + for (uint32_t j = 0; j < sources.Length(); j++) { + nsAutoString str; + sources[j].mString->GetAnimValue(str, sources[j].mElement); + + int32_t sourceIndex = 0; + if (str.EqualsLiteral("SourceGraphic")) { + sourceIndex = mSourceGraphicIndex; + } else if (str.EqualsLiteral("SourceAlpha")) { + sourceIndex = GetOrCreateSourceAlphaIndex(aPrimitiveDescrs); + } else if (str.EqualsLiteral("FillPaint")) { + sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint; + } else if (str.EqualsLiteral("StrokePaint")) { + sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint; + } else if (str.EqualsLiteral("BackgroundImage") || + str.EqualsLiteral("BackgroundAlpha")) { + return NS_ERROR_NOT_IMPLEMENTED; + } else if (str.EqualsLiteral("")) { + sourceIndex = GetLastResultIndex(aPrimitiveDescrs); + } else { + bool inputExists = aImageTable.Get(str, &sourceIndex); + if (!inputExists) + return NS_ERROR_FAILURE; + } + + aSourceIndices.AppendElement(sourceIndex); + } + return NS_OK; +} + +nsresult +nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + nsTArray<RefPtr<SourceSurface>>& aInputImages, + bool aInputIsTainted) +{ + mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs); + + // Clip previous filter's output to this filter's filter region. + if (mSourceGraphicIndex >= 0) { + FilterPrimitiveDescription& sourceDescr = aPrimitiveDescrs[mSourceGraphicIndex]; + sourceDescr.SetPrimitiveSubregion(sourceDescr.PrimitiveSubregion().Intersect(mFilterSpaceBounds)); + } + + // Get the filter primitive elements. + nsTArray<RefPtr<nsSVGFE> > primitives; + for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); + child; + child = child->GetNextSibling()) { + RefPtr<nsSVGFE> primitive; + CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive)); + if (primitive) { + primitives.AppendElement(primitive); + } + } + + // Maps source image name to source index. + nsDataHashtable<nsStringHashKey, int32_t> imageTable(8); + + // The principal that we check principals of any loaded images against. + nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal(); + + for (uint32_t primitiveElementIndex = 0; + primitiveElementIndex < primitives.Length(); + ++primitiveElementIndex) { + nsSVGFE* filter = primitives[primitiveElementIndex]; + + AutoTArray<int32_t,2> sourceIndices; + nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices); + if (NS_FAILED(rv)) { + return rv; + } + + IntRect primitiveSubregion = + ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices); + + nsTArray<bool> sourcesAreTainted; + GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted, sourcesAreTainted); + + FilterPrimitiveDescription descr = + filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages); + + descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal)); + descr.SetFilterSpaceBounds(mFilterSpaceBounds); + descr.SetPrimitiveSubregion(primitiveSubregion.Intersect(descr.FilterSpaceBounds())); + + for (uint32_t i = 0; i < sourceIndices.Length(); i++) { + int32_t inputIndex = sourceIndices[i]; + descr.SetInputPrimitive(i, inputIndex); + + ColorSpace inputColorSpace = inputIndex >= 0 + ? aPrimitiveDescrs[inputIndex].OutputColorSpace() + : ColorSpace(ColorSpace::SRGB); + + ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace); + descr.SetInputColorSpace(i, desiredInputColorSpace); + if (i == 0) { + // the output color space is whatever in1 is if there is an in1 + descr.SetOutputColorSpace(desiredInputColorSpace); + } + } + + if (sourceIndices.Length() == 0) { + descr.SetOutputColorSpace(filter->GetOutputColorSpace()); + } + + aPrimitiveDescrs.AppendElement(descr); + uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; + + nsAutoString str; + filter->GetResultImageName().GetAnimValue(str, filter); + imageTable.Put(str, primitiveDescrIndex); + } + + return NS_OK; +} diff --git a/layout/svg/nsSVGFilterInstance.h b/layout/svg/nsSVGFilterInstance.h new file mode 100644 index 0000000000..a4001dbd4f --- /dev/null +++ b/layout/svg/nsSVGFilterInstance.h @@ -0,0 +1,285 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGFILTERINSTANCE_H__ +#define __NS_SVGFILTERINSTANCE_H__ + +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsSVGFilters.h" +#include "nsSVGNumber2.h" +#include "nsSVGNumberPair.h" +#include "nsTArray.h" + +class nsSVGFilterFrame; +struct nsStyleFilter; + +namespace mozilla { +namespace dom { +class SVGFilterElement; +} // namespace dom +} // namespace mozilla + +/** + * This class helps nsFilterInstance build its filter graph by processing a + * single SVG reference filter. + * + * In BuildPrimitives, this class iterates through the referenced <filter> + * element's primitive elements, creating a FilterPrimitiveDescription for + * each one. + * + * This class uses several different coordinate spaces, defined as follows: + * + * "user space" + * The filtered SVG element's user space or the filtered HTML element's + * CSS pixel space. The origin for an HTML element is the top left corner of + * its border box. + * + * "filter space" + * User space scaled to device pixels. Shares the same origin as user space. + * This space is the same across chained SVG and CSS filters. To compute the + * overall filter space for a chain, we first need to build each filter's + * FilterPrimitiveDescriptions in some common space. That space is + * filter space. + * + * To understand the spaces better, let's take an example filter: + * <filter id="f">...</filter> + * + * And apply the filter to a div element: + * <div style="filter: url(#f); ...">...</div> + * + * And let's say there are 2 device pixels for every 1 CSS pixel. + * + * Finally, let's define an arbitrary point in user space: + * "user space point" = (10, 10) + * + * The point will be inset 10 CSS pixels from both the top and left edges of the + * div element's border box. + * + * Now, let's transform the point from user space to filter space: + * "filter space point" = "user space point" * "device pixels per CSS pixel" + * "filter space point" = (10, 10) * 2 + * "filter space point" = (20, 20) + */ +class nsSVGFilterInstance +{ + typedef mozilla::gfx::Point3D Point3D; + typedef mozilla::gfx::IntRect IntRect; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::gfx::FilterPrimitiveDescription FilterPrimitiveDescription; + typedef mozilla::dom::UserSpaceMetrics UserSpaceMetrics; + +public: + /** + * @param aFilter The SVG filter reference from the style system. This class + * stores aFilter by reference, so callers should avoid modifying or + * deleting aFilter during the lifetime of nsSVGFilterInstance. + * @param aTargetContent The filtered element. + * @param aTargetBBox The SVG bbox to use for the target frame, computed by + * the caller. The caller may decide to override the actual SVG bbox. + */ + nsSVGFilterInstance(const nsStyleFilter& aFilter, + nsIFrame* aTargetFrame, + nsIContent* aTargetContent, + const UserSpaceMetrics& aMetrics, + const gfxRect& aTargetBBox, + const gfxSize& aUserSpaceToFilterSpaceScale, + const gfxSize& aFilterSpaceToUserSpaceScale); + + /** + * Returns true if the filter instance was created successfully. + */ + bool IsInitialized() const { return mInitialized; } + + /** + * Iterates through the <filter> element's primitive elements, creating a + * FilterPrimitiveDescription for each one. Appends the new + * FilterPrimitiveDescription(s) to the aPrimitiveDescrs list. Also, appends + * new images from feImage filter primitive elements to the aInputImages list. + * aInputIsTainted describes whether the input to this filter is tainted, i.e. + * whether it contains security-sensitive content. This is needed to propagate + * taintedness to the FilterPrimitive that take tainted inputs. Something being + * tainted means that it contains security sensitive content. + * The input to this filter is the previous filter's output, i.e. the last + * element in aPrimitiveDescrs, or the SourceGraphic input if this is the first + * filter in the filter chain. + */ + nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + nsTArray<RefPtr<SourceSurface>>& aInputImages, + bool aInputIsTainted); + + /** + * Returns the user specified "filter region", in the filtered element's user + * space, after it has been adjusted out (if necessary) so that its edges + * coincide with pixel boundaries of the offscreen surface into which the + * filtered output would/will be painted. + */ + gfxRect GetFilterRegion() const { return mUserSpaceBounds; } + + /** + * Returns the size of the user specified "filter region", in filter space. + */ + nsIntRect GetFilterSpaceBounds() const { return mFilterSpaceBounds; } + + float GetPrimitiveNumber(uint8_t aCtxType, const nsSVGNumber2 *aNumber) const + { + return GetPrimitiveNumber(aCtxType, aNumber->GetAnimValue()); + } + float GetPrimitiveNumber(uint8_t aCtxType, const nsSVGNumberPair *aNumberPair, + nsSVGNumberPair::PairIndex aIndex) const + { + return GetPrimitiveNumber(aCtxType, aNumberPair->GetAnimValue(aIndex)); + } + + /** + * Converts a userSpaceOnUse/objectBoundingBoxUnits unitless point + * into filter space, depending on the value of mPrimitiveUnits. (For + * objectBoundingBoxUnits, the bounding box offset is applied to the point.) + */ + Point3D ConvertLocation(const Point3D& aPoint) const; + + /** + * Transform a rect between user space and filter space. + */ + gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const; + +private: + /** + * Finds the filter frame associated with this SVG filter. + */ + nsSVGFilterFrame* GetFilterFrame(nsIFrame* aTargetFrame); + + /** + * Computes the filter primitive subregion for the given primitive. + */ + IntRect ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTArray<int32_t>& aInputIndices); + + /** + * Takes the input indices of a filter primitive and returns for each input + * whether the input's output is tainted. + */ + void GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTArray<int32_t>& aInputIndices, + bool aFilterInputIsTainted, + nsTArray<bool>& aOutInputsAreTainted); + + /** + * Scales a numeric filter primitive length in the X, Y or "XY" directions + * into a length in filter space (no offset is applied). + */ + float GetPrimitiveNumber(uint8_t aCtxType, float aValue) const; + + /** + * Transform a rect between user space and filter space. + */ + gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const; + + /** + * Returns the transform from frame space to the coordinate space that + * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the + * top-left corner of its border box, aka the top left corner of its mRect. + */ + gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const; + + /** + * Appends a new FilterPrimitiveDescription to aPrimitiveDescrs that + * converts the FilterPrimitiveDescription at mSourceGraphicIndex into + * a SourceAlpha input for the next FilterPrimitiveDescription. + * + * The new FilterPrimitiveDescription zeros out the SourceGraphic's RGB + * channels and keeps the alpha channel intact. + */ + int32_t GetOrCreateSourceAlphaIndex(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs); + + /** + * Finds the index in aPrimitiveDescrs of each input to aPrimitiveElement. + * For example, if aPrimitiveElement is: + * <feGaussianBlur in="another-primitive" .../> + * Then, the resulting aSourceIndices will contain the index of the + * FilterPrimitiveDescription representing "another-primitive". + */ + nsresult GetSourceIndices(nsSVGFE* aPrimitiveElement, + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable, + nsTArray<int32_t>& aSourceIndices); + + /** + * Compute the filter region in user space, filter space, and filter + * space. + */ + nsresult ComputeBounds(); + + /** + * The SVG reference filter originally from the style system. + */ + const nsStyleFilter& mFilter; + + /** + * The filtered element. + */ + nsIContent* mTargetContent; + + /** + * The SVG user space metrics that SVG lengths are resolved against. + */ + const UserSpaceMetrics& mMetrics; + + /** + * The filter element referenced by mTargetFrame's element. + */ + const mozilla::dom::SVGFilterElement* mFilterElement; + + /** + * The frame for the SVG filter element. + */ + nsSVGFilterFrame* mFilterFrame; + + /** + * The SVG bbox of the element that is being filtered, in user space. + */ + gfxRect mTargetBBox; + + /** + * The "filter region" in various spaces. + */ + gfxRect mUserSpaceBounds; + nsIntRect mFilterSpaceBounds; + + /** + * The scale factors between user space and filter space. + */ + gfxSize mUserSpaceToFilterSpaceScale; + gfxSize mFilterSpaceToUserSpaceScale; + + /** + * The 'primitiveUnits' attribute value (objectBoundingBox or userSpaceOnUse). + */ + uint16_t mPrimitiveUnits; + + /** + * The index of the FilterPrimitiveDescription that this SVG filter should use + * as its SourceGraphic, or the SourceGraphic keyword index if this is the + * first filter in a chain. Initialized in BuildPrimitives + */ + MOZ_INIT_OUTSIDE_CTOR int32_t mSourceGraphicIndex; + + /** + * The index of the FilterPrimitiveDescription that this SVG filter should use + * as its SourceAlpha, or the SourceAlpha keyword index if this is the first + * filter in a chain. Initialized in BuildPrimitives + */ + MOZ_INIT_OUTSIDE_CTOR int32_t mSourceAlphaIndex; + + /** + * SourceAlpha is available if GetOrCreateSourceAlphaIndex has been called. + */ + int32_t mSourceAlphaAvailable; + + bool mInitialized; +}; + +#endif diff --git a/layout/svg/nsSVGFilterPaintCallback.h b/layout/svg/nsSVGFilterPaintCallback.h new file mode 100644 index 0000000000..3c4bf3f707 --- /dev/null +++ b/layout/svg/nsSVGFilterPaintCallback.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGFILTERPAINTCALLBACK_H__ +#define __NS_SVGFILTERPAINTCALLBACK_H__ + +#include "nsRect.h" + +class nsIFrame; +class gfxContext; + +class nsSVGFilterPaintCallback { +public: + typedef mozilla::image::DrawResult DrawResult; + + /** + * Paint the frame contents. + * SVG frames will have had matrix propagation set to false already. + * Non-SVG frames have to do their own thing. + * The caller will do a Save()/Restore() as necessary so feel free + * to mess with context state. + * The context will be configured to use the "user space" coordinate + * system. + * @param aDirtyRect the dirty rect *in user space pixels* + * @param aTransformRoot the outermost frame whose transform should be taken + * into account when painting an SVG glyph + */ + virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) = 0; +}; + +#endif diff --git a/layout/svg/nsSVGForeignObjectFrame.cpp b/layout/svg/nsSVGForeignObjectFrame.cpp new file mode 100644 index 0000000000..afa5912d2c --- /dev/null +++ b/layout/svg/nsSVGForeignObjectFrame.cpp @@ -0,0 +1,592 @@ +/* -*- 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 "nsSVGForeignObjectFrame.h" + +// Keep others in (case-insensitive) order: +#include "DrawResult.h" +#include "gfxContext.h" +#include "nsDisplayList.h" +#include "nsGkAtoms.h" +#include "nsNameSpaceManager.h" +#include "nsLayoutUtils.h" +#include "nsRegion.h" +#include "nsRenderingContext.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGEffects.h" +#include "mozilla/dom/SVGForeignObjectElement.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGOuterSVGFrame.h" +#include "nsSVGUtils.h" +#include "mozilla/AutoRestore.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::image; + +//---------------------------------------------------------------------- +// Implementation + +nsContainerFrame* +NS_NewSVGForeignObjectFrame(nsIPresShell *aPresShell, + nsStyleContext *aContext) +{ + return new (aPresShell) nsSVGForeignObjectFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame) + +nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) + , mInReflow(false) +{ + AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED | + NS_FRAME_SVG_LAYOUT); +} + +//---------------------------------------------------------------------- +// nsIFrame methods + +NS_QUERYFRAME_HEAD(nsSVGForeignObjectFrame) + NS_QUERYFRAME_ENTRY(nsISVGChildFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +void +nsSVGForeignObjectFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::foreignObject), + "Content is not an SVG foreignObject!"); + + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); + AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER | + NS_FRAME_FONT_INFLATION_FLOW_ROOT); + if (!(mState & NS_FRAME_IS_NONDISPLAY)) { + nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this); + } +} + +void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // Only unregister if we registered in the first place: + if (!(mState & NS_FRAME_IS_NONDISPLAY)) { + nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this); + } + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +nsIAtom * +nsSVGForeignObjectFrame::GetType() const +{ + return nsGkAtoms::svgForeignObjectFrame; +} + +nsresult +nsSVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom *aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + // XXXjwatt: why mark intrinsic widths dirty? can't we just use eResize? + RequestReflow(nsIPresShell::eStyleChange); + } else if (aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + } else if (aAttribute == nsGkAtoms::transform) { + // We don't invalidate for transform changes (the layers code does that). + // Also note that SVGTransformableElement::GetAttributeChangeHint will + // return nsChangeHint_UpdateOverflow for "transform" attribute changes + // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. + mCanvasTM = nullptr; + } else if (aAttribute == nsGkAtoms::viewBox || + aAttribute == nsGkAtoms::preserveAspectRatio) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + } + } + + return NS_OK; +} + +void +nsSVGForeignObjectFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "Should not have been called"); + + // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY, + // so if that bit is still set we still have a resize pending. If we hit + // this assertion, then we should get the presShell to skip reflow roots + // that have a dirty parent since a reflow is going to come via the + // reflow root's parent anyway. + NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY), + "Reflowing while a resize is pending is wasteful"); + + // ReflowSVG makes sure mRect is up to date before we're called. + + NS_ASSERTION(!aReflowInput.mParentReflowInput, + "should only get reflow from being reflow root"); + NS_ASSERTION(aReflowInput.ComputedWidth() == GetSize().width && + aReflowInput.ComputedHeight() == GetSize().height, + "reflow roots should be reflowed at existing size and " + "svg.css should ensure we have no padding/border/margin"); + + DoReflow(); + + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalSize finalSize(wm, aReflowInput.ComputedISize(), + aReflowInput.ComputedBSize()); + aDesiredSize.SetSize(wm, finalSize); + aDesiredSize.SetOverflowAreasToDesiredBounds(); + aStatus = NS_FRAME_COMPLETE; +} + +void +nsSVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) { + return; + } + DisplayOutline(aBuilder, aLists); + BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists); +} + +bool +nsSVGForeignObjectFrame::IsSVGTransformed(Matrix *aOwnTransform, + Matrix *aFromParentTransform) const +{ + bool foundTransform = false; + + // Check if our parent has children-only transforms: + nsIFrame *parent = GetParent(); + if (parent && + parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { + foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> + HasChildrenOnlyTransform(aFromParentTransform); + } + + nsSVGElement *content = static_cast<nsSVGElement*>(mContent); + nsSVGAnimatedTransformList* transformList = + content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo( + gfxMatrix(), + eUserSpaceToParent)); + } + foundTransform = true; + } + return foundTransform; +} + +DrawResult +nsSVGForeignObjectFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) +{ + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + if (IsDisabled()) + return DrawResult::SUCCESS; + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return DrawResult::SUCCESS; + + if (aTransform.IsSingular()) { + NS_WARNING("Can't render foreignObject element!"); + return DrawResult::BAD_ARGS; + } + + nsRect kidDirtyRect = kid->GetVisualOverflowRect(); + + /* Check if we need to draw anything. */ + if (aDirtyRect) { + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "Display lists handle dirty rect intersection test"); + // Transform the dirty rect into app units in our userspace. + gfxMatrix invmatrix = aTransform; + DebugOnly<bool> ok = invmatrix.Invert(); + NS_ASSERTION(ok, "inverse of non-singular matrix should be non-singular"); + + gfxRect transDirtyRect = gfxRect(aDirtyRect->x, aDirtyRect->y, + aDirtyRect->width, aDirtyRect->height); + transDirtyRect = invmatrix.TransformBounds(transDirtyRect); + + kidDirtyRect.IntersectRect(kidDirtyRect, + nsLayoutUtils::RoundGfxRectToAppRect(transDirtyRect, + PresContext()->AppUnitsPerCSSPixel())); + + // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect, + // not with kidDirtyRect. I.e. + // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); + // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect) + if (kidDirtyRect.IsEmpty()) + return DrawResult::SUCCESS; + } + + aContext.Save(); + + if (StyleDisplay()->IsScrollableOverflow()) { + float x, y, width, height; + static_cast<nsSVGElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + gfxRect clipRect = + nsSVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height); + nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect); + } + + // SVG paints in CSS px, but normally frames paint in dev pixels. Here we + // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children + // paint correctly. + float cssPxPerDevPx = PresContext()-> + AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel()); + gfxMatrix canvasTMForChildren = aTransform; + canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx); + + aContext.Multiply(canvasTMForChildren); + + using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; + PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM; + if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) { + flags |= PaintFrameFlags::PAINT_TO_WINDOW; + } + nsRenderingContext rendCtx(&aContext); + nsresult rv = nsLayoutUtils::PaintFrame(&rendCtx, kid, nsRegion(kidDirtyRect), + NS_RGBA(0,0,0,0), + nsDisplayListBuilderMode::PAINTING, + flags); + + aContext.Restore(); + + return NS_FAILED(rv) ? DrawResult::BAD_ARGS : DrawResult::SUCCESS; +} + +nsIFrame* +nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only hit-testing of a " + "clipPath's contents should take this code path"); + + if (IsDisabled() || (GetStateBits() & NS_FRAME_IS_NONDISPLAY)) + return nullptr; + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return nullptr; + + float x, y, width, height; + static_cast<nsSVGElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + if (!gfxRect(x, y, width, height).Contains(aPoint) || + !nsSVGUtils::HitTestClip(this, aPoint)) { + return nullptr; + } + + // Convert the point to app units relative to the top-left corner of the + // viewport that's established by the foreignObject element: + + gfxPoint pt = (aPoint + gfxPoint(x, y)) * nsPresContext::AppUnitsPerCSSPixel(); + nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y)); + + return nsLayoutUtils::GetFrameForPoint(kid, point); +} + +nsRect +nsSVGForeignObjectFrame::GetCoveredRegion() +{ + float x, y, w, h; + static_cast<SVGForeignObjectElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); + if (w < 0.0f) w = 0.0f; + if (h < 0.0f) h = 0.0f; + // GetCanvasTM includes the x,y translation + return nsSVGUtils::ToCanvasBounds(gfxRect(0.0, 0.0, w, h), + GetCanvasTM(), + PresContext()); +} + +void +nsSVGForeignObjectFrame::ReflowSVG() +{ + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!nsSVGUtils::NeedsReflowSVG(this)) { + return; + } + + // We update mRect before the DoReflow call so that DoReflow uses the + // correct dimensions: + + float x, y, w, h; + static_cast<SVGForeignObjectElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); + + // If mRect's width or height are negative, reflow blows up! We must clamp! + if (w < 0.0f) w = 0.0f; + if (h < 0.0f) h = 0.0f; + + mRect = nsLayoutUtils::RoundGfxRectToAppRect( + gfxRect(x, y, w, h), + PresContext()->AppUnitsPerCSSPixel()); + + // Fully mark our kid dirty so that it gets resized if necessary + // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case): + nsIFrame* kid = PrincipalChildList().FirstChild(); + kid->AddStateBits(NS_FRAME_IS_DIRTY); + + // Make sure to not allow interrupts if we're not being reflown as a root: + nsPresContext::InterruptPreventer noInterrupts(PresContext()); + + DoReflow(); + + if (mState & NS_FRAME_FIRST_REFLOW) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + nsSVGEffects::UpdateEffects(this); + } + + // If we have a filter, we need to invalidate ourselves because filter + // output can change even if none of our descendants need repainting. + if (StyleEffects()->HasFilters()) { + InvalidateFrame(); + } + + // TODO: once we support |overflow:visible| on foreignObject, then we will + // need to take account of our descendants here. + nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); + nsOverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + // Now unset the various reflow bits: + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsSVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + bool needNewBounds = false; // i.e. mRect or visual overflow rect + bool needReflow = false; + bool needNewCanvasTM = false; + + if (aFlags & COORD_CONTEXT_CHANGED) { + SVGForeignObjectElement *fO = + static_cast<SVGForeignObjectElement*>(mContent); + // Coordinate context changes affect mCanvasTM if we have a + // percentage 'x' or 'y' + if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_X].IsPercentage() || + fO->mLengthAttributes[SVGForeignObjectElement::ATTR_Y].IsPercentage()) { + needNewBounds = true; + needNewCanvasTM = true; + } + // Our coordinate context's width/height has changed. If we have a + // percentage width/height our dimensions will change so we must reflow. + if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_WIDTH].IsPercentage() || + fO->mLengthAttributes[SVGForeignObjectElement::ATTR_HEIGHT].IsPercentage()) { + needNewBounds = true; + needReflow = true; + } + } + + if (aFlags & TRANSFORM_CHANGED) { + if (mCanvasTM && mCanvasTM->IsSingular()) { + needNewBounds = true; // old bounds are bogus + } + needNewCanvasTM = true; + // In an ideal world we would reflow when our CTM changes. This is because + // glyph metrics do not necessarily scale uniformly with change in scale + // and, as a result, CTM changes may require text to break at different + // points. The problem would be how to keep performance acceptable when + // e.g. the transform of an ancestor is animated. + // We also seem to get some sort of infinite loop post bug 421584 if we + // reflow. + } + + if (needNewBounds) { + // Ancestor changes can't affect how we render from the perspective of + // any rendering observers that we may have, so we don't need to + // invalidate them. We also don't need to invalidate ourself, since our + // changed ancestor will have invalidated its entire area, which includes + // our area. + nsSVGUtils::ScheduleReflowSVG(this); + } + + // If we're called while the PresShell is handling reflow events then we + // must have been called as a result of the NotifyViewportChange() call in + // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow + // at this point (i.e. during reflow) because it could confuse the + // PresShell and prevent it from reflowing us properly in future. Besides + // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us + // synchronously, so there's no need. + if (needReflow && !PresContext()->PresShell()->IsReflowLocked()) { + RequestReflow(nsIPresShell::eResize); + } + + if (needNewCanvasTM) { + // Do this after calling InvalidateAndScheduleBoundsUpdate in case we + // change the code and it needs to use it. + mCanvasTM = nullptr; + } +} + +SVGBBox +nsSVGForeignObjectFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) +{ + SVGForeignObjectElement *content = + static_cast<SVGForeignObjectElement*>(mContent); + + float x, y, w, h; + content->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); + + if (w < 0.0f) w = 0.0f; + if (h < 0.0f) h = 0.0f; + + if (aToBBoxUserspace.IsSingular()) { + // XXX ReportToConsole + return SVGBBox(); + } + return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h)); +} + +//---------------------------------------------------------------------- + +gfxMatrix +nsSVGForeignObjectFrame::GetCanvasTM() +{ + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + + nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); + SVGForeignObjectElement *content = + static_cast<SVGForeignObjectElement*>(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = new gfxMatrix(tm); + } + return *mCanvasTM; +} + +//---------------------------------------------------------------------- +// Implementation helpers + +void nsSVGForeignObjectFrame::RequestReflow(nsIPresShell::IntrinsicDirty aType) +{ + if (GetStateBits() & NS_FRAME_FIRST_REFLOW) + // If we haven't had a ReflowSVG() yet, nothing to do. + return; + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return; + + PresContext()->PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY); +} + +void +nsSVGForeignObjectFrame::DoReflow() +{ + MarkInReflow(); + // Skip reflow if we're zero-sized, unless this is our first reflow. + if (IsDisabled() && + !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) + return; + + nsPresContext *presContext = PresContext(); + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) + return; + + // initiate a synchronous reflow here and now: + nsRenderingContext renderingContext( + presContext->PresShell()->CreateReferenceRenderingContext()); + + mInReflow = true; + + WritingMode wm = kid->GetWritingMode(); + ReflowInput reflowInput(presContext, kid, + &renderingContext, + LogicalSize(wm, ISize(wm), + NS_UNCONSTRAINEDSIZE)); + ReflowOutput desiredSize(reflowInput); + nsReflowStatus status; + + // We don't use mRect.height above because that tells the child to do + // page/column breaking at that height. + NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && + reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), + "style system should ensure that :-moz-svg-foreign-content " + "does not get styled"); + NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm), + "reflow state made child wrong size"); + reflowInput.SetComputedBSize(BSize(wm)); + + ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0, + NS_FRAME_NO_MOVE_FRAME, status); + NS_ASSERTION(mRect.width == desiredSize.Width() && + mRect.height == desiredSize.Height(), "unexpected size"); + FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0, + NS_FRAME_NO_MOVE_FRAME); + + mInReflow = false; +} + +nsRect +nsSVGForeignObjectFrame::GetInvalidRegion() +{ + MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(), + "Only called by nsDisplayOuterSVG code"); + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->HasInvalidFrameInSubtree()) { + gfxRect r(mRect.x, mRect.y, mRect.width, mRect.height); + r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel()); + nsRect rect = nsSVGUtils::ToCanvasBounds(r, GetCanvasTM(), PresContext()); + rect = nsSVGUtils::GetPostFilterVisualOverflowRect(this, rect); + return rect; + } + return nsRect(); +} + + diff --git a/layout/svg/nsSVGForeignObjectFrame.h b/layout/svg/nsSVGForeignObjectFrame.h new file mode 100644 index 0000000000..57df6d5b5b --- /dev/null +++ b/layout/svg/nsSVGForeignObjectFrame.h @@ -0,0 +1,105 @@ +/* -*- 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/. */ + +#ifndef NSSVGFOREIGNOBJECTFRAME_H__ +#define NSSVGFOREIGNOBJECTFRAME_H__ + +#include "mozilla/Attributes.h" +#include "nsAutoPtr.h" +#include "nsContainerFrame.h" +#include "nsIPresShell.h" +#include "nsISVGChildFrame.h" +#include "nsRegion.h" +#include "nsSVGUtils.h" + +class gfxContext; + +class nsSVGForeignObjectFrame : public nsContainerFrame + , public nsISVGChildFrame +{ + friend nsContainerFrame* + NS_NewSVGForeignObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGForeignObjectFrame(nsStyleContext* aContext); + +public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame: + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgForeignObjectFrame + */ + virtual nsIAtom* GetType() const override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType(aFlags & + ~(nsIFrame::eSVG | nsIFrame::eSVGForeignObject)); + } + + virtual bool IsSVGTransformed(Matrix *aOwnTransform, + Matrix *aFromParentTransform) const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGForeignObject"), aResult); + } +#endif + + // nsISVGChildFrame interface: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) override; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + virtual nsRect GetCoveredRegion() override; + virtual void ReflowSVG() override; + virtual void NotifySVGChanged(uint32_t aFlags) override; + virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) override; + virtual bool IsDisplayContainer() override { return true; } + + gfxMatrix GetCanvasTM(); + + nsRect GetInvalidRegion(); + +protected: + // implementation helpers: + void DoReflow(); + void RequestReflow(nsIPresShell::IntrinsicDirty aType); + + // If width or height is less than or equal to zero we must disable rendering + bool IsDisabled() const { return mRect.width <= 0 || mRect.height <= 0; } + + nsAutoPtr<gfxMatrix> mCanvasTM; + + bool mInReflow; +}; + +#endif diff --git a/layout/svg/nsSVGGFrame.cpp b/layout/svg/nsSVGGFrame.cpp new file mode 100644 index 0000000000..1fc1249564 --- /dev/null +++ b/layout/svg/nsSVGGFrame.cpp @@ -0,0 +1,98 @@ +/* -*- 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 "nsSVGGFrame.h" + +// Keep others in (case-insensitive) order: +#include "nsGkAtoms.h" +#include "SVGTransformableElement.h" +#include "nsIFrame.h" +#include "SVGGraphicsElement.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGUtils.h" + +using namespace mozilla::dom; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGGFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGGFrame) + +#ifdef DEBUG +void +nsSVGGFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement() && + static_cast<nsSVGElement*>(aContent)->IsTransformable(), + "The element doesn't support nsIDOMSVGTransformable"); + + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGGFrame::GetType() const +{ + return nsGkAtoms::svgGFrame; +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +void +nsSVGGFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + if (aFlags & TRANSFORM_CHANGED) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + } + + nsSVGDisplayContainerFrame::NotifySVGChanged(aFlags); +} + +gfxMatrix +nsSVGGFrame::GetCanvasTM() +{ + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + + nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); + SVGGraphicsElement *content = static_cast<SVGGraphicsElement*>(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = new gfxMatrix(tm); + } + return *mCanvasTM; +} + +nsresult +nsSVGGFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::transform) { + // We don't invalidate for transform changes (the layers code does that). + // Also note that SVGTransformableElement::GetAttributeChangeHint will + // return nsChangeHint_UpdateOverflow for "transform" attribute changes + // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. + NotifySVGChanged(TRANSFORM_CHANGED); + } + + return NS_OK; +} diff --git a/layout/svg/nsSVGGFrame.h b/layout/svg/nsSVGGFrame.h new file mode 100644 index 0000000000..2f10451d52 --- /dev/null +++ b/layout/svg/nsSVGGFrame.h @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +#ifndef NSSVGGFRAME_H +#define NSSVGGFRAME_H + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "nsAutoPtr.h" +#include "nsSVGContainerFrame.h" + +class nsSVGGFrame : public nsSVGDisplayContainerFrame +{ + friend nsIFrame* + NS_NewSVGGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGGFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) {} + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgGFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGG"), aResult); + } +#endif + + // nsIFrame interface: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + // nsISVGChildFrame interface: + virtual void NotifySVGChanged(uint32_t aFlags) override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + nsAutoPtr<gfxMatrix> mCanvasTM; +}; + +#endif diff --git a/layout/svg/nsSVGGenericContainerFrame.cpp b/layout/svg/nsSVGGenericContainerFrame.cpp new file mode 100644 index 0000000000..1bff8bad92 --- /dev/null +++ b/layout/svg/nsSVGGenericContainerFrame.cpp @@ -0,0 +1,54 @@ +/* -*- 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 "nsSVGGenericContainerFrame.h" +#include "nsSVGIntegrationUtils.h" + +//---------------------------------------------------------------------- +// nsSVGGenericContainerFrame Implementation + +nsIFrame* +NS_NewSVGGenericContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGGenericContainerFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGGenericContainerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods + +nsresult +nsSVGGenericContainerFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ +#ifdef DEBUG + nsAutoString str; + aAttribute->ToString(str); + printf("** nsSVGGenericContainerFrame::AttributeChanged(%s)\n", + NS_LossyConvertUTF16toASCII(str).get()); +#endif + + return NS_OK; +} + +nsIAtom * +nsSVGGenericContainerFrame::GetType() const +{ + return nsGkAtoms::svgGenericContainerFrame; +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +gfxMatrix +nsSVGGenericContainerFrame::GetCanvasTM() +{ + NS_ASSERTION(GetParent(), "null parent"); + + return static_cast<nsSVGContainerFrame*>(GetParent())->GetCanvasTM(); +} diff --git a/layout/svg/nsSVGGenericContainerFrame.h b/layout/svg/nsSVGGenericContainerFrame.h new file mode 100644 index 0000000000..eff7375baf --- /dev/null +++ b/layout/svg/nsSVGGenericContainerFrame.h @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGGENERICCONTAINERFRAME_H__ +#define __NS_SVGGENERICCONTAINERFRAME_H__ + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "nsFrame.h" +#include "nsLiteralString.h" +#include "nsQueryFrame.h" +#include "nsSVGContainerFrame.h" + +class nsIAtom; +class nsIFrame; +class nsIPresShell; +class nsStyleContext; + +class nsSVGGenericContainerFrame : public nsSVGDisplayContainerFrame +{ + friend nsIFrame* + NS_NewSVGGenericContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +protected: + explicit nsSVGGenericContainerFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) {} + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgGenericContainerFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGGenericContainer"), aResult); + } +#endif + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; +}; + +#endif // __NS_SVGGENERICCONTAINERFRAME_H__ diff --git a/layout/svg/nsSVGGradientFrame.cpp b/layout/svg/nsSVGGradientFrame.cpp new file mode 100644 index 0000000000..217ab8c4a4 --- /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) diff --git a/layout/svg/nsSVGGradientFrame.h b/layout/svg/nsSVGGradientFrame.h new file mode 100644 index 0000000000..f12b132533 --- /dev/null +++ b/layout/svg/nsSVGGradientFrame.h @@ -0,0 +1,213 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGGRADIENTFRAME_H__ +#define __NS_SVGGRADIENTFRAME_H__ + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "nsCOMPtr.h" +#include "nsFrame.h" +#include "nsLiteralString.h" +#include "nsSVGPaintServerFrame.h" + +class gfxPattern; +class nsIAtom; +class nsIContent; +class nsIFrame; +class nsIPresShell; +class nsStyleContext; + +struct gfxRect; + +namespace mozilla { +class nsSVGAnimatedTransformList; + +namespace dom { +class SVGLinearGradientElement; +class SVGRadialGradientElement; +} // namespace dom +} // namespace mozilla + +/** + * Gradients can refer to other gradients. We create an nsSVGPaintingProperty + * with property type nsGkAtoms::href to track the referenced gradient. + */ +class nsSVGGradientFrame : public nsSVGPaintServerFrame +{ + typedef mozilla::gfx::ExtendMode ExtendMode; + +protected: + explicit nsSVGGradientFrame(nsStyleContext* aContext); + +public: + NS_DECL_ABSTRACT_FRAME(nsSVGGradientFrame) + + // nsSVGPaintServerFrame methods: + virtual already_AddRefed<gfxPattern> + GetPaintServerPattern(nsIFrame* aSource, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, + const gfxRect* aOverrideBounds) override; + + // nsIFrame interface: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGGradient"), aResult); + } +#endif // DEBUG + +private: + + // Parse our xlink:href and set up our nsSVGPaintingProperty if we + // reference another gradient and we don't have a property. Return + // the referenced gradient's frame if available, null otherwise. + nsSVGGradientFrame* GetReferencedGradient(); + + // Optionally get a stop frame (returns stop index/count) + void GetStopFrames(nsTArray<nsIFrame*>* aStopFrames); + + const mozilla::nsSVGAnimatedTransformList* GetGradientTransformList( + nsIContent* aDefault); + // Will be singular for gradientUnits="objectBoundingBox" with an empty bbox. + gfxMatrix GetGradientTransform(nsIFrame *aSource, + const gfxRect *aOverrideBounds); + +protected: + virtual bool GradientVectorLengthIsZero() = 0; + virtual already_AddRefed<gfxPattern> CreateGradient() = 0; + + // Internal methods for handling referenced gradients + class AutoGradientReferencer; + nsSVGGradientFrame* GetReferencedGradientIfNotInUse(); + + // Accessors to lookup gradient attributes + uint16_t GetEnumValue(uint32_t aIndex, nsIContent *aDefault); + uint16_t GetEnumValue(uint32_t aIndex) + { + return GetEnumValue(aIndex, mContent); + } + uint16_t GetGradientUnits(); + uint16_t GetSpreadMethod(); + + // Gradient-type-specific lookups since the length values differ between + // linear and radial gradients + virtual mozilla::dom::SVGLinearGradientElement * GetLinearGradientWithLength( + uint32_t aIndex, mozilla::dom::SVGLinearGradientElement* aDefault); + virtual mozilla::dom::SVGRadialGradientElement * GetRadialGradientWithLength( + uint32_t aIndex, mozilla::dom::SVGRadialGradientElement* aDefault); + + // The frame our gradient is (currently) being applied to + nsIFrame* mSource; + +private: + // Flag to mark this frame as "in use" during recursive calls along our + // gradient's reference chain so we can detect reference loops. See: + // http://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute + bool mLoopFlag; + // Gradients often don't reference other gradients, so here we cache + // the fact that that isn't happening. + bool mNoHRefURI; +}; + + +// ------------------------------------------------------------------------- +// Linear Gradients +// ------------------------------------------------------------------------- + +class nsSVGLinearGradientFrame : public nsSVGGradientFrame +{ + friend nsIFrame* NS_NewSVGLinearGradientFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); +protected: + explicit nsSVGLinearGradientFrame(nsStyleContext* aContext) + : nsSVGGradientFrame(aContext) {} + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame interface: +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual nsIAtom* GetType() const override; // frame type: nsGkAtoms::svgLinearGradientFrame + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGLinearGradient"), aResult); + } +#endif // DEBUG + +protected: + float GetLengthValue(uint32_t aIndex); + virtual mozilla::dom::SVGLinearGradientElement* GetLinearGradientWithLength( + uint32_t aIndex, mozilla::dom::SVGLinearGradientElement* aDefault) override; + virtual bool GradientVectorLengthIsZero() override; + virtual already_AddRefed<gfxPattern> CreateGradient() override; +}; + +// ------------------------------------------------------------------------- +// Radial Gradients +// ------------------------------------------------------------------------- + +class nsSVGRadialGradientFrame : public nsSVGGradientFrame +{ + friend nsIFrame* NS_NewSVGRadialGradientFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); +protected: + explicit nsSVGRadialGradientFrame(nsStyleContext* aContext) + : nsSVGGradientFrame(aContext) {} + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame interface: +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual nsIAtom* GetType() const override; // frame type: nsGkAtoms::svgRadialGradientFrame + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGRadialGradient"), aResult); + } +#endif // DEBUG + +protected: + float GetLengthValue(uint32_t aIndex); + float GetLengthValue(uint32_t aIndex, float aDefaultValue); + float GetLengthValueFromElement(uint32_t aIndex, + mozilla::dom::SVGRadialGradientElement& aElement); + virtual mozilla::dom::SVGRadialGradientElement* GetRadialGradientWithLength( + uint32_t aIndex, mozilla::dom::SVGRadialGradientElement* aDefault) override; + virtual bool GradientVectorLengthIsZero() override; + virtual already_AddRefed<gfxPattern> CreateGradient() override; +}; + +#endif // __NS_SVGGRADIENTFRAME_H__ + diff --git a/layout/svg/nsSVGImageFrame.cpp b/layout/svg/nsSVGImageFrame.cpp new file mode 100644 index 0000000000..c0a7f9419b --- /dev/null +++ b/layout/svg/nsSVGImageFrame.cpp @@ -0,0 +1,667 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "imgIContainer.h" +#include "nsContainerFrame.h" +#include "nsIImageLoadingContent.h" +#include "nsLayoutUtils.h" +#include "imgINotificationObserver.h" +#include "nsSVGEffects.h" +#include "nsSVGPathGeometryFrame.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsSVGUtils.h" +#include "SVGContentUtils.h" +#include "SVGImageContext.h" +#include "mozilla/dom/SVGImageElement.h" +#include "nsContentUtils.h" +#include "nsIReflowCallback.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +class nsSVGImageFrame; + +class nsSVGImageListener final : public imgINotificationObserver +{ +public: + explicit nsSVGImageListener(nsSVGImageFrame *aFrame); + + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + + void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; } + +private: + ~nsSVGImageListener() {} + + nsSVGImageFrame *mFrame; +}; + +class nsSVGImageFrame : public nsSVGPathGeometryFrame + , public nsIReflowCallback +{ + friend nsIFrame* + NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +protected: + explicit nsSVGImageFrame(nsStyleContext* aContext) + : nsSVGPathGeometryFrame(aContext) + , mReflowCallbackPosted(false) + { + EnableVisibilityTracking(); + } + + virtual ~nsSVGImageFrame(); + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsISVGChildFrame interface: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) override; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + virtual void ReflowSVG() override; + + // nsSVGPathGeometryFrame methods: + virtual uint16_t GetHitTestFlags() override; + + // nsIFrame interface: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + void OnVisibilityChange(Visibility aNewVisibility, + Maybe<OnNonvisible> aNonvisibleAction = Nothing()) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgImageFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGImage"), aResult); + } +#endif + + // nsIReflowCallback + virtual bool ReflowFinished() override; + virtual void ReflowCallbackCanceled() override; + +private: + gfx::Matrix GetRasterImageTransform(int32_t aNativeWidth, + int32_t aNativeHeight); + gfx::Matrix GetVectorImageTransform(); + bool TransformContextForPainting(gfxContext* aGfxContext, + const gfxMatrix& aTransform); + + nsCOMPtr<imgINotificationObserver> mListener; + + nsCOMPtr<imgIContainer> mImageContainer; + + bool mReflowCallbackPosted; + + friend class nsSVGImageListener; +}; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGImageFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGImageFrame) + +nsSVGImageFrame::~nsSVGImageFrame() +{ + // set the frame to null so we don't send messages to a dead object. + if (mListener) { + nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); + if (imageLoader) { + imageLoader->RemoveObserver(mListener); + } + reinterpret_cast<nsSVGImageListener*>(mListener.get())->SetFrame(nullptr); + } + mListener = nullptr; +} + +void +nsSVGImageFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image), + "Content is not an SVG image!"); + + nsSVGPathGeometryFrame::Init(aContent, aParent, aPrevInFlow); + + if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + // Non-display frames are likely to be patterns, masks or the like. + // Treat them as always visible. + IncApproximateVisibleCount(); + } + + mListener = new nsSVGImageListener(this); + nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); + if (!imageLoader) { + NS_RUNTIMEABORT("Why is this not an image loading content?"); + } + + // We should have a PresContext now, so let's notify our image loader that + // we need to register any image animations with the refresh driver. + imageLoader->FrameCreated(this); + + imageLoader->AddObserver(mListener); +} + +/* virtual */ void +nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + DecApproximateVisibleCount(); + } + + if (mReflowCallbackPosted) { + PresContext()->PresShell()->CancelReflowCallback(this); + mReflowCallbackPosted = false; + } + + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsFrame::mContent); + + if (imageLoader) { + imageLoader->FrameDestroyed(this); + } + + nsFrame::DestroyFrom(aDestructRoot); +} + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult +nsSVGImageFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + return NS_OK; + } + else if (aAttribute == nsGkAtoms::preserveAspectRatio) { + // We don't paint the content of the image using display lists, therefore + // we have to invalidate for this children-only transform changes since + // there is no layer tree to notice that the transform changed and + // recomposite. + InvalidateFrame(); + return NS_OK; + } + } + if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + SVGImageElement *element = static_cast<SVGImageElement*>(mContent); + + bool hrefIsSet = + element->mStringAttributes[SVGImageElement::HREF].IsExplicitlySet() || + element->mStringAttributes[SVGImageElement::XLINK_HREF].IsExplicitlySet(); + if (hrefIsSet) { + element->LoadSVGImage(true, true); + } else { + element->CancelImageRequests(true); + } + } + + return nsSVGPathGeometryFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +void +nsSVGImageFrame::OnVisibilityChange(Visibility aNewVisibility, + Maybe<OnNonvisible> aNonvisibleAction) +{ + nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); + if (!imageLoader) { + nsSVGPathGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); + return; + } + + imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); + + nsSVGPathGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); +} + +gfx::Matrix +nsSVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth, + int32_t aNativeHeight) +{ + float x, y, width, height; + SVGImageElement *element = static_cast<SVGImageElement*>(mContent); + element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + Matrix viewBoxTM = + SVGContentUtils::GetViewBoxTransform(width, height, + 0, 0, aNativeWidth, aNativeHeight, + element->mPreserveAspectRatio); + + return viewBoxTM * gfx::Matrix::Translation(x, y); +} + +gfx::Matrix +nsSVGImageFrame::GetVectorImageTransform() +{ + float x, y, width, height; + SVGImageElement *element = static_cast<SVGImageElement*>(mContent); + element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + // No viewBoxTM needed here -- our height/width overrides any concept of + // "native size" that the SVG image has, and it will handle viewBox and + // preserveAspectRatio on its own once we give it a region to draw into. + + return gfx::Matrix::Translation(x, y); +} + +bool +nsSVGImageFrame::TransformContextForPainting(gfxContext* aGfxContext, + const gfxMatrix& aTransform) +{ + gfx::Matrix imageTransform; + if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { + imageTransform = GetVectorImageTransform() * ToMatrix(aTransform); + } else { + int32_t nativeWidth, nativeHeight; + if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) || + NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) || + nativeWidth == 0 || nativeHeight == 0) { + return false; + } + imageTransform = + GetRasterImageTransform(nativeWidth, nativeHeight) * ToMatrix(aTransform); + + // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else + // it'll get applied an extra time by DrawSingleUnscaledImage. + nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); + gfxFloat pageZoomFactor = + nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx); + imageTransform.PreScale(pageZoomFactor, pageZoomFactor); + } + + if (imageTransform.IsSingular()) { + return false; + } + + aGfxContext->Multiply(ThebesMatrix(imageTransform)); + return true; +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods: +DrawResult +nsSVGImageFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) +{ + if (!StyleVisibility()->IsVisible()) + return DrawResult::SUCCESS; + + float x, y, width, height; + SVGImageElement *imgElem = static_cast<SVGImageElement*>(mContent); + imgElem->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + NS_ASSERTION(width > 0 && height > 0, + "Should only be painting things with valid width/height"); + + if (!mImageContainer) { + nsCOMPtr<imgIRequest> currentRequest; + nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); + if (imageLoader) + imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + + if (currentRequest) + currentRequest->GetImage(getter_AddRefs(mImageContainer)); + } + + DrawResult result = DrawResult::SUCCESS; + if (mImageContainer) { + gfxContextAutoSaveRestore autoRestorer(&aContext); + + if (StyleDisplay()->IsScrollableOverflow()) { + gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, x, y, + width, height); + nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect); + } + + if (!TransformContextForPainting(&aContext, aTransform)) { + return DrawResult::SUCCESS; + } + + // fill-opacity doesn't affect <image>, so if we're allowed to + // optimize group opacity, the opacity used for compositing the + // image into the current canvas is just the group opacity. + float opacity = 1.0f; + if (nsSVGUtils::CanOptimizeOpacity(this)) { + opacity = StyleEffects()->mOpacity; + } + + if (opacity != 1.0f || StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + aContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity); + } + + nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); + nsRect dirtyRect; // only used if aDirtyRect is non-null + if (aDirtyRect) { + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "Display lists handle dirty rect intersection test"); + dirtyRect = ToAppUnits(*aDirtyRect, appUnitsPerDevPx); + // Adjust dirtyRect to match our local coordinate system. + nsRect rootRect = + nsSVGUtils::TransformFrameRectToOuterSVG(mRect, aTransform, + PresContext()); + dirtyRect.MoveBy(-rootRect.TopLeft()); + } + + // XXXbholley - I don't think huge images in SVGs are common enough to + // warrant worrying about the responsiveness impact of doing synchronous + // decodes. The extra code complexity of determinining when we want to + // force sync probably just isn't worth it, so always pass FLAG_SYNC_DECODE + uint32_t drawFlags = imgIContainer::FLAG_SYNC_DECODE; + + if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { + // Package up the attributes of this image element which can override the + // attributes of mImageContainer's internal SVG document. The 'width' & + // 'height' values we're passing in here are in CSS units (though they + // come from width/height *attributes* in SVG). They influence the region + // of the SVG image's internal document that is visible, in combination + // with preserveAspectRatio and viewBox. + SVGImageContext context(CSSIntSize::Truncate(width, height), + Some(imgElem->mPreserveAspectRatio.GetAnimValue()), + 1.0, true); + + // For the actual draw operation to draw crisply (and at the right size), + // our destination rect needs to be |width|x|height|, *in dev pixels*. + LayoutDeviceSize devPxSize(width, height); + nsRect destRect(nsPoint(), + LayoutDevicePixel::ToAppUnits(devPxSize, + appUnitsPerDevPx)); + + // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case. + // That method needs our image to have a fixed native width & height, + // and that's not always true for TYPE_VECTOR images. + result = nsLayoutUtils::DrawSingleImage( + aContext, + PresContext(), + mImageContainer, + nsLayoutUtils::GetSamplingFilterForFrame(this), + destRect, + aDirtyRect ? dirtyRect : destRect, + &context, + drawFlags); + } else { // mImageContainer->GetType() == TYPE_RASTER + result = nsLayoutUtils::DrawSingleUnscaledImage( + aContext, + PresContext(), + mImageContainer, + nsLayoutUtils::GetSamplingFilterForFrame(this), + nsPoint(0, 0), + aDirtyRect ? &dirtyRect : nullptr, + drawFlags); + } + + if (opacity != 1.0f || StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + aContext.PopGroupAndBlend(); + } + // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext + } + + return result; +} + +nsIFrame* +nsSVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + if (!(GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) && !GetHitTestFlags()) { + return nullptr; + } + + Rect rect; + SVGImageElement *element = static_cast<SVGImageElement*>(mContent); + element->GetAnimatedLengthValues(&rect.x, &rect.y, + &rect.width, &rect.height, nullptr); + + if (!rect.Contains(ToPoint(aPoint))) { + return nullptr; + } + + // Special case for raster images -- we only want to accept points that fall + // in the underlying image's (scaled to fit) native bounds. That region + // doesn't necessarily map to our <image> element's [x,y,width,height] if the + // raster image's aspect ratio is being preserved. We have to look up the + // native image size & our viewBox transform in order to filter out points + // that fall outside that area. (This special case doesn't apply to vector + // images because they don't limit their drawing to explicit "native + // bounds" -- they have an infinite canvas on which to place content.) + if (StyleDisplay()->IsScrollableOverflow() && mImageContainer) { + if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) { + int32_t nativeWidth, nativeHeight; + if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) || + NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) || + nativeWidth == 0 || nativeHeight == 0) { + return nullptr; + } + Matrix viewBoxTM = + SVGContentUtils::GetViewBoxTransform(rect.width, rect.height, + 0, 0, nativeWidth, nativeHeight, + element->mPreserveAspectRatio); + if (!nsSVGUtils::HitTestRect(viewBoxTM, + 0, 0, nativeWidth, nativeHeight, + aPoint.x - rect.x, aPoint.y - rect.y)) { + return nullptr; + } + } + } + + return this; +} + +nsIAtom * +nsSVGImageFrame::GetType() const +{ + return nsGkAtoms::svgImageFrame; +} + +//---------------------------------------------------------------------- +// nsSVGPathGeometryFrame methods: + +// Lie about our fill/stroke so that covered region and hit detection work properly + +void +nsSVGImageFrame::ReflowSVG() +{ + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!nsSVGUtils::NeedsReflowSVG(this)) { + return; + } + + float x, y, width, height; + SVGImageElement *element = static_cast<SVGImageElement*>(mContent); + element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + Rect extent(x, y, width, height); + + if (!extent.IsEmpty()) { + mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, + PresContext()->AppUnitsPerCSSPixel()); + } else { + mRect.SetEmpty(); + } + + if (mState & NS_FRAME_FIRST_REFLOW) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + nsSVGEffects::UpdateEffects(this); + + if (!mReflowCallbackPosted) { + nsIPresShell* shell = PresContext()->PresShell(); + mReflowCallbackPosted = true; + shell->PostReflowCallback(this); + } + } + + nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); + nsOverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); + + // Invalidate, but only if this is not our first reflow (since if it is our + // first reflow then we haven't had our first paint yet). + if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { + InvalidateFrame(); + } +} + +bool +nsSVGImageFrame::ReflowFinished() +{ + mReflowCallbackPosted = false; + + // XXX(seth): We don't need this. The purpose of updating visibility + // synchronously is to ensure that animated images start animating + // immediately. In the short term, however, + // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that + // animations start as soon as the image is painted for the first time, and in + // the long term we want to update visibility information from the display + // list whenever we paint, so we don't actually need to do this. However, to + // avoid behavior changes during the transition from the old image visibility + // code, we'll leave it in for now. + UpdateVisibilitySynchronously(); + + return false; +} + +void +nsSVGImageFrame::ReflowCallbackCanceled() +{ + mReflowCallbackPosted = false; +} + +uint16_t +nsSVGImageFrame::GetHitTestFlags() +{ + uint16_t flags = 0; + + switch (StyleUserInterface()->mPointerEvents) { + case NS_STYLE_POINTER_EVENTS_NONE: + break; + case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: + case NS_STYLE_POINTER_EVENTS_AUTO: + if (StyleVisibility()->IsVisible()) { + /* XXX: should check pixel transparency */ + flags |= SVG_HIT_TEST_FILL; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: + case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: + case NS_STYLE_POINTER_EVENTS_VISIBLE: + if (StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL; + } + break; + case NS_STYLE_POINTER_EVENTS_PAINTED: + /* XXX: should check pixel transparency */ + flags |= SVG_HIT_TEST_FILL; + break; + case NS_STYLE_POINTER_EVENTS_FILL: + case NS_STYLE_POINTER_EVENTS_STROKE: + case NS_STYLE_POINTER_EVENTS_ALL: + flags |= SVG_HIT_TEST_FILL; + break; + default: + NS_ERROR("not reached"); + break; + } + + return flags; +} + +//---------------------------------------------------------------------- +// nsSVGImageListener implementation + +NS_IMPL_ISUPPORTS(nsSVGImageListener, imgINotificationObserver) + +nsSVGImageListener::nsSVGImageListener(nsSVGImageFrame *aFrame) : mFrame(aFrame) +{ +} + +NS_IMETHODIMP +nsSVGImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) +{ + if (!mFrame) + return NS_ERROR_FAILURE; + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + mFrame->InvalidateFrame(); + nsLayoutUtils::PostRestyleEvent( + mFrame->GetContent()->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(mFrame); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + // No new dimensions, so we don't need to call + // nsSVGUtils::InvalidateAndScheduleBoundsUpdate. + nsLayoutUtils::PostRestyleEvent( + mFrame->GetContent()->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + mFrame->InvalidateFrame(); + } + + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + // Called once the resource's dimensions have been obtained. + aRequest->GetImage(getter_AddRefs(mFrame->mImageContainer)); + mFrame->InvalidateFrame(); + nsLayoutUtils::PostRestyleEvent( + mFrame->GetContent()->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(mFrame); + } + + return NS_OK; +} + diff --git a/layout/svg/nsSVGInnerSVGFrame.cpp b/layout/svg/nsSVGInnerSVGFrame.cpp new file mode 100644 index 0000000000..8b5750d167 --- /dev/null +++ b/layout/svg/nsSVGInnerSVGFrame.cpp @@ -0,0 +1,322 @@ +/* -*- 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 "nsSVGInnerSVGFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "nsIFrame.h" +#include "nsISVGChildFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGIntegrationUtils.h" +#include "mozilla/dom/SVGSVGElement.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +nsIFrame* +NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGInnerSVGFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGInnerSVGFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods + +NS_QUERYFRAME_HEAD(nsSVGInnerSVGFrame) + NS_QUERYFRAME_ENTRY(nsSVGInnerSVGFrame) + NS_QUERYFRAME_ENTRY(nsISVGSVGFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame) + +#ifdef DEBUG +void +nsSVGInnerSVGFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), + "Content is not an SVG 'svg' element!"); + + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGInnerSVGFrame::GetType() const +{ + return nsGkAtoms::svgInnerSVGFrame; +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +DrawResult +nsSVGInnerSVGFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) +{ + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + gfxContextAutoSaveRestore autoSR; + + if (StyleDisplay()->IsScrollableOverflow()) { + float x, y, width, height; + static_cast<SVGSVGElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + if (width <= 0 || height <= 0) { + return DrawResult::SUCCESS; + } + + autoSR.SetContext(&aContext); + gfxRect clipRect = + nsSVGUtils::GetClipRectForFrame(this, x, y, width, height); + nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect); + } + + return nsSVGDisplayContainerFrame::PaintSVG(aContext, aTransform, aDirtyRect); +} + +nsRect +nsSVGInnerSVGFrame::GetCoveredRegion() +{ + float x, y, w, h; + static_cast<SVGSVGElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); + if (w < 0.0f) w = 0.0f; + if (h < 0.0f) h = 0.0f; + // GetCanvasTM includes the x,y translation + nsRect bounds = nsSVGUtils::ToCanvasBounds(gfxRect(0.0, 0.0, w, h), + GetCanvasTM(), + PresContext()); + + if (!StyleDisplay()->IsScrollableOverflow()) { + bounds.UnionRect(bounds, nsSVGUtils::GetCoveredRegion(mFrames)); + } + return bounds; +} + +void +nsSVGInnerSVGFrame::ReflowSVG() +{ + // mRect must be set before FinishAndStoreOverflow is called in order + // for our overflow areas to be clipped correctly. + float x, y, width, height; + static_cast<SVGSVGElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + mRect = nsLayoutUtils::RoundGfxRectToAppRect( + gfxRect(x, y, width, height), + PresContext()->AppUnitsPerCSSPixel()); + + // If we have a filter, we need to invalidate ourselves because filter + // output can change even if none of our descendants need repainting. + if (StyleEffects()->HasFilters()) { + InvalidateFrame(); + } + + nsSVGDisplayContainerFrame::ReflowSVG(); +} + +void +nsSVGInnerSVGFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + if (aFlags & COORD_CONTEXT_CHANGED) { + + SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent); + + bool xOrYIsPercentage = + svg->mLengthAttributes[SVGSVGElement::ATTR_X].IsPercentage() || + svg->mLengthAttributes[SVGSVGElement::ATTR_Y].IsPercentage(); + bool widthOrHeightIsPercentage = + svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH].IsPercentage() || + svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT].IsPercentage(); + + if (xOrYIsPercentage || widthOrHeightIsPercentage) { + // Ancestor changes can't affect how we render from the perspective of + // any rendering observers that we may have, so we don't need to + // invalidate them. We also don't need to invalidate ourself, since our + // changed ancestor will have invalidated its entire area, which includes + // our area. + // For perf reasons we call this before calling NotifySVGChanged() below. + nsSVGUtils::ScheduleReflowSVG(this); + } + + // Coordinate context changes affect mCanvasTM if we have a + // percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND + // a 'viewBox'. + + if (!(aFlags & TRANSFORM_CHANGED) && + (xOrYIsPercentage || + (widthOrHeightIsPercentage && svg->HasViewBoxRect()))) { + aFlags |= TRANSFORM_CHANGED; + } + + if (svg->HasViewBoxRect() || !widthOrHeightIsPercentage) { + // Remove COORD_CONTEXT_CHANGED, since we establish the coordinate + // context for our descendants and this notification won't change its + // dimensions: + aFlags &= ~COORD_CONTEXT_CHANGED; + + if (!aFlags) { + return; // No notification flags left + } + } + } + + if (aFlags & TRANSFORM_CHANGED) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + } + + nsSVGDisplayContainerFrame::NotifySVGChanged(aFlags); +} + +nsresult +nsSVGInnerSVGFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + !(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + + SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent); + + if (aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + + if (content->HasViewBoxOrSyntheticViewBox()) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + content->ChildrenOnlyTransformChanged(); + nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED); + } else { + uint32_t flags = COORD_CONTEXT_CHANGED; + if (mCanvasTM && mCanvasTM->IsSingular()) { + mCanvasTM = nullptr; + flags |= TRANSFORM_CHANGED; + } + nsSVGUtils::NotifyChildrenOfSVGChange(this, flags); + } + + } else if (aAttribute == nsGkAtoms::transform || + aAttribute == nsGkAtoms::preserveAspectRatio || + aAttribute == nsGkAtoms::viewBox || + aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + + nsSVGUtils::NotifyChildrenOfSVGChange( + this, aAttribute == nsGkAtoms::viewBox ? + TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED); + + // We don't invalidate for transform changes (the layers code does that). + // Also note that SVGTransformableElement::GetAttributeChangeHint will + // return nsChangeHint_UpdateOverflow for "transform" attribute changes + // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. + + if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + } else if (aAttribute == nsGkAtoms::viewBox || + (aAttribute == nsGkAtoms::preserveAspectRatio && + content->HasViewBoxOrSyntheticViewBox())) { + content->ChildrenOnlyTransformChanged(); + // SchedulePaint sets a global state flag so we only need to call it once + // (on ourself is fine), not once on each child (despite bug 828240). + SchedulePaint(); + } + } + } + + return NS_OK; +} + +nsIFrame* +nsSVGInnerSVGFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only hit-testing of non-display " + "SVG should take this code path"); + + if (StyleDisplay()->IsScrollableOverflow()) { + Rect clip; + static_cast<nsSVGElement*>(mContent)-> + GetAnimatedLengthValues(&clip.x, &clip.y, + &clip.width, &clip.height, nullptr); + if (!clip.Contains(ToPoint(aPoint))) { + return nullptr; + } + } + + return nsSVGDisplayContainerFrame::GetFrameForPoint(aPoint); +} + +//---------------------------------------------------------------------- +// nsISVGSVGFrame methods: + +void +nsSVGInnerSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) +{ + // The dimensions of inner-<svg> frames are purely defined by their "width" + // and "height" attributes, and transform changes can only occur as a result + // of changes to their "width", "height", "viewBox" or "preserveAspectRatio" + // attributes. Changes to all of these attributes are handled in + // AttributeChanged(), so we should never be called. + NS_ERROR("Not called for nsSVGInnerSVGFrame"); +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +gfxMatrix +nsSVGInnerSVGFrame::GetCanvasTM() +{ + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + + nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = new gfxMatrix(tm); + } + return *mCanvasTM; +} + +bool +nsSVGInnerSVGFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const +{ + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + + if (content->HasViewBoxOrSyntheticViewBox()) { + // XXX Maybe return false if the transform is the identity transform? + if (aTransform) { + *aTransform = content->GetViewBoxTransform(); + } + return true; + } + return false; +} diff --git a/layout/svg/nsSVGInnerSVGFrame.h b/layout/svg/nsSVGInnerSVGFrame.h new file mode 100644 index 0000000000..5675509c19 --- /dev/null +++ b/layout/svg/nsSVGInnerSVGFrame.h @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGINNERSVGFRAME_H__ +#define __NS_SVGINNERSVGFRAME_H__ + +#include "mozilla/Attributes.h" +#include "nsAutoPtr.h" +#include "nsSVGContainerFrame.h" +#include "nsISVGSVGFrame.h" + +class gfxContext; + +class nsSVGInnerSVGFrame : public nsSVGDisplayContainerFrame + , public nsISVGSVGFrame +{ + friend nsIFrame* + NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGInnerSVGFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) {} + +public: + NS_DECL_QUERYFRAME_TARGET(nsSVGInnerSVGFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgInnerSVGFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGInnerSVG"), aResult); + } +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + // nsISVGChildFrame interface: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect = nullptr) override; + virtual nsRect GetCoveredRegion() override; + virtual void ReflowSVG() override; + virtual void NotifySVGChanged(uint32_t aFlags) override; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override; + + // nsISVGSVGFrame interface: + virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) override; + +protected: + + nsAutoPtr<gfxMatrix> mCanvasTM; +}; + +#endif + diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp new file mode 100644 index 0000000000..498f693937 --- /dev/null +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -0,0 +1,1142 @@ +/* -*- 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 "nsSVGIntegrationUtils.h" + +// Keep others in (case-insensitive) order: +#include "gfxDrawable.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSClipPathInstance.h" +#include "nsDisplayList.h" +#include "nsFilterInstance.h" +#include "nsLayoutUtils.h" +#include "nsRenderingContext.h" +#include "nsSVGClipPathFrame.h" +#include "nsSVGEffects.h" +#include "nsSVGElement.h" +#include "nsSVGFilterPaintCallback.h" +#include "nsSVGMaskFrame.h" +#include "nsSVGPaintServerFrame.h" +#include "nsSVGUtils.h" +#include "FrameLayerBuilder.h" +#include "BasicLayers.h" +#include "mozilla/gfx/Point.h" +#include "nsCSSRendering.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::image; + +// ---------------------------------------------------------------------- + +/** + * This class is used to get the pre-effects visual overflow rect of a frame, + * or, in the case of a frame with continuations, to collect the union of the + * pre-effects visual overflow rects of all the continuations. The result is + * relative to the origin (top left corner of the border box) of the frame, or, + * if the frame has continuations, the origin of the _first_ continuation. + */ +class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback +{ +public: + /** + * If the pre-effects visual overflow rect of the frame being examined + * happens to be known, it can be passed in as aCurrentFrame and its + * pre-effects visual overflow rect can be passed in as + * aCurrentFrameOverflowArea. This is just an optimization to save a + * frame property lookup - these arguments are optional. + */ + PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation, + nsIFrame* aCurrentFrame, + const nsRect& aCurrentFrameOverflowArea) + : mFirstContinuation(aFirstContinuation) + , mCurrentFrame(aCurrentFrame) + , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea) + { + NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(), + "We want the first continuation here"); + } + + virtual void AddBox(nsIFrame* aFrame) override { + nsRect overflow = (aFrame == mCurrentFrame) ? + mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame); + mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation)); + } + + nsRect GetResult() const { + return mResult; + } + +private: + + static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) { + nsRect* r = aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty()); + if (r) { + return *r; + } + // Despite the fact that we're invoked for frames with SVG effects applied, + // we can actually get here. All continuations and IB split siblings of a + // frame with SVG effects applied will have the PreEffectsBBoxProperty + // property set on them. Therefore, the frames that are passed to us will + // always have that property set...well, with one exception. If the frames + // for an element with SVG effects applied have been subject to an "IB + // split", then the block frame(s) that caused the split will have been + // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type + // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous + // blocks will have the PreEffectsBBoxProperty property set on them, but + // they will never be passed to us. Instead, we'll be passed the block + // children that they wrap, which don't have the PreEffectsBBoxProperty + // property set on them. This is actually okay. What we care about is + // collecting the _pre_ effects visual overflow rects of the frames to + // which the SVG effects have been applied. Since the IB split results in + // any overflow rect adjustments for transforms, effects, etc. taking + // place on the anonymous block wrappers, the wrapped children are left + // with their overflow rects unaffected. In other words, calling + // GetVisualOverflowRect() on the children will return their pre-effects + // visual overflow rects, just as we need. + // + // A couple of tests that demonstrate the IB split and cause us to get here + // are: + // + // * reftests/svg/svg-integration/clipPath-html-06.xhtml + // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml + // + // If we ever got passed a frame with the PreTransformOverflowAreasProperty + // property set, that would be bad, since then our GetVisualOverflowRect() + // call would give us the post-effects, and post-transform, overflow rect. + // + NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() == + nsCSSAnonBoxes::mozAnonymousBlock, + "How did we getting here, then?"); + NS_ASSERTION(!aFrame->Properties().Get( + aFrame->PreTransformOverflowAreasProperty()), + "GetVisualOverflowRect() won't return the pre-effects rect!"); + return aFrame->GetVisualOverflowRect(); + } + + nsIFrame* mFirstContinuation; + nsIFrame* mCurrentFrame; + const nsRect& mCurrentFrameOverflowArea; + nsRect mResult; +}; + +/** + * Gets the union of the pre-effects visual overflow rects of all of a frame's + * continuations, in "user space". + */ +static nsRect +GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation, + nsIFrame* aCurrentFrame, + const nsRect& aCurrentFramePreEffectsOverflow, + const nsPoint& aFirstContinuationToUserSpace) +{ + NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), + "Need first continuation here"); + PreEffectsVisualOverflowCollector collector(aFirstContinuation, + aCurrentFrame, + aCurrentFramePreEffectsOverflow); + // Compute union of all overflow areas relative to aFirstContinuation: + nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector); + // Return the result in user space: + return collector.GetResult() + aFirstContinuationToUserSpace; +} + + +bool +nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) +{ + // Even when SVG display lists are disabled, returning true for SVG frames + // does not adversely affect any of our callers. Therefore we don't bother + // checking the SDL prefs here, since we don't know if we're being called for + // painting or hit-testing anyway. + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + return aFrame->StyleEffects()->HasFilters() || + style->HasClipPath() || + style->mMask.HasLayerWithImage(); +} + +bool +nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) +{ + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + return style->HasClipPath() || + style->mMask.HasLayerWithImage(); +} + +nsPoint +nsSVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) +{ + if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the + // covered region relative to the nsSVGOuterSVGFrame, which is absolutely + // not what we want. SVG frames are always in user space, so they have + // no offset adjustment to make. + return nsPoint(); + } + + // The GetAllInFlowRectsUnion() call gets the union of the frame border-box + // rects over all continuations, relative to the origin (top-left of the + // border box) of its second argument (here, aFrame, the first continuation). + return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft(); +} + +/* static */ nsSize +nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) +{ + NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG frames should not get here"); + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size(); +} + +/* static */ gfx::Size +nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame) +{ + NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG frames should not get here"); + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame); + nsPresContext* presContext = firstFrame->PresContext(); + return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width), + presContext->AppUnitsToFloatCSSPixels(r.height)); +} + +gfxRect +nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame) +{ + NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG frames should not get here"); + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + // 'r' is in "user space": + nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(), + GetOffsetToBoundingBox(firstFrame)); + return nsLayoutUtils::RectToGfxRect(r, + aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel()); +} + +// XXX Since we're called during reflow, this method is broken for frames with +// continuations. When we're called for a frame with continuations, we're +// called for each continuation in turn as it's reflowed. However, it isn't +// until the last continuation is reflowed that this method's +// GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will +// obtain valid border boxes for all the continuations. As a result, we'll +// end up returning bogus post-filter visual overflow rects for all the prior +// continuations. Unfortunately, by the time the last continuation is +// reflowed, it's too late to go back and set and propagate the overflow +// rects on the previous continuations. +// +// The reason that we need to pass an override bbox to +// GetPreEffectsVisualOverflowUnion rather than just letting it call into our +// GetSVGBBoxForNonSVGFrame method is because we get called by +// ComputeEffectsRect when it has been called with +// aStoreRectProperties set to false. In this case the pre-effects visual +// overflow rect that it has been passed may be different to that stored on +// aFrame, resulting in a different bbox. +// +// XXXjwatt The pre-effects visual overflow rect passed to +// ComputeEffectsRect won't include continuation overflows, so +// for frames with continuation the following filter analysis will likely end +// up being carried out with a bbox created as if the frame didn't have +// continuations. +// +// XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right +// for SVG frames, since for SVG frames the SVG spec defines the bbox to be +// something quite different to the pre-effects visual overflow rect. However, +// we're essentially calculating an invalidation area here, and using the +// pre-effects overflow rect will actually overestimate that area which, while +// being a bit wasteful, isn't otherwise a problem. +// +nsRect + nsSVGIntegrationUtils:: + ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame, + const nsRect& aPreEffectsOverflowRect) +{ + NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT), + "Don't call this on SVG child frames"); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + if (!effectProperties.HasValidFilter()) { + return aPreEffectsOverflowRect; + } + + // Create an override bbox - see comment above: + nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame); + // overrideBBox is in "user space", in _CSS_ pixels: + // XXX Why are we rounding out to pixel boundaries? We don't do that in + // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary. + gfxRect overrideBBox = + nsLayoutUtils::RectToGfxRect( + GetPreEffectsVisualOverflowUnion(firstFrame, aFrame, + aPreEffectsOverflowRect, + firstFrameToBoundingBox), + aFrame->PresContext()->AppUnitsPerCSSPixel()); + overrideBBox.RoundOut(); + + nsRect overflowRect = + nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox); + + // Return overflowRect relative to aFrame, rather than "user space": + return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox); +} + +nsIntRegion +nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, + const nsPoint& aToReferenceFrame, + const nsIntRegion& aInvalidRegion) +{ + if (aInvalidRegion.IsEmpty()) { + return nsIntRect(); + } + + // Don't bother calling GetEffectProperties; the filter property should + // already have been set up during reflow/ComputeFrameEffectsRect + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); + if (!prop || !prop->IsInObserverLists()) { + return aInvalidRegion; + } + + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + if (!prop || !prop->ReferencesValidResources()) { + // The frame is either not there or not currently available, + // perhaps because we're in the middle of tearing stuff down. + // Be conservative, return our visual overflow rect relative + // to the reference frame. + nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame; + return overflow.ToOutsidePixels(appUnitsPerDevPixel); + } + + // Convert aInvalidRegion into bounding box frame space in app units: + nsPoint toBoundingBox = + aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); + // The initial rect was relative to the reference frame, so we need to + // remove that offset to get a rect relative to the current frame. + toBoundingBox -= aToReferenceFrame; + nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox); + + // Adjust the dirty area for effects, and shift it back to being relative to + // the reference frame. + nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame, + preEffectsRegion).MovedBy(-toBoundingBox); + // Return the result, in pixels relative to the reference frame. + return result.ToOutsidePixels(appUnitsPerDevPixel); +} + +nsRect +nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame, + const nsRect& aDirtyRect) +{ + // Don't bother calling GetEffectProperties; the filter property should + // already have been set up during reflow/ComputeFrameEffectsRect + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); + if (!prop || !prop->ReferencesValidResources()) { + return aDirtyRect; + } + + // Convert aDirtyRect into "user space" in app units: + nsPoint toUserSpace = + aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); + nsRect postEffectsRect = aDirtyRect + toUserSpace; + + // Return ther result, relative to aFrame, not in user space: + return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds() + - toUserSpace; +} + +bool +nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt) +{ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + // Convert aPt to user space: + nsPoint toUserSpace; + if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) { + // XXXmstange Isn't this wrong for svg:use and innerSVG frames? + toUserSpace = aFrame->GetPosition(); + } else { + toUserSpace = + aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); + } + nsPoint pt = aPt + toUserSpace; + gfxPoint userSpacePt = + gfxPoint(pt.x, pt.y) / aFrame->PresContext()->AppUnitsPerCSSPixel(); + return nsSVGUtils::HitTestClip(firstFrame, userSpacePt); +} + +class RegularFramePaintCallback : public nsSVGFilterPaintCallback +{ +public: + RegularFramePaintCallback(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + const nsPoint& aOffset) + : mBuilder(aBuilder), mLayerManager(aManager), + mOffset(aOffset) {} + + virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) override + { + BasicLayerManager* basic = mLayerManager->AsBasicLayerManager(); + basic->SetTarget(&aContext); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(-mOffset, + aTarget->PresContext()->AppUnitsPerDevPixel()); + + gfxContextMatrixAutoSaveRestore autoSR(&aContext); + aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset)); + + mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder); + return DrawResult::SUCCESS; + } + +private: + nsDisplayListBuilder* mBuilder; + LayerManager* mLayerManager; + nsPoint mOffset; +}; + +/** + * Returns true if any of the masks is an image mask (and not an SVG mask). + */ +static bool +HasNonSVGMask(const nsTArray<nsSVGMaskFrame*>& aMaskFrames) +{ + for (size_t i = 0; i < aMaskFrames.Length() ; i++) { + nsSVGMaskFrame *maskFrame = aMaskFrames[i]; + if (!maskFrame) { + return true; + } + } + + return false; +} + +typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams; + +/** + * Paint css-positioned-mask onto a given target(aMaskDT). + */ +static DrawResult +PaintMaskSurface(const PaintFramesParams& aParams, + DrawTarget* aMaskDT, float aOpacity, nsStyleContext* aSC, + const nsTArray<nsSVGMaskFrame*>& aMaskFrames, + const gfxMatrix& aMaskSurfaceMatrix, + const nsPoint& aOffsetToUserSpace) +{ + MOZ_ASSERT(aMaskFrames.Length() > 0); + MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8); + + const nsStyleSVGReset *svgReset = aSC->StyleSVGReset(); + gfxMatrix cssPxToDevPxMatrix = + nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame); + + nsPresContext* presContext = aParams.frame->PresContext(); + gfxPoint devPixelOffsetToUserSpace = + nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace, + presContext->AppUnitsPerDevPixel()); + + RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT); + MOZ_ASSERT(maskContext); + maskContext->SetMatrix(aMaskSurfaceMatrix); + + // Multiple SVG masks interleave with image mask. Paint each layer onto + // aMaskDT one at a time. + for (int i = aMaskFrames.Length() - 1; i >= 0 ; i--) { + nsSVGMaskFrame *maskFrame = aMaskFrames[i]; + + CompositionOp compositionOp = (i == int(aMaskFrames.Length() - 1)) + ? CompositionOp::OP_OVER + : nsCSSRendering::GetGFXCompositeMode(svgReset->mMask.mLayers[i].mComposite); + + // maskFrame != nullptr means we get a SVG mask. + // maskFrame == nullptr means we get an image mask. + if (maskFrame) { + Matrix svgMaskMatrix; + RefPtr<SourceSurface> svgMask = + maskFrame->GetMaskForMaskedFrame(maskContext, aParams.frame, + cssPxToDevPxMatrix, + aOpacity, + &svgMaskMatrix, + svgReset->mMask.mLayers[i].mMaskMode); + if (svgMask) { + gfxContextMatrixAutoSaveRestore matRestore(maskContext); + + maskContext->Multiply(ThebesMatrix(svgMaskMatrix)); + Rect drawRect = IntRectToRect(IntRect(IntPoint(0, 0), svgMask->GetSize())); + aMaskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask, + drawRect.TopLeft(), + DrawOptions(1.0, compositionOp)); + } + } else { + gfxContextMatrixAutoSaveRestore matRestore(maskContext); + + maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace)); + nsRenderingContext rc(maskContext); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer(*presContext, + rc, aParams.dirtyRect, + aParams.borderArea, + aParams.frame, + aParams.builder->GetBackgroundPaintFlags() | + nsCSSRendering::PAINTBG_MASK_IMAGE, + i, compositionOp); + + DrawResult result = + nsCSSRendering::PaintBackgroundWithSC(params, aSC, + *aParams.frame->StyleBorder()); + if (result != DrawResult::SUCCESS) { + return result; + } + } + } + + return DrawResult::SUCCESS; +} + +static DrawResult +CreateAndPaintMaskSurface(const PaintFramesParams& aParams, + float aOpacity, nsStyleContext* aSC, + const nsTArray<nsSVGMaskFrame*>& aMaskFrames, + const nsPoint& aOffsetToUserSpace, + Matrix& aOutMaskTransform, + RefPtr<SourceSurface>& aOutMaskSurface, + bool& aOpacityApplied) +{ + const nsStyleSVGReset *svgReset = aSC->StyleSVGReset(); + MOZ_ASSERT(aMaskFrames.Length() > 0); + + gfxContext& ctx = aParams.ctx; + + // There is only one SVG mask. + if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) { + gfxMatrix cssPxToDevPxMatrix = + nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame); + + aOpacityApplied = true; + aOutMaskSurface = + aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame, + cssPxToDevPxMatrix, aOpacity, + &aOutMaskTransform, + svgReset->mMask.mLayers[0].mMaskMode); + return DrawResult::SUCCESS; + } + + const IntRect& maskSurfaceRect = aParams.maskRect; + if (maskSurfaceRect.IsEmpty()) { + return DrawResult::SUCCESS; + } + + RefPtr<DrawTarget> maskDT = + ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(), + SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return DrawResult::TEMPORARY_ERROR; + } + + // Set aAppliedOpacity as true only if all mask layers are svg mask. + // In this case, we will apply opacity into the final mask surface, so the + // caller does not need to apply it again. + aOpacityApplied = !HasNonSVGMask(aMaskFrames); + + // Set context's matrix on maskContext, offset by the maskSurfaceRect's + // position. This makes sure that we combine the masks in device space. + gfxMatrix maskSurfaceMatrix = + ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft()); + + DrawResult result = PaintMaskSurface(aParams, maskDT, + aOpacityApplied ? aOpacity : 1.0, + aSC, aMaskFrames, maskSurfaceMatrix, + aOffsetToUserSpace); + if (result != DrawResult::SUCCESS) { + return result; + } + + aOutMaskTransform = ToMatrix(maskSurfaceMatrix); + if (!aOutMaskTransform.Invert()) { + return DrawResult::SUCCESS; + } + + aOutMaskSurface = maskDT->Snapshot(); + return DrawResult::SUCCESS; +} + +static bool +ValidateSVGFrame(nsIFrame* aFrame) +{ +#ifdef DEBUG + NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) || + (NS_SVGDisplayListPaintingEnabled() && + !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)), + "Should not use nsSVGIntegrationUtils on this SVG frame"); +#endif + + bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout) { +#ifdef DEBUG + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); + MOZ_ASSERT(svgChildFrame && aFrame->GetContent()->IsSVGElement(), + "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?"); +#endif + + const nsIContent* content = aFrame->GetContent(); + if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + // The SVG spec says not to draw _anything_ + return false; + } + } + + return true; +} + +/** + * Setup transform matrix of a gfx context by a specific frame. Depend on + * aClipCtx, this function may clip that context by the visual overflow area + * of aFrame. + * + * @param aFrame is the target frame. + * @param aOffsetToBoundingBox returns the offset between the reference frame + * and the bounding box of aFrame. + * @oaram aOffsetToUserSpace returns the offset between the reference frame and + * the user space coordinate of aFrame. + */ +static void +SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams, + nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace) +{ + aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) - + nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame); + if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { + /* Snap the offset if the reference frame is not a SVG frame, + * since other frames will be snapped to pixel when rendering. */ + aOffsetToBoundingBox = nsPoint( + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x), + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y)); + } + + // After applying only "aOffsetToBoundingBox", aParams.ctx would have its + // origin at the top left corner of frame's bounding box (over all + // continuations). + // However, SVG painting needs the origin to be located at the origin of the + // SVG frame's "user space", i.e. the space in which, for example, the + // frame's BBox lives. + // SVG geometry frames and foreignObject frames apply their own offsets, so + // their position is relative to their user space. So for these frame types, + // if we want aCtx to be in user space, we first need to subtract the + // frame's position so that SVG painting can later add it again and the + // frame is painted in the right place. + + gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); + nsPoint toUserSpace = + nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), + nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); + + aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace; + +#ifdef DEBUG + bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); + NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace, + "For non-SVG frames there shouldn't be any additional offset"); +#endif + + gfxPoint devPixelOffsetToUserSpace = + nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace, + aFrame->PresContext()->AppUnitsPerDevPixel()); + gfxContext& context = aParams.ctx; + context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace)); +} + +bool +nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame) +{ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); + const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); + + for (uint32_t i = 0; i < maskFrames.Length(); i++) { + // Refers to a valid SVG mask. + if (maskFrames[i]) { + continue; + } + + // Refers to an external resource, which is not ready yet. + if (!svgReset->mMask.mLayers[i].mImage.IsComplete()) { + return false; + } + } + + // Either all mask resources are ready, or no mask resource is needed. + return true; +} + +DrawResult +nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams) +{ + nsSVGUtils::MaskUsage maskUsage; + nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, + maskUsage); + MOZ_ASSERT(maskUsage.shouldGenerateMaskLayer); + + nsIFrame* frame = aParams.frame; + if (!ValidateSVGFrame(frame)) { + return DrawResult::SUCCESS; + } + + if (maskUsage.opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + gfxContext& ctx = aParams.ctx; + + gfxContextMatrixAutoSaveRestore matSR(&ctx); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames(); + bool opacityApplied = !HasNonSVGMask(maskFrames); + + nsPoint offsetToBoundingBox; + nsPoint offsetToUserSpace; + SetupContextMatrix(frame, aParams, offsetToBoundingBox, + offsetToUserSpace); + + return PaintMaskSurface(aParams, ctx.GetDrawTarget(), + opacityApplied ? maskUsage.opacity : 1.0, + firstFrame->StyleContext(), maskFrames, + ctx.CurrentMatrix(), offsetToUserSpace); +} + +DrawResult +nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams) +{ + MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame), + "Should not use this method when no mask or clipPath effect" + "on this frame"); + + /* SVG defines the following rendering model: + * + * 1. Render geometry + * 2. Apply filter + * 3. Apply clipping, masking, group opacity + * + * We handle #3 here and perform a couple of optimizations: + * + * + Use cairo's clipPath when representable natively (single object + * clip region). + * + * + Merge opacity and masking if both used together. + */ + nsIFrame* frame = aParams.frame; + DrawResult result = DrawResult::SUCCESS; + if (!ValidateSVGFrame(frame)) { + return result; + } + + nsSVGUtils::MaskUsage maskUsage; + nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, + maskUsage); + + if (maskUsage.opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + gfxContext& context = aParams.ctx; + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context); + + /* Properties are added lazily and may have been removed by a restyle, + so make sure all applicable ones are set again. */ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + + gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame); + nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); + + nsPoint offsetToBoundingBox; + nsPoint offsetToUserSpace; + + bool shouldGenerateMask = (maskUsage.opacity != 1.0f || + maskUsage.shouldGenerateClipMaskLayer || + maskUsage.shouldGenerateMaskLayer); + + /* Check if we need to do additional operations on this child's + * rendering, which necessitates rendering into another surface. */ + if (shouldGenerateMask) { + gfxContextMatrixAutoSaveRestore matSR; + + Matrix maskTransform; + RefPtr<SourceSurface> maskSurface; + bool opacityApplied = false; + + if (maskUsage.shouldGenerateMaskLayer) { + matSR.SetContext(&context); + + // For css-mask, we want to generate a mask for each continuation frame, + // so we setup context matrix by the position of the current frame, + // instead of the first continuation frame. + SetupContextMatrix(frame, aParams, offsetToBoundingBox, + offsetToUserSpace); + result = CreateAndPaintMaskSurface(aParams, maskUsage.opacity, + firstFrame->StyleContext(), + maskFrames, offsetToUserSpace, + maskTransform, maskSurface, + opacityApplied); + if (!maskSurface) { + // Entire surface is clipped out. + return result; + } + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + matSR.Restore(); + matSR.SetContext(&context); + + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + Matrix clippedMaskTransform; + RefPtr<SourceSurface> clipMaskSurface = + clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix, + &clippedMaskTransform, maskSurface, + maskTransform, &result); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + maskTransform = clippedMaskTransform; + } else { + // Either entire surface is clipped out, or gfx buffer allocation + // failure in nsSVGClipPathFrame::GetClipMask. + return result; + } + } + + // opacity != 1.0f. + if (!maskUsage.shouldGenerateClipMaskLayer && + !maskUsage.shouldGenerateMaskLayer) { + MOZ_ASSERT(maskUsage.opacity != 1.0f); + + matSR.SetContext(&context); + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + } + + if (aParams.layerManager->GetRoot()->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) { + context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, + opacityApplied + ? 1.0 + : maskUsage.opacity, + maskSurface, maskTransform); + } else { + context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, + opacityApplied ? 1.0 : maskUsage.opacity, + maskSurface, maskTransform); + } + } + + /* If this frame has only a trivial clipPath, set up cairo's clipping now so + * we can just do normal painting and get it clipped appropriately. + */ + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + gfxContextMatrixAutoSaveRestore matSR(&context); + + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + + MOZ_ASSERT(!maskUsage.shouldApplyClipPath || + !maskUsage.shouldApplyBasicShape); + if (maskUsage.shouldApplyClipPath) { + clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix); + } else { + nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame); + } + } + + /* Paint the child */ + context.SetMatrix(matrixAutoSaveRestore.Matrix()); + BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager(); + RefPtr<gfxContext> oldCtx = basic->GetTarget(); + basic->SetTarget(&context); + aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, + aParams.builder); + basic->SetTarget(oldCtx); + + if (gfxPrefs::DrawMaskLayer()) { + gfxContextAutoSaveRestore saver(&context); + + context.NewPath(); + gfxRect drawingRect = + nsLayoutUtils::RectToGfxRect(aParams.borderArea, + frame->PresContext()->AppUnitsPerDevPixel()); + context.Rectangle(drawingRect, true); + context.SetColor(Color(0.0, 1.0, 0.0, 1.0)); + context.Fill(); + } + + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + context.PopClip(); + } + + if (shouldGenerateMask) { + context.PopGroupAndBlend(); + } + + return result; +} + +DrawResult +nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams) +{ + MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(), + "Filter effect is discarded while generating glyph mask."); + MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(), + "Should not use this method when no filter effect on this frame"); + + nsIFrame* frame = aParams.frame; + if (!ValidateSVGFrame(frame)) { + return DrawResult::SUCCESS; + } + + float opacity = nsSVGUtils::ComputeOpacity(frame, aParams.handleOpacity); + if (opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + /* Properties are added lazily and may have been removed by a restyle, + so make sure all applicable ones are set again. */ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + + if (!effectProperties.HasValidFilter()) { + return DrawResult::NOT_READY; + } + + gfxContext& context = aParams.ctx; + nsPoint offsetToBoundingBox; + nsPoint offsetToUserSpace; + + gfxContextAutoSaveRestore autoSR(&context); + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + + if (opacity != 1.0f) { + context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, + nullptr, Matrix()); + } + + /* Paint the child and apply filters */ + RegularFramePaintCallback callback(aParams.builder, aParams.layerManager, + offsetToUserSpace); + nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox; + gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame); + nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(), + tm, &callback, &dirtyRegion); + + if (opacity != 1.0f) { + context.PopGroupAndBlend(); + } + + return DrawResult::SUCCESS; +} + +gfxMatrix +nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) +{ + int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel(); + float devPxPerCSSPx = + 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel); + + return gfxMatrix(devPxPerCSSPx, 0.0, + 0.0, devPxPerCSSPx, + 0.0, 0.0); +} + +class PaintFrameCallback : public gfxDrawingCallback { +public: + PaintFrameCallback(nsIFrame* aFrame, + const nsSize aPaintServerSize, + const IntSize aRenderSize, + uint32_t aFlags) + : mFrame(aFrame) + , mPaintServerSize(aPaintServerSize) + , mRenderSize(aRenderSize) + , mFlags (aFlags) + {} + virtual bool operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform) override; +private: + nsIFrame* mFrame; + nsSize mPaintServerSize; + IntSize mRenderSize; + uint32_t mFlags; +}; + +bool +PaintFrameCallback::operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform) +{ + if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER) + return false; + + mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + + aContext->Save(); + + // Clip to aFillRect so that we don't paint outside. + aContext->NewPath(); + aContext->Rectangle(aFillRect); + aContext->Clip(); + + gfxMatrix invmatrix = aTransform; + if (!invmatrix.Invert()) { + return false; + } + aContext->Multiply(invmatrix); + + // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want + // to have it anchored at the top left corner of the bounding box of all of + // mFrame's continuations. So we add a translation transform. + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsPoint offset = nsSVGIntegrationUtils::GetOffsetToBoundingBox(mFrame); + gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; + aContext->Multiply(gfxMatrix::Translation(devPxOffset)); + + gfxSize paintServerSize = + gfxSize(mPaintServerSize.width, mPaintServerSize.height) / + mFrame->PresContext()->AppUnitsPerDevPixel(); + + // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we + // want it to render with mRenderSize, so we need to set up a scale transform. + gfxFloat scaleX = mRenderSize.width / paintServerSize.width; + gfxFloat scaleY = mRenderSize.height / paintServerSize.height; + aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY)); + + // Draw. + nsRect dirty(-offset.x, -offset.y, + mPaintServerSize.width, mPaintServerSize.height); + + using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; + PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM; + if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) { + flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES; + } + nsRenderingContext context(aContext); + nsLayoutUtils::PaintFrame(&context, mFrame, + dirty, NS_RGBA(0, 0, 0, 0), + nsDisplayListBuilderMode::PAINTING, + flags); + + nsIFrame* currentFrame = mFrame; + while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) { + offset = currentFrame->GetOffsetToCrossDoc(mFrame); + devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; + + aContext->Save(); + aContext->Multiply(gfxMatrix::Scaling(1/scaleX, 1/scaleY)); + aContext->Multiply(gfxMatrix::Translation(devPxOffset)); + aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY)); + + nsLayoutUtils::PaintFrame(&context, currentFrame, + dirty - offset, NS_RGBA(0, 0, 0, 0), + nsDisplayListBuilderMode::PAINTING, + flags); + + aContext->Restore(); + } + + aContext->Restore(); + + mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + + return true; +} + +/* static */ already_AddRefed<gfxDrawable> +nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame, + nsIFrame* aTarget, + const nsSize& aPaintServerSize, + const IntSize& aRenderSize, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + uint32_t aFlags) +{ + // aPaintServerSize is the size that would be filled when using + // background-repeat:no-repeat and background-size:auto. For normal background + // images, this would be the intrinsic size of the image; for gradients and + // patterns this would be the whole target frame fill area. + // aRenderSize is what we will be actually filling after accounting for + // background-size. + if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) { + // aFrame is either a pattern or a gradient. These fill the whole target + // frame by default, so aPaintServerSize is the whole target background fill + // area. + nsSVGPaintServerFrame* server = + static_cast<nsSVGPaintServerFrame*>(aFrame); + + gfxRect overrideBounds(0, 0, + aPaintServerSize.width, aPaintServerSize.height); + overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel()); + RefPtr<gfxPattern> pattern = + server->GetPaintServerPattern(aTarget, aDrawTarget, + aContextMatrix, &nsStyleSVG::mFill, 1.0, + &overrideBounds); + + if (!pattern) + return nullptr; + + // pattern is now set up to fill aPaintServerSize. But we want it to + // fill aRenderSize, so we need to add a scaling transform. + // We couldn't just have set overrideBounds to aRenderSize - it would have + // worked for gradients, but for patterns it would result in a different + // pattern size. + gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width; + gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height; + gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY); + pattern->SetMatrix(scaleMatrix * pattern->GetMatrix()); + RefPtr<gfxDrawable> drawable = + new gfxPatternDrawable(pattern, aRenderSize); + return drawable.forget(); + } + + if (aFrame->IsFrameOfType(nsIFrame::eSVG) && + !static_cast<nsISVGChildFrame*>(do_QueryFrame(aFrame))) { + MOZ_ASSERT_UNREACHABLE("We should prevent painting of unpaintable SVG " + "before we get here"); + return nullptr; + } + + // We don't want to paint into a surface as long as we don't need to, so we + // set up a drawing callback. + RefPtr<gfxDrawingCallback> cb = + new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); + RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize); + return drawable.forget(); +} diff --git a/layout/svg/nsSVGIntegrationUtils.h b/layout/svg/nsSVGIntegrationUtils.h new file mode 100644 index 0000000000..e3eb3c5f22 --- /dev/null +++ b/layout/svg/nsSVGIntegrationUtils.h @@ -0,0 +1,231 @@ +/* -*- 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/. */ + +#ifndef NSSVGINTEGRATIONUTILS_H_ +#define NSSVGINTEGRATIONUTILS_H_ + +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsRegionFwd.h" +#include "mozilla/gfx/Rect.h" + +class gfxContext; +class gfxDrawable; +class nsDisplayList; +class nsDisplayListBuilder; +class nsIFrame; + +struct nsRect; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +namespace layers { +class LayerManager; +} // namespace layers +} // namespace mozilla + +struct nsPoint; +struct nsSize; + +/** + * Integration of SVG effects (clipPath clipping, masking and filters) into + * regular display list based painting and hit-testing. + */ +class nsSVGIntegrationUtils final +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntRect IntRect; + typedef mozilla::image::DrawResult DrawResult; + +public: + /** + * Returns true if SVG effects are currently applied to this frame. + */ + static bool + UsingEffectsForFrame(const nsIFrame* aFrame); + + /** + * Returns true if mask or clippath are currently applied to this frame. + */ + static bool + UsingMaskOrClipPathForFrame(const nsIFrame* aFrame); + + /** + * Returns the size of the union of the border-box rects of all of + * aNonSVGFrame's continuations. + */ + static nsSize + GetContinuationUnionSize(nsIFrame* aNonSVGFrame); + + /** + * When SVG effects need to resolve percentage, userSpaceOnUse lengths, they + * need a coordinate context to resolve them against. This method provides + * that coordinate context for non-SVG frames with SVG effects applied to + * them. The gfxSize returned is the size of the union of all of the given + * frame's continuations' border boxes, converted to SVG user units (equal to + * CSS px units), as required by the SVG code. + */ + static mozilla::gfx::Size + GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame); + + /** + * SVG effects such as SVG filters, masking and clipPath may require an SVG + * "bbox" for the element they're being applied to in order to make decisions + * about positioning, and to resolve various lengths against. This method + * provides the "bbox" for non-SVG frames. The bbox returned is in CSS px + * units, and is the union of all aNonSVGFrame's continuations' overflow + * areas, relative to the top-left of the union of all aNonSVGFrame's + * continuations' border box rects. + */ + static gfxRect + GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame); + + /** + * Used to adjust a frame's pre-effects visual overflow rect to take account + * of SVG effects. + * + * XXX This method will not do the right thing for frames with continuations. + * It really needs all the continuations to have been reflowed before being + * called, but we currently call it on each continuation as its overflow + * rects are set during the reflow of each particular continuation. Gecko's + * current reflow architecture does not allow us to set the overflow rects + * for a whole chain of continuations for a given element at the point when + * the last continuation is reflowed. See: + * http://groups.google.com/group/mozilla.dev.tech.layout/msg/6b179066f3051f65 + */ + static nsRect + ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame, + const nsRect& aPreEffectsOverflowRect); + + /** + * Used to adjust the area of a frame that needs to be invalidated to take + * account of SVG effects. + * + * @param aFrame The effects frame. + * @param aToReferenceFrame The offset (in app units) from aFrame to its + * reference display item. + * @param aInvalidRegion The pre-effects invalid region in pixels relative to + * the reference display item. + * @return The post-effects invalid rect in pixels relative to the reference + * display item. + */ + static nsIntRegion + AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, const nsPoint& aToReferenceFrame, + const nsIntRegion& aInvalidRegion); + + /** + * Figure out which area of the source is needed given an area to + * repaint + */ + static nsRect + GetRequiredSourceForInvalidArea(nsIFrame* aFrame, const nsRect& aDamageRect); + + /** + * Returns true if the given point is not clipped out by effects. + * @param aPt in appunits relative to aFrame + */ + static bool + HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt); + + struct PaintFramesParams { + gfxContext& ctx; + nsIFrame* frame; + const nsRect& dirtyRect; + const nsRect& borderArea; + nsDisplayListBuilder* builder; + mozilla::layers::LayerManager* layerManager; + bool handleOpacity; // If true, PaintMaskAndClipPath/ PaintFilter should + // apply css opacity. + IntRect maskRect; + + explicit PaintFramesParams(gfxContext& aCtx, nsIFrame* aFrame, + const nsRect& aDirtyRect, + const nsRect& aBorderArea, + nsDisplayListBuilder* aBuilder, + mozilla::layers::LayerManager* aLayerManager, + bool aHandleOpacity) + : ctx(aCtx), frame(aFrame), dirtyRect(aDirtyRect), + borderArea(aBorderArea), builder(aBuilder), + layerManager(aLayerManager), handleOpacity(aHandleOpacity) + { } + }; + + /** + * Paint non-SVG frame with mask, clipPath and opacity effect. + */ + static DrawResult + PaintMaskAndClipPath(const PaintFramesParams& aParams); + + /** + * Paint mask of non-SVG frame onto a given context, aParams.ctx. + * aParams.ctx must contain an A8 surface. + */ + static DrawResult + PaintMask(const PaintFramesParams& aParams); + + /** + * Return true if all the mask resource of aFrame are ready. + */ + static bool + IsMaskResourceReady(nsIFrame* aFrame); + + /** + * Paint non-SVG frame with filter and opacity effect. + */ + static DrawResult + PaintFilter(const PaintFramesParams& aParams); + + /** + * SVG frames expect to paint in SVG user units, which are equal to CSS px + * units. This method provides a transform matrix to multiply onto a + * gfxContext's current transform to convert the context's current units from + * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy. + */ + static gfxMatrix + GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame); + + /** + * @param aRenderingContext the target rendering context in which the paint + * server will be rendered + * @param aTarget the target frame onto which the paint server will be + * rendered + * @param aPaintServer a first-continuation frame to use as the source + * @param aFilter a filter to be applied when scaling + * @param aDest the area the paint server image should be mapped to + * @param aFill the area to be filled with copies of the paint server image + * @param aAnchor a point in aFill which we will ensure is pixel-aligned in + * the output + * @param aDirty pixels outside this area may be skipped + * @param aPaintServerSize the size that would be filled when using + * background-repeat:no-repeat and background-size:auto. For normal background + * images, this would be the intrinsic size of the image; for gradients and + * patterns this would be the whole target frame fill area. + * @param aFlags pass FLAG_SYNC_DECODE_IMAGES and any images in the paint + * server will be decoding synchronously if they are not decoded already. + */ + enum { + FLAG_SYNC_DECODE_IMAGES = 0x01, + }; + + static already_AddRefed<gfxDrawable> + DrawableFromPaintServer(nsIFrame* aFrame, + nsIFrame* aTarget, + const nsSize& aPaintServerSize, + const mozilla::gfx::IntSize& aRenderSize, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + uint32_t aFlags); + + /** + * For non-SVG frames, this gives the offset to the frame's "user space". + * For SVG frames, this returns a zero offset. + */ + static nsPoint + GetOffsetToBoundingBox(nsIFrame* aFrame); +}; + +#endif /*NSSVGINTEGRATIONUTILS_H_*/ diff --git a/layout/svg/nsSVGMarkerFrame.cpp b/layout/svg/nsSVGMarkerFrame.cpp new file mode 100644 index 0000000000..93b76b17fd --- /dev/null +++ b/layout/svg/nsSVGMarkerFrame.cpp @@ -0,0 +1,278 @@ +/* -*- 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 "nsSVGMarkerFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfxContext.h" +#include "nsSVGEffects.h" +#include "mozilla/dom/SVGMarkerElement.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGPathGeometryFrame.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; + +nsContainerFrame* +NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGMarkerFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult +nsSVGMarkerFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::markerUnits || + aAttribute == nsGkAtoms::refX || + aAttribute == nsGkAtoms::refY || + aAttribute == nsGkAtoms::markerWidth || + aAttribute == nsGkAtoms::markerHeight || + aAttribute == nsGkAtoms::orient || + aAttribute == nsGkAtoms::preserveAspectRatio || + aAttribute == nsGkAtoms::viewBox)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + return nsSVGContainerFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +#ifdef DEBUG +void +nsSVGMarkerFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::marker), "Content is not an SVG marker"); + + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGMarkerFrame::GetType() const +{ + return nsGkAtoms::svgMarkerFrame; +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +gfxMatrix +nsSVGMarkerFrame::GetCanvasTM() +{ + NS_ASSERTION(mMarkedFrame, "null nsSVGPathGeometry frame"); + + if (mInUse2) { + // We're going to be bailing drawing the marker, so return an identity. + return gfxMatrix(); + } + + SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent); + + mInUse2 = true; + gfxMatrix markedTM = mMarkedFrame->GetCanvasTM(); + mInUse2 = false; + + Matrix markerTM = content->GetMarkerTransform(mStrokeWidth, mX, mY, + mAutoAngle, mIsStart); + Matrix viewBoxTM = content->GetViewBoxTransform(); + + return ThebesMatrix(viewBoxTM * markerTM) * markedTM; +} + +static nsIFrame* +GetAnonymousChildFrame(nsIFrame* aFrame) +{ + nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); + MOZ_ASSERT(kid && kid->GetType() == nsGkAtoms::svgMarkerAnonChildFrame, + "expected to find anonymous child of marker frame"); + return kid; +} + +nsresult +nsSVGMarkerFrame::PaintMark(gfxContext& aContext, + const gfxMatrix& aToMarkedFrameUserSpace, + nsSVGPathGeometryFrame *aMarkedFrame, + nsSVGMark *aMark, float aStrokeWidth) +{ + // If the flag is set when we get here, it means this marker frame + // has already been used painting the current mark, and the document + // has a marker reference loop. + if (mInUse) + return NS_OK; + + AutoMarkerReferencer markerRef(this, aMarkedFrame); + + SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent); + if (!marker->HasValidDimensions()) { + return NS_OK; + } + + const nsSVGViewBoxRect viewBox = marker->GetViewBoxRect(); + + if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { + // We must disable rendering if the viewBox width or height are zero. + return NS_OK; + } + + mStrokeWidth = aStrokeWidth; + mX = aMark->x; + mY = aMark->y; + mAutoAngle = aMark->angle; + mIsStart = aMark->type == nsSVGMark::eStart; + + Matrix viewBoxTM = marker->GetViewBoxTransform(); + + Matrix markerTM = marker->GetMarkerTransform(mStrokeWidth, mX, mY, + mAutoAngle, mIsStart); + + gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(markerTM) * + aToMarkedFrameUserSpace; + + if (StyleDisplay()->IsScrollableOverflow()) { + aContext.Save(); + gfxRect clipRect = + nsSVGUtils::GetClipRectForFrame(this, viewBox.x, viewBox.y, + viewBox.width, viewBox.height); + nsSVGUtils::SetClipRect(&aContext, markTM, clipRect); + } + + + nsIFrame* kid = GetAnonymousChildFrame(this); + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + // The CTM of each frame referencing us may be different. + SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); + DrawResult result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM); + + if (StyleDisplay()->IsScrollableOverflow()) + aContext.Restore(); + + return (result == DrawResult::SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +SVGBBox +nsSVGMarkerFrame::GetMarkBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags, + nsSVGPathGeometryFrame *aMarkedFrame, + const nsSVGMark *aMark, + float aStrokeWidth) +{ + SVGBBox bbox; + + // If the flag is set when we get here, it means this marker frame + // has already been used in calculating the current mark bbox, and + // the document has a marker reference loop. + if (mInUse) + return bbox; + + AutoMarkerReferencer markerRef(this, aMarkedFrame); + + SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent); + if (!content->HasValidDimensions()) { + return bbox; + } + + const nsSVGViewBoxRect viewBox = content->GetViewBoxRect(); + + if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { + return bbox; + } + + mStrokeWidth = aStrokeWidth; + mX = aMark->x; + mY = aMark->y; + mAutoAngle = aMark->angle; + mIsStart = aMark->type == nsSVGMark::eStart; + + Matrix markerTM = + content->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle, mIsStart); + Matrix viewBoxTM = content->GetViewBoxTransform(); + + Matrix tm = viewBoxTM * markerTM * aToBBoxUserspace; + + nsISVGChildFrame* child = do_QueryFrame(GetAnonymousChildFrame(this)); + // When we're being called to obtain the invalidation area, we need to + // pass down all the flags so that stroke is included. However, once DOM + // getBBox() accepts flags, maybe we should strip some of those here? + + // We need to include zero width/height vertical/horizontal lines, so we have + // to use UnionEdges. + bbox.UnionEdges(child->GetBBoxContribution(tm, aFlags)); + + return bbox; +} + +void +nsSVGMarkerFrame::SetParentCoordCtxProvider(SVGSVGElement *aContext) +{ + SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent); + marker->SetParentCoordCtxProvider(aContext); +} + +//---------------------------------------------------------------------- +// helper class + +nsSVGMarkerFrame::AutoMarkerReferencer::AutoMarkerReferencer( + nsSVGMarkerFrame *aFrame, + nsSVGPathGeometryFrame *aMarkedFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mFrame(aFrame) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mFrame->mInUse = true; + mFrame->mMarkedFrame = aMarkedFrame; + + SVGSVGElement *ctx = + static_cast<nsSVGElement*>(aMarkedFrame->GetContent())->GetCtx(); + mFrame->SetParentCoordCtxProvider(ctx); +} + +nsSVGMarkerFrame::AutoMarkerReferencer::~AutoMarkerReferencer() +{ + mFrame->SetParentCoordCtxProvider(nullptr); + + mFrame->mMarkedFrame = nullptr; + mFrame->mInUse = false; +} + +//---------------------------------------------------------------------- +// Implementation of nsSVGMarkerAnonChildFrame + +nsContainerFrame* +NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGMarkerAnonChildFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerAnonChildFrame) + +#ifdef DEBUG +void +nsSVGMarkerAnonChildFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + MOZ_ASSERT(aParent->GetType() == nsGkAtoms::svgMarkerFrame, + "Unexpected parent"); + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif + +nsIAtom * +nsSVGMarkerAnonChildFrame::GetType() const +{ + return nsGkAtoms::svgMarkerAnonChildFrame; +} diff --git a/layout/svg/nsSVGMarkerFrame.h b/layout/svg/nsSVGMarkerFrame.h new file mode 100644 index 0000000000..22ac017090 --- /dev/null +++ b/layout/svg/nsSVGMarkerFrame.h @@ -0,0 +1,174 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGMARKERFRAME_H__ +#define __NS_SVGMARKERFRAME_H__ + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsFrame.h" +#include "nsLiteralString.h" +#include "nsQueryFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGUtils.h" + +class gfxContext; +class nsSVGPathGeometryFrame; + +namespace mozilla { +namespace dom { +class SVGSVGElement; +} // namespace dom +} // namespace mozilla + +struct nsSVGMark; + +class nsSVGMarkerFrame : public nsSVGContainerFrame +{ + friend class nsSVGMarkerAnonChildFrame; + friend nsContainerFrame* + NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGMarkerFrame(nsStyleContext* aContext) + : nsSVGContainerFrame(aContext) + , mMarkedFrame(nullptr) + , mInUse(false) + , mInUse2(false) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame interface: +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgMarkerFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGMarker"), aResult); + } +#endif + + virtual nsContainerFrame* GetContentInsertionFrame() override { + // Any children must be added to our single anonymous inner frame kid. + MOZ_ASSERT(PrincipalChildList().FirstChild() && + PrincipalChildList().FirstChild()->GetType() == + nsGkAtoms::svgMarkerAnonChildFrame, + "Where is our anonymous child?"); + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + // nsSVGMarkerFrame methods: + nsresult PaintMark(gfxContext& aContext, + const gfxMatrix& aToMarkedFrameUserSpace, + nsSVGPathGeometryFrame *aMarkedFrame, + nsSVGMark *aMark, + float aStrokeWidth); + + SVGBBox GetMarkBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags, + nsSVGPathGeometryFrame *aMarkedFrame, + const nsSVGMark *aMark, + float aStrokeWidth); + +private: + // stuff needed for callback + nsSVGPathGeometryFrame *mMarkedFrame; + float mStrokeWidth, mX, mY, mAutoAngle; + bool mIsStart; // whether the callback is for a marker-start marker + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + // A helper class to allow us to paint markers safely. The helper + // automatically sets and clears the mInUse flag on the marker frame (to + // prevent nasty reference loops) as well as the reference to the marked + // frame and its coordinate context. It's easy to mess this up + // and break things, so this helper makes the code far more robust. + class MOZ_RAII AutoMarkerReferencer + { + public: + AutoMarkerReferencer(nsSVGMarkerFrame *aFrame, + nsSVGPathGeometryFrame *aMarkedFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~AutoMarkerReferencer(); + private: + nsSVGMarkerFrame *mFrame; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + }; + + // nsSVGMarkerFrame methods: + void SetParentCoordCtxProvider(mozilla::dom::SVGSVGElement *aContext); + + // recursion prevention flag + bool mInUse; + + // second recursion prevention flag, for GetCanvasTM() + bool mInUse2; +}; + +//////////////////////////////////////////////////////////////////////// +// nsMarkerAnonChildFrame class + +class nsSVGMarkerAnonChildFrame : public nsSVGDisplayContainerFrame +{ + friend nsContainerFrame* + NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + explicit nsSVGMarkerAnonChildFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) + {} + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(NS_LITERAL_STRING("SVGMarkerAnonChild"), aResult); + } +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgMarkerAnonChildFrame + */ + virtual nsIAtom* GetType() const override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override + { + return static_cast<nsSVGMarkerFrame*>(GetParent())->GetCanvasTM(); + } +}; +#endif diff --git a/layout/svg/nsSVGMaskFrame.cpp b/layout/svg/nsSVGMaskFrame.cpp new file mode 100644 index 0000000000..b8e4b32ae9 --- /dev/null +++ b/layout/svg/nsSVGMaskFrame.cpp @@ -0,0 +1,403 @@ +/* -*- 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 "nsSVGMaskFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsSVGEffects.h" +#include "mozilla/dom/SVGMaskElement.h" +#ifdef BUILD_ARM_NEON +#include "mozilla/arm.h" +#include "nsSVGMaskFrameNEON.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +// c = n / 255 +// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5 +static const uint8_t gsRGBToLinearRGBMap[256] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, + 8, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 12, 12, 12, 13, + 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 17, 18, 18, 19, 19, 20, + 20, 21, 22, 22, 23, 23, 24, 24, + 25, 25, 26, 27, 27, 28, 29, 29, + 30, 30, 31, 32, 32, 33, 34, 35, + 35, 36, 37, 37, 38, 39, 40, 41, + 41, 42, 43, 44, 45, 45, 46, 47, + 48, 49, 50, 51, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 76, 77, 78, 79, + 80, 81, 82, 84, 85, 86, 87, 88, + 90, 91, 92, 93, 95, 96, 97, 99, +100, 101, 103, 104, 105, 107, 108, 109, +111, 112, 114, 115, 116, 118, 119, 121, +122, 124, 125, 127, 128, 130, 131, 133, +134, 136, 138, 139, 141, 142, 144, 146, +147, 149, 151, 152, 154, 156, 157, 159, +161, 163, 164, 166, 168, 170, 171, 173, +175, 177, 179, 181, 183, 184, 186, 188, +190, 192, 194, 196, 198, 200, 202, 204, +206, 208, 210, 212, 214, 216, 218, 220, +222, 224, 226, 229, 231, 233, 235, 237, +239, 242, 244, 246, 248, 250, 253, 255 +}; + +static void +ComputesRGBLuminanceMask(const uint8_t *aSourceData, + int32_t aSourceStride, + uint8_t *aDestData, + int32_t aDestStride, + const IntSize &aSize, + float aOpacity) +{ +#ifdef BUILD_ARM_NEON + if (mozilla::supports_neon()) { + ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride, + aDestData, aDestStride, + aSize, aOpacity); + return; + } +#endif + + int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity + int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity + int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721 + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + const uint8_t *sourcePixel = aSourceData; + int32_t destOffset = aDestStride - aSize.width; + uint8_t *destPixel = aDestData; + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A]; + + if (a) { + *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] + + greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] + + blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8; + } else { + *destPixel = 0; + } + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} + +static void +ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData, + int32_t aSourceStride, + uint8_t *aDestData, + int32_t aDestStride, + const IntSize &aSize, + float aOpacity) +{ + int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity + int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity + int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721 + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + const uint8_t *sourcePixel = aSourceData; + int32_t destOffset = aDestStride - aSize.width; + uint8_t *destPixel = aDestData; + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A]; + + // unpremultiply + if (a) { + if (a == 255) { + /* sRGB -> linearRGB -> intensity */ + *destPixel = + static_cast<uint8_t> + ((gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] * + redFactor + + gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] * + greenFactor + + gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] * + blueFactor) >> 8); + } else { + uint8_t tempPixel[4]; + tempPixel[GFX_ARGB32_OFFSET_B] = + (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a; + tempPixel[GFX_ARGB32_OFFSET_G] = + (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a; + tempPixel[GFX_ARGB32_OFFSET_R] = + (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a; + + /* sRGB -> linearRGB -> intensity */ + *destPixel = + static_cast<uint8_t> + (((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] * + redFactor + + gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] * + greenFactor + + gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] * + blueFactor) >> 8) * (a / 255.0f)); + } + } else { + *destPixel = 0; + } + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} + +static void +ComputeAlphaMask(const uint8_t *aSourceData, + int32_t aSourceStride, + uint8_t *aDestData, + int32_t aDestStride, + const IntSize &aSize, + float aOpacity) +{ + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + const uint8_t *sourcePixel = aSourceData; + int32_t destOffset = aDestStride - aSize.width; + uint8_t *destPixel = aDestData; + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + *destPixel = sourcePixel[GFX_ARGB32_OFFSET_A] * aOpacity; + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGMaskFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGMaskFrame) + +already_AddRefed<SourceSurface> +nsSVGMaskFrame::GetMaskForMaskedFrame(gfxContext* aContext, + nsIFrame* aMaskedFrame, + const gfxMatrix &aMatrix, + float aOpacity, + Matrix* aMaskTransform, + uint8_t aMaskOp) +{ + // If the flag is set when we get here, it means this mask frame + // has already been used painting the current mask, and the document + // has a mask reference loop. + if (mInUse) { + NS_WARNING("Mask loop detected!"); + return nullptr; + } + AutoMaskReferencer maskRef(this); + + gfxRect maskArea = GetMaskArea(aMaskedFrame); + + // Get the clip extents in device space: + // Minimizing the mask surface extents (using both the current clip extents + // and maskArea) is important for performance. + aContext->Save(); + nsSVGUtils::SetClipRect(aContext, aMatrix, maskArea); + aContext->SetMatrix(gfxMatrix()); + gfxRect maskSurfaceRect = aContext->GetClipExtents(); + maskSurfaceRect.RoundOut(); + aContext->Restore(); + + bool resultOverflows; + IntSize maskSurfaceSize = + nsSVGUtils::ConvertToSurfaceSize(maskSurfaceRect.Size(), &resultOverflows); + + if (resultOverflows || maskSurfaceSize.IsEmpty()) { + // XXXjwatt we should return an empty surface so we don't paint aMaskedFrame! + return nullptr; + } + + RefPtr<DrawTarget> maskDT = + Factory::CreateDrawTarget(BackendType::CAIRO, maskSurfaceSize, + SurfaceFormat::B8G8R8A8); + if (!maskDT || !maskDT->IsValid()) { + return nullptr; + } + + gfxMatrix maskSurfaceMatrix = + aContext->CurrentMatrix() * gfxMatrix::Translation(-maskSurfaceRect.TopLeft()); + + RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(maskDT); + MOZ_ASSERT(tmpCtx); // already checked the draw target above + tmpCtx->SetMatrix(maskSurfaceMatrix); + + mMatrixForChildren = GetMaskTransform(aMaskedFrame) * aMatrix; + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + // The CTM of each frame referencing us can be different + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); + } + gfxMatrix m = mMatrixForChildren; + if (kid->GetContent()->IsSVGElement()) { + m = static_cast<nsSVGElement*>(kid->GetContent())-> + PrependLocalTransformsTo(m, eUserSpaceToParent); + } + Unused << nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m); + } + + RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot(); + if (!maskSnapshot) { + return nullptr; + } + RefPtr<DataSourceSurface> maskSurface = maskSnapshot->GetDataSurface(); + DataSourceSurface::MappedSurface map; + if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return nullptr; + } + + // Create alpha channel mask for output + RefPtr<DataSourceSurface> destMaskSurface = + Factory::CreateDataSourceSurface(maskSurfaceSize, SurfaceFormat::A8); + if (!destMaskSurface) { + return nullptr; + } + DataSourceSurface::MappedSurface destMap; + if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + return nullptr; + } + + uint8_t maskType; + if (aMaskOp == NS_STYLE_MASK_MODE_MATCH_SOURCE) { + maskType = StyleSVGReset()->mMaskType; + } else { + maskType = aMaskOp == NS_STYLE_MASK_MODE_LUMINANCE ? + NS_STYLE_MASK_TYPE_LUMINANCE : NS_STYLE_MASK_TYPE_ALPHA; + } + + if (maskType == NS_STYLE_MASK_TYPE_LUMINANCE) { + if (StyleSVG()->mColorInterpolation == + NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) { + ComputeLinearRGBLuminanceMask(map.mData, map.mStride, + destMap.mData, destMap.mStride, + maskSurfaceSize, aOpacity); + } else { + ComputesRGBLuminanceMask(map.mData, map.mStride, + destMap.mData, destMap.mStride, + maskSurfaceSize, aOpacity); + } + } else { + ComputeAlphaMask(map.mData, map.mStride, + destMap.mData, destMap.mStride, + maskSurfaceSize, aOpacity); + } + + maskSurface->Unmap(); + destMaskSurface->Unmap(); + + // Moz2D transforms in the opposite direction to Thebes + if (!maskSurfaceMatrix.Invert()) { + return nullptr; + } + + *aMaskTransform = ToMatrix(maskSurfaceMatrix); + return destMaskSurface.forget(); +} + +gfxRect +nsSVGMaskFrame::GetMaskArea(nsIFrame* aMaskedFrame) +{ + SVGMaskElement *maskElem = static_cast<SVGMaskElement*>(mContent); + + uint16_t units = + maskElem->mEnumAttributes[SVGMaskElement::MASKUNITS].GetAnimValue(); + gfxRect bbox; + if (units == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + bbox = nsSVGUtils::GetBBox(aMaskedFrame); + } + + // Bounds in the user space of aMaskedFrame + gfxRect maskArea = nsSVGUtils::GetRelativeRect(units, + &maskElem->mLengthAttributes[SVGMaskElement::ATTR_X], + bbox, aMaskedFrame); + + return maskArea; +} + +nsresult +nsSVGMaskFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height|| + aAttribute == nsGkAtoms::maskUnits || + aAttribute == nsGkAtoms::maskContentUnits)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + return nsSVGContainerFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +#ifdef DEBUG +void +nsSVGMaskFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::mask), + "Content is not an SVG mask"); + + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGMaskFrame::GetType() const +{ + return nsGkAtoms::svgMaskFrame; +} + +gfxMatrix +nsSVGMaskFrame::GetCanvasTM() +{ + return mMatrixForChildren; +} + +gfxMatrix +nsSVGMaskFrame::GetMaskTransform(nsIFrame* aMaskedFrame) +{ + SVGMaskElement *content = static_cast<SVGMaskElement*>(mContent); + + nsSVGEnum* maskContentUnits = + &content->mEnumAttributes[SVGMaskElement::MASKCONTENTUNITS]; + + return nsSVGUtils::AdjustMatrixForUnits(gfxMatrix(), maskContentUnits, + aMaskedFrame); +} diff --git a/layout/svg/nsSVGMaskFrame.h b/layout/svg/nsSVGMaskFrame.h new file mode 100644 index 0000000000..380306a481 --- /dev/null +++ b/layout/svg/nsSVGMaskFrame.h @@ -0,0 +1,131 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGMASKFRAME_H__ +#define __NS_SVGMASKFRAME_H__ + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "gfxPattern.h" +#include "gfxMatrix.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGUtils.h" + +class gfxContext; + +/** + * Byte offsets of channels in a native packed gfxColor or cairo image surface. + */ +#ifdef IS_BIG_ENDIAN +#define GFX_ARGB32_OFFSET_A 0 +#define GFX_ARGB32_OFFSET_R 1 +#define GFX_ARGB32_OFFSET_G 2 +#define GFX_ARGB32_OFFSET_B 3 +#else +#define GFX_ARGB32_OFFSET_A 3 +#define GFX_ARGB32_OFFSET_R 2 +#define GFX_ARGB32_OFFSET_G 1 +#define GFX_ARGB32_OFFSET_B 0 +#endif + +class nsSVGMaskFrame final : public nsSVGContainerFrame +{ + friend nsIFrame* + NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::SourceSurface SourceSurface; + +protected: + explicit nsSVGMaskFrame(nsStyleContext* aContext) + : nsSVGContainerFrame(aContext) + , mInUse(false) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsSVGMaskFrame method: + already_AddRefed<SourceSurface> + GetMaskForMaskedFrame(gfxContext* aContext, + nsIFrame* aMaskedFrame, + const gfxMatrix &aMatrix, + float aOpacity, + Matrix* aMaskTransform, + uint8_t aMaskOp = NS_STYLE_MASK_MODE_MATCH_SOURCE); + + gfxRect + GetMaskArea(nsIFrame* aMaskedFrame); + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgMaskFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGMask"), aResult); + } +#endif + +private: + /** + * If the mask element transforms its children due to + * maskContentUnits="objectBoundingBox" being set on it, this function + * returns the resulting transform. + */ + gfxMatrix GetMaskTransform(nsIFrame* aMaskedFrame); + + // A helper class to allow us to paint masks safely. The helper + // automatically sets and clears the mInUse flag on the mask frame + // (to prevent nasty reference loops). It's easy to mess this up + // and break things, so this helper makes the code far more robust. + class MOZ_RAII AutoMaskReferencer + { + public: + explicit AutoMaskReferencer(nsSVGMaskFrame *aFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mFrame(aFrame) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + NS_ASSERTION(!mFrame->mInUse, "reference loop!"); + mFrame->mInUse = true; + } + ~AutoMaskReferencer() { + mFrame->mInUse = false; + } + private: + nsSVGMaskFrame *mFrame; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + }; + + gfxMatrix mMatrixForChildren; + // recursion prevention flag + bool mInUse; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; +}; + +#endif diff --git a/layout/svg/nsSVGMaskFrameNEON.cpp b/layout/svg/nsSVGMaskFrameNEON.cpp new file mode 100644 index 0000000000..f690b8164d --- /dev/null +++ b/layout/svg/nsSVGMaskFrameNEON.cpp @@ -0,0 +1,73 @@ +/* -*- 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/. */ + +#include "nsSVGMaskFrameNEON.h" +#include "nsSVGMaskFrame.h" +#include <arm_neon.h> + +using namespace mozilla::gfx; + +void +ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData, + int32_t aSourceStride, + uint8_t *aDestData, + int32_t aDestStride, + const IntSize &aSize, + float aOpacity) +{ + int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity + int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity + int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721 + const uint8_t *sourcePixel = aSourceData; + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + uint8_t *destPixel = aDestData; + int32_t destOffset = aDestStride - aSize.width; + + sourcePixel = aSourceData; + int32_t remainderWidth = aSize.width % 8; + int32_t roundedWidth = aSize.width - remainderWidth; + uint16x8_t temp; + uint8x8_t gray; + uint8x8_t redVector = vdup_n_u8(redFactor); + uint8x8_t greenVector = vdup_n_u8(greenFactor); + uint8x8_t blueVector = vdup_n_u8(blueFactor); + uint8x8_t fullBitVector = vdup_n_u8(255); + uint8x8_t oneVector = vdup_n_u8(1); + for (int32_t y = 0; y < aSize.height; y++) { + // Calculate luminance by neon with 8 pixels per loop + for (int32_t x = 0; x < roundedWidth; x += 8) { + uint8x8x4_t argb = vld4_u8(sourcePixel); + temp = vmull_u8(argb.val[GFX_ARGB32_OFFSET_R], redVector); // temp = red * redFactor + temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_G], greenVector); // temp += green * greenFactor + temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_B], blueVector); // temp += blue * blueFactor + gray = vshrn_n_u16(temp, 8); // gray = temp >> 8 + + // Check alpha value + uint8x8_t alphaVector = vtst_u8(argb.val[GFX_ARGB32_OFFSET_A], fullBitVector); + gray = vmul_u8(gray, vand_u8(alphaVector, oneVector)); + + // Put the result to the 8 pixels + vst1_u8(destPixel, gray); + sourcePixel += 8 * 4; + destPixel += 8; + } + + // Calculate the rest pixels of the line by cpu + for (int32_t x = 0; x < remainderWidth; x++) { + if (sourcePixel[GFX_ARGB32_OFFSET_A] > 0) { + *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R]+ + greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] + + blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8; + } else { + *destPixel = 0; + } + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} + diff --git a/layout/svg/nsSVGMaskFrameNEON.h b/layout/svg/nsSVGMaskFrameNEON.h new file mode 100644 index 0000000000..1da901ba7f --- /dev/null +++ b/layout/svg/nsSVGMaskFrameNEON.h @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGMASKFRAMENEON_H__ +#define __NS_SVGMASKFRAMENEON_H__ + +#include "mozilla/gfx/Point.h" + +void +ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData, + int32_t aSourceStride, + uint8_t *aDestData, + int32_t aDestStride, + const mozilla::gfx::IntSize &aSize, + float aOpacity); + +#endif /* __NS_SVGMASKFRAMENEON_H__ */ diff --git a/layout/svg/nsSVGOuterSVGFrame.cpp b/layout/svg/nsSVGOuterSVGFrame.cpp new file mode 100644 index 0000000000..683f10bc7b --- /dev/null +++ b/layout/svg/nsSVGOuterSVGFrame.cpp @@ -0,0 +1,999 @@ +/* -*- 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 "nsSVGOuterSVGFrame.h" + +// Keep others in (case-insensitive) order: +#include "nsDisplayList.h" +#include "nsIDocument.h" +#include "nsIDOMHTMLIFrameElement.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIObjectLoadingContent.h" +#include "nsRenderingContext.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGForeignObjectFrame.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGViewElement.h" +#include "nsSubDocumentFrame.h" + +using namespace mozilla; +using namespace mozilla::dom; + +//---------------------------------------------------------------------- +// Implementation helpers + +void +nsSVGOuterSVGFrame::RegisterForeignObject(nsSVGForeignObjectFrame* aFrame) +{ + NS_ASSERTION(aFrame, "Who on earth is calling us?!"); + + if (!mForeignObjectHash) { + mForeignObjectHash = new nsTHashtable<nsPtrHashKey<nsSVGForeignObjectFrame> >(); + } + + NS_ASSERTION(!mForeignObjectHash->GetEntry(aFrame), + "nsSVGForeignObjectFrame already registered!"); + + mForeignObjectHash->PutEntry(aFrame); + + NS_ASSERTION(mForeignObjectHash->GetEntry(aFrame), + "Failed to register nsSVGForeignObjectFrame!"); +} + +void +nsSVGOuterSVGFrame::UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame) +{ + NS_ASSERTION(aFrame, "Who on earth is calling us?!"); + NS_ASSERTION(mForeignObjectHash && mForeignObjectHash->GetEntry(aFrame), + "nsSVGForeignObjectFrame not in registry!"); + return mForeignObjectHash->RemoveEntry(aFrame); +} + +//---------------------------------------------------------------------- +// Implementation + +nsContainerFrame* +NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGOuterSVGFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGFrame) + +nsSVGOuterSVGFrame::nsSVGOuterSVGFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) + , mFullZoom(aContext->PresContext()->GetFullZoom()) + , mViewportInitialized(false) + , mIsRootContent(false) +{ + // Outer-<svg> has CSS layout, so remove this bit: + RemoveStateBits(NS_FRAME_SVG_LAYOUT); +} + +// helper +static inline bool +DependsOnIntrinsicSize(const nsIFrame* aEmbeddingFrame) +{ + const nsStylePosition *pos = aEmbeddingFrame->StylePosition(); + const nsStyleCoord &width = pos->mWidth; + const nsStyleCoord &height = pos->mHeight; + + // XXX it would be nice to know if the size of aEmbeddingFrame's containing + // block depends on aEmbeddingFrame, then we'd know if we can return false + // for eStyleUnit_Percent too. + return !width.ConvertsToLength() || + !height.ConvertsToLength(); +} + +void +nsSVGOuterSVGFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), + "Content is not an SVG 'svg' element!"); + + AddStateBits(NS_STATE_IS_OUTER_SVG | + NS_FRAME_FONT_INFLATION_CONTAINER | + NS_FRAME_FONT_INFLATION_FLOW_ROOT); + + // Check for conditional processing attributes here rather than in + // nsCSSFrameConstructor::FindSVGData because we want to avoid + // simply giving failing outer <svg> elements an nsSVGContainerFrame. + // We don't create other SVG frames if PassesConditionalProcessingTests + // returns false, but since we do create nsSVGOuterSVGFrame frames we + // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The + // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if + // the value returned by PassesConditionalProcessingTests changes. + SVGSVGElement *svg = static_cast<SVGSVGElement*>(aContent); + if (!svg->PassesConditionalProcessingTests()) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); + + nsIDocument* doc = mContent->GetUncomposedDoc(); + if (doc) { + // we only care about our content's zoom and pan values if it's the root element + if (doc->GetRootElement() == mContent) { + mIsRootContent = true; + + nsIFrame* embeddingFrame; + if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) { + if (MOZ_UNLIKELY(!embeddingFrame->HasAllStateBits(NS_FRAME_IS_DIRTY)) && + DependsOnIntrinsicSize(embeddingFrame)) { + // Looks like this document is loading after the embedding element + // has had its first reflow, and that its size depends on our + // intrinsic size. We need it to resize itself to use our (now + // available) intrinsic size: + embeddingFrame->PresContext()->PresShell()-> + FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + } + } + } +} + +//---------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(nsSVGOuterSVGFrame) + NS_QUERYFRAME_ENTRY(nsISVGSVGFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods +//---------------------------------------------------------------------- +// reflowing + +/* virtual */ nscoord +nsSVGOuterSVGFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + + result = nscoord(0); + + return result; +} + +/* virtual */ nscoord +nsSVGOuterSVGFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + + SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent); + WritingMode wm = GetWritingMode(); + const nsSVGLength2& isize = wm.IsVertical() + ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT] + : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + + if (isize.IsPercentage()) { + // It looks like our containing block's isize may depend on our isize. In + // that case our behavior is undefined according to CSS 2.1 section 10.3.2. + // As a last resort, we'll fall back to returning zero. + result = nscoord(0); + + // Returning zero may be unhelpful, however, as it leads to unexpected + // disappearance of %-sized SVGs in orthogonal contexts, where our + // containing block wants to shrink-wrap. So let's look for an ancestor + // with non-zero size in this dimension, and use that as a (somewhat + // arbitrary) result instead. + nsIFrame *parent = GetParent(); + while (parent) { + nscoord parentISize = parent->GetLogicalSize(wm).ISize(wm); + if (parentISize > 0 && parentISize != NS_UNCONSTRAINEDSIZE) { + result = parentISize; + break; + } + parent = parent->GetParent(); + } + } else { + result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg)); + if (result < 0) { + result = nscoord(0); + } + } + + return result; +} + +/* virtual */ IntrinsicSize +nsSVGOuterSVGFrame::GetIntrinsicSize() +{ + // XXXjwatt Note that here we want to return the CSS width/height if they're + // specified and we're embedded inside an nsIObjectLoadingContent. + + IntrinsicSize intrinsicSize; + + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + + if (!width.IsPercentage()) { + nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)); + if (val < 0) val = 0; + intrinsicSize.width.SetCoordValue(val); + } + + if (!height.IsPercentage()) { + nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content)); + if (val < 0) val = 0; + intrinsicSize.height.SetCoordValue(val); + } + + return intrinsicSize; +} + +/* virtual */ nsSize +nsSVGOuterSVGFrame::GetIntrinsicRatio() +{ + // We only have an intrinsic size/ratio if our width and height attributes + // are both specified and set to non-percentage values, or we have a viewBox + // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing + + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + + if (!width.IsPercentage() && !height.IsPercentage()) { + nsSize ratio(NSToCoordRoundWithClamp(width.GetAnimValue(content)), + NSToCoordRoundWithClamp(height.GetAnimValue(content))); + if (ratio.width < 0) { + ratio.width = 0; + } + if (ratio.height < 0) { + ratio.height = 0; + } + return ratio; + } + + SVGViewElement* viewElement = content->GetCurrentViewElement(); + const nsSVGViewBoxRect* viewbox = nullptr; + + // The logic here should match HasViewBox(). + if (viewElement && viewElement->mViewBox.HasRect()) { + viewbox = &viewElement->mViewBox.GetAnimValue(); + } else if (content->mViewBox.HasRect()) { + viewbox = &content->mViewBox.GetAnimValue(); + } + + if (viewbox) { + float viewBoxWidth = viewbox->width; + float viewBoxHeight = viewbox->height; + + if (viewBoxWidth < 0.0f) { + viewBoxWidth = 0.0f; + } + if (viewBoxHeight < 0.0f) { + viewBoxHeight = 0.0f; + } + return nsSize(NSToCoordRoundWithClamp(viewBoxWidth), + NSToCoordRoundWithClamp(viewBoxHeight)); + } + + return nsSVGDisplayContainerFrame::GetIntrinsicRatio(); +} + +/* virtual */ +LogicalSize +nsSVGOuterSVGFrame::ComputeSize(nsRenderingContext *aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + if (IsRootOfImage() || IsRootOfReplacedElementSubDoc()) { + // The embedding element has sized itself using the CSS replaced element + // sizing rules, using our intrinsic dimensions as necessary. The SVG spec + // says that the width and height of embedded SVG is overridden by the + // width and height of the embedding element, so we just need to size to + // the viewport that the embedding element has established for us. + return aCBSize; + } + + LogicalSize cbSize = aCBSize; + IntrinsicSize intrinsicSize = GetIntrinsicSize(); + + if (!mContent->GetParent()) { + // We're the root of the outermost browsing context, so we need to scale + // cbSize by the full-zoom so that SVGs with percentage width/height zoom: + + NS_ASSERTION(aCBSize.ISize(aWM) != NS_AUTOHEIGHT && + aCBSize.BSize(aWM) != NS_AUTOHEIGHT, + "root should not have auto-width/height containing block"); + cbSize.ISize(aWM) *= PresContext()->GetFullZoom(); + cbSize.BSize(aWM) *= PresContext()->GetFullZoom(); + + // We also need to honour the width and height attributes' default values + // of 100% when we're the root of a browsing context. (GetIntrinsicSize() + // doesn't report these since there's no such thing as a percentage + // intrinsic size. Also note that explicit percentage values are mapped + // into style, so the following isn't for them.) + + SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent); + + nsSVGLength2 &width = + content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + if (width.IsPercentage()) { + MOZ_ASSERT(intrinsicSize.width.GetUnit() == eStyleUnit_None, + "GetIntrinsicSize should have reported no intrinsic width"); + float val = width.GetAnimValInSpecifiedUnits() / 100.0f; + if (val < 0.0f) val = 0.0f; + intrinsicSize.width.SetCoordValue(val * cbSize.Width(aWM)); + } + + nsSVGLength2 &height = + content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + NS_ASSERTION(aCBSize.BSize(aWM) != NS_AUTOHEIGHT, + "root should not have auto-height containing block"); + if (height.IsPercentage()) { + MOZ_ASSERT(intrinsicSize.height.GetUnit() == eStyleUnit_None, + "GetIntrinsicSize should have reported no intrinsic height"); + float val = height.GetAnimValInSpecifiedUnits() / 100.0f; + if (val < 0.0f) val = 0.0f; + intrinsicSize.height.SetCoordValue(val * cbSize.Height(aWM)); + } + MOZ_ASSERT(intrinsicSize.height.GetUnit() == eStyleUnit_Coord && + intrinsicSize.width.GetUnit() == eStyleUnit_Coord, + "We should have just handled the only situation where" + "we lack an intrinsic height or width."); + } + + return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM, + intrinsicSize, GetIntrinsicRatio(), + cbSize, aMargin, aBorder, aPadding, + aFlags); +} + +void +nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsSVGOuterSVGFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("enter nsSVGOuterSVGFrame::Reflow: availSize=%d,%d", + aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); + + NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); + + aStatus = NS_FRAME_COMPLETE; + + aDesiredSize.Width() = aReflowInput.ComputedWidth() + + aReflowInput.ComputedPhysicalBorderPadding().LeftRight(); + aDesiredSize.Height() = aReflowInput.ComputedHeight() + + aReflowInput.ComputedPhysicalBorderPadding().TopBottom(); + + NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages."); + + SVGSVGElement *svgElem = static_cast<SVGSVGElement*>(mContent); + + nsSVGOuterSVGAnonChildFrame *anonKid = + static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild()); + + if (mState & NS_FRAME_FIRST_REFLOW) { + // Initialize + svgElem->UpdateHasChildrenOnlyTransform(); + } + + // If our SVG viewport has changed, update our content and notify. + // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace + + svgFloatSize newViewportSize( + nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedWidth()), + nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedHeight())); + + svgFloatSize oldViewportSize = svgElem->GetViewportSize(); + + uint32_t changeBits = 0; + if (newViewportSize != oldViewportSize) { + // When our viewport size changes, we may need to update the overflow rects + // of our child frames. This is the case if: + // + // * We have a real/synthetic viewBox (a children-only transform), since + // the viewBox transform will change as the viewport dimensions change. + // + // * We do not have a real/synthetic viewBox, but the last time we + // reflowed (or the last time UpdateOverflow() was called) we did. + // + // We only handle the former case here, in which case we mark all our child + // frames as dirty so that we reflow them below and update their overflow + // rects. + // + // In the latter case, updating of overflow rects is handled for removal of + // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic + // viewBox "removal" (e.g. a document references the same SVG via both an + // <svg:image> and then as a CSS background image (a synthetic viewBox is + // used when painting the former, but not when painting the latter)) is + // handled in SVGSVGElement::FlushImageTransformInvalidation. + // + if (svgElem->HasViewBoxOrSyntheticViewBox()) { + nsIFrame* anonChild = PrincipalChildList().FirstChild(); + anonChild->AddStateBits(NS_FRAME_IS_DIRTY); + for (nsIFrame* child : anonChild->PrincipalChildList()) { + child->AddStateBits(NS_FRAME_IS_DIRTY); + } + } + changeBits |= COORD_CONTEXT_CHANGED; + svgElem->SetViewportSize(newViewportSize); + } + if (mFullZoom != PresContext()->GetFullZoom()) { + changeBits |= FULL_ZOOM_CHANGED; + mFullZoom = PresContext()->GetFullZoom(); + } + if (changeBits) { + NotifyViewportOrTransformChanged(changeBits); + } + mViewportInitialized = true; + + // Now that we've marked the necessary children as dirty, call + // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending + // on whether we are non-display. + mCallingReflowSVG = true; + if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + ReflowSVGNonDisplayText(this); + } else { + // Update the mRects and visual overflow rects of all our descendants, + // including our anonymous wrapper kid: + anonKid->AddStateBits(mState & NS_FRAME_IS_DIRTY); + anonKid->ReflowSVG(); + MOZ_ASSERT(!anonKid->GetNextSibling(), + "We should have one anonymous child frame wrapping our real " + "children"); + } + mCallingReflowSVG = false; + + // Set our anonymous kid's offset from our border box: + anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft()); + + // Including our size in our overflow rects regardless of the value of + // 'background', 'border', etc. makes sure that we usually (when we clip to + // our content area) don't have to keep changing our overflow rects as our + // descendants move about (see perf comment below). Including our size in our + // scrollable overflow rect also makes sure that we scroll if we're too big + // for our viewport. + // + // <svg> never allows scrolling to anything outside its mRect (only panning), + // so we must always keep our scrollable overflow set to our size. + // + // With regards to visual overflow, we always clip root-<svg> (see our + // BuildDisplayList method) regardless of the value of the 'overflow' + // property since that is per-spec, even for the initial 'visible' value. For + // that reason there's no point in adding descendant visual overflow to our + // own when this frame is for a root-<svg>. That said, there's also a very + // good performance reason for us wanting to avoid doing so. If we did, then + // the frame's overflow would often change as descendants that are partially + // or fully outside its rect moved (think animation on/off screen), and that + // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the + // entire document tree each such move (see bug 875175). + // + // So it's only non-root outer-<svg> that has the visual overflow of its + // descendants added to its own. (Note that the default user-agent style + // sheet makes 'hidden' the default value for :not(root(svg)), so usually + // FinishAndStoreOverflow will still clip this back to the frame's rect.) + // + // WARNING!! Keep UpdateBounds below in sync with whatever we do for our + // overflow rects here! (Again, see bug 875175.) + // + aDesiredSize.SetOverflowAreasToDesiredBounds(); + if (!mIsRootContent) { + aDesiredSize.mOverflowAreas.VisualOverflow().UnionRect( + aDesiredSize.mOverflowAreas.VisualOverflow(), + anonKid->GetVisualOverflowRect() + anonKid->GetPosition()); + } + FinishAndStoreOverflow(&aDesiredSize); + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit nsSVGOuterSVGFrame::Reflow: size=%d,%d", + aDesiredSize.Width(), aDesiredSize.Height())); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +void +nsSVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput, + nsDidReflowStatus aStatus) +{ + nsSVGDisplayContainerFrame::DidReflow(aPresContext,aReflowInput,aStatus); + + // Make sure elements styled by :hover get updated if script/animation moves + // them under or out from under the pointer: + PresContext()->PresShell()->SynthesizeMouseMove(false); +} + +/* virtual */ void +nsSVGOuterSVGFrame::UnionChildOverflow(nsOverflowAreas& aOverflowAreas) +{ + // See the comments in Reflow above. + + // WARNING!! Keep this in sync with Reflow above! + + if (!mIsRootContent) { + nsIFrame *anonKid = PrincipalChildList().FirstChild(); + aOverflowAreas.VisualOverflow().UnionRect( + aOverflowAreas.VisualOverflow(), + anonKid->GetVisualOverflowRect() + anonKid->GetPosition()); + } +} + + +//---------------------------------------------------------------------- +// container methods + +/** + * Used to paint/hit-test SVG when SVG display lists are disabled. + */ +class nsDisplayOuterSVG : public nsDisplayItem { +public: + nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder, + nsSVGOuterSVGFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayOuterSVG); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayOuterSVG() { + MOZ_COUNT_DTOR(nsDisplayOuterSVG); + } +#endif + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*> *aOutFrames) override; + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) override; + + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override + { + return new nsDisplayItemGenericImageGeometry(this, aBuilder); + } + + NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG) +}; + +void +nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) +{ + nsSVGOuterSVGFrame *outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(mFrame); + + nsPoint refFrameToContentBox = + ToReferenceFrame() + outerSVGFrame->GetContentRectRelativeToSelf().TopLeft(); + + nsPoint pointRelativeToContentBox = + nsPoint(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2) - + refFrameToContentBox; + + gfxPoint svgViewportRelativePoint = + gfxPoint(pointRelativeToContentBox.x, pointRelativeToContentBox.y) / + outerSVGFrame->PresContext()->AppUnitsPerCSSPixel(); + + nsSVGOuterSVGAnonChildFrame *anonKid = + static_cast<nsSVGOuterSVGAnonChildFrame*>( + outerSVGFrame->PrincipalChildList().FirstChild()); + + nsIFrame* frame = + nsSVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint); + if (frame) { + aOutFrames->AppendElement(frame); + } +} + +void +nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aContext) +{ +#if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING) + PRTime start = PR_Now(); +#endif + + // Create an SVGAutoRenderState so we can call SetPaintingToWindow on it. + SVGAutoRenderState state(aContext->GetDrawTarget()); + + if (aBuilder->IsPaintingToWindow()) { + state.SetPaintingToWindow(true); + } + + nsRect viewportRect = + mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame(); + + nsRect clipRect = mVisibleRect.Intersect(viewportRect); + + uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + nsIntRect contentAreaDirtyRect = + (clipRect - viewportRect.TopLeft()). + ToOutsidePixels(appUnitsPerDevPixel); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(viewportRect.TopLeft(), appUnitsPerDevPixel); + + aContext->ThebesContext()->Save(); + // We include the offset of our frame and a scale from device pixels to user + // units (i.e. CSS px) in the matrix that we pass to our children): + gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) * + gfxMatrix::Translation(devPixelOffset); + DrawResult result = + nsSVGUtils::PaintFrameWithEffects(mFrame, *aContext->ThebesContext(), tm, + &contentAreaDirtyRect); + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); + aContext->ThebesContext()->Restore(); + +#if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING) + PRTime end = PR_Now(); + printf("SVG Paint Timing: %f ms\n", (end-start)/1000.0); +#endif +} + +nsRegion +nsSVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame) +{ + nsRegion result; + if (mForeignObjectHash && mForeignObjectHash->Count()) { + for (auto it = mForeignObjectHash->Iter(); !it.Done(); it.Next()) { + result.Or(result, it.Get()->GetKey()->GetInvalidRegion()); + } + } + return result; +} + +void +nsDisplayOuterSVG::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) +{ + nsSVGOuterSVGFrame *frame = static_cast<nsSVGOuterSVGFrame*>(mFrame); + frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame)); + + nsRegion result = frame->GetInvalidRegion(); + result.MoveBy(ToReferenceFrame()); + frame->ClearInvalidRegion(); + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); + aInvalidRegion->Or(*aInvalidRegion, result); + + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } +} + +nsresult +nsSVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + !(GetStateBits() & (NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY))) { + if (aAttribute == nsGkAtoms::viewBox || + aAttribute == nsGkAtoms::preserveAspectRatio || + aAttribute == nsGkAtoms::transform) { + + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + + nsSVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), + aAttribute == nsGkAtoms::viewBox ? + TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED); + + if (aAttribute != nsGkAtoms::transform) { + static_cast<SVGSVGElement*>(mContent)->ChildrenOnlyTransformChanged(); + } + + } else if (aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height) { + + // Don't call ChildrenOnlyTransformChanged() here, since we call it + // under Reflow if the width/height actually changed. + + nsIFrame* embeddingFrame; + if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) { + if (DependsOnIntrinsicSize(embeddingFrame)) { + // Tell embeddingFrame's presShell it needs to be reflowed (which takes + // care of reflowing us too). + embeddingFrame->PresContext()->PresShell()-> + FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + // else our width and height is overridden - don't reflow anything + } else { + // We are not embedded by reference, so our 'width' and 'height' + // attributes are not overridden - we need to reflow. + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + } + } + + return NS_OK; +} + +//---------------------------------------------------------------------- +// painting + +void +nsSVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + return; + } + + DisplayBorderBackgroundOutline(aBuilder, aLists); + + // Per-spec, we always clip root-<svg> even when 'overflow' has its initial + // value of 'visible'. See also the "visual overflow" comments in Reflow. + DisplayListClipState::AutoSaveRestore autoSR(aBuilder); + if (mIsRootContent || + StyleDisplay()->IsScrollableOverflow()) { + autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this); + } + + if ((aBuilder->IsForEventDelivery() && + NS_SVGDisplayListHitTestingEnabled()) || + (!aBuilder->IsForEventDelivery() && + NS_SVGDisplayListPaintingEnabled())) { + nsDisplayList *contentList = aLists.Content(); + nsDisplayListSet set(contentList, contentList, contentList, + contentList, contentList, contentList); + BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, set); + } else if (IsVisibleForPainting(aBuilder) || !aBuilder->IsForPainting()) { + aLists.Content()->AppendNewToTop( + new (aBuilder) nsDisplayOuterSVG(aBuilder, this)); + } +} + +nsSplittableType +nsSVGOuterSVGFrame::GetSplittableType() const +{ + return NS_FRAME_NOT_SPLITTABLE; +} + +nsIAtom * +nsSVGOuterSVGFrame::GetType() const +{ + return nsGkAtoms::svgOuterSVGFrame; +} + +//---------------------------------------------------------------------- +// nsISVGSVGFrame methods: + +void +nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags && + !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED | + FULL_ZOOM_CHANGED)), + "Unexpected aFlags value"); + + // No point in doing anything when were not init'ed yet: + if (!mViewportInitialized) { + return; + } + + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + + if (aFlags & COORD_CONTEXT_CHANGED) { + if (content->HasViewBoxRect()) { + // Percentage lengths on children resolve against the viewBox rect so we + // don't need to notify them of the viewport change, but the viewBox + // transform will have changed, so we need to notify them of that instead. + aFlags = TRANSFORM_CHANGED; + } + else if (content->ShouldSynthesizeViewBox()) { + // In the case of a synthesized viewBox, the synthetic viewBox's rect + // changes as the viewport changes. As a result we need to maintain the + // COORD_CONTEXT_CHANGED flag. + aFlags |= TRANSFORM_CHANGED; + } + else if (mCanvasTM && mCanvasTM->IsSingular()) { + // A width/height of zero will result in us having a singular mCanvasTM + // even when we don't have a viewBox. So we also want to recompute our + // mCanvasTM for this width/height change even though we don't have a + // viewBox. + aFlags |= TRANSFORM_CHANGED; + } + } + + bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED); + + if (aFlags & FULL_ZOOM_CHANGED) { + // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED: + aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED; + } + + if (aFlags & TRANSFORM_CHANGED) { + // Make sure our canvas transform matrix gets (lazily) recalculated: + mCanvasTM = nullptr; + + if (haveNonFulLZoomTransformChange && + !(mState & NS_FRAME_IS_NONDISPLAY)) { + uint32_t flags = (mState & NS_FRAME_IN_REFLOW) ? + SVGSVGElement::eDuringReflow : 0; + content->ChildrenOnlyTransformChanged(flags); + } + } + + nsSVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), aFlags); +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods: + +DrawResult +nsSVGOuterSVGFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) +{ + NS_ASSERTION(PrincipalChildList().FirstChild()->GetType() == + nsGkAtoms::svgOuterSVGAnonChildFrame && + !PrincipalChildList().FirstChild()->GetNextSibling(), + "We should have a single, anonymous, child"); + nsSVGOuterSVGAnonChildFrame *anonKid = + static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild()); + return anonKid->PaintSVG(aContext, aTransform, aDirtyRect); +} + +SVGBBox +nsSVGOuterSVGFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace, + uint32_t aFlags) +{ + NS_ASSERTION(PrincipalChildList().FirstChild()->GetType() == + nsGkAtoms::svgOuterSVGAnonChildFrame && + !PrincipalChildList().FirstChild()->GetNextSibling(), + "We should have a single, anonymous, child"); + // We must defer to our child so that we don't include our + // content->PrependLocalTransformsTo() transforms. + nsSVGOuterSVGAnonChildFrame *anonKid = + static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild()); + return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags); +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +gfxMatrix +nsSVGOuterSVGFrame::GetCanvasTM() +{ + if (!mCanvasTM) { + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + + float devPxPerCSSPx = + 1.0f / PresContext()->AppUnitsToFloatCSSPixels( + PresContext()->AppUnitsPerDevPixel()); + + gfxMatrix tm = content->PrependLocalTransformsTo( + gfxMatrix::Scaling(devPxPerCSSPx, devPxPerCSSPx)); + mCanvasTM = new gfxMatrix(tm); + } + return *mCanvasTM; +} + +//---------------------------------------------------------------------- +// Implementation helpers + +bool +nsSVGOuterSVGFrame::IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame) +{ + if (!mContent->GetParent()) { + // Our content is the document element + nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell(); + nsCOMPtr<nsPIDOMWindowOuter> window; + if (docShell) { + window = docShell->GetWindow(); + } + + if (window) { + nsCOMPtr<nsIDOMElement> frameElement = window->GetFrameElement(); + nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(frameElement); + nsCOMPtr<nsIDOMHTMLIFrameElement> iframeElement = + do_QueryInterface(frameElement); + if (olc || iframeElement) { + // Our document is inside an HTML 'object', 'embed', 'applet' + // or 'iframe' element + if (aEmbeddingFrame) { + nsCOMPtr<nsIContent> element = do_QueryInterface(frameElement); + *aEmbeddingFrame = element->GetPrimaryFrame(); + NS_ASSERTION(*aEmbeddingFrame, "Yikes, no embedding frame!"); + } + return true; + } + } + } + if (aEmbeddingFrame) { + *aEmbeddingFrame = nullptr; + } + return false; +} + +bool +nsSVGOuterSVGFrame::IsRootOfImage() +{ + if (!mContent->GetParent()) { + // Our content is the document element + nsIDocument* doc = mContent->GetUncomposedDoc(); + if (doc && doc->IsBeingUsedAsImage()) { + // Our document is being used as an image + return true; + } + } + + return false; +} + +bool +nsSVGOuterSVGFrame::VerticalScrollbarNotNeeded() const +{ + nsSVGLength2 &height = static_cast<SVGSVGElement*>(mContent)-> + mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100; +} + + +//---------------------------------------------------------------------- +// Implementation of nsSVGOuterSVGAnonChildFrame + +nsContainerFrame* +NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGOuterSVGAnonChildFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGAnonChildFrame) + +#ifdef DEBUG +void +nsSVGOuterSVGAnonChildFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + MOZ_ASSERT(aParent->GetType() == nsGkAtoms::svgOuterSVGFrame, + "Unexpected parent"); + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif + +nsIAtom * +nsSVGOuterSVGAnonChildFrame::GetType() const +{ + return nsGkAtoms::svgOuterSVGAnonChildFrame; +} + +bool +nsSVGOuterSVGAnonChildFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const +{ + // We must claim our nsSVGOuterSVGFrame's children-only transforms as our own + // so that the children we are used to wrap are transformed properly. + + SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent); + + bool hasTransform = content->HasChildrenOnlyTransform(); + + if (hasTransform && aTransform) { + // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here. + gfxMatrix identity; + *aTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo(identity, eChildToUserSpace)); + } + + return hasTransform; +} diff --git a/layout/svg/nsSVGOuterSVGFrame.h b/layout/svg/nsSVGOuterSVGFrame.h new file mode 100644 index 0000000000..a085936789 --- /dev/null +++ b/layout/svg/nsSVGOuterSVGFrame.h @@ -0,0 +1,277 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGOUTERSVGFRAME_H__ +#define __NS_SVGOUTERSVGFRAME_H__ + +#include "mozilla/Attributes.h" +#include "nsAutoPtr.h" +#include "nsISVGSVGFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsRegion.h" + +class gfxContext; +class nsSVGForeignObjectFrame; + +//////////////////////////////////////////////////////////////////////// +// nsSVGOuterSVGFrame class + +class nsSVGOuterSVGFrame final : public nsSVGDisplayContainerFrame + , public nsISVGSVGFrame +{ + friend nsContainerFrame* + NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGOuterSVGFrame(nsStyleContext* aContext); + +public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + ~nsSVGOuterSVGFrame() { + NS_ASSERTION(!mForeignObjectHash || mForeignObjectHash->Count() == 0, + "foreignObject(s) still registered!"); + } +#endif + + // nsIFrame: + virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; + + virtual mozilla::IntrinsicSize GetIntrinsicSize() override; + virtual nsSize GetIntrinsicRatio() override; + + virtual mozilla::LogicalSize + ComputeSize(nsRenderingContext *aRenderingContext, + mozilla::WritingMode aWritingMode, + const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorder, + const mozilla::LogicalSize& aPadding, + ComputeSizeFlags aFlags) override; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + virtual void DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput, + nsDidReflowStatus aStatus) override; + + virtual void UnionChildOverflow(nsOverflowAreas& aOverflowAreas) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual nsSplittableType GetSplittableType() const override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgOuterSVGFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGOuterSVG"), aResult); + } +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual nsContainerFrame* GetContentInsertionFrame() override { + // Any children must be added to our single anonymous inner frame kid. + MOZ_ASSERT(PrincipalChildList().FirstChild() && + PrincipalChildList().FirstChild()->GetType() == + nsGkAtoms::svgOuterSVGAnonChildFrame, + "Where is our anonymous child?"); + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + virtual bool IsSVGTransformed(Matrix *aOwnTransform, + Matrix *aFromParentTransform) const override { + // Our anonymous wrapper performs the transforms. We simply + // return whether we are transformed here but don't apply the transforms + // themselves. + return PrincipalChildList().FirstChild()->IsSVGTransformed(); + } + + // nsISVGSVGFrame interface: + virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) override; + + // nsISVGChildFrame methods: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) override; + virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + /* Methods to allow descendant nsSVGForeignObjectFrame frames to register and + * unregister themselves with their nearest nsSVGOuterSVGFrame ancestor. This + * is temporary until display list based invalidation is impleented for SVG. + * Maintaining a list of our foreignObject descendants allows us to search + * them for areas that need to be invalidated, without having to also search + * the SVG frame tree for foreignObjects. This is important so that bug 539356 + * does not slow down SVG in general (only foreignObjects, until bug 614732 is + * fixed). + */ + void RegisterForeignObject(nsSVGForeignObjectFrame* aFrame); + void UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame); + + virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override { + // Our anonymous wrapper child must claim our children-only transforms as + // its own so that our real children (the frames it wraps) are transformed + // by them, and we must pretend we don't have any children-only transforms + // so that our anonymous child is _not_ transformed by them. + return false; + } + + /** + * Return true only if the height is unspecified (defaulting to 100%) or else + * the height is explicitly set to a percentage value no greater than 100%. + */ + bool VerticalScrollbarNotNeeded() const; + + bool IsCallingReflowSVG() const { + return mCallingReflowSVG; + } + + void InvalidateSVG(const nsRegion& aRegion) + { + if (!aRegion.IsEmpty()) { + mInvalidRegion.Or(mInvalidRegion, aRegion); + InvalidateFrame(); + } + } + + void ClearInvalidRegion() { mInvalidRegion.SetEmpty(); } + + const nsRegion& GetInvalidRegion() { + nsRect rect; + if (!IsInvalid(rect)) { + mInvalidRegion.SetEmpty(); + } + return mInvalidRegion; + } + + nsRegion FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame); + +protected: + + bool mCallingReflowSVG; + + /* Returns true if our content is the document element and our document is + * embedded in an HTML 'object', 'embed' or 'applet' element. Set + * aEmbeddingFrame to obtain the nsIFrame for the embedding HTML element. + */ + bool IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame = nullptr); + + /* Returns true if our content is the document element and our document is + * being used as an image. + */ + bool IsRootOfImage(); + + // This is temporary until display list based invalidation is implemented for + // SVG. + // A hash-set containing our nsSVGForeignObjectFrame descendants. Note we use + // a hash-set to avoid the O(N^2) behavior we'd get tearing down an SVG frame + // subtree if we were to use a list (see bug 381285 comment 20). + nsAutoPtr<nsTHashtable<nsPtrHashKey<nsSVGForeignObjectFrame> > > mForeignObjectHash; + + nsAutoPtr<gfxMatrix> mCanvasTM; + + nsRegion mInvalidRegion; + + float mFullZoom; + + bool mViewportInitialized; + bool mIsRootContent; +}; + +//////////////////////////////////////////////////////////////////////// +// nsSVGOuterSVGAnonChildFrame class + +/** + * nsSVGOuterSVGFrames have a single direct child that is an instance of this + * class, and which is used to wrap their real child frames. Such anonymous + * wrapper frames created from this class exist because SVG frames need their + * GetPosition() offset to be their offset relative to "user space" (in app + * units) so that they can play nicely with nsDisplayTransform. This is fine + * for all SVG frames except for direct children of an nsSVGOuterSVGFrame, + * since an nsSVGOuterSVGFrame can have CSS border and padding (unlike other + * SVG frames). The direct children can't include the offsets due to any such + * border/padding in their mRects since that would break nsDisplayTransform, + * but not including these offsets would break other parts of the Mozilla code + * that assume a frame's mRect contains its border-box-to-parent-border-box + * offset, in particular nsIFrame::GetOffsetTo and the functions that depend on + * it. Wrapping an nsSVGOuterSVGFrame's children in an instance of this class + * with its GetPosition() set to its nsSVGOuterSVGFrame's border/padding offset + * keeps both nsDisplayTransform and nsIFrame::GetOffsetTo happy. + * + * The reason that this class inherit from nsSVGDisplayContainerFrame rather + * than simply from nsContainerFrame is so that we can avoid having special + * handling for these inner wrappers in multiple parts of the SVG code. For + * example, the implementations of IsSVGTransformed and GetCanvasTM assume + * nsSVGContainerFrame instances all the way up to the nsSVGOuterSVGFrame. + */ +class nsSVGOuterSVGAnonChildFrame : public nsSVGDisplayContainerFrame +{ + friend nsContainerFrame* + NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + explicit nsSVGOuterSVGAnonChildFrame(nsStyleContext* aContext) + : nsSVGDisplayContainerFrame(aContext) + {} + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(NS_LITERAL_STRING("SVGOuterSVGAnonChild"), aResult); + } +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgOuterSVGAnonChildFrame + */ + virtual nsIAtom* GetType() const override; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override { + // GetCanvasTM returns the transform from an SVG frame to the frame's + // nsSVGOuterSVGFrame's content box, so we do not include any x/y offset + // set on us for any CSS border or padding on our nsSVGOuterSVGFrame. + return static_cast<nsSVGOuterSVGFrame*>(GetParent())->GetCanvasTM(); + } + + virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override; +}; + +#endif diff --git a/layout/svg/nsSVGPaintServerFrame.h b/layout/svg/nsSVGPaintServerFrame.h new file mode 100644 index 0000000000..6b568f8727 --- /dev/null +++ b/layout/svg/nsSVGPaintServerFrame.h @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGPAINTSERVERFRAME_H__ +#define __NS_SVGPAINTSERVERFRAME_H__ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsFrame.h" +#include "nsIFrame.h" +#include "nsQueryFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGUtils.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +class gfxContext; +class gfxPattern; +class nsStyleContext; + +struct gfxRect; + +class nsSVGPaintServerFrame : public nsSVGContainerFrame +{ +protected: + typedef mozilla::gfx::DrawTarget DrawTarget; + + explicit nsSVGPaintServerFrame(nsStyleContext* aContext) + : nsSVGContainerFrame(aContext) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_ABSTRACT_FRAME(nsSVGPaintServerFrame) + + /** + * Constructs a gfxPattern of the paint server rendering. + * + * @param aContextMatrix The transform matrix that is currently applied to + * the gfxContext that is being drawn to. This is needed by SVG patterns so + * that surfaces of the correct size can be created. (SVG gradients are + * vector based, so it's not used there.) + */ + virtual already_AddRefed<gfxPattern> + GetPaintServerPattern(nsIFrame *aSource, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aOpacity, + const gfxRect *aOverrideBounds = nullptr) = 0; + + // nsIFrame methods: + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsSVGContainerFrame::IsFrameOfType(aFlags & ~nsIFrame::eSVGPaintServer); + } +}; + +#endif // __NS_SVGPAINTSERVERFRAME_H__ diff --git a/layout/svg/nsSVGPathGeometryFrame.cpp b/layout/svg/nsSVGPathGeometryFrame.cpp new file mode 100644 index 0000000000..32cd0e4965 --- /dev/null +++ b/layout/svg/nsSVGPathGeometryFrame.cpp @@ -0,0 +1,928 @@ +/* -*- 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 "nsSVGPathGeometryFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContextPaint.h" +#include "nsDisplayList.h" +#include "nsGkAtoms.h" +#include "nsLayoutUtils.h" +#include "nsRenderingContext.h" +#include "nsSVGEffects.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGMarkerFrame.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGUtils.h" +#include "mozilla/ArrayUtils.h" +#include "SVGAnimatedTransformList.h" +#include "SVGContentUtils.h" +#include "SVGGraphicsElement.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGPathGeometryFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame) + +//---------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame) + NS_QUERYFRAME_ENTRY(nsISVGChildFrame) + NS_QUERYFRAME_ENTRY(nsSVGPathGeometryFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsFrame) + +//---------------------------------------------------------------------- +// Display list item: + +class nsDisplaySVGPathGeometry : public nsDisplayItem { +public: + nsDisplaySVGPathGeometry(nsDisplayListBuilder* aBuilder, + nsSVGPathGeometryFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) + { + MOZ_COUNT_CTOR(nsDisplaySVGPathGeometry); + MOZ_ASSERT(aFrame, "Must have a frame!"); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplaySVGPathGeometry() { + MOZ_COUNT_DTOR(nsDisplaySVGPathGeometry); + } +#endif + + NS_DISPLAY_DECL_NAME("nsDisplaySVGPathGeometry", TYPE_SVG_PATH_GEOMETRY) + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override; + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override + { + return new nsDisplayItemGenericImageGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) override; +}; + +void +nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) +{ + nsSVGPathGeometryFrame *frame = static_cast<nsSVGPathGeometryFrame*>(mFrame); + nsPoint pointRelativeToReferenceFrame = aRect.Center(); + // ToReferenceFrame() includes frame->GetPosition(), our user space position. + nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame - + (ToReferenceFrame() - frame->GetPosition()); + gfxPoint userSpacePt = + gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) / + frame->PresContext()->AppUnitsPerCSSPixel(); + if (frame->GetFrameForPoint(userSpacePt)) { + aOutFrames->AppendElement(frame); + } +} + +void +nsDisplaySVGPathGeometry::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + // ToReferenceFrame includes our mRect offset, but painting takes + // account of that too. To avoid double counting, we subtract that + // here. + nsPoint offset = ToReferenceFrame() - mFrame->GetPosition(); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel); + + gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) * + gfxMatrix::Translation(devPixelOffset); + DrawResult result = + static_cast<nsSVGPathGeometryFrame*>(mFrame)->PaintSVG(*aCtx->ThebesContext(), tm); + + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); +} + +void +nsDisplaySVGPathGeometry::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) +{ + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +//---------------------------------------------------------------------- +// nsIFrame methods + +void +nsSVGPathGeometryFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); + nsFrame::Init(aContent, aParent, aPrevInFlow); +} + +nsresult +nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + // We don't invalidate for transform changes (the layers code does that). + // Also note that SVGTransformableElement::GetAttributeChangeHint will + // return nsChangeHint_UpdateOverflow for "transform" attribute changes + // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. + + if (aNameSpaceID == kNameSpaceID_None && + (static_cast<nsSVGPathGeometryElement*> + (mContent)->AttributeDefinesGeometry(aAttribute))) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + } + return NS_OK; +} + +/* virtual */ void +nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsFrame::DidSetStyleContext(aOldStyleContext); + + if (aOldStyleContext) { + auto oldStyleEffects = aOldStyleContext->PeekStyleEffects(); + if (oldStyleEffects && + StyleEffects()->mOpacity != oldStyleEffects->mOpacity && + nsSVGUtils::CanOptimizeOpacity(this)) { + // nsIFrame::BuildDisplayListForStackingContext() is not going to create an + // nsDisplayOpacity display list item, so DLBI won't invalidate for us. + InvalidateFrame(); + } + + nsSVGPathGeometryElement* element = + static_cast<nsSVGPathGeometryElement*>(mContent); + + auto oldStyleSVG = aOldStyleContext->PeekStyleSVG(); + if (oldStyleSVG && !SVGContentUtils::ShapeTypeHasNoCorners(mContent)) { + if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap && + element->IsSVGElement(nsGkAtoms::path)) { + // If the stroke-linecap changes to or from "butt" then our element + // needs to update its cached Moz2D Path, since SVGPathData::BuildPath + // decides whether or not to insert little lines into the path for zero + // length subpaths base on that property. + element->ClearAnyCachedPath(); + } else if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { + if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) { + // Moz2D Path objects are fill-rule specific. + // For clipPath we use clip-rule as the path's fill-rule. + element->ClearAnyCachedPath(); + } + } else { + if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) { + // Moz2D Path objects are fill-rule specific. + element->ClearAnyCachedPath(); + } + } + } + } +} + +nsIAtom * +nsSVGPathGeometryFrame::GetType() const +{ + return nsGkAtoms::svgPathGeometryFrame; +} + +bool +nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, + gfx::Matrix *aFromParentTransform) const +{ + bool foundTransform = false; + + // Check if our parent has children-only transforms: + nsIFrame *parent = GetParent(); + if (parent && + parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { + foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> + HasChildrenOnlyTransform(aFromParentTransform); + } + + nsSVGElement *content = static_cast<nsSVGElement*>(mContent); + nsSVGAnimatedTransformList* transformList = + content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo( + gfxMatrix(), + eUserSpaceToParent)); + } + foundTransform = true; + } + return foundTransform; +} + +void +nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions() || + (!IsVisibleForPainting(aBuilder) && aBuilder->IsForPainting())) { + return; + } + DisplayOutline(aBuilder, aLists); + aLists.Content()->AppendNewToTop( + new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this)); +} + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +DrawResult +nsSVGPathGeometryFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) +{ + if (!StyleVisibility()->IsVisible()) + return DrawResult::SUCCESS; + + gfxMatrix childToUserSpace = aTransform; + if (GetContent()->IsSVGElement()) { + childToUserSpace = static_cast<const nsSVGElement*>(GetContent())-> + PrependLocalTransformsTo(childToUserSpace, + eChildToUserSpace); + } + + // Matrix to the geometry's user space: + gfxMatrix newMatrix = + aContext.CurrentMatrix().PreMultiply(childToUserSpace).NudgeToIntegers(); + if (newMatrix.IsSingular()) { + return DrawResult::BAD_ARGS; + } + + uint32_t paintOrder = StyleSVG()->mPaintOrder; + if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) { + Render(&aContext, eRenderFill | eRenderStroke, newMatrix); + PaintMarkers(aContext, aTransform); + } else { + while (paintOrder) { + uint32_t component = + paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1); + switch (component) { + case NS_STYLE_PAINT_ORDER_FILL: + Render(&aContext, eRenderFill, newMatrix); + break; + case NS_STYLE_PAINT_ORDER_STROKE: + Render(&aContext, eRenderStroke, newMatrix); + break; + case NS_STYLE_PAINT_ORDER_MARKERS: + PaintMarkers(aContext, aTransform); + break; + } + paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH; + } + } + + return DrawResult::SUCCESS; +} + +nsIFrame* +nsSVGPathGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + FillRule fillRule; + uint16_t hitTestFlags; + if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { + hitTestFlags = SVG_HIT_TEST_FILL; + fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mClipRule); + } else { + hitTestFlags = GetHitTestFlags(); + if (!hitTestFlags) { + return nullptr; + } + if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) { + gfxRect rect = + nsLayoutUtils::RectToGfxRect(mRect, PresContext()->AppUnitsPerCSSPixel()); + if (!rect.Contains(aPoint)) { + return nullptr; + } + } + fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule); + } + + bool isHit = false; + + nsSVGPathGeometryElement* content = + static_cast<nsSVGPathGeometryElement*>(mContent); + + // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit- + // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing + // so that we get more consistent/backwards compatible results? + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<Path> path = content->GetOrBuildPath(*drawTarget, fillRule); + if (!path) { + return nullptr; // no path, so we don't paint anything that can be hit + } + + if (hitTestFlags & SVG_HIT_TEST_FILL) { + isHit = path->ContainsPoint(ToPoint(aPoint), Matrix()); + } + if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) { + Point point = ToPoint(aPoint); + SVGContentUtils::AutoStrokeOptions stroke; + SVGContentUtils::GetStrokeOptions(&stroke, content, StyleContext(), nullptr); + gfxMatrix userToOuterSVG; + if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { + // We need to transform the path back into the appropriate ancestor + // coordinate system in order for non-scaled stroke to be correct. + // Naturally we also need to transform the point into the same + // coordinate system in order to hit-test against the path. + point = ToMatrix(userToOuterSVG).TransformPoint(point); + RefPtr<PathBuilder> builder = + path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule); + path = builder->Finish(); + } + isHit = path->StrokeContainsPoint(stroke, point, Matrix()); + } + + if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) + return this; + + return nullptr; +} + +nsRect +nsSVGPathGeometryFrame::GetCoveredRegion() +{ + gfxMatrix canvasTM = GetCanvasTM(); + if (canvasTM.PreservesAxisAlignedRectangles()) { + return nsSVGUtils::TransformFrameRectToOuterSVG( + mRect, canvasTM, PresContext()); + } + + // To get tight bounds we need to compute directly in outer SVG coordinates + uint32_t flags = nsSVGUtils::eBBoxIncludeFill | + nsSVGUtils::eBBoxIncludeStroke | + nsSVGUtils::eBBoxIncludeMarkers; + gfxRect extent = + GetBBoxContribution(ToMatrix(canvasTM), flags).ToThebesRect(); + nsRect region = nsLayoutUtils::RoundGfxRectToAppRect( + extent, PresContext()->AppUnitsPerCSSPixel()); + + return nsSVGUtils::TransformFrameRectToOuterSVG( + region, gfxMatrix(), PresContext()); +} + +void +nsSVGPathGeometryFrame::ReflowSVG() +{ + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!nsSVGUtils::NeedsReflowSVG(this)) { + return; + } + + uint32_t flags = nsSVGUtils::eBBoxIncludeFill | + nsSVGUtils::eBBoxIncludeStroke | + nsSVGUtils::eBBoxIncludeMarkers; + // Our "visual" overflow rect needs to be valid for building display lists + // for hit testing, which means that for certain values of 'pointer-events' + // it needs to include the geometry of the fill or stroke even when the fill/ + // stroke don't actually render (e.g. when stroke="none" or + // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'. + uint16_t hitTestFlags = GetHitTestFlags(); + if ((hitTestFlags & SVG_HIT_TEST_FILL)) { + flags |= nsSVGUtils::eBBoxIncludeFillGeometry; + } + if ((hitTestFlags & SVG_HIT_TEST_STROKE)) { + flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry; + } + + gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect(); + mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, + PresContext()->AppUnitsPerCSSPixel()); + + if (mState & NS_FRAME_FIRST_REFLOW) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + nsSVGEffects::UpdateEffects(this); + } + + nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); + nsOverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); + + // Invalidate, but only if this is not our first reflow (since if it is our + // first reflow then we haven't had our first paint yet). + if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { + InvalidateFrame(); + } +} + +void +nsSVGPathGeometryFrame::NotifySVGChanged(uint32_t aFlags) +{ + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + // Changes to our ancestors may affect how we render when we are rendered as + // part of our ancestor (specifically, if our coordinate context changes size + // and we have percentage lengths defining our geometry, then we need to be + // reflowed). However, ancestor changes cannot affect how we render when we + // are rendered as part of any rendering observers that we may have. + // Therefore no need to notify rendering observers here. + + // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls + // for the stroke properties examined below. Checking HasStroke() is not + // enough, since what we care about is whether we include the stroke in our + // overflow rects or not, and we sometimes deliberately include stroke + // when it's not visible. See the complexities of GetBBoxContribution. + + if (aFlags & COORD_CONTEXT_CHANGED) { + // Stroke currently contributes to our mRect, which is why we have to take + // account of stroke-width here. Note that we do not need to take account + // of stroke-dashoffset since, although that can have a percentage value + // that is resolved against our coordinate context, it does not affect our + // mRect. + if (static_cast<nsSVGPathGeometryElement*>(mContent)->GeometryDependsOnCoordCtx() || + StyleSVG()->mStrokeWidth.HasPercent()) { + static_cast<nsSVGPathGeometryElement*>(mContent)->ClearAnyCachedPath(); + nsSVGUtils::ScheduleReflowSVG(this); + } + } + + if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) { + // Stroke currently contributes to our mRect, and our stroke depends on + // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|. + nsSVGUtils::ScheduleReflowSVG(this); + } +} + +SVGBBox +nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) +{ + SVGBBox bbox; + + if (aToBBoxUserspace.IsSingular()) { + // XXX ReportToConsole + return bbox; + } + + nsSVGPathGeometryElement* element = + static_cast<nsSVGPathGeometryElement*>(mContent); + + bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) || + ((aFlags & nsSVGUtils::eBBoxIncludeFill) && + StyleSVG()->mFill.Type() != eStyleSVGPaintType_None); + + bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) || + ((aFlags & nsSVGUtils::eBBoxIncludeStroke) && + nsSVGUtils::HasStroke(this)); + + SVGContentUtils::AutoStrokeOptions strokeOptions; + if (getStroke) { + SVGContentUtils::GetStrokeOptions(&strokeOptions, element, + StyleContext(), nullptr, + SVGContentUtils::eIgnoreStrokeDashing); + } else { + // Override the default line width of 1.f so that when we call + // GetGeometryBounds below the result doesn't include stroke bounds. + strokeOptions.mLineWidth = 0.f; + } + + Rect simpleBounds; + bool gotSimpleBounds = false; + gfxMatrix userToOuterSVG; + if (getStroke && + nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { + Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG); + if (moz2dUserToOuterSVG.IsSingular()) { + return bbox; + } + gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, + strokeOptions, + aToBBoxUserspace, + &moz2dUserToOuterSVG); + } else { + gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, + strokeOptions, + aToBBoxUserspace); + } + + if (gotSimpleBounds) { + bbox = simpleBounds; + } else { + // Get the bounds using a Moz2D Path object (more expensive): + RefPtr<DrawTarget> tmpDT; +#ifdef XP_WIN + // Unfortunately D2D backed DrawTarget produces bounds with rounding errors + // when whole number results are expected, even in the case of trivial + // calculations. To avoid that and meet the expectations of web content we + // have to use a CAIRO DrawTarget. The most efficient way to do that is to + // wrap the cached cairo_surface_t from ScreenReferenceSurface(): + RefPtr<gfxASurface> refSurf = + gfxPlatform::GetPlatform()->ScreenReferenceSurface(); + tmpDT = gfxPlatform::GetPlatform()-> + CreateDrawTargetForSurface(refSurf, IntSize(1, 1)); +#else + tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); +#endif + + FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule); + RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule); + if (!pathInUserSpace) { + return bbox; + } + RefPtr<Path> pathInBBoxSpace; + if (aToBBoxUserspace.IsIdentity()) { + pathInBBoxSpace = pathInUserSpace; + } else { + RefPtr<PathBuilder> builder = + pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule); + pathInBBoxSpace = builder->Finish(); + if (!pathInBBoxSpace) { + return bbox; + } + } + + // Be careful when replacing the following logic to get the fill and stroke + // extents independently (instead of computing the stroke extents from the + // path extents). You may think that you can just use the stroke extents if + // there is both a fill and a stroke. In reality it's necessary to + // calculate both the fill and stroke extents, and take the union of the + // two. There are two reasons for this: + // + // # Due to stroke dashing, in certain cases the fill extents could + // actually extend outside the stroke extents. + // # If the stroke is very thin, cairo won't paint any stroke, and so the + // stroke bounds that it will return will be empty. + + Rect pathBBoxExtents = pathInBBoxSpace->GetBounds(); + if (!pathBBoxExtents.IsFinite()) { + // This can happen in the case that we only have a move-to command in the + // path commands, in which case we know nothing gets rendered. + return bbox; + } + + // Account for fill: + if (getFill) { + bbox = pathBBoxExtents; + } + + // Account for stroke: + if (getStroke) { +#if 0 + // This disabled code is how we would calculate the stroke bounds using + // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing + // it there are two problems that prevent us from using it. + // + // First, it seems that some of the Moz2D backends are really dumb. Not + // only do some GetStrokeOptions() implementations sometimes + // significantly overestimate the stroke bounds, but if an argument is + // passed for the aTransform parameter then they just return bounds-of- + // transformed-bounds. These two things combined can lead the bounds to + // be unacceptably oversized, leading to massive over-invalidation. + // + // Second, the way we account for non-scaling-stroke by transforming the + // path using the transform to the outer-<svg> element is not compatible + // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale + // into aToBBoxUserspace and then scales the bounds that we return. + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, element, + StyleContext(), nullptr, + SVGContentUtils::eIgnoreStrokeDashing); + Rect strokeBBoxExtents; + gfxMatrix userToOuterSVG; + if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { + Matrix outerSVGToUser = ToMatrix(userToOuterSVG); + outerSVGToUser.Invert(); + Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser; + RefPtr<PathBuilder> builder = + pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG)); + RefPtr<Path> pathInOuterSVGSpace = builder->Finish(); + strokeBBoxExtents = + pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox); + } else { + strokeBBoxExtents = + pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace); + } + MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad"); + bbox.UnionEdges(strokeBBoxExtents); +#else + // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents: + gfxRect strokeBBoxExtents = + nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents), + this, + ThebesMatrix(aToBBoxUserspace)); + MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad"); + bbox.UnionEdges(strokeBBoxExtents); +#endif + } + } + + // Account for markers: + if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 && + static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) { + + float strokeWidth = nsSVGUtils::GetStrokeWidth(this); + MarkerProperties properties = GetMarkerProperties(this); + + if (properties.MarkersExist()) { + nsTArray<nsSVGMark> marks; + static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks); + uint32_t num = marks.Length(); + + // These are in the same order as the nsSVGMark::Type constants. + nsSVGMarkerFrame* markerFrames[] = { + properties.GetMarkerStartFrame(), + properties.GetMarkerMidFrame(), + properties.GetMarkerEndFrame(), + }; + static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount, + "Number of Marker frames should be equal to eTypeCount"); + + for (uint32_t i = 0; i < num; i++) { + nsSVGMark& mark = marks[i]; + nsSVGMarkerFrame* frame = markerFrames[mark.type]; + if (frame) { + SVGBBox mbbox = + frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this, + &marks[i], strokeWidth); + MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad"); + bbox.UnionEdges(mbbox); + } + } + } + } + + return bbox; +} + +//---------------------------------------------------------------------- +// nsSVGPathGeometryFrame methods: + +gfxMatrix +nsSVGPathGeometryFrame::GetCanvasTM() +{ + NS_ASSERTION(GetParent(), "null parent"); + + nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); + dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent); + + return content->PrependLocalTransformsTo(parent->GetCanvasTM()); +} + +nsSVGPathGeometryFrame::MarkerProperties +nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame) +{ + NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); + + MarkerProperties result; + nsCOMPtr<nsIURI> markerURL = + nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart); + result.mMarkerStart = + nsSVGEffects::GetMarkerProperty(markerURL, aFrame, + nsSVGEffects::MarkerBeginProperty()); + + markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid); + result.mMarkerMid = + nsSVGEffects::GetMarkerProperty(markerURL, aFrame, + nsSVGEffects::MarkerMiddleProperty()); + + markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd); + result.mMarkerEnd = + nsSVGEffects::GetMarkerProperty(markerURL, aFrame, + nsSVGEffects::MarkerEndProperty()); + return result; +} + +nsSVGMarkerFrame * +nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame() +{ + if (!mMarkerStart) + return nullptr; + return static_cast<nsSVGMarkerFrame *> + (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); +} + +nsSVGMarkerFrame * +nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame() +{ + if (!mMarkerMid) + return nullptr; + return static_cast<nsSVGMarkerFrame *> + (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); +} + +nsSVGMarkerFrame * +nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame() +{ + if (!mMarkerEnd) + return nullptr; + return static_cast<nsSVGMarkerFrame *> + (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); +} + +void +nsSVGPathGeometryFrame::Render(gfxContext* aContext, + uint32_t aRenderComponents, + const gfxMatrix& aNewTransform) +{ + MOZ_ASSERT(!aNewTransform.IsSingular()); + + DrawTarget* drawTarget = aContext->GetDrawTarget(); + + FillRule fillRule = + nsSVGUtils::ToFillRule((GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) ? + StyleSVG()->mClipRule : StyleSVG()->mFillRule); + + nsSVGPathGeometryElement* element = + static_cast<nsSVGPathGeometryElement*>(mContent); + + AntialiasMode aaMode = + (StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED || + StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ? + AntialiasMode::NONE : AntialiasMode::SUBPIXEL; + + // We wait as late as possible before setting the transform so that we don't + // set it unnecessarily if we return early (it's an expensive operation for + // some backends). + gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext); + aContext->SetMatrix(aNewTransform); + + if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { + // We don't complicate this code with GetAsSimplePath since the cost of + // masking will dwarf Path creation overhead anyway. + RefPtr<Path> path = element->GetOrBuildPath(*drawTarget, fillRule); + if (path) { + ColorPattern white(ToDeviceColor(Color(1.0f, 1.0f, 1.0f, 1.0f))); + drawTarget->Fill(path, white, + DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode)); + } + return; + } + + nsSVGPathGeometryElement::SimplePath simplePath; + RefPtr<Path> path; + + element->GetAsSimplePath(&simplePath); + if (!simplePath.IsPath()) { + path = element->GetOrBuildPath(*drawTarget, fillRule); + if (!path) { + return; + } + } + + SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent); + + if (aRenderComponents & eRenderFill) { + GeneralPattern fillPattern; + nsSVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, contextPaint); + if (fillPattern.GetPattern()) { + DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode); + if (simplePath.IsRect()) { + drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions); + } else if (path) { + drawTarget->Fill(path, fillPattern, drawOptions); + } + } + } + + if ((aRenderComponents & eRenderStroke) && + nsSVGUtils::HasStroke(this, contextPaint)) { + // Account for vector-effect:non-scaling-stroke: + gfxMatrix userToOuterSVG; + if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { + // A simple Rect can't be transformed with rotate/skew, so let's switch + // to using a real path: + if (!path) { + path = element->GetOrBuildPath(*drawTarget, fillRule); + if (!path) { + return; + } + simplePath.Reset(); + } + // We need to transform the path back into the appropriate ancestor + // coordinate system, and paint it it that coordinate system, in order + // for non-scaled stroke to paint correctly. + gfxMatrix outerSVGToUser = userToOuterSVG; + outerSVGToUser.Invert(); + aContext->Multiply(outerSVGToUser); + RefPtr<PathBuilder> builder = + path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule); + path = builder->Finish(); + } + GeneralPattern strokePattern; + nsSVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, contextPaint); + if (strokePattern.GetPattern()) { + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, + static_cast<nsSVGElement*>(mContent), + StyleContext(), contextPaint); + // GetStrokeOptions may set the line width to zero as an optimization + if (strokeOptions.mLineWidth <= 0) { + return; + } + DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode); + if (simplePath.IsRect()) { + drawTarget->StrokeRect(simplePath.AsRect(), strokePattern, + strokeOptions, drawOptions); + } else if (simplePath.IsLine()) { + drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(), + strokePattern, strokeOptions, drawOptions); + } else { + drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions); + } + } + } +} + +void +nsSVGPathGeometryFrame::PaintMarkers(gfxContext& aContext, + const gfxMatrix& aTransform) +{ + SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent); + + if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) { + MarkerProperties properties = GetMarkerProperties(this); + + if (properties.MarkersExist()) { + float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint); + + nsTArray<nsSVGMark> marks; + static_cast<nsSVGPathGeometryElement*> + (mContent)->GetMarkPoints(&marks); + + uint32_t num = marks.Length(); + if (num) { + // These are in the same order as the nsSVGMark::Type constants. + nsSVGMarkerFrame* markerFrames[] = { + properties.GetMarkerStartFrame(), + properties.GetMarkerMidFrame(), + properties.GetMarkerEndFrame(), + }; + static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount, + "Number of Marker frames should be equal to eTypeCount"); + + for (uint32_t i = 0; i < num; i++) { + nsSVGMark& mark = marks[i]; + nsSVGMarkerFrame* frame = markerFrames[mark.type]; + if (frame) { + frame->PaintMark(aContext, aTransform, this, &mark, strokeWidth); + } + } + } + } + } +} + +uint16_t +nsSVGPathGeometryFrame::GetHitTestFlags() +{ + return nsSVGUtils::GetGeometryHitTestFlags(this); +} diff --git a/layout/svg/nsSVGPathGeometryFrame.h b/layout/svg/nsSVGPathGeometryFrame.h new file mode 100644 index 0000000000..6b7c75d97c --- /dev/null +++ b/layout/svg/nsSVGPathGeometryFrame.h @@ -0,0 +1,147 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGPATHGEOMETRYFRAME_H__ +#define __NS_SVGPATHGEOMETRYFRAME_H__ + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsFrame.h" +#include "nsISVGChildFrame.h" +#include "nsLiteralString.h" +#include "nsQueryFrame.h" +#include "nsSVGUtils.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +class gfxContext; +class nsDisplaySVGPathGeometry; +class nsIAtom; +class nsIFrame; +class nsIPresShell; +class nsStyleContext; +class nsSVGMarkerFrame; +class nsSVGMarkerProperty; + +struct nsRect; + +class nsSVGPathGeometryFrame : public nsFrame + , public nsISVGChildFrame +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + + friend nsIFrame* + NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + friend class nsDisplaySVGPathGeometry; + +protected: + explicit nsSVGPathGeometryFrame(nsStyleContext* aContext) + : nsFrame(aContext) + { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_MAY_BE_TRANSFORMED); + } + +public: + NS_DECL_QUERYFRAME_TARGET(nsSVGPathGeometryFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame interface: + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGGeometry)); + } + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgPathGeometryFrame + */ + virtual nsIAtom* GetType() const override; + + virtual bool IsSVGTransformed(Matrix *aOwnTransforms = nullptr, + Matrix *aFromParentTransforms = nullptr) const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGPathGeometry"), aResult); + } +#endif + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + // nsSVGPathGeometryFrame methods + gfxMatrix GetCanvasTM(); +protected: + // nsISVGChildFrame interface: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) override; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + virtual nsRect GetCoveredRegion() override; + virtual void ReflowSVG() override; + virtual void NotifySVGChanged(uint32_t aFlags) override; + virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) override; + virtual bool IsDisplayContainer() override { return false; } + + /** + * This function returns a set of bit flags indicating which parts of the + * element (fill, stroke, bounds) should intercept pointer events. It takes + * into account the type of element and the value of the 'pointer-events' + * property on the element. + */ + virtual uint16_t GetHitTestFlags(); +private: + enum { eRenderFill = 1, eRenderStroke = 2 }; + void Render(gfxContext* aContext, uint32_t aRenderComponents, + const gfxMatrix& aTransform); + + /** + * @param aMatrix The transform that must be multiplied onto aContext to + * establish this frame's SVG user space. + */ + void PaintMarkers(gfxContext& aContext, const gfxMatrix& aMatrix); + + struct MarkerProperties { + nsSVGMarkerProperty* mMarkerStart; + nsSVGMarkerProperty* mMarkerMid; + nsSVGMarkerProperty* mMarkerEnd; + + bool MarkersExist() const { + return mMarkerStart || mMarkerMid || mMarkerEnd; + } + + nsSVGMarkerFrame *GetMarkerStartFrame(); + nsSVGMarkerFrame *GetMarkerMidFrame(); + nsSVGMarkerFrame *GetMarkerEndFrame(); + }; + + /** + * @param aFrame should be the first continuation + */ + static MarkerProperties GetMarkerProperties(nsSVGPathGeometryFrame *aFrame); +}; + +#endif // __NS_SVGPATHGEOMETRYFRAME_H__ diff --git a/layout/svg/nsSVGPatternFrame.cpp b/layout/svg/nsSVGPatternFrame.cpp new file mode 100644 index 0000000000..198163d7f5 --- /dev/null +++ b/layout/svg/nsSVGPatternFrame.cpp @@ -0,0 +1,748 @@ +/* -*- 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 "nsSVGPatternFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxMatrix.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsISVGChildFrame.h" +#include "nsStyleContext.h" +#include "nsSVGEffects.h" +#include "nsSVGPathGeometryFrame.h" +#include "mozilla/dom/SVGPatternElement.h" +#include "nsSVGUtils.h" +#include "nsSVGAnimatedTransformList.h" +#include "SVGContentUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +//---------------------------------------------------------------------- +// Helper classes + +class MOZ_RAII nsSVGPatternFrame::AutoPatternReferencer +{ +public: + explicit AutoPatternReferencer(nsSVGPatternFrame *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; + } + ~AutoPatternReferencer() { + mFrame->mLoopFlag = false; + } +private: + nsSVGPatternFrame *mFrame; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +//---------------------------------------------------------------------- +// Implementation + +nsSVGPatternFrame::nsSVGPatternFrame(nsStyleContext* aContext) + : nsSVGPaintServerFrame(aContext) + , mLoopFlag(false) + , mNoHRefURI(false) +{ +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGPatternFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult +nsSVGPatternFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::patternUnits || + aAttribute == nsGkAtoms::patternContentUnits || + aAttribute == nsGkAtoms::patternTransform || + aAttribute == nsGkAtoms::x || + aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::preserveAspectRatio || + aAttribute == nsGkAtoms::viewBox)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + 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); +} + +#ifdef DEBUG +void +nsSVGPatternFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::pattern), "Content is not an SVG pattern"); + + nsSVGPaintServerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom* +nsSVGPatternFrame::GetType() const +{ + return nsGkAtoms::svgPatternFrame; +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +// If our GetCanvasTM is getting called, we +// need to return *our current* transformation +// matrix, which depends on our units parameters +// and X, Y, Width, and Height +gfxMatrix +nsSVGPatternFrame::GetCanvasTM() +{ + if (mCTM) { + return *mCTM; + } + + // Do we know our rendering parent? + if (mSource) { + // Yes, use it! + return mSource->GetCanvasTM(); + } + + // We get here when geometry in the <pattern> container is updated + return gfxMatrix(); +} + +// ------------------------------------------------------------------------- +// Helper functions +// ------------------------------------------------------------------------- + +/** Calculate the maximum expansion of a matrix */ +static float +MaxExpansion(const Matrix &aMatrix) +{ + // maximum expansion derivation from + // http://lists.cairographics.org/archives/cairo/2004-October/001980.html + // and also implemented in cairo_matrix_transformed_circle_major_axis + double a = aMatrix._11; + double b = aMatrix._12; + double c = aMatrix._21; + double d = aMatrix._22; + double f = (a * a + b * b + c * c + d * d) / 2; + double g = (a * a + b * b - c * c - d * d) / 2; + double h = a * c + b * d; + return sqrt(f + sqrt(g * g + h * h)); +} + +// The SVG specification says that the 'patternContentUnits' attribute "has no effect if +// attribute ‘viewBox’ is specified". We still need to include a bbox scale +// if the viewBox is specified and _patternUnits_ is set to or defaults to +// objectBoundingBox though, since in that case the viewBox is relative to the bbox +static bool +IncludeBBoxScale(const nsSVGViewBox& aViewBox, + uint32_t aPatternContentUnits, uint32_t aPatternUnits) +{ + return (!aViewBox.IsExplicitlySet() && + aPatternContentUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) || + (aViewBox.IsExplicitlySet() && + aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX); +} + +// Given the matrix for the pattern element's own transform, this returns a +// combined matrix including the transforms applicable to its target. +static Matrix +GetPatternMatrix(uint16_t aPatternUnits, + const Matrix &patternTransform, + const gfxRect &bbox, + const gfxRect &callerBBox, + const Matrix &callerCTM) +{ + // We really want the pattern matrix to handle translations + gfxFloat minx = bbox.X(); + gfxFloat miny = bbox.Y(); + + if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + minx += callerBBox.X(); + miny += callerBBox.Y(); + } + + float scale = 1.0f / MaxExpansion(callerCTM); + Matrix patternMatrix = patternTransform; + patternMatrix.PreScale(scale, scale); + patternMatrix.PreTranslate(minx, miny); + + return patternMatrix; +} + +static nsresult +GetTargetGeometry(gfxRect *aBBox, + const nsSVGViewBox &aViewBox, + uint16_t aPatternContentUnits, + uint16_t aPatternUnits, + nsIFrame *aTarget, + const Matrix &aContextMatrix, + const gfxRect *aOverrideBounds) +{ + *aBBox = aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(aTarget); + + // Sanity check + if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits) && + (aBBox->Width() <= 0 || aBBox->Height() <= 0)) { + return NS_ERROR_FAILURE; + } + + // OK, now fix up the bounding box to reflect user coordinates + // We handle device unit scaling in pattern matrix + float scale = MaxExpansion(aContextMatrix); + if (scale <= 0) { + return NS_ERROR_FAILURE; + } + aBBox->Scale(scale); + return NS_OK; +} + +already_AddRefed<SourceSurface> +nsSVGPatternFrame::PaintPattern(const DrawTarget* aDrawTarget, + Matrix* patternMatrix, + const Matrix &aContextMatrix, + nsIFrame *aSource, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, + const gfxRect *aOverrideBounds) +{ + /* + * General approach: + * Set the content geometry stuff + * Calculate our bbox (using x,y,width,height & patternUnits & + * patternTransform) + * Create the surface + * Calculate the content transformation matrix + * Get our children (we may need to get them from another Pattern) + * Call SVGPaint on all of our children + * Return + */ + + nsSVGPatternFrame* patternWithChildren = GetPatternWithChildren(); + if (!patternWithChildren) { + return nullptr; // Either no kids or a bad reference + } + nsIFrame* firstKid = patternWithChildren->mFrames.FirstChild(); + + const nsSVGViewBox& viewBox = GetViewBox(); + + uint16_t patternContentUnits = + GetEnumValue(SVGPatternElement::PATTERNCONTENTUNITS); + uint16_t patternUnits = + GetEnumValue(SVGPatternElement::PATTERNUNITS); + + /* + * Get the content geometry information. This is a little tricky -- + * our parent is probably a <defs>, but we are rendering in the context + * of some geometry source. Our content geometry information needs to + * come from our rendering parent as opposed to our content parent. We + * get that information from aSource, which is passed to us from the + * backend renderer. + * + * There are three "geometries" that we need: + * 1) The bounding box for the pattern. We use this to get the + * width and height for the surface, and as the return to + * GetBBox. + * 2) The transformation matrix for the pattern. This is not *quite* + * the same as the canvas transformation matrix that we will + * provide to our rendering children since we "fudge" it a little + * to get the renderer to handle the translations correctly for us. + * 3) The CTM that we return to our children who make up the pattern. + */ + + // Get all of the information we need from our "caller" -- i.e. + // the geometry that is being rendered with a pattern + gfxRect callerBBox; + if (NS_FAILED(GetTargetGeometry(&callerBBox, + viewBox, + patternContentUnits, patternUnits, + aSource, + aContextMatrix, + aOverrideBounds))) { + return nullptr; + } + + // Construct the CTM that we will provide to our children when we + // render them into the tile. + gfxMatrix ctm = ConstructCTM(viewBox, patternContentUnits, patternUnits, + callerBBox, aContextMatrix, aSource); + if (ctm.IsSingular()) { + return nullptr; + } + + if (patternWithChildren->mCTM) { + *patternWithChildren->mCTM = ctm; + } else { + patternWithChildren->mCTM = new gfxMatrix(ctm); + } + + // Get the bounding box of the pattern. This will be used to determine + // the size of the surface, and will also be used to define the bounding + // box for the pattern tile. + gfxRect bbox = GetPatternRect(patternUnits, callerBBox, aContextMatrix, aSource); + if (bbox.Width() <= 0.0 || bbox.Height() <= 0.0) { + return nullptr; + } + + // Get the pattern transform + Matrix patternTransform = ToMatrix(GetPatternTransform()); + + // revert the vector effect transform so that the pattern appears unchanged + if (aFillOrStroke == &nsStyleSVG::mStroke) { + gfxMatrix userToOuterSVG; + if (nsSVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) { + patternTransform *= ToMatrix(userToOuterSVG); + if (patternTransform.IsSingular()) { + NS_WARNING("Singular matrix painting non-scaling-stroke"); + return nullptr; + } + } + } + + // Get the transformation matrix that we will hand to the renderer's pattern + // routine. + *patternMatrix = GetPatternMatrix(patternUnits, patternTransform, + bbox, callerBBox, aContextMatrix); + if (patternMatrix->IsSingular()) { + return nullptr; + } + + // Now that we have all of the necessary geometries, we can + // create our surface. + gfxRect transformedBBox = ThebesRect(patternTransform.TransformBounds(ToRect(bbox))); + + bool resultOverflows; + IntSize surfaceSize = + nsSVGUtils::ConvertToSurfaceSize( + transformedBBox.Size(), &resultOverflows); + + // 0 disables rendering, < 0 is an error + if (surfaceSize.width <= 0 || surfaceSize.height <= 0) { + return nullptr; + } + + gfxFloat patternWidth = bbox.Width(); + gfxFloat patternHeight = bbox.Height(); + + if (resultOverflows || + patternWidth != surfaceSize.width || + patternHeight != surfaceSize.height) { + // scale drawing to pattern surface size + gfxMatrix tempTM = + gfxMatrix(surfaceSize.width / patternWidth, 0.0, + 0.0, surfaceSize.height / patternHeight, + 0.0, 0.0); + patternWithChildren->mCTM->PreMultiply(tempTM); + + // and rescale pattern to compensate + patternMatrix->PreScale(patternWidth / surfaceSize.width, + patternHeight / surfaceSize.height); + } + + RefPtr<DrawTarget> dt = + aDrawTarget->CreateSimilarDrawTarget(surfaceSize, SurfaceFormat::B8G8R8A8); + if (!dt || !dt->IsValid()) { + return nullptr; + } + dt->ClearRect(Rect(0, 0, surfaceSize.width, surfaceSize.height)); + + RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(gfx); // already checked the draw target above + + if (aGraphicOpacity != 1.0f) { + gfx->Save(); + gfx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aGraphicOpacity); + } + + // OK, now render -- note that we use "firstKid", which + // we got at the beginning because it takes care of the + // referenced pattern situation for us + + if (aSource->IsFrameOfType(nsIFrame::eSVGGeometry)) { + // Set the geometrical parent of the pattern we are rendering + patternWithChildren->mSource = static_cast<nsSVGPathGeometryFrame*>(aSource); + } + + // Delay checking NS_FRAME_DRAWING_AS_PAINTSERVER bit until here so we can + // give back a clear surface if there's a loop + if (!(patternWithChildren->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)) { + patternWithChildren->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + for (nsIFrame* kid = firstKid; kid; + kid = kid->GetNextSibling()) { + // The CTM of each frame referencing us can be different + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); + } + gfxMatrix tm = *(patternWithChildren->mCTM); + if (kid->GetContent()->IsSVGElement()) { + tm = static_cast<nsSVGElement*>(kid->GetContent())-> + PrependLocalTransformsTo(tm, eUserSpaceToParent); + } + Unused << nsSVGUtils::PaintFrameWithEffects(kid, *gfx, tm); + } + patternWithChildren->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + } + + patternWithChildren->mSource = nullptr; + + if (aGraphicOpacity != 1.0f) { + gfx->PopGroupAndBlend(); + gfx->Restore(); + } + + // caller now owns the surface + return dt->Snapshot(); +} + +/* Will probably need something like this... */ +// How do we handle the insertion of a new frame? +// We really don't want to rerender this every time, +// do we? +nsSVGPatternFrame* +nsSVGPatternFrame::GetPatternWithChildren() +{ + // Do we have any children ourselves? + if (!mFrames.IsEmpty()) + return this; + + // No, see if we chain to someone who does + AutoPatternReferencer patternRef(this); + + nsSVGPatternFrame* next = GetReferencedPatternIfNotInUse(); + if (!next) + return nullptr; + + return next->GetPatternWithChildren(); +} + +uint16_t +nsSVGPatternFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault) +{ + nsSVGEnum& thisEnum = + static_cast<SVGPatternElement *>(mContent)->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) + return thisEnum.GetAnimValue(); + + AutoPatternReferencer patternRef(this); + + nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse(); + return next ? next->GetEnumValue(aIndex, aDefault) : + static_cast<SVGPatternElement *>(aDefault)-> + mEnumAttributes[aIndex].GetAnimValue(); +} + +nsSVGAnimatedTransformList* +nsSVGPatternFrame::GetPatternTransformList(nsIContent* aDefault) +{ + nsSVGAnimatedTransformList *thisTransformList = + static_cast<SVGPatternElement *>(mContent)->GetAnimatedTransformList(); + + if (thisTransformList && thisTransformList->IsExplicitlySet()) + return thisTransformList; + + AutoPatternReferencer patternRef(this); + + nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse(); + return next ? next->GetPatternTransformList(aDefault) : + static_cast<SVGPatternElement *>(aDefault)->mPatternTransform.get(); +} + +gfxMatrix +nsSVGPatternFrame::GetPatternTransform() +{ + nsSVGAnimatedTransformList* animTransformList = + GetPatternTransformList(mContent); + if (!animTransformList) + return gfxMatrix(); + + return animTransformList->GetAnimValue().GetConsolidationMatrix(); +} + +const nsSVGViewBox & +nsSVGPatternFrame::GetViewBox(nsIContent* aDefault) +{ + const nsSVGViewBox &thisViewBox = + static_cast<SVGPatternElement *>(mContent)->mViewBox; + + if (thisViewBox.IsExplicitlySet()) + return thisViewBox; + + AutoPatternReferencer patternRef(this); + + nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse(); + return next ? next->GetViewBox(aDefault) : + static_cast<SVGPatternElement *>(aDefault)->mViewBox; +} + +const SVGAnimatedPreserveAspectRatio & +nsSVGPatternFrame::GetPreserveAspectRatio(nsIContent *aDefault) +{ + const SVGAnimatedPreserveAspectRatio &thisPar = + static_cast<SVGPatternElement *>(mContent)->mPreserveAspectRatio; + + if (thisPar.IsExplicitlySet()) + return thisPar; + + AutoPatternReferencer patternRef(this); + + nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse(); + return next ? next->GetPreserveAspectRatio(aDefault) : + static_cast<SVGPatternElement *>(aDefault)->mPreserveAspectRatio; +} + +const nsSVGLength2 * +nsSVGPatternFrame::GetLengthValue(uint32_t aIndex, nsIContent *aDefault) +{ + const nsSVGLength2 *thisLength = + &static_cast<SVGPatternElement *>(mContent)->mLengthAttributes[aIndex]; + + if (thisLength->IsExplicitlySet()) + return thisLength; + + AutoPatternReferencer patternRef(this); + + nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse(); + return next ? next->GetLengthValue(aIndex, aDefault) : + &static_cast<SVGPatternElement *>(aDefault)->mLengthAttributes[aIndex]; +} + +// Private (helper) methods +nsSVGPatternFrame * +nsSVGPatternFrame::GetReferencedPattern() +{ + if (mNoHRefURI) + return nullptr; + + nsSVGPaintingProperty *property = + Properties().Get(nsSVGEffects::HrefAsPaintingProperty()); + + if (!property) { + // Fetch our pattern element's href or xlink:href attribute + SVGPatternElement *pattern = static_cast<SVGPatternElement *>(mContent); + nsAutoString href; + if (pattern->mStringAttributes[SVGPatternElement::HREF].IsExplicitlySet()) { + pattern->mStringAttributes[SVGPatternElement::HREF] + .GetAnimValue(href, pattern); + } else { + pattern->mStringAttributes[SVGPatternElement::XLINK_HREF] + .GetAnimValue(href, pattern); + } + + 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::svgPatternFrame) + return nullptr; + + return static_cast<nsSVGPatternFrame*>(result); +} + +nsSVGPatternFrame * +nsSVGPatternFrame::GetReferencedPatternIfNotInUse() +{ + nsSVGPatternFrame *referenced = GetReferencedPattern(); + if (!referenced) + return nullptr; + + if (referenced->mLoopFlag) { + // XXXjwatt: we should really send an error to the JavaScript Console here: + NS_WARNING("pattern reference loop detected while inheriting attribute!"); + return nullptr; + } + + return referenced; +} + +gfxRect +nsSVGPatternFrame::GetPatternRect(uint16_t aPatternUnits, + const gfxRect &aTargetBBox, + const Matrix &aTargetCTM, + nsIFrame *aTarget) +{ + // We need to initialize our box + float x,y,width,height; + + // Get the pattern x,y,width, and height + const nsSVGLength2 *tmpX, *tmpY, *tmpHeight, *tmpWidth; + tmpX = GetLengthValue(SVGPatternElement::ATTR_X); + tmpY = GetLengthValue(SVGPatternElement::ATTR_Y); + tmpHeight = GetLengthValue(SVGPatternElement::ATTR_HEIGHT); + tmpWidth = GetLengthValue(SVGPatternElement::ATTR_WIDTH); + + if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + x = nsSVGUtils::ObjectSpace(aTargetBBox, tmpX); + y = nsSVGUtils::ObjectSpace(aTargetBBox, tmpY); + width = nsSVGUtils::ObjectSpace(aTargetBBox, tmpWidth); + height = nsSVGUtils::ObjectSpace(aTargetBBox, tmpHeight); + } else { + float scale = MaxExpansion(aTargetCTM); + x = nsSVGUtils::UserSpace(aTarget, tmpX) * scale; + y = nsSVGUtils::UserSpace(aTarget, tmpY) * scale; + width = nsSVGUtils::UserSpace(aTarget, tmpWidth) * scale; + height = nsSVGUtils::UserSpace(aTarget, tmpHeight) * scale; + } + + return gfxRect(x, y, width, height); +} + +gfxMatrix +nsSVGPatternFrame::ConstructCTM(const nsSVGViewBox& aViewBox, + uint16_t aPatternContentUnits, + uint16_t aPatternUnits, + const gfxRect &callerBBox, + const Matrix &callerCTM, + nsIFrame *aTarget) +{ + SVGSVGElement *ctx = nullptr; + nsIContent* targetContent = aTarget->GetContent(); + gfxFloat scaleX, scaleY; + + // The objectBoundingBox conversion must be handled in the CTM: + if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits)) { + scaleX = callerBBox.Width(); + scaleY = callerBBox.Height(); + } else { + if (targetContent->IsSVGElement()) { + ctx = static_cast<nsSVGElement*>(targetContent)->GetCtx(); + } + scaleX = scaleY = MaxExpansion(callerCTM); + } + + if (!aViewBox.IsExplicitlySet()) { + return gfxMatrix(scaleX, 0.0, 0.0, scaleY, 0.0, 0.0); + } + const nsSVGViewBoxRect viewBoxRect = aViewBox.GetAnimValue(); + + if (viewBoxRect.height <= 0.0f || viewBoxRect.width <= 0.0f) { + return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + + float viewportWidth, viewportHeight; + if (targetContent->IsSVGElement()) { + // If we're dealing with an SVG target only retrieve the context once. + // Calling the nsIFrame* variant of GetAnimValue would look it up on + // every call. + viewportWidth = + GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(ctx); + viewportHeight = + GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(ctx); + } else { + // No SVG target, call the nsIFrame* variant of GetAnimValue. + viewportWidth = + GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(aTarget); + viewportHeight = + GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(aTarget); + } + + if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) { + return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + + Matrix tm = SVGContentUtils::GetViewBoxTransform( + viewportWidth * scaleX, viewportHeight * scaleY, + viewBoxRect.x, viewBoxRect.y, + viewBoxRect.width, viewBoxRect.height, + GetPreserveAspectRatio()); + + return ThebesMatrix(tm); +} + +//---------------------------------------------------------------------- +// nsSVGPaintServerFrame methods: + +already_AddRefed<gfxPattern> +nsSVGPatternFrame::GetPaintServerPattern(nsIFrame *aSource, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, + const gfxRect *aOverrideBounds) +{ + if (aGraphicOpacity == 0.0f) { + RefPtr<gfxPattern> pattern = new gfxPattern(Color()); + return pattern.forget(); + } + + // Paint it! + Matrix pMatrix; + RefPtr<SourceSurface> surface = + PaintPattern(aDrawTarget, &pMatrix, ToMatrix(aContextMatrix), aSource, + aFillOrStroke, aGraphicOpacity, aOverrideBounds); + + if (!surface) { + return nullptr; + } + + RefPtr<gfxPattern> pattern = new gfxPattern(surface, pMatrix); + + if (!pattern || pattern->CairoStatus()) + return nullptr; + + pattern->SetExtend(ExtendMode::REPEAT); + return pattern.forget(); +} + +// ------------------------------------------------------------------------- +// Public functions +// ------------------------------------------------------------------------- + +nsIFrame* NS_NewSVGPatternFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGPatternFrame(aContext); +} + diff --git a/layout/svg/nsSVGPatternFrame.h b/layout/svg/nsSVGPatternFrame.h new file mode 100644 index 0000000000..7c3cd1ad18 --- /dev/null +++ b/layout/svg/nsSVGPatternFrame.h @@ -0,0 +1,154 @@ +/* -*- 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/. */ + +#ifndef __NS_SVGPATTERNFRAME_H__ +#define __NS_SVGPATTERNFRAME_H__ + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsAutoPtr.h" +#include "nsSVGPaintServerFrame.h" + +class nsIFrame; +class nsSVGLength2; +class nsSVGPathGeometryFrame; +class nsSVGViewBox; + +namespace mozilla { +class SVGAnimatedPreserveAspectRatio; +class nsSVGAnimatedTransformList; +} // namespace mozilla + +/** + * Patterns can refer to other patterns. We create an nsSVGPaintingProperty + * with property type nsGkAtoms::href to track the referenced pattern. + */ +class nsSVGPatternFrame : public nsSVGPaintServerFrame +{ + typedef mozilla::gfx::SourceSurface SourceSurface; + +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewSVGPatternFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + explicit nsSVGPatternFrame(nsStyleContext* aContext); + + // nsSVGPaintServerFrame methods: + virtual already_AddRefed<gfxPattern> + GetPaintServerPattern(nsIFrame *aSource, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aOpacity, + const gfxRect *aOverrideBounds) override; + +public: + typedef mozilla::SVGAnimatedPreserveAspectRatio SVGAnimatedPreserveAspectRatio; + + // nsSVGContainerFrame methods: + virtual gfxMatrix GetCanvasTM() override; + + // nsIFrame interface: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgPatternFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGPattern"), aResult); + } +#endif // DEBUG + +protected: + // Internal methods for handling referenced patterns + class AutoPatternReferencer; + nsSVGPatternFrame* GetReferencedPattern(); + nsSVGPatternFrame* GetReferencedPatternIfNotInUse(); + + // Accessors to lookup pattern attributes + uint16_t GetEnumValue(uint32_t aIndex, nsIContent *aDefault); + uint16_t GetEnumValue(uint32_t aIndex) + { + return GetEnumValue(aIndex, mContent); + } + mozilla::nsSVGAnimatedTransformList* GetPatternTransformList( + nsIContent* aDefault); + gfxMatrix GetPatternTransform(); + const nsSVGViewBox &GetViewBox(nsIContent *aDefault); + const nsSVGViewBox &GetViewBox() { return GetViewBox(mContent); } + const SVGAnimatedPreserveAspectRatio &GetPreserveAspectRatio( + nsIContent *aDefault); + const SVGAnimatedPreserveAspectRatio &GetPreserveAspectRatio() + { + return GetPreserveAspectRatio(mContent); + } + const nsSVGLength2 *GetLengthValue(uint32_t aIndex, nsIContent *aDefault); + const nsSVGLength2 *GetLengthValue(uint32_t aIndex) + { + return GetLengthValue(aIndex, mContent); + } + + already_AddRefed<SourceSurface> + PaintPattern(const DrawTarget* aDrawTarget, + Matrix *patternMatrix, + const Matrix &aContextMatrix, + nsIFrame *aSource, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, + const gfxRect *aOverrideBounds); + + /** + * A <pattern> element may reference another <pattern> element using + * xlink:href and, if it doesn't have any child content of its own, then it + * will "inherit" the children of the referenced pattern (which may itself be + * inheriting its children if it references another <pattern>). This + * function returns this nsSVGPatternFrame or the first pattern along the + * reference chain (if there is one) to have children. + */ + nsSVGPatternFrame* GetPatternWithChildren(); + + gfxRect GetPatternRect(uint16_t aPatternUnits, + const gfxRect &bbox, + const Matrix &callerCTM, + nsIFrame *aTarget); + gfxMatrix ConstructCTM(const nsSVGViewBox& aViewBox, + uint16_t aPatternContentUnits, + uint16_t aPatternUnits, + const gfxRect &callerBBox, + const Matrix &callerCTM, + nsIFrame *aTarget); + +private: + // this is a *temporary* reference to the frame of the element currently + // referencing our pattern. This must be temporary because different + // referencing frames will all reference this one frame + nsSVGPathGeometryFrame *mSource; + nsAutoPtr<gfxMatrix> mCTM; + +protected: + // This flag is used to detect loops in xlink:href processing + bool mLoopFlag; + bool mNoHRefURI; +}; + +#endif diff --git a/layout/svg/nsSVGStopFrame.cpp b/layout/svg/nsSVGStopFrame.cpp new file mode 100644 index 0000000000..1b75fa1028 --- /dev/null +++ b/layout/svg/nsSVGStopFrame.cpp @@ -0,0 +1,116 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsContainerFrame.h" +#include "nsFrame.h" +#include "nsGkAtoms.h" +#include "nsStyleContext.h" +#include "nsSVGEffects.h" + +// This is a very simple frame whose only purpose is to capture style change +// events and propagate them to the parent. Most of the heavy lifting is done +// within the nsSVGGradientFrame, which is the parent for this frame + +class nsSVGStopFrame : public nsFrame +{ + friend nsIFrame* + NS_NewSVGStopFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGStopFrame(nsStyleContext* aContext) + : nsFrame(aContext) + { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + +public: + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame interface: +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgStopFrame + */ + virtual nsIAtom* GetType() const override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGStop"), aResult); + } +#endif +}; + +//---------------------------------------------------------------------- +// Implementation + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGStopFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +#ifdef DEBUG +void +nsSVGStopFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::stop), "Content is not a stop element"); + + nsFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGStopFrame::GetType() const +{ + return nsGkAtoms::svgStopFrame; +} + +nsresult +nsSVGStopFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::offset) { + MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgLinearGradientFrame || + GetParent()->GetType() == nsGkAtoms::svgRadialGradientFrame, + "Observers observe the gradient, so that's what we must invalidate"); + nsSVGEffects::InvalidateDirectRenderingObservers(GetParent()); + } + + return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +// ------------------------------------------------------------------------- +// Public functions +// ------------------------------------------------------------------------- + +nsIFrame* NS_NewSVGStopFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGStopFrame(aContext); +} diff --git a/layout/svg/nsSVGSwitchFrame.cpp b/layout/svg/nsSVGSwitchFrame.cpp new file mode 100644 index 0000000000..26e77071b9 --- /dev/null +++ b/layout/svg/nsSVGSwitchFrame.cpp @@ -0,0 +1,268 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "gfxRect.h" +#include "nsSVGEffects.h" +#include "nsSVGGFrame.h" +#include "mozilla/dom/SVGSwitchElement.h" +#include "nsSVGUtils.h" + +using namespace mozilla::gfx; + +class nsSVGSwitchFrame : public nsSVGGFrame +{ + friend nsIFrame* + NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +protected: + explicit nsSVGSwitchFrame(nsStyleContext* aContext) + : nsSVGGFrame(aContext) {} + +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgSwitchFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGSwitch"), aResult); + } +#endif + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + // nsISVGChildFrame interface: + virtual DrawResult PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + nsRect GetCoveredRegion() override; + virtual void ReflowSVG() override; + virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) override; + +private: + nsIFrame *GetActiveChildFrame(); +}; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGSwitchFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGSwitchFrame) + +#ifdef DEBUG +void +nsSVGSwitchFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch), + "Content is not an SVG switch"); + + nsSVGGFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGSwitchFrame::GetType() const +{ + return nsGkAtoms::svgSwitchFrame; +} + +void +nsSVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsIFrame* kid = GetActiveChildFrame(); + if (kid) { + BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + } +} + +DrawResult +nsSVGSwitchFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) +{ + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + if (StyleEffects()->mOpacity == 0.0) + return DrawResult::SUCCESS; + + DrawResult result = DrawResult::SUCCESS; + nsIFrame *kid = GetActiveChildFrame(); + if (kid) { + gfxMatrix tm = aTransform; + if (kid->GetContent()->IsSVGElement()) { + tm = static_cast<nsSVGElement*>(kid->GetContent())-> + PrependLocalTransformsTo(tm, eUserSpaceToParent); + } + result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, tm, aDirtyRect); + } + return result; +} + + +nsIFrame* +nsSVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) +{ + NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY), + "If display lists are enabled, only hit-testing of non-display " + "SVG should take this code path"); + + nsIFrame *kid = GetActiveChildFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(kid); + if (svgFrame) { + // Transform the point from our SVG user space to our child's. + gfxPoint point = aPoint; + gfxMatrix m = + static_cast<const nsSVGElement*>(mContent)-> + PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); + m = static_cast<const nsSVGElement*>(kid->GetContent())-> + PrependLocalTransformsTo(m, eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.Transform(point); + } + return svgFrame->GetFrameForPoint(point); + } + + return nullptr; +} + +nsRect +nsSVGSwitchFrame::GetCoveredRegion() +{ + nsRect rect; + + nsIFrame *kid = GetActiveChildFrame(); + nsISVGChildFrame* child = do_QueryFrame(kid); + if (child) { + rect = child->GetCoveredRegion(); + } + return rect; +} + +void +nsSVGSwitchFrame::ReflowSVG() +{ + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!nsSVGUtils::NeedsReflowSVG(this)) { + return; + } + + // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, + // then our outer-<svg> has previously had its initial reflow. In that case + // we need to make sure that that bit has been removed from ourself _before_ + // recursing over our children to ensure that they know too. Otherwise, we + // need to remove it _after_ recursing over our children so that they know + // the initial reflow is currently underway. + + bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW); + + bool outerSVGHasHadFirstReflow = + (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0; + + if (outerSVGHasHadFirstReflow) { + mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children + } + + nsOverflowAreas overflowRects; + + nsIFrame *child = GetActiveChildFrame(); + nsISVGChildFrame* svgChild = do_QueryFrame(child); + if (svgChild) { + MOZ_ASSERT(!(child->GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "Check for this explicitly in the |if|, then"); + svgChild->ReflowSVG(); + + // We build up our child frame overflows here instead of using + // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same + // frame list, and we're iterating over that list now anyway. + ConsiderChildOverflow(overflowRects, child); + } + + if (isFirstReflow) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + nsSVGEffects::UpdateEffects(this); + } + + FinishAndStoreOverflow(overflowRects, mRect.Size()); + + // Remove state bits after FinishAndStoreOverflow so that it doesn't + // invalidate on first reflow: + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +SVGBBox +nsSVGSwitchFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags) +{ + nsIFrame* kid = GetActiveChildFrame(); + nsISVGChildFrame* svgKid = do_QueryFrame(kid); + if (svgKid) { + nsIContent *content = kid->GetContent(); + gfxMatrix transform = ThebesMatrix(aToBBoxUserspace); + if (content->IsSVGElement()) { + transform = static_cast<nsSVGElement*>(content)-> + PrependLocalTransformsTo(transform); + } + return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags); + } + return SVGBBox(); +} + +nsIFrame * +nsSVGSwitchFrame::GetActiveChildFrame() +{ + nsIContent *activeChild = + static_cast<mozilla::dom::SVGSwitchElement*>(mContent)->GetActiveChild(); + + if (activeChild) { + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + + if (activeChild == kid->GetContent()) { + return kid; + } + } + } + return nullptr; +} diff --git a/layout/svg/nsSVGUseFrame.cpp b/layout/svg/nsSVGUseFrame.cpp new file mode 100644 index 0000000000..118d24f691 --- /dev/null +++ b/layout/svg/nsSVGUseFrame.cpp @@ -0,0 +1,259 @@ +/* -*- 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/. */ + +// Keep in (case-insensitive) order: +#include "nsIAnonymousContentCreator.h" +#include "nsSVGEffects.h" +#include "nsSVGGFrame.h" +#include "mozilla/dom/SVGUseElement.h" +#include "nsContentList.h" + +using namespace mozilla::dom; + +class nsSVGUseFrame : public nsSVGGFrame + , public nsIAnonymousContentCreator +{ + friend nsIFrame* + NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +protected: + explicit nsSVGUseFrame(nsStyleContext* aContext) + : nsSVGGFrame(aContext) + , mHasValidDimensions(true) + {} + +public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIFrame interface: + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::svgUseFrame + */ + virtual nsIAtom* GetType() const override; + + virtual bool IsLeaf() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGUse"), aResult); + } +#endif + + // nsISVGChildFrame interface: + virtual void ReflowSVG() override; + virtual void NotifySVGChanged(uint32_t aFlags) override; + + // nsIAnonymousContentCreator + virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, + uint32_t aFilter) override; + +private: + bool mHasValidDimensions; +}; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* +NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGUseFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGUseFrame) + +nsIAtom * +nsSVGUseFrame::GetType() const +{ + return nsGkAtoms::svgUseFrame; +} + +//---------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(nsSVGUseFrame) + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) +NS_QUERYFRAME_TAIL_INHERITING(nsSVGGFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +void +nsSVGUseFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::use), + "Content is not an SVG use!"); + + mHasValidDimensions = + static_cast<SVGUseElement*>(aContent)->HasValidDimensions(); + + nsSVGGFrame::Init(aContent, aParent, aPrevInFlow); +} + +nsresult +nsSVGUseFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + SVGUseElement *useElement = static_cast<SVGUseElement*>(mContent); + + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + nsLayoutUtils::PostRestyleEvent( + useElement, nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED); + } else if (aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height) { + bool invalidate = false; + if (mHasValidDimensions != useElement->HasValidDimensions()) { + mHasValidDimensions = !mHasValidDimensions; + invalidate = true; + } + if (useElement->OurWidthAndHeightAreUsed()) { + invalidate = true; + useElement->SyncWidthOrHeight(aAttribute); + } + if (invalidate) { + nsLayoutUtils::PostRestyleEvent( + useElement, nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + } + } + } + + if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // we're changing our nature, clear out the clone information + nsLayoutUtils::PostRestyleEvent( + useElement, nsRestyleHint(0), + nsChangeHint_InvalidateRenderingObservers); + nsSVGUtils::ScheduleReflowSVG(this); + useElement->mOriginal = nullptr; + useElement->UnlinkSource(); + useElement->TriggerReclone(); + } + + return nsSVGGFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +void +nsSVGUseFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + RefPtr<SVGUseElement> use = static_cast<SVGUseElement*>(mContent); + nsSVGGFrame::DestroyFrom(aDestructRoot); + use->DestroyAnonymousContent(); +} + +bool +nsSVGUseFrame::IsLeaf() const +{ + return true; +} + + +//---------------------------------------------------------------------- +// nsISVGChildFrame methods + +void +nsSVGUseFrame::ReflowSVG() +{ + // We only handle x/y offset here, since any width/height that is in force is + // handled by the nsSVGOuterSVGFrame for the anonymous <svg> that will be + // created for that purpose. + float x, y; + static_cast<SVGUseElement*>(mContent)-> + GetAnimatedLengthValues(&x, &y, nullptr); + mRect.MoveTo(nsLayoutUtils::RoundGfxRectToAppRect( + gfxRect(x, y, 0.0, 0.0), + PresContext()->AppUnitsPerCSSPixel()).TopLeft()); + + // If we have a filter, we need to invalidate ourselves because filter + // output can change even if none of our descendants need repainting. + if (StyleEffects()->HasFilters()) { + InvalidateFrame(); + } + + nsSVGGFrame::ReflowSVG(); +} + +void +nsSVGUseFrame::NotifySVGChanged(uint32_t aFlags) +{ + if (aFlags & COORD_CONTEXT_CHANGED && + !(aFlags & TRANSFORM_CHANGED)) { + // Coordinate context changes affect mCanvasTM if we have a + // percentage 'x' or 'y' + SVGUseElement *use = static_cast<SVGUseElement*>(mContent); + if (use->mLengthAttributes[SVGUseElement::ATTR_X].IsPercentage() || + use->mLengthAttributes[SVGUseElement::ATTR_Y].IsPercentage()) { + aFlags |= TRANSFORM_CHANGED; + // Ancestor changes can't affect how we render from the perspective of + // any rendering observers that we may have, so we don't need to + // invalidate them. We also don't need to invalidate ourself, since our + // changed ancestor will have invalidated its entire area, which includes + // our area. + // For perf reasons we call this before calling NotifySVGChanged() below. + nsSVGUtils::ScheduleReflowSVG(this); + } + } + + // We don't remove the TRANSFORM_CHANGED flag here if we have a viewBox or + // non-percentage width/height, since if they're set then they are cloned to + // an anonymous child <svg>, and its nsSVGInnerSVGFrame will do that. + + nsSVGGFrame::NotifySVGChanged(aFlags); +} + +//---------------------------------------------------------------------- +// nsIAnonymousContentCreator methods: + +nsresult +nsSVGUseFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) +{ + SVGUseElement *use = static_cast<SVGUseElement*>(mContent); + + nsIContent* clone = use->CreateAnonymousContent(); + nsLayoutUtils::PostRestyleEvent( + use, nsRestyleHint(0), nsChangeHint_InvalidateRenderingObservers); + if (!clone) + return NS_ERROR_FAILURE; + if (!aElements.AppendElement(clone)) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +void +nsSVGUseFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, + uint32_t aFilter) +{ + SVGUseElement *use = static_cast<SVGUseElement*>(mContent); + nsIContent* clone = use->GetAnonymousContent(); + if (clone) { + aElements.AppendElement(clone); + } +} diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp new file mode 100644 index 0000000000..ff74d5bafc --- /dev/null +++ b/layout/svg/nsSVGUtils.cpp @@ -0,0 +1,1829 @@ +/* -*- 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: +// This is also necessary to ensure our definition of M_SQRT1_2 is picked up +#include "nsSVGUtils.h" +#include <algorithm> + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxMatrix.h" +#include "gfxPlatform.h" +#include "gfxRect.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "mozilla/Preferences.h" +#include "mozilla/SVGContextPaint.h" +#include "nsCSSClipPathInstance.h" +#include "nsCSSFrameConstructor.h" +#include "nsDisplayList.h" +#include "nsFilterInstance.h" +#include "nsFrameList.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIFrame.h" +#include "nsIPresShell.h" +#include "nsISVGChildFrame.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsStyleCoord.h" +#include "nsStyleStruct.h" +#include "nsSVGClipPathFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGEffects.h" +#include "nsSVGFilterPaintCallback.h" +#include "nsSVGForeignObjectFrame.h" +#include "nsSVGInnerSVGFrame.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGLength2.h" +#include "nsSVGMaskFrame.h" +#include "nsSVGOuterSVGFrame.h" +#include "mozilla/dom/SVGClipPathElement.h" +#include "mozilla/dom/SVGPathElement.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGPathGeometryFrame.h" +#include "nsSVGPaintServerFrame.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsTextFrame.h" +#include "SVGContentUtils.h" +#include "SVGTextFrame.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +static bool sSVGPathCachingEnabled; +static bool sSVGDisplayListHitTestingEnabled; +static bool sSVGDisplayListPaintingEnabled; +static bool sSVGNewGetBBoxEnabled; + +bool +NS_SVGPathCachingEnabled() +{ + return sSVGPathCachingEnabled; +} + +bool +NS_SVGDisplayListHitTestingEnabled() +{ + return sSVGDisplayListHitTestingEnabled; +} + +bool +NS_SVGDisplayListPaintingEnabled() +{ + return sSVGDisplayListPaintingEnabled; +} + +bool +NS_SVGNewGetBBoxEnabled() +{ + return sSVGNewGetBBoxEnabled; +} + + +// we only take the address of this: +static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey; + +SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mDrawTarget(aDrawTarget) + , mOriginalRenderState(nullptr) + , mPaintingToWindow(false) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mOriginalRenderState = + aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); + // We always remove ourselves from aContext before it dies, so + // passing nullptr as the destroy function is okay. + aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); +} + +SVGAutoRenderState::~SVGAutoRenderState() +{ + mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); + if (mOriginalRenderState) { + mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, + mOriginalRenderState, nullptr); + } +} + +void +SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) +{ + mPaintingToWindow = aPaintingToWindow; +} + +/* static */ bool +SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) +{ + void *state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey); + if (state) { + return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow; + } + return false; +} + +void +nsSVGUtils::Init() +{ + Preferences::AddBoolVarCache(&sSVGPathCachingEnabled, + "svg.path-caching.enabled"); + + Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled, + "svg.display-lists.hit-testing.enabled"); + + Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled, + "svg.display-lists.painting.enabled"); + + Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled, + "svg.new-getBBox.enabled"); +} + +nsSVGDisplayContainerFrame* +nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame) +{ + NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); + if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { + return nullptr; + } + while ((aFrame = aFrame->GetParent())) { + NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); + if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame || + aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { + return do_QueryFrame(aFrame); + } + } + NS_NOTREACHED("This is not reached. It's only needed to compile."); + return nullptr; +} + +nsRect +nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame, + const nsRect &aPreFilterRect) +{ + MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT, + "Called on invalid frame type"); + + nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); + if (!property || !property->ReferencesValidResources()) { + return aPreFilterRect; + } + + return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect); +} + +bool +nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame) +{ + return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); +} + +bool +nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) +{ + nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); + do { + if (outer->IsCallingReflowSVG()) { + return true; + } + outer = GetOuterSVGFrame(outer->GetParent()); + } while (outer); + return false; +} + +void +nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame) +{ + MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), + "Passed bad frame!"); + + // If this is triggered, the callers should be fixed to call us before + // ReflowSVG is called. If we try to mark dirty bits on frames while we're + // in the process of removing them, things will get messed up. + NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), + "Do not call under nsISVGChildFrame::ReflowSVG!"); + + // We don't call nsSVGEffects::InvalidateRenderingObservers here because + // we should only be called under InvalidateAndScheduleReflowSVG (which + // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames + // (at which point the frame has no observers). + + if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + return; + } + + if (aFrame->GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { + // Nothing to do if we're already dirty, or if the outer-<svg> + // hasn't yet had its initial reflow. + return; + } + + nsSVGOuterSVGFrame *outerSVGFrame = nullptr; + + // We must not add dirty bits to the nsSVGOuterSVGFrame or else + // PresShell::FrameNeedsReflow won't work when we pass it in below. + if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { + outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame); + } else { + aFrame->AddStateBits(NS_FRAME_IS_DIRTY); + + nsIFrame *f = aFrame->GetParent(); + while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { + if (f->GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { + return; + } + f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + f = f->GetParent(); + MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG), + "NS_STATE_IS_OUTER_SVG check above not valid!"); + } + + outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f); + + MOZ_ASSERT(outerSVGFrame && + outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame, + "Did not find nsSVGOuterSVGFrame!"); + } + + if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { + // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no + // need to call PresShell::FrameNeedsReflow, since we have an + // nsSVGOuterSVGFrame::DidReflow call pending. + return; + } + + nsFrameState dirtyBit = + (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); + + aFrame->PresContext()->PresShell()->FrameNeedsReflow( + outerSVGFrame, nsIPresShell::eResize, dirtyBit); +} + +bool +nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame) +{ + MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG uses bits differently!"); + + // The flags we test here may change, hence why we have this separate + // function. + return NS_SUBTREE_DIRTY(aFrame); +} + +void +nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame) +{ + MOZ_ASSERT(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), + "Not expecting to be called on the outer SVG Frame"); + + aFrame = aFrame->GetParent(); + + while (aFrame) { + if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) + return; + + nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); + if (property) { + property->Invalidate(); + } + aFrame = aFrame->GetParent(); + } +} + +Size +nsSVGUtils::GetContextSize(const nsIFrame* aFrame) +{ + Size size; + + MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); + const nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent()); + + SVGSVGElement* ctx = element->GetCtx(); + if (ctx) { + size.width = ctx->GetLength(SVGContentUtils::X); + size.height = ctx->GetLength(SVGContentUtils::Y); + } + return size; +} + +float +nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength) +{ + float axis; + + switch (aLength->GetCtxType()) { + case SVGContentUtils::X: + axis = aRect.Width(); + break; + case SVGContentUtils::Y: + axis = aRect.Height(); + break; + case SVGContentUtils::XY: + axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( + aRect.Width(), aRect.Height())); + break; + default: + NS_NOTREACHED("unexpected ctx type"); + axis = 0.0f; + break; + } + if (aLength->IsPercentage()) { + // Multiply first to avoid precision errors: + return axis * aLength->GetAnimValInSpecifiedUnits() / 100; + } + return aLength->GetAnimValue(static_cast<SVGSVGElement*>(nullptr)) * axis; +} + +float +nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength) +{ + return aLength->GetAnimValue(aSVGElement); +} + +float +nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength) +{ + return aLength->GetAnimValue(aNonSVGContext); +} + +float +nsSVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, const nsSVGLength2 *aLength) +{ + return aLength->GetAnimValue(aMetrics); +} + +nsSVGOuterSVGFrame * +nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) +{ + while (aFrame) { + if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { + return static_cast<nsSVGOuterSVGFrame*>(aFrame); + } + aFrame = aFrame->GetParent(); + } + + return nullptr; +} + +nsIFrame* +nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) +{ + nsISVGChildFrame* svg = do_QueryFrame(aFrame); + if (!svg) + return nullptr; + nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); + if (outer == svg) { + return nullptr; + } + nsMargin bp = outer->GetUsedBorderAndPadding(); + *aRect = ((aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ? + nsRect(0, 0, 0, 0) : svg->GetCoveredRegion()) + + nsPoint(bp.left, bp.top); + return outer; +} + +gfxMatrix +nsSVGUtils::GetCanvasTM(nsIFrame *aFrame) +{ + // XXX yuck, we really need a common interface for GetCanvasTM + + if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { + return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); + } + + nsIAtom* type = aFrame->GetType(); + if (type == nsGkAtoms::svgForeignObjectFrame) { + return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM(); + } + if (type == nsGkAtoms::svgOuterSVGFrame) { + return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); + } + + nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame); + if (containerFrame) { + return containerFrame->GetCanvasTM(); + } + + return static_cast<nsSVGPathGeometryFrame*>(aFrame)->GetCanvasTM(); +} + +gfxMatrix +nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame) +{ + nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame); + NS_ASSERTION(svgFrame, "bad frame"); + + gfxMatrix tm; + if (svgFrame) { + nsSVGElement *content = static_cast<nsSVGElement*>(aFrame->GetContent()); + tm = content->PrependLocalTransformsTo( + GetCanvasTM(aFrame->GetParent()), + eUserSpaceToParent); + } + return tm; +} + +void +nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags) +{ + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(aFlags); + } else { + NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(), + "SVG frame expected"); + // recurse into the children of container frames e.g. <clipPath>, <mask> + // in case they have child frames with transformation matrices + if (kid->IsFrameOfType(nsIFrame::eSVG)) { + NotifyChildrenOfSVGChange(kid, aFlags); + } + } + } +} + +// ************************************************************ + +class SVGPaintCallback : public nsSVGFilterPaintCallback +{ +public: + virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) override + { + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget); + NS_ASSERTION(svgChildFrame, "Expected SVG frame here"); + + nsIntRect* dirtyRect = nullptr; + nsIntRect tmpDirtyRect; + + // aDirtyRect is in user-space pixels, we need to convert to + // outer-SVG-frame-relative device pixels. + if (aDirtyRect) { + gfxMatrix userToDeviceSpace = aTransform; + if (userToDeviceSpace.IsSingular()) { + return DrawResult::SUCCESS; + } + gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( + gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); + dirtyBounds.RoundOut(); + if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { + dirtyRect = &tmpDirtyRect; + } + } + + return svgChildFrame->PaintSVG(aContext, aTransform, dirtyRect); + } +}; + +float +nsSVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity) +{ + float opacity = aFrame->StyleEffects()->mOpacity; + + if (opacity != 1.0f && + (nsSVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) { + return 1.0f; + } + + return opacity; +} + +void +nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, + MaskUsage& aUsage) +{ + aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset(); + + nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); + +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + // For a HTML doc: + // According to css-masking spec, always create a mask surface when we + // have any item in maskFrame even if all of those items are + // non-resolvable <mask-sources> or <images>, we still need to create a + // transparent black mask layer under this condition. + // For a SVG doc: + // SVG 1.1 say that if we fail to resolve a mask, we should draw the + // object unmasked. + aUsage.shouldGenerateMaskLayer = + (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) + ? maskFrames.Length() == 1 && maskFrames[0] + : maskFrames.Length() > 0; +#else + // Since we do not support image mask so far, we should treat any + // unresolvable mask as no mask. Otherwise, any object with a valid image + // mask, e.g. url("xxx.png"), will become invisible just because we can not + // handle image mask correctly. (See bug 1294171) + aUsage.shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0]; +#endif + + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + MOZ_ASSERT_IF(clipPathFrame, + svgReset->mClipPath.GetType() == StyleShapeSourceType::URL); + + switch (svgReset->mClipPath.GetType()) { + case StyleShapeSourceType::URL: + if (clipPathFrame) { + if (clipPathFrame->IsTrivial()) { + aUsage.shouldApplyClipPath = true; + } else { + aUsage.shouldGenerateClipMaskLayer = true; + } + } + break; + case StyleShapeSourceType::Shape: + case StyleShapeSourceType::Box: + aUsage.shouldApplyBasicShape = true; + break; + case StyleShapeSourceType::None: + MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer && + !aUsage.shouldApplyClipPath && !aUsage.shouldApplyBasicShape); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); + break; + } +} + +static IntRect +ComputeClipExtsInDeviceSpace(gfxContext& aCtx) +{ + gfxContextMatrixAutoSaveRestore matRestore(&aCtx); + + // Get the clip extents in device space. + aCtx.SetMatrix(gfxMatrix()); + gfxRect clippedFrameSurfaceRect = aCtx.GetClipExtents(); + clippedFrameSurfaceRect.RoundOut(); + + IntRect result; + ToRect(clippedFrameSurfaceRect).ToIntRect(&result); + return mozilla::gfx::Factory::CheckSurfaceSize(result.Size()) ? result + : IntRect(); +} + +static already_AddRefed<gfxContext> +CreateBlendTarget(gfxContext* aContext, IntPoint& aTargetOffset) +{ + // Create a temporary context to draw to so we can blend it back with + // another operator. + IntRect drawRect = ComputeClipExtsInDeviceSpace(*aContext); + + RefPtr<DrawTarget> targetDT = + aContext->GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), + SurfaceFormat::B8G8R8A8); + if (!targetDT || !targetDT->IsValid()) { + return nullptr; + } + + RefPtr<gfxContext> target = gfxContext::CreateOrNull(targetDT); + MOZ_ASSERT(target); // already checked the draw target above + target->SetMatrix(aContext->CurrentMatrix() * + gfxMatrix::Translation(-drawRect.TopLeft())); + aTargetOffset = drawRect.TopLeft(); + + return target.forget(); +} + +static void +BlendToTarget(nsIFrame* aFrame, gfxContext* aSource, gfxContext* aTarget, + const IntPoint& aTargetOffset) +{ + MOZ_ASSERT(aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL); + + RefPtr<DrawTarget> targetDT = aTarget->GetDrawTarget(); + RefPtr<SourceSurface> targetSurf = targetDT->Snapshot(); + + gfxContextAutoSaveRestore save(aSource); + aSource->SetMatrix(gfxMatrix()); // This will be restored right after. + RefPtr<gfxPattern> pattern = new gfxPattern(targetSurf, Matrix::Translation(aTargetOffset.x, aTargetOffset.y)); + aSource->SetPattern(pattern); + aSource->Paint(); +} + +DrawResult +nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, + gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) +{ + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || + aFrame->PresContext()->IsGlyph(), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); + if (!svgChildFrame) + return DrawResult::SUCCESS; + + MaskUsage maskUsage; + DetermineMaskUsage(aFrame, true, maskUsage); + if (maskUsage.opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + const nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement() && + !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + return DrawResult::SUCCESS; + } + + if (aDirtyRect && + !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + // Here we convert aFrame's paint bounds to outer-<svg> device space, + // compare it to aDirtyRect, and return early if they don't intersect. + // We don't do this optimization for nondisplay SVG since nondisplay + // SVG doesn't maintain bounds/overflow rects. + nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); + if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || + aFrame->IsSVGText()) { + // Unlike containers, leaf frames do not include GetPosition() in + // GetCanvasTM(). + overflowRect = overflowRect + aFrame->GetPosition(); + } + int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); + gfxMatrix tm = aTransform; + if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { + gfx::Matrix childrenOnlyTM; + if (static_cast<nsSVGContainerFrame*>(aFrame)-> + HasChildrenOnlyTransform(&childrenOnlyTM)) { + // Undo the children-only transform: + if (!childrenOnlyTM.Invert()) { + return DrawResult::SUCCESS; + } + tm = ThebesMatrix(childrenOnlyTM) * tm; + } + } + nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect, + tm, aFrame->PresContext()). + ToOutsidePixels(appUnitsPerDevPx); + if (!aDirtyRect->Intersects(bounds)) { + return DrawResult::SUCCESS; + } + } + + /* SVG defines the following rendering model: + * + * 1. Render fill + * 2. Render stroke + * 3. Render markers + * 4. Apply filter + * 5. Apply clipping, masking, group opacity + * + * We follow this, but perform a couple of optimizations: + * + * + Use cairo's clipPath when representable natively (single object + * clip region). + *f + * + Merge opacity and masking if both used together. + */ + + /* Properties are added lazily and may have been removed by a restyle, + so make sure all applicable ones are set again. */ + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(aFrame); + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + nsSVGMaskFrame *maskFrame = effectProperties.GetFirstMaskFrame(&isOK); + if (!isOK) { + // Some resource is invalid. We shouldn't paint anything. + return DrawResult::SUCCESS; + } + + // These are used if we require a temporary surface for a custom blend mode. + // Clip the source context first, so that we can generate a smaller temporary + // surface. (Since we will clip this context in SetupContextMatrix, a pair + // of save/restore is needed.) + aContext.Save(); + if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + // aFrame has a valid visual overflow rect, so clip to it before calling + // PushGroup() to minimize the size of the surfaces we'll composite: + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext); + aContext.Multiply(aTransform); + nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); + if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || + aFrame->IsSVGText()) { + // Unlike containers, leaf frames do not include GetPosition() in + // GetCanvasTM(). + overflowRect = overflowRect + aFrame->GetPosition(); + } + aContext.Clip(NSRectToSnappedRect(overflowRect, + aFrame->PresContext()->AppUnitsPerDevPixel(), + *aContext.GetDrawTarget())); + } + IntPoint targetOffset; + RefPtr<gfxContext> target = + (aFrame->StyleEffects()->mMixBlendMode == NS_STYLE_BLEND_NORMAL) + ? RefPtr<gfxContext>(&aContext).forget() + : CreateBlendTarget(&aContext, targetOffset); + aContext.Restore(); + + if (!target) { + return DrawResult::TEMPORARY_ERROR; + } + + /* Check if we need to do additional operations on this child's + * rendering, which necessitates rendering into another surface. */ + bool shouldGenerateMask = (maskUsage.opacity != 1.0f || + maskUsage.shouldGenerateClipMaskLayer || + maskUsage.shouldGenerateMaskLayer || + aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL); + + if (shouldGenerateMask) { + Matrix maskTransform; + RefPtr<SourceSurface> maskSurface; + + if (maskUsage.shouldGenerateMaskLayer) { + maskSurface = + maskFrame->GetMaskForMaskedFrame(&aContext, aFrame, aTransform, + maskUsage.opacity, &maskTransform); + + if (!maskSurface) { + // Entire surface is clipped out. + return DrawResult::SUCCESS; + } + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + Matrix clippedMaskTransform; + RefPtr<SourceSurface> clipMaskSurface = + clipPathFrame->GetClipMask(aContext, aFrame, aTransform, + &clippedMaskTransform, maskSurface, + maskTransform); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + maskTransform = clippedMaskTransform; + } + } + + // SVG mask multiply opacity into maskSurface already, so we do not bother + // to apply opacity again. + float opacity = maskFrame ? 1.0 : maskUsage.opacity; + target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, + maskSurface, maskTransform); + } + + /* If this frame has only a trivial clipPath, set up cairo's clipping now so + * we can just do normal painting and get it clipped appropriately. + */ + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyClipPath) { + clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); + } else { + nsCSSClipPathInstance::ApplyBasicShapeClip(aContext, aFrame); + } + } + + DrawResult result = DrawResult::SUCCESS; + + /* Paint the child */ + if (effectProperties.HasValidFilter()) { + nsRegion* dirtyRegion = nullptr; + nsRegion tmpDirtyRegion; + if (aDirtyRect) { + // aDirtyRect is in outer-<svg> device pixels, but the filter code needs + // it in frame space. + gfxMatrix userToDeviceSpace = GetUserToCanvasTM(aFrame); + if (userToDeviceSpace.IsSingular()) { + return DrawResult::SUCCESS; + } + gfxMatrix deviceToUserSpace = userToDeviceSpace; + deviceToUserSpace.Invert(); + gfxRect dirtyBounds = deviceToUserSpace.TransformBounds( + gfxRect(aDirtyRect->x, aDirtyRect->y, + aDirtyRect->width, aDirtyRect->height)); + tmpDirtyRegion = + nsLayoutUtils::RoundGfxRectToAppRect( + dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) - + aFrame->GetPosition(); + dirtyRegion = &tmpDirtyRegion; + } + SVGPaintCallback paintCallback; + nsFilterInstance::PaintFilteredFrame(aFrame, target->GetDrawTarget(), + aTransform, &paintCallback, + dirtyRegion); + } else { + result = svgChildFrame->PaintSVG(*target, aTransform, aDirtyRect); + } + + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + aContext.PopClip(); + } + + if (shouldGenerateMask) { + target->PopGroupAndBlend(); + } + + if (aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + MOZ_ASSERT(target != &aContext); + BlendToTarget(aFrame, &aContext, target, targetOffset); + } + + return result; +} + +bool +nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint) +{ + nsSVGEffects::EffectProperties props = + nsSVGEffects::GetEffectProperties(aFrame); + if (!props.mClipPath) { + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + if (style->HasClipPath()) { + return nsCSSClipPathInstance::HitTestBasicShapeClip(aFrame, aPoint); + } + return true; + } + + bool isOK = true; + nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK); + if (!isOK) { + // clipPath is not a valid resource, so nothing gets painted, so + // hit-testing must fail. + return false; + } + if (!clipPathFrame) { + // clipPath doesn't exist, ignore it. + return true; + } + + return clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); +} + +nsIFrame * +nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame, + const gfxPoint& aPoint) +{ + // First we transform aPoint into the coordinate space established by aFrame + // for its children (e.g. take account of any 'viewBox' attribute): + gfxPoint point = aPoint; + if (aFrame->GetContent()->IsSVGElement()) { // must check before cast + gfxMatrix m = static_cast<const nsSVGElement*>(aFrame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), + eChildToUserSpace); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.Transform(point); + } + } + + // Traverse the list in reverse order, so that if we get a hit we know that's + // the topmost frame that intersects the point; then we can just return it. + nsIFrame* result = nullptr; + for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); + current; + current = current->GetPrevSibling()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(current); + if (SVGFrame) { + const nsIContent* content = current->GetContent(); + if (content->IsSVGElement() && + !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + continue; + } + // GetFrameForPoint() expects a point in its frame's SVG user space, so + // we need to convert to that space: + gfxPoint p = point; + if (content->IsSVGElement()) { // must check before cast + gfxMatrix m = static_cast<const nsSVGElement*>(content)-> + PrependLocalTransformsTo(gfxMatrix(), + eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + continue; + } + p = m.Transform(p); + } + } + result = SVGFrame->GetFrameForPoint(p); + if (result) + break; + } + } + + if (result && !HitTestClip(aFrame, aPoint)) + result = nullptr; + + return result; +} + +nsRect +nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames) +{ + nsRect rect; + + for (nsIFrame* kid = aFrames.FirstChild(); + kid; + kid = kid->GetNextSibling()) { + nsISVGChildFrame* child = do_QueryFrame(kid); + if (child) { + nsRect childRect = child->GetCoveredRegion(); + rect.UnionRect(rect, childRect); + } + } + + return rect; +} + +nsRect +nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, + const gfxMatrix& aMatrix, + nsPresContext* aPresContext) +{ + gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); + r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel()); + return nsLayoutUtils::RoundGfxRectToAppRect( + aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); +} + +IntSize +nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, + bool *aResultOverflows) +{ + IntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); + + *aResultOverflows = surfaceSize.width != ceil(aSize.width) || + surfaceSize.height != ceil(aSize.height); + + if (!Factory::CheckSurfaceSize(surfaceSize)) { + surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, + surfaceSize.width); + surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, + surfaceSize.height); + *aResultOverflows = true; + } + + return surfaceSize; +} + +bool +nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix, + float aRX, float aRY, float aRWidth, float aRHeight, + float aX, float aY) +{ + gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); + if (rect.IsEmpty() || aMatrix.IsSingular()) { + return false; + } + gfx::Matrix toRectSpace = aMatrix; + toRectSpace.Invert(); + gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY)); + return rect.x <= p.x && p.x <= rect.XMost() && + rect.y <= p.y && p.y <= rect.YMost(); +} + +gfxRect +nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame, + float aX, float aY, float aWidth, float aHeight) +{ + const nsStyleDisplay* disp = aFrame->StyleDisplay(); + const nsStyleEffects* effects = aFrame->StyleEffects(); + + if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) { + NS_ASSERTION(effects->mClipFlags == NS_STYLE_CLIP_AUTO, + "We don't know about this type of clip."); + return gfxRect(aX, aY, aWidth, aHeight); + } + + if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN || + disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) { + + nsIntRect clipPxRect = + effects->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel()); + gfxRect clipRect = + gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); + + if (NS_STYLE_CLIP_RIGHT_AUTO & effects->mClipFlags) { + clipRect.width = aWidth - clipRect.X(); + } + if (NS_STYLE_CLIP_BOTTOM_AUTO & effects->mClipFlags) { + clipRect.height = aHeight - clipRect.Y(); + } + + if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) { + clipRect.x = aX; + clipRect.width = aWidth; + } + if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) { + clipRect.y = aY; + clipRect.height = aHeight; + } + + return clipRect; + } + return gfxRect(aX, aY, aWidth, aHeight); +} + +void +nsSVGUtils::SetClipRect(gfxContext *aContext, + const gfxMatrix &aCTM, + const gfxRect &aRect) +{ + if (aCTM.IsSingular()) + return; + + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); + aContext->Multiply(aCTM); + aContext->Clip(aRect); +} + +gfxRect +nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags) +{ + if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { + aFrame = aFrame->GetParent(); + } + gfxRect bbox; + nsISVGChildFrame *svg = do_QueryFrame(aFrame); + if (svg || aFrame->IsSVGText()) { + // It is possible to apply a gradient, pattern, clipping path, mask or + // filter to text. When one of these facilities is applied to text + // the bounding box is the entire text element in all + // cases. + if (aFrame->IsSVGText()) { + nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); + if (ancestor && ancestor->IsSVGText()) { + while (ancestor->GetType() != nsGkAtoms::svgTextFrame) { + ancestor = ancestor->GetParent(); + } + } + svg = do_QueryFrame(ancestor); + } + nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement() && + !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + return bbox; + } + + FrameProperties props = aFrame->Properties(); + + if (aFlags == eBBoxIncludeFillGeometry) { + gfxRect* prop = props.Get(ObjectBoundingBoxProperty()); + if (prop) { + return *prop; + } + } + + gfxMatrix matrix; + if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || + aFrame->GetType() == nsGkAtoms::svgUseFrame) { + // The spec says getBBox "Returns the tight bounding box in *current user + // space*". So we should really be doing this for all elements, but that + // needs investigation to check that we won't break too much content. + // NOTE: When changing this to apply to other frame types, make sure to + // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. + MOZ_ASSERT(content->IsSVGElement(), "bad cast"); + nsSVGElement *element = static_cast<nsSVGElement*>(content); + matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace); + } + bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); + // Account for 'clipped'. + if (aFlags & nsSVGUtils::eBBoxIncludeClipped) { + gfxRect clipRect(0, 0, 0, 0); + float x, y, width, height; + gfxMatrix tm; + gfxRect fillBBox = + svg->GetBBoxContribution(ToMatrix(tm), + nsSVGUtils::eBBoxIncludeFill).ToThebesRect(); + x = fillBBox.x; + y = fillBBox.y; + width = fillBBox.width; + height = fillBBox.height; + bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); + if (hasClip) { + clipRect = + nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height); + if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || + aFrame->GetType() == nsGkAtoms::svgUseFrame) { + clipRect = matrix.TransformBounds(clipRect); + } + } + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(aFrame); + bool isOK = true; + nsSVGClipPathFrame *clipPathFrame = + effectProperties.GetClipPathFrame(&isOK); + if (clipPathFrame && isOK) { + SVGClipPathElement *clipContent = + static_cast<SVGClipPathElement*>(clipPathFrame->GetContent()); + RefPtr<SVGAnimatedEnumeration> units = clipContent->ClipPathUnits(); + if (units->AnimVal() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + matrix.Translate(gfxPoint(x, y)); + matrix.Scale(width, height); + } else if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { + matrix.Reset(); + } + bbox = + clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix).ToThebesRect(); + if (hasClip) { + bbox = bbox.Intersect(clipRect); + } + } else { + if (!isOK) { + bbox = gfxRect(0, 0, 0, 0); + } else { + if (hasClip) { + bbox = bbox.Intersect(clipRect); + } + } + } + if (bbox.IsEmpty()) { + bbox = gfxRect(0, 0, 0, 0); + } + } + + if (aFlags == eBBoxIncludeFillGeometry) { + // Obtaining the bbox for objectBoundingBox calculations is common so we + // cache the result for future calls, since calculation can be expensive: + props.Set(ObjectBoundingBoxProperty(), new gfxRect(bbox)); + } + + return bbox; + } + return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame); +} + +gfxPoint +nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame) +{ + if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + // The user space for non-SVG frames is defined as the bounding box of the + // frame's border-box rects over all continuations. + return gfxPoint(); + } + + // Leaf frames apply their own offset inside their user space. + if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || + aFrame->IsSVGText()) { + return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), + nsPresContext::AppUnitsPerCSSPixel()).TopLeft(); + } + + // For foreignObject frames, nsSVGUtils::GetBBox applies their local + // transform, so we need to do the same here. + if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || + aFrame->GetType() == nsGkAtoms::svgUseFrame) { + gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); + NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); + return transform.GetTranslation(); + } + + return gfxPoint(); +} + +static gfxRect +GetBoundingBoxRelativeRect(const nsSVGLength2 *aXYWH, + const gfxRect& aBBox) +{ + return gfxRect(aBBox.x + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[0]), + aBBox.y + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[1]), + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[2]), + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[3])); +} + +gfxRect +nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, + const gfxRect& aBBox, + const UserSpaceMetrics& aMetrics) +{ + if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + return GetBoundingBoxRelativeRect(aXYWH, aBBox); + } + return gfxRect(UserSpace(aMetrics, &aXYWH[0]), + UserSpace(aMetrics, &aXYWH[1]), + UserSpace(aMetrics, &aXYWH[2]), + UserSpace(aMetrics, &aXYWH[3])); +} + +gfxRect +nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, + const gfxRect& aBBox, nsIFrame *aFrame) +{ + if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + return GetBoundingBoxRelativeRect(aXYWH, aBBox); + } + nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement()) { + nsSVGElement* svgElement = static_cast<nsSVGElement*>(content); + return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement)); + } + return GetRelativeRect(aUnits, aXYWH, aBBox, NonSVGFrameUserSpaceMetrics(aFrame)); +} + +bool +nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) +{ + if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + return false; + } + nsIAtom *type = aFrame->GetType(); + if (type != nsGkAtoms::svgImageFrame && + type != nsGkAtoms::svgPathGeometryFrame) { + return false; + } + if (aFrame->StyleEffects()->HasFilters()) { + return false; + } + // XXX The SVG WG is intending to allow fill, stroke and markers on <image> + if (type == nsGkAtoms::svgImageFrame) { + return true; + } + const nsStyleSVG *style = aFrame->StyleSVG(); + if (style->HasMarker()) { + return false; + } + if (!style->HasFill() || !HasStroke(aFrame)) { + return true; + } + return false; +} + +gfxMatrix +nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix, + nsSVGEnum *aUnits, + nsIFrame *aFrame) +{ + if (aFrame && + aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + gfxRect bbox = GetBBox(aFrame); + gfxMatrix tm = aMatrix; + tm.Translate(gfxPoint(bbox.X(), bbox.Y())); + tm.Scale(bbox.Width(), bbox.Height()); + return tm; + } + return aMatrix; +} + +nsIFrame* +nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) +{ + for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame; + ancestorFrame = ancestorFrame->GetParent()) { + if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) { + return ancestorFrame; + } + } + return nullptr; +} + +bool +nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame *aFrame, + gfxMatrix* aUserToOuterSVG) +{ + if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { + aFrame = aFrame->GetParent(); + } + + if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) { + return false; + } + + nsIContent *content = aFrame->GetContent(); + MOZ_ASSERT(content->IsSVGElement(), "bad cast"); + + *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM( + static_cast<nsSVGElement*>(content), true)); + + return !aUserToOuterSVG->IsIdentity(); +} + +// The logic here comes from _cairo_stroke_style_max_distance_from_path +static gfxRect +PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsIFrame* aFrame, + double aStyleExpansionFactor, + const gfxMatrix& aMatrix) +{ + double style_expansion = + aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame); + + gfxMatrix matrix = aMatrix; + + gfxMatrix outerSVGToUser; + if (nsSVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) { + outerSVGToUser.Invert(); + matrix.PreMultiply(outerSVGToUser); + } + + double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21)); + double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12)); + + gfxRect strokeExtents = aPathExtents; + strokeExtents.Inflate(dx, dy); + return strokeExtents; +} + +/*static*/ gfxRect +nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsTextFrame* aFrame, + const gfxMatrix& aMatrix) +{ + NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text"); + return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); +} + +/*static*/ gfxRect +nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsSVGPathGeometryFrame* aFrame, + const gfxMatrix& aMatrix) +{ + bool strokeMayHaveCorners = + !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent()); + + // For a shape without corners the stroke can only extend half the stroke + // width from the path in the x/y-axis directions. For shapes with corners + // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line + // with stroke-linecaps="square"). + double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5; + + // The stroke can extend even further for paths that can be affected by + // stroke-miterlimit. + bool affectedByMiterlimit = + aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::path, + nsGkAtoms::polyline, + nsGkAtoms::polygon); + + if (affectedByMiterlimit) { + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER && + styleExpansionFactor < style->mStrokeMiterlimit / 2.0) { + styleExpansionFactor = style->mStrokeMiterlimit / 2.0; + } + } + + return ::PathExtentsToMaxStrokeExtents(aPathExtents, + aFrame, + styleExpansionFactor, + aMatrix); +} + +// ---------------------------------------------------------------------- + +/* static */ nscolor +nsSVGUtils::GetFallbackOrPaintColor(nsStyleContext *aStyleContext, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke) +{ + const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke; + nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited(); + bool isServer = paint.Type() == eStyleSVGPaintType_Server || + paint.Type() == eStyleSVGPaintType_ContextFill || + paint.Type() == eStyleSVGPaintType_ContextStroke; + nscolor color = isServer ? paint.GetFallbackColor() : paint.GetColor(); + if (styleIfVisited) { + const nsStyleSVGPaint &paintIfVisited = + styleIfVisited->StyleSVG()->*aFillOrStroke; + // To prevent Web content from detecting if a user has visited a URL + // (via URL loading triggered by paint servers or performance + // differences between paint servers or between a paint server and a + // color), we do not allow whether links are visited to change which + // paint server is used or switch between paint servers and simple + // colors. A :visited style may only override a simple color with + // another simple color. + if (paintIfVisited.Type() == eStyleSVGPaintType_Color && + paint.Type() == eStyleSVGPaintType_Color) { + nscolor colors[2] = { color, paintIfVisited.GetColor() }; + return nsStyleContext::CombineVisitedColors( + colors, aStyleContext->RelevantLinkVisited()); + } + } + return color; +} + +/* static */ void +nsSVGUtils::MakeFillPatternFor(nsIFrame* aFrame, + gfxContext* aContext, + GeneralPattern* aOutPattern, + SVGContextPaint* aContextPaint) +{ + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mFill.Type() == eStyleSVGPaintType_None) { + return; + } + + const float opacity = aFrame->StyleEffects()->mOpacity; + + float fillOpacity = GetOpacity(style->FillOpacitySource(), + style->mFillOpacity, + aContextPaint); + if (opacity < 1.0f && + nsSVGUtils::CanOptimizeOpacity(aFrame)) { + // Combine the group opacity into the fill opacity (we will have skipped + // creating an offscreen surface to apply the group opacity). + fillOpacity *= opacity; + } + + const DrawTarget* dt = aContext->GetDrawTarget(); + + nsSVGPaintServerFrame *ps = + nsSVGEffects::GetPaintServer(aFrame, &nsStyleSVG::mFill, + nsSVGEffects::FillProperty()); + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(), + &nsStyleSVG::mFill, fillOpacity); + if (pattern) { + pattern->CacheColorStops(dt); + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (aContextPaint) { + RefPtr<gfxPattern> pattern; + switch (style->mFill.Type()) { + case eStyleSVGPaintType_ContextFill: + pattern = aContextPaint->GetFillPattern(dt, fillOpacity, + aContext->CurrentMatrix()); + break; + case eStyleSVGPaintType_ContextStroke: + pattern = aContextPaint->GetStrokePattern(dt, fillOpacity, + aContext->CurrentMatrix()); + break; + default: + ; + } + if (pattern) { + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + // On failure, use the fallback colour in case we have an + // objectBoundingBox where the width or height of the object is zero. + // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox + Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(), + &nsStyleSVG::mFill))); + color.a *= fillOpacity; + aOutPattern->InitColorPattern(ToDeviceColor(color)); +} + +/* static */ void +nsSVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, + gfxContext* aContext, + GeneralPattern* aOutPattern, + SVGContextPaint* aContextPaint) +{ + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mStroke.Type() == eStyleSVGPaintType_None) { + return; + } + + const float opacity = aFrame->StyleEffects()->mOpacity; + + float strokeOpacity = GetOpacity(style->StrokeOpacitySource(), + style->mStrokeOpacity, + aContextPaint); + if (opacity < 1.0f && + nsSVGUtils::CanOptimizeOpacity(aFrame)) { + // Combine the group opacity into the stroke opacity (we will have skipped + // creating an offscreen surface to apply the group opacity). + strokeOpacity *= opacity; + } + + const DrawTarget* dt = aContext->GetDrawTarget(); + + nsSVGPaintServerFrame *ps = + nsSVGEffects::GetPaintServer(aFrame, &nsStyleSVG::mStroke, + nsSVGEffects::StrokeProperty()); + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(), + &nsStyleSVG::mStroke, strokeOpacity); + if (pattern) { + pattern->CacheColorStops(dt); + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (aContextPaint) { + RefPtr<gfxPattern> pattern; + switch (style->mStroke.Type()) { + case eStyleSVGPaintType_ContextFill: + pattern = aContextPaint->GetFillPattern(dt, strokeOpacity, + aContext->CurrentMatrix()); + break; + case eStyleSVGPaintType_ContextStroke: + pattern = aContextPaint->GetStrokePattern(dt, strokeOpacity, + aContext->CurrentMatrix()); + break; + default: + ; + } + if (pattern) { + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + // On failure, use the fallback colour in case we have an + // objectBoundingBox where the width or height of the object is zero. + // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox + Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(), + &nsStyleSVG::mStroke))); + color.a *= strokeOpacity; + aOutPattern->InitColorPattern(ToDeviceColor(color)); +} + +/* static */ float +nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType, + const float& aOpacity, + SVGContextPaint *aContextPaint) +{ + float opacity = 1.0f; + switch (aOpacityType) { + case eStyleSVGOpacitySource_Normal: + opacity = aOpacity; + break; + case eStyleSVGOpacitySource_ContextFillOpacity: + if (aContextPaint) { + opacity = aContextPaint->GetFillOpacity(); + } else { + NS_WARNING("Content used context-fill-opacity when not in a context element"); + } + break; + case eStyleSVGOpacitySource_ContextStrokeOpacity: + if (aContextPaint) { + opacity = aContextPaint->GetStrokeOpacity(); + } else { + NS_WARNING("Content used context-stroke-opacity when not in a context element"); + } + break; + default: + NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph"); + } + return opacity; +} + +bool +nsSVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint) +{ + const nsStyleSVG *style = aFrame->StyleSVG(); + return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; +} + +float +nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, SVGContextPaint* aContextPaint) +{ + const nsStyleSVG *style = aFrame->StyleSVG(); + if (aContextPaint && style->StrokeWidthFromObject()) { + return aContextPaint->GetStrokeWidth(); + } + + nsIContent* content = aFrame->GetContent(); + if (content->IsNodeOfType(nsINode::eTEXT)) { + content = content->GetParent(); + } + + nsSVGElement *ctx = static_cast<nsSVGElement*>(content); + + return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth); +} + +static bool +GetStrokeDashData(nsIFrame* aFrame, + nsTArray<gfxFloat>& aDashes, + gfxFloat* aDashOffset, + SVGContextPaint* aContextPaint) +{ + const nsStyleSVG* style = aFrame->StyleSVG(); + nsIContent *content = aFrame->GetContent(); + nsSVGElement *ctx = static_cast<nsSVGElement*> + (content->IsNodeOfType(nsINode::eTEXT) ? + content->GetParent() : content); + + gfxFloat totalLength = 0.0; + if (aContextPaint && style->StrokeDasharrayFromObject()) { + aDashes = aContextPaint->GetStrokeDashArray(); + + for (uint32_t i = 0; i < aDashes.Length(); i++) { + if (aDashes[i] < 0.0) { + return false; + } + totalLength += aDashes[i]; + } + + } else { + uint32_t count = style->mStrokeDasharray.Length(); + if (!count || !aDashes.SetLength(count, fallible)) { + return false; + } + + gfxFloat pathScale = 1.0; + + if (content->IsSVGElement(nsGkAtoms::path)) { + pathScale = static_cast<SVGPathElement*>(content)-> + GetPathLengthScale(SVGPathElement::eForStroking); + if (pathScale <= 0) { + return false; + } + } + + const nsTArray<nsStyleCoord>& dasharray = style->mStrokeDasharray; + + for (uint32_t i = 0; i < count; i++) { + aDashes[i] = SVGContentUtils::CoordToFloat(ctx, + dasharray[i]) * pathScale; + if (aDashes[i] < 0.0) { + return false; + } + totalLength += aDashes[i]; + } + } + + if (aContextPaint && style->StrokeDashoffsetFromObject()) { + *aDashOffset = aContextPaint->GetStrokeDashOffset(); + } else { + *aDashOffset = SVGContentUtils::CoordToFloat(ctx, + style->mStrokeDashoffset); + } + + return (totalLength > 0.0); +} + +void +nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame, + gfxContext *aContext, + SVGContextPaint* aContextPaint) +{ + float width = GetStrokeWidth(aFrame, aContextPaint); + if (width <= 0) + return; + aContext->SetLineWidth(width); + + // Apply any stroke-specific transform + gfxMatrix outerSVGToUser; + if (GetNonScalingStrokeTransform(aFrame, &outerSVGToUser) && + outerSVGToUser.Invert()) { + aContext->Multiply(outerSVGToUser); + } + + const nsStyleSVG* style = aFrame->StyleSVG(); + + switch (style->mStrokeLinecap) { + case NS_STYLE_STROKE_LINECAP_BUTT: + aContext->SetLineCap(CapStyle::BUTT); + break; + case NS_STYLE_STROKE_LINECAP_ROUND: + aContext->SetLineCap(CapStyle::ROUND); + break; + case NS_STYLE_STROKE_LINECAP_SQUARE: + aContext->SetLineCap(CapStyle::SQUARE); + break; + } + + aContext->SetMiterLimit(style->mStrokeMiterlimit); + + switch (style->mStrokeLinejoin) { + case NS_STYLE_STROKE_LINEJOIN_MITER: + aContext->SetLineJoin(JoinStyle::MITER_OR_BEVEL); + break; + case NS_STYLE_STROKE_LINEJOIN_ROUND: + aContext->SetLineJoin(JoinStyle::ROUND); + break; + case NS_STYLE_STROKE_LINEJOIN_BEVEL: + aContext->SetLineJoin(JoinStyle::BEVEL); + break; + } + + AutoTArray<gfxFloat, 10> dashes; + gfxFloat dashOffset; + if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) { + aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset); + } +} + +uint16_t +nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) +{ + uint16_t flags = 0; + + switch (aFrame->StyleUserInterface()->mPointerEvents) { + case NS_STYLE_POINTER_EVENTS_NONE: + break; + case NS_STYLE_POINTER_EVENTS_AUTO: + case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: + if (aFrame->StyleVisibility()->IsVisible()) { + if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_FILL; + if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_STROKE; + if (aFrame->StyleSVG()->mStrokeOpacity > 0) + flags |= SVG_HIT_TEST_CHECK_MRECT; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_STROKE; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLE: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; + } + break; + case NS_STYLE_POINTER_EVENTS_PAINTED: + if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_FILL; + if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_STROKE; + if (aFrame->StyleSVG()->mStrokeOpacity) + flags |= SVG_HIT_TEST_CHECK_MRECT; + break; + case NS_STYLE_POINTER_EVENTS_FILL: + flags |= SVG_HIT_TEST_FILL; + break; + case NS_STYLE_POINTER_EVENTS_STROKE: + flags |= SVG_HIT_TEST_STROKE; + break; + case NS_STYLE_POINTER_EVENTS_ALL: + flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; + break; + default: + NS_ERROR("not reached"); + break; + } + + return flags; +} + +bool +nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(frame); + if (!svgFrame) { + return false; + } + gfxMatrix m; + if (frame->GetContent()->IsSVGElement()) { + // PaintSVG() expects the passed transform to be the transform to its own + // SVG user space, so we need to account for any 'transform' attribute: + m = static_cast<nsSVGElement*>(frame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); + } + DrawResult result = svgFrame->PaintSVG(*aContext, m); + return (result == DrawResult::SUCCESS); +} + +bool +nsSVGUtils::GetSVGGlyphExtents(Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(frame); + if (!svgFrame) { + return false; + } + + gfxMatrix transform(aSVGToAppSpace); + nsIContent* content = frame->GetContent(); + if (content->IsSVGElement()) { + transform = static_cast<nsSVGElement*>(content)-> + PrependLocalTransformsTo(aSVGToAppSpace); + } + + *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform), + nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | + nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | + nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect(); + return true; +} + +nsRect +nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect, + const gfxMatrix &aToCanvas, + const nsPresContext *presContext) +{ + return nsLayoutUtils::RoundGfxRectToAppRect( + aToCanvas.TransformBounds(aUserspaceRect), + presContext->AppUnitsPerDevPixel()); +} diff --git a/layout/svg/nsSVGUtils.h b/layout/svg/nsSVGUtils.h new file mode 100644 index 0000000000..edfc43bee7 --- /dev/null +++ b/layout/svg/nsSVGUtils.h @@ -0,0 +1,600 @@ +/* -*- 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/. */ + +#ifndef NS_SVGUTILS_H +#define NS_SVGUTILS_H + +// include math.h to pick up definition of M_ maths defines e.g. M_PI +#include <math.h> + +#include "DrawMode.h" +#include "gfx2DGlue.h" +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "mozilla/gfx/Rect.h" +#include "nsAlgorithm.h" +#include "nsChangeHint.h" +#include "nsColor.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIFrame.h" +#include "nsISupportsBase.h" +#include "nsMathUtils.h" +#include "nsStyleStruct.h" +#include <algorithm> + +class gfxContext; +class nsFrameList; +class nsIContent; +class nsIDocument; +class nsIFrame; +class nsPresContext; +class nsStyleContext; +class nsStyleSVGPaint; +class nsSVGDisplayContainerFrame; +class nsSVGElement; +class nsSVGEnum; +class nsSVGLength2; +class nsSVGOuterSVGFrame; +class nsSVGPathGeometryFrame; +class nsTextFrame; + +struct nsStyleSVG; +struct nsRect; + +namespace mozilla { +class SVGContextPaint; +struct SVGContextPaintImpl; +namespace dom { +class Element; +class UserSpaceMetrics; +} // namespace dom +namespace gfx { +class DrawTarget; +class GeneralPattern; +} // namespace gfx +} // namespace mozilla + +// maximum dimension of an offscreen surface - choose so that +// the surface size doesn't overflow a 32-bit signed int using +// 4 bytes per pixel; in line with Factory::CheckSurfaceSize +// In fact Macs can't even manage that +#define NS_SVG_OFFSCREEN_MAX_DIMENSION 4096 + +#define SVG_HIT_TEST_FILL 0x01 +#define SVG_HIT_TEST_STROKE 0x02 +#define SVG_HIT_TEST_CHECK_MRECT 0x04 + + +bool NS_SVGPathCachingEnabled(); +bool NS_SVGDisplayListHitTestingEnabled(); +bool NS_SVGDisplayListPaintingEnabled(); +bool NS_SVGNewGetBBoxEnabled(); + +/** + * Sometimes we need to distinguish between an empty box and a box + * that contains an element that has no size e.g. a point at the origin. + */ +class SVGBBox { + typedef mozilla::gfx::Rect Rect; + +public: + SVGBBox() + : mIsEmpty(true) {} + + MOZ_IMPLICIT SVGBBox(const Rect& aRect) + : mBBox(aRect), mIsEmpty(false) {} + + MOZ_IMPLICIT SVGBBox(const gfxRect& aRect) + : mBBox(ToRect(aRect)), mIsEmpty(false) {} + + gfxRect ToThebesRect() const { + return ThebesRect(mBBox); + } + + bool IsEmpty() const { + return mIsEmpty; + } + + bool IsFinite() const { + return mBBox.IsFinite(); + } + + void Scale(float aScale) { + mBBox.Scale(aScale); + } + + void UnionEdges(const SVGBBox& aSVGBBox) { + if (aSVGBBox.mIsEmpty) { + return; + } + mBBox = mIsEmpty ? aSVGBBox.mBBox : mBBox.UnionEdges(aSVGBBox.mBBox); + mIsEmpty = false; + } + + void Intersect(const SVGBBox& aSVGBBox) { + if (!mIsEmpty && !aSVGBBox.mIsEmpty) { + mBBox = mBBox.Intersect(aSVGBBox.mBBox); + if (mBBox.IsEmpty()) { + mIsEmpty = true; + mBBox = Rect(0, 0, 0, 0); + } + } else { + mIsEmpty = true; + mBBox = Rect(0, 0, 0, 0); + } + } + +private: + Rect mBBox; + bool mIsEmpty; +}; + +// GRRR WINDOWS HATE HATE HATE +#undef CLIP_MASK + +class MOZ_RAII SVGAutoRenderState +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + +public: + explicit SVGAutoRenderState(DrawTarget* aDrawTarget + MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~SVGAutoRenderState(); + + void SetPaintingToWindow(bool aPaintingToWindow); + + static bool IsPaintingToWindow(DrawTarget* aDrawTarget); + +private: + DrawTarget* mDrawTarget; + void* mOriginalRenderState; + bool mPaintingToWindow; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + + +#define NS_ISVGFILTERREFERENCE_IID \ +{ 0x9744ee20, 0x1bcf, 0x4c62, \ + { 0x86, 0x7d, 0xd3, 0x7a, 0x91, 0x60, 0x3e, 0xef } } + +class nsISVGFilterReference : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISVGFILTERREFERENCE_IID) + virtual void Invalidate() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsISVGFilterReference, NS_ISVGFILTERREFERENCE_IID) + +/** + * General functions used by all of SVG layout and possibly content code. + * If a method is used by content and depends only on other content methods + * it should go in SVGContentUtils instead. + */ +class nsSVGUtils +{ +public: + typedef mozilla::dom::Element Element; + typedef mozilla::gfx::AntialiasMode AntialiasMode; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::FillRule FillRule; + typedef mozilla::gfx::GeneralPattern GeneralPattern; + typedef mozilla::gfx::Size Size; + typedef mozilla::SVGContextPaint SVGContextPaint; + typedef mozilla::SVGContextPaintImpl SVGContextPaintImpl; + typedef mozilla::image::DrawResult DrawResult; + + static void Init(); + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(ObjectBoundingBoxProperty, gfxRect) + + /** + * Gets the nearest nsSVGInnerSVGFrame or nsSVGOuterSVGFrame frame. aFrame + * must be an SVG frame. If aFrame is of type nsGkAtoms::svgOuterSVGFrame, + * returns nullptr. + */ + static nsSVGDisplayContainerFrame* GetNearestSVGViewport(nsIFrame *aFrame); + + /** + * Returns the frame's post-filter visual overflow rect when passed the + * frame's pre-filter visual overflow rect. If the frame is not currently + * being filtered, this function simply returns aUnfilteredRect. + */ + static nsRect GetPostFilterVisualOverflowRect(nsIFrame *aFrame, + const nsRect &aUnfilteredRect); + + /** + * Schedules an update of the frame's bounds (which will in turn invalidate + * the new area that the frame should paint to). + * + * This does nothing when passed an NS_FRAME_IS_NONDISPLAY frame. + * In future we may want to allow ReflowSVG to be called on such frames, + * but that would be better implemented as a ForceReflowSVG function to + * be called synchronously while painting them without marking or paying + * attention to dirty bits like this function. + * + * This is very similar to PresShell::FrameNeedsReflow. The main reason that + * we have this function instead of using FrameNeedsReflow is because we need + * to be able to call it under nsSVGOuterSVGFrame::NotifyViewportChange when + * that function is called by nsSVGOuterSVGFrame::Reflow. FrameNeedsReflow + * is not suitable for calling during reflow though, and it asserts as much. + * The reason that we want to be callable under NotifyViewportChange is + * because we want to synchronously notify and dirty the nsSVGOuterSVGFrame's + * children so that when nsSVGOuterSVGFrame::DidReflow is called its children + * will be updated for the new size as appropriate. Otherwise we'd have to + * post an event to the event loop to mark dirty flags and request an update. + * + * Another reason that we don't currently want to call + * PresShell::FrameNeedsReflow is because passing eRestyle to it to get it to + * mark descendants dirty would cause it to descend through + * nsSVGForeignObjectFrame frames to mark their children dirty, but we want to + * handle nsSVGForeignObjectFrame specially. It would also do unnecessary work + * descending into NS_FRAME_IS_NONDISPLAY frames. + */ + static void ScheduleReflowSVG(nsIFrame *aFrame); + + /** + * Returns true if the frame or any of its children need ReflowSVG + * to be called on them. + */ + static bool NeedsReflowSVG(nsIFrame *aFrame); + + /* + * Update the filter invalidation region for ancestor frames, if relevant. + */ + static void NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame); + + /** + * Percentage lengths in SVG are resolved against the width/height of the + * nearest viewport (or its viewBox, if set). This helper returns the size + * of this "context" for the given frame so that percentage values can be + * resolved. + */ + static Size GetContextSize(const nsIFrame* aFrame); + + /* Computes the input length in terms of object space coordinates. + Input: rect - bounding box + length - length to be converted + */ + static float ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength); + + /* Computes the input length in terms of user space coordinates. + Input: content - object to be used for determining user space + Input: length - length to be converted + */ + static float UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength); + static float UserSpace(nsIFrame *aFrame, const nsSVGLength2 *aLength); + static float UserSpace(const mozilla::dom::UserSpaceMetrics& aMetrics, const nsSVGLength2 *aLength); + + /* Find the outermost SVG frame of the passed frame */ + static nsSVGOuterSVGFrame * + GetOuterSVGFrame(nsIFrame *aFrame); + + /** + * Get the covered region for a frame. Return null if it's not an SVG frame. + * @param aRect gets a rectangle in app units + * @return the outer SVG frame which aRect is relative to + */ + static nsIFrame* + GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect); + + /* Paint SVG frame with SVG effects - aDirtyRect is the area being + * redrawn, in device pixel coordinates relative to the outer svg */ + static DrawResult + PaintFrameWithEffects(nsIFrame *aFrame, + gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect = nullptr); + + /* Hit testing - check if point hits the clipPath of indicated + * frame. Returns true if no clipPath set. */ + static bool + HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint); + + /** + * Hit testing - check if point hits any children of aFrame. aPoint is + * expected to be in the coordinate space established by aFrame for its + * children (e.g. the space established by the 'viewBox' attribute on <svg>). + */ + static nsIFrame * + HitTestChildren(nsSVGDisplayContainerFrame *aFrame, const gfxPoint &aPoint); + + /* + * Returns the CanvasTM of the indicated frame, whether it's a + * child SVG frame, container SVG frame, or a regular frame. + * For regular frames, we just return an identity matrix. + */ + static gfxMatrix GetCanvasTM(nsIFrame* aFrame); + + /** + * Returns the transform from aFrame's user space to canvas space. Only call + * with SVG frames. This is like GetCanvasTM, except that it only includes + * the transforms from aFrame's user space (i.e. the coordinate context + * established by its 'transform' attribute, or else the coordinate context + * that its _parent_ establishes for its children) to outer-<svg> device + * space. Specifically, it does not include any other transforms introduced + * by the frame such as x/y offsets and viewBox attributes. + */ + static gfxMatrix GetUserToCanvasTM(nsIFrame* aFrame); + + /** + * Notify the descendants of aFrame of a change to one of their ancestors + * that might affect them. + */ + static void + NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags); + + /* + * Get frame's covered region by walking the children and doing union. + */ + static nsRect + GetCoveredRegion(const nsFrameList &aFrames); + + static nsRect + TransformFrameRectToOuterSVG(const nsRect& aRect, + const gfxMatrix& aMatrix, + nsPresContext* aPresContext); + + /* + * Convert a surface size to an integer for use by thebes + * possibly making it smaller in the process so the surface does not + * use excessive memory. + * + * @param aSize the desired surface size + * @param aResultOverflows true if the desired surface size is too big + * @return the surface size to use + */ + static mozilla::gfx::IntSize ConvertToSurfaceSize(const gfxSize& aSize, + bool *aResultOverflows); + + /* + * Hit test a given rectangle/matrix. + */ + static bool + HitTestRect(const mozilla::gfx::Matrix &aMatrix, + float aRX, float aRY, float aRWidth, float aRHeight, + float aX, float aY); + + + /** + * Get the clip rect for the given frame, taking into account the CSS 'clip' + * property. See: + * http://www.w3.org/TR/SVG11/masking.html#OverflowAndClipProperties + * The arguments for aX, aY, aWidth and aHeight should be the dimensions of + * the viewport established by aFrame. + */ + static gfxRect + GetClipRectForFrame(nsIFrame *aFrame, + float aX, float aY, float aWidth, float aHeight); + + static void SetClipRect(gfxContext *aContext, + const gfxMatrix &aCTM, + const gfxRect &aRect); + + /* Using group opacity instead of fill or stroke opacity on a + * geometry object seems to be a common authoring mistake. If we're + * not applying filters and not both stroking and filling, we can + * generate the same result without going through the overhead of a + * push/pop group. */ + static bool + CanOptimizeOpacity(nsIFrame *aFrame); + + /** + * Take the CTM to userspace for an element, and adjust it to a CTM to its + * object bounding box space if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX. + * (I.e. so that [0,0] is at the top left of its bbox, and [1,1] is at the + * bottom right of its bbox). + * + * If the bbox is empty, this will return a singular matrix. + */ + static gfxMatrix + AdjustMatrixForUnits(const gfxMatrix &aMatrix, + nsSVGEnum *aUnits, + nsIFrame *aFrame); + + enum BBoxFlags { + eBBoxIncludeFill = 1 << 0, + eBBoxIncludeFillGeometry = 1 << 1, + eBBoxIncludeStroke = 1 << 2, + eBBoxIncludeStrokeGeometry = 1 << 3, + eBBoxIncludeMarkers = 1 << 4, + eBBoxIncludeClipped = 1 << 5 + }; + /** + * Get the SVG bbox (the SVG spec's simplified idea of bounds) of aFrame in + * aFrame's userspace. + */ + static gfxRect GetBBox(nsIFrame *aFrame, + // If the default arg changes, update the handling for + // ObjectBoundingBoxProperty() in the implementation. + uint32_t aFlags = eBBoxIncludeFillGeometry); + + /* + * "User space" is the space that the frame's BBox (as calculated by + * nsSVGUtils::GetBBox) is in. "Frame space" is the space that has its origin + * at the top left of the union of the frame's border-box rects over all + * continuations. + * This function returns the offset one needs to add to something in frame + * space in order to get its coordinates in user space. + */ + static gfxPoint FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame); + + /** + * Convert a userSpaceOnUse/objectBoundingBoxUnits rectangle that's specified + * using four nsSVGLength2 values into a user unit rectangle in user space. + * + * @param aXYWH pointer to 4 consecutive nsSVGLength2 objects containing + * the x, y, width and height values in that order + * @param aBBox the bounding box of the object the rect is relative to; + * may be null if aUnits is not SVG_UNIT_TYPE_OBJECTBOUNDINGBOX + * @param aFrame the object in which to interpret user-space units; + * may be null if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX + */ + static gfxRect + GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, + const gfxRect& aBBox, nsIFrame *aFrame); + + static gfxRect + GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, + const gfxRect& aBBox, + const mozilla::dom::UserSpaceMetrics& aMetrics); + + /** + * Find the first frame, starting with aStartFrame and going up its + * parent chain, that is not an svgAFrame. + */ + static nsIFrame* GetFirstNonAAncestorFrame(nsIFrame* aStartFrame); + + static bool OuterSVGIsCallingReflowSVG(nsIFrame *aFrame); + static bool AnyOuterSVGIsCallingReflowSVG(nsIFrame *aFrame); + + /** + * See https://svgwg.org/svg2-draft/painting.html#NonScalingStroke + * + * If the computed value of the 'vector-effect' property on aFrame is + * 'non-scaling-stroke', then this function will set aUserToOuterSVG to the + * transform from aFrame's SVG user space to the initial coordinate system + * established by the viewport of aFrame's outer-<svg>'s (the coordinate + * system in which the stroke is fixed). If aUserToOuterSVG is set to a + * non-identity matrix this function returns true, else it returns false. + */ + static bool GetNonScalingStrokeTransform(nsIFrame *aFrame, + gfxMatrix* aUserToOuterSVG); + + /** + * Compute the maximum possible device space stroke extents of a path given + * the path's device space path extents, its stroke style and its ctm. + * + * This is a workaround for the lack of suitable cairo API for getting the + * tight device space stroke extents of a path. This basically gives us the + * tightest extents that we can guarantee fully enclose the inked stroke + * without doing the calculations for the actual tight extents. We exploit + * the fact that cairo does have an API for getting the tight device space + * fill/path extents. + * + * This should die once bug 478152 is fixed. + */ + static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsTextFrame* aFrame, + const gfxMatrix& aMatrix); + static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsSVGPathGeometryFrame* aFrame, + const gfxMatrix& aMatrix); + + /** + * Convert a floating-point value to a 32-bit integer value, clamping to + * the range of valid integers. + */ + static int32_t ClampToInt(double aVal) + { + return NS_lround(std::max(double(INT32_MIN), + std::min(double(INT32_MAX), aVal))); + } + + static nscolor GetFallbackOrPaintColor(nsStyleContext *aStyleContext, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke); + + static void + MakeFillPatternFor(nsIFrame *aFrame, + gfxContext* aContext, + GeneralPattern* aOutPattern, + SVGContextPaint* aContextPaint = nullptr); + + static void + MakeStrokePatternFor(nsIFrame* aFrame, + gfxContext* aContext, + GeneralPattern* aOutPattern, + SVGContextPaint* aContextPaint = nullptr); + + static float GetOpacity(nsStyleSVGOpacitySource aOpacityType, + const float& aOpacity, + SVGContextPaint* aContextPaint); + + /* + * @return false if there is no stroke + */ + static bool HasStroke(nsIFrame* aFrame, + SVGContextPaint* aContextPaint = nullptr); + + static float GetStrokeWidth(nsIFrame* aFrame, + SVGContextPaint* aContextPaint = nullptr); + + /* + * Set up a cairo context for a stroked path (including any dashing that + * applies). + */ + static void SetupCairoStrokeGeometry(nsIFrame* aFrame, gfxContext *aContext, + SVGContextPaint* aContextPaint = nullptr); + + /** + * This function returns a set of bit flags indicating which parts of the + * element (fill, stroke, bounds) should intercept pointer events. It takes + * into account the type of element and the value of the 'pointer-events' + * property on the element. + */ + static uint16_t GetGeometryHitTestFlags(nsIFrame* aFrame); + + static FillRule ToFillRule(mozilla::StyleFillRule aFillRule) { + return aFillRule == mozilla::StyleFillRule::Evenodd ? + FillRule::FILL_EVEN_ODD : FillRule::FILL_WINDING; + } + + static AntialiasMode ToAntialiasMode(uint8_t aTextRendering) { + return aTextRendering == NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED ? + AntialiasMode::NONE : AntialiasMode::SUBPIXEL; + } + + /** + * Render a SVG glyph. + * @param aElement the SVG glyph element to render + * @param aContext the thebes aContext to draw to + * @return true if rendering succeeded + */ + static bool PaintSVGGlyph(Element* aElement, gfxContext* aContext); + /** + * Get the extents of a SVG glyph. + * @param aElement the SVG glyph element + * @param aSVGToAppSpace the matrix mapping the SVG glyph space to the + * target context space + * @param aResult the result (valid when true is returned) + * @return true if calculating the extents succeeded + */ + static bool GetSVGGlyphExtents(Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult); + + /** + * Returns the app unit canvas bounds of a userspace rect. + * + * @param aToCanvas Transform from userspace to canvas device space. + */ + static nsRect + ToCanvasBounds(const gfxRect &aUserspaceRect, + const gfxMatrix &aToCanvas, + const nsPresContext *presContext); + + struct MaskUsage { + bool shouldGenerateMaskLayer; + bool shouldGenerateClipMaskLayer; + bool shouldApplyClipPath; + bool shouldApplyBasicShape; + float opacity; + + MaskUsage() + : shouldGenerateMaskLayer(false), shouldGenerateClipMaskLayer(false), + shouldApplyClipPath(false), shouldApplyBasicShape(false), opacity(0.0) + { } + }; + + static void + DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, MaskUsage& aUsage); + + static float + ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity); +}; + +#endif diff --git a/layout/svg/resources/content/svgBindings.xml b/layout/svg/resources/content/svgBindings.xml new file mode 100644 index 0000000000..15abee34d2 --- /dev/null +++ b/layout/svg/resources/content/svgBindings.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<!-- NOTE: This file is currently unused, but it's still in the tree because + bug 163068 will likely involve adding content to it. --> +<bindings id="svgBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:xbl="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" > +</bindings> diff --git a/layout/svg/svg.css b/layout/svg/svg.css new file mode 100644 index 0000000000..79aed0482c --- /dev/null +++ b/layout/svg/svg.css @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +@namespace url(http://www.w3.org/2000/svg); +@namespace xml url(http://www.w3.org/XML/1998/namespace); + +style, script, symbol { + display: none; +} + +switch { + -moz-binding: none !important; +} + +svg:not(:root), symbol, image, marker, pattern, foreignObject { + overflow: hidden; +} + +@media all and (-moz-is-glyph) { + :root { + fill: context-fill; + fill-opacity: context-fill-opacity; + stroke: context-stroke; + stroke-opacity: context-stroke-opacity; + stroke-width: context-value; + stroke-dasharray: context-value; + stroke-dashoffset: context-value; + } +} + +foreignObject { + -moz-appearance: none ! important; + margin: 0 ! important; + padding: 0 ! important; + border-width: 0 ! important; + white-space: normal; +} + +@media all and (-moz-is-resource-document) { + foreignObject *|* { + -moz-appearance: none !important; + } +} + +*|*::-moz-svg-foreign-content { + display: block !important; + /* We need to be an absolute and fixed container */ + transform: translate(0) !important; + text-indent: 0; +} + +/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>, + noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>. +*/ +*:not(svg), +*:not(foreignObject) > svg { + transform-origin:0 0; +} + +*|*::-moz-svg-text { + unicode-bidi: inherit; + vector-effect: inherit; +} + +*[xml|space=preserve] { + white-space: -moz-pre-space; +} + +*|*::-moz-svg-marker-anon-child { + clip-path: inherit; + filter: inherit; + mask: inherit; + opacity: inherit; +} + +*:-moz-focusring { + /* Don't specify the outline-color, we should always use initial value. */ + outline: 1px dotted; +} + +/* nsDocumentViewer::CreateStyleSet doesn't load ua.css. + * A few styles are common to html and SVG though + * so we copy the rules below from that file. + */ + +/* Links */ + +*|*:any-link { + cursor: pointer; +} + +*|*:any-link:-moz-focusring { + /* Don't specify the outline-color, we should always use initial value. */ + outline: 1px dotted; +} + +/* + * SVG-as-an-image needs this rule + */ +*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas { + display: block !important; + background-color: inherit; +} diff --git a/layout/svg/tests/example.xml b/layout/svg/tests/example.xml new file mode 100644 index 0000000000..fb82a7becb --- /dev/null +++ b/layout/svg/tests/example.xml @@ -0,0 +1,227 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="svg.css" type="text/css"?> +<!DOCTYPE svg SYSTEM "SVG-20000202.dtd" > +<!--<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 10 January 2000//EN" --> +<!-- "http://www.w3.org/Graphics/SVG/SVG-19991203.dtd"> --> +<svg xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.svg" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <html:script> +<![CDATA[ + +var gIsInit = false; +var barXPnts = new Array(); +var barYPnts = new Array(); + +var nodes = new Array(); +var gBarMax = 200; +var gHeight = 80; +var gBarCount = 10; +var gBarDir = 1; +var gGo = true; + +function init() +{ + dump("----------------\n"); + nodes[0] = findNode(document.documentElement, "bar21"); + nodes[1] = findNode(document.documentElement, "bar22"); + nodes[2] = findNode(document.documentElement, "bar23"); + nodes[3] = findNode(document.documentElement, "bargrid21"); + nodes[4] = findNode(document.documentElement, "bargrid22"); + nodes[5] = findNode(document.documentElement, "bargrid23"); + dump("----------------\n"); + gGo = true; + setTimeout("moveit()", 100); +} +function stop() +{ + gGo = false; +} +function ChangeBar(name, height) +{ + today = new Date(); + stime = today.getMilliseconds(); + dump("----------------\n"); + str = name+"1"; + node = findNode(document.documentElement, str); + //node = document.getElementById(str); + today = new Date(); + etime = today.getMilliseconds(); + dump("1----------------"+(etime-stime)+"\n"); + attr = document.createAttribute("points"); + attr.value = "30 " + height + " 30 210 50 210 50 " + height; + node.attributes.setNamedItem(attr); + today = new Date(); + stime = today.getMilliseconds(); + dump("2----------------"+(stime-etime)+"\n"); + + str = name+"2"; + node = findNode(document.documentElement, str); + dump("3----------------\n"); + attr.value = "30 " + height + " 50 " + height + " 60 " + (height-10) + " 40 " + (height-10); + node.attributes.setNamedItem(attr); + dump("4----------------\n"); + + str = name+"3"; + node = findNode(document.documentElement, str); + dump("5----------------\n"); + attr.value = "50 " + height + " 60 " + (height-10) + " 60 200 50 210"; + node.attributes.setNamedItem(attr); + dump("=================\n"); +} + +function ChangeBarWithNodes(node1, node2, node3, height) +{ + attr = document.createAttribute("points"); + attr.value = "30 " + height + " 30 210 50 210 50 " + height; + node1.attributes.setNamedItem(attr); + + attr.value = "30 " + height + " 50 " + height + " 60 " + (height-10) + " 40 " + (height-10); + node2.attributes.setNamedItem(attr); + + attr.value = "50 " + height + " 60 " + (height-10) + " 60 200 50 210"; + node3.attributes.setNamedItem(attr); +} + +function moveit() +{ + //ChangeBar("bar2", 150); + //ChangeBar("bargrid2", 150); + + ChangeBarWithNodes(nodes[0], nodes[1], nodes[2], gHeight); + ChangeBarWithNodes(nodes[3], nodes[4], nodes[5], gHeight); + + gHeight += gBarDir; + gBarCount--; + //dump("gHeight: "+gHeight+" gBarCount: "+gBarCount+" gBarDir: "+gBarDir+"\n"); + if (gHeight > gBarMax || gHeight < 1) { + gBarDir *= -1; + gBarCount = (Math.random() * 15)+3; + //dump("Changining directions: "+gBarDir+"\n"); + if (gHeight > gBarMax) { + gHeight = gBarMax; + } else { + gHeight = 1; + } + } else { + if (gBarCount < 1) { + gBarDir *= -1; + gBarCount = (Math.random() * 15)+3; + //dump("----> "+gBarCount+"\n"); + } + } + if (gGo) { + setTimeout("moveit()", 100); + } + +} + +function findNode(node, nodename) +{ + var type = node.nodeType; + if (type == Node.ELEMENT_NODE) { + + // open tag + //dump("\<" + node.tagName); + + // dump the attributes if any + attributes = node.attributes; + if (null != attributes) { + var countAttrs = attributes.length; + var index = 0; + while(index < countAttrs) { + att = attributes[index]; + if (null != att) { + //dump(" " + att.name + "=" + att.value+" ["+nodename+"]\n"); + if (att.name == "id" && att.value == nodename) { + //dump("Found it!\n"); + return node; + } + } + index++; + } + } + + // recursively dump the children + if (node.hasChildNodes()) { + // close tag + //dump(">"); + + // get the children + var children = node.childNodes; + var length = children.length; + var count = 0; + while(count < length) { + child = children[count]; + fndNode = findNode(child, nodename); + if (fndNode != null) { + return fndNode; + } + count++; + } + //dump("</" + node.tagName + ">"); + } else { + // close tag + //dump("/>"); + } + + + } + // if it's a piece of text just dump the text + else if (type == Node.TEXT_NODE) { + //dump(node.data); + } + return null; +} + +]]> + </html:script> + + <g> + <polyline x="55" y="10" id="bg" points="20 0 220 0 220 200 20 200"/> + <polyline x="55" y="10" id="bg" points="20 0 20 200 0 220 0 20 0 20 20 0"/> + <polyline x="55" y="10" id="bg" points="20 200 220 200 200 220 0 220"/> + + <polyline x="55" y="10" id="grid" points="20 0 220 0 220 200 20 200"/> + <polyline x="55" y="10" id="grid" points="20 0 20 200 0 220 0 20 0 20"/> + <polyline x="55" y="10" id="grid" points="20 200 220 200 200 220 0 220"/> + + <polyline x="55" y="10" id="grid" points="20 220 40 200 40 0"/> + <polyline x="55" y="10" id="grid" points="40 220 60 200 60 0"/> + <polyline x="55" y="10" id="grid" points="60 220 80 200 80 0"/> + <polyline x="55" y="10" id="grid" points="80 220 100 200 100 0"/> + <polyline x="55" y="10" id="grid" points="100 220 120 200 120 0"/> + <polyline x="55" y="10" id="grid" points="120 220 140 200 140 0"/> + <polyline x="55" y="10" id="grid" points="140 220 160 200 160 0"/> + <polyline x="55" y="10" id="grid" points="160 220 180 200 180 0"/> + <polyline x="55" y="10" id="grid" points="180 220 200 200 200 0"/> + + <polygon x="55" y="10" id="bar1" points="30 60 30 210 50 210 50 60"/> + <polygon x="55" y="10" id="bar1" points="30 60 50 60 60 50 40 50"/> + <polygon x="55" y="10" id="bar1" points="50 60 60 50 60 200 50 210"/> + + <polyline x="55" y="10" id="grid" points="30 60 30 210 50 210 50 60"/> + <polyline x="55" y="10" id="grid" points="30 60 50 60 60 50 40 50"/> + <polyline x="55" y="10" id="grid" points="50 60 60 50 60 200 50 210"/> + + + <polygon x="95" y="10" id="bar21" points="30 80 30 210 50 210 50 80"/> + <polygon x="95" y="10" id="bar22" points="30 80 50 80 60 70 40 70"/> + <polygon x="95" y="10" id="bar23" points="50 80 60 70 60 200 50 210"/> + + <polyline x="95" y="10" id="bargrid21" points="30 80 30 210 50 210 50 80"/> + <polyline x="95" y="10" id="bargrid22" points="30 80 50 80 60 70 40 70"/> + <polyline x="95" y="10" id="bargrid23" points="50 80 60 70 60 200 50 210"/> + + <polygon x="400" y="20" id="rect" points="10 10 50 10 50 50"/> + <polygon x="400" y="75" id="poly" points="10 10 50 10 50 50 45 70 32 32 80 20"/> + <polyline x="400" y="150" id="poly" points="10 10 50 10 50 50 45 70 32 32 80 20"/> + + </g> + <foreignobject> + <html:div style="position:absolute;top:5px;left:385px;">Simple Polygons</html:div> + <html:div style="position:absolute;top:240px;left:115px;">A Simple Graph</html:div> + <html:input type="button" style="position:absolute;top:260px;left:350px;" onclick="init();" value="Start"/> + <html:input type="button" style="position:absolute;top:260px;left:390px;" onclick="stop();" value="Stop"/> + </foreignobject> +</svg> diff --git a/layout/svg/tests/svg.css b/layout/svg/tests/svg.css new file mode 100644 index 0000000000..358811af7e --- /dev/null +++ b/layout/svg/tests/svg.css @@ -0,0 +1,33 @@ + +polygon { + color: black; +} + +polygon[id="grid"] { + color: black; +} + +polygon[id="bar1"] { + color: green; +} + +polygon[id="rect"] { + color: blue; +} + +polygon[id="bg"] { + color: #DDDDDD; +} + +polygon[id="bar21"], polygon[id="bar22"], polygon[id="bar23"] { + color: #6F00DD; +} + +polygon[id="bargrid21"], polygon[id="bargrid22"], polygon[id="bargrid23"] { + color: black; +} + +polygon[id="poly"] { + color: #c0d0f0; +} + |