diff options
Diffstat (limited to '')
351 files changed, 31034 insertions, 0 deletions
diff --git a/layout/svg/AutoReferenceChainGuard.h b/layout/svg/AutoReferenceChainGuard.h new file mode 100644 index 0000000000..c25ba0ea70 --- /dev/null +++ b/layout/svg/AutoReferenceChainGuard.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_ +#define LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_ + +#include "Element.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ReentrancyGuard.h" +#include "mozilla/Likely.h" +#include "nsDebug.h" +#include "mozilla/dom/Document.h" +#include "nsIFrame.h" + +namespace mozilla { + +/** + * This helper class helps us to protect against two related issues that can + * occur in SVG content: reference loops, and reference chains that we deem to + * be too long. + * + * Some SVG effects can reference another effect of the same type to produce + * a chain of effects to be applied (e.g. clipPath), while in other cases it is + * possible that while processing an effect of a certain type another effect + * of the same type may be encountered indirectly (e.g. pattern). In order to + * avoid stack overflow crashes and performance issues we need to impose an + * arbitrary limit on the length of the reference chains that SVG content may + * try to create. (Some SVG authoring tools have been known to create absurdly + * long reference chains. For example, bug 1253590 details a case where Adobe + * Illustrator was used to created an SVG with a chain of 5000 clip paths which + * could cause us to run out of stack space and crash.) + * + * This class is intended to be used with the nsIFrame's of SVG effects that + * may involve reference chains. To use it add a boolean member, something + * like this: + * + * // Flag used to indicate whether a methods that may reenter due to + * // following a reference to another instance is currently executing. + * bool mIsBeingProcessed; + * + * Make sure to initialize the member to false in the class' constructons. + * + * Then add the following to the top of any methods that may be reentered due + * to following a reference: + * + * static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + * + * AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, + * &sRefChainLengthCounter); + * if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + * return; // Break reference chain + * } + * + * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used + * by the frame except when it initialize them as indicated above. + */ +class MOZ_RAII AutoReferenceChainGuard { + static const int16_t sDefaultMaxChainLength = 10; // arbitrary length + + public: + static const int16_t noChain = -2; + + /** + * @param aFrame The frame for an effect that may involve a reference chain. + * @param aFrameInUse The member variable on aFrame that is used to indicate + * whether one of aFrame's methods that may involve following a reference + * to another effect of the same type is currently being executed. + * @param aChainCounter A static variable in the method in which this class + * is instantiated that is used to keep track of how many times the method + * is reentered (and thus how long the a reference chain is). + * @param aMaxChainLength The maximum number of links that are allowed in + * a reference chain. + */ + AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse, + int16_t* aChainCounter, + int16_t aMaxChainLength = sDefaultMaxChainLength) + : mFrame(aFrame), + mFrameInUse(aFrameInUse), + mChainCounter(aChainCounter), + mMaxChainLength(aMaxChainLength), + mBrokeReference(false) { + MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter); + MOZ_ASSERT(aMaxChainLength > 0); + MOZ_ASSERT(*aChainCounter == noChain || + (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength)); + } + + ~AutoReferenceChainGuard() { + if (mBrokeReference) { + // We didn't change mFrameInUse or mChainCounter + return; + } + + *mFrameInUse = false; + + // 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 mChainCounter. + MOZ_ASSERT(*mChainCounter < mMaxChainLength); + + (*mChainCounter)++; + + if (*mChainCounter == mMaxChainLength) { + *mChainCounter = noChain; // 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). + * If it returns false then an error message will be reported to the DevTools + * console (only once). + */ + [[nodiscard]] bool Reference() { + if (MOZ_UNLIKELY(*mFrameInUse)) { + mBrokeReference = true; + ReportErrorToConsole(); + return false; + } + + if (*mChainCounter == noChain) { + // Initialize - we start at aMaxChainLength and decrement towards zero. + *mChainCounter = mMaxChainLength; + } else { + // 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(*mChainCounter >= 0); + + if (MOZ_UNLIKELY(*mChainCounter < 1)) { + mBrokeReference = true; + ReportErrorToConsole(); + return false; + } + } + + // We only set these once we know we're returning true. + *mFrameInUse = true; + (*mChainCounter)--; + + return true; + } + + private: + void ReportErrorToConsole() { + AutoTArray<nsString, 2> params; + dom::Element* element = mFrame->GetContent()->AsElement(); + element->GetTagName(*params.AppendElement()); + element->GetId(*params.AppendElement()); + auto doc = mFrame->GetContent()->OwnerDoc(); + auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop + : dom::Document::eSVGRefChainLengthExceeded; + doc->WarnOnceAbout(warning, /* asError */ true, params); + } + + nsIFrame* mFrame; + bool* mFrameInUse; + int16_t* mChainCounter; + const int16_t mMaxChainLength; + bool mBrokeReference; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_ diff --git a/layout/svg/CSSClipPathInstance.cpp b/layout/svg/CSSClipPathInstance.cpp new file mode 100644 index 0000000000..8fd51b2799 --- /dev/null +++ b/layout/svg/CSSClipPathInstance.cpp @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "CSSClipPathInstance.h" + +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGPathData.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ShapeUtils.h" +#include "mozilla/SVGUtils.h" +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "nsCSSRendering.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; + +namespace mozilla { + +/* static*/ +void CSSClipPathInstance::ApplyBasicShapeOrPathClip( + gfxContext& aContext, nsIFrame* aFrame, const gfxMatrix& aTransform) { + RefPtr<Path> path = + CreateClipPathForFrame(aContext.GetDrawTarget(), aFrame, aTransform); + if (!path) { + // This behavior matches |SVGClipPathFrame::ApplyClipPath()|. + // https://www.w3.org/TR/css-masking-1/#ClipPathElement: + // "An empty clipping path will completely clip away the element that had + // the clip-path property applied." + aContext.Clip(Rect()); + return; + } + aContext.Clip(path); +} + +/* static*/ +RefPtr<Path> CSSClipPathInstance::CreateClipPathForFrame( + gfx::DrawTarget* aDt, nsIFrame* aFrame, const gfxMatrix& aTransform) { + const auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; + MOZ_ASSERT(clipPathStyle.IsShape() || clipPathStyle.IsBox() || + clipPathStyle.IsPath(), + "This is used with basic-shape, geometry-box, and path() only"); + + CSSClipPathInstance instance(aFrame, clipPathStyle); + + return instance.CreateClipPath(aDt, aTransform); +} + +/* static*/ +bool CSSClipPathInstance::HitTestBasicShapeOrPathClip(nsIFrame* aFrame, + const gfxPoint& aPoint) { + const auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; + MOZ_ASSERT(!clipPathStyle.IsNone(), "unexpected none value"); + MOZ_ASSERT(!clipPathStyle.IsUrl(), "unexpected url value"); + + CSSClipPathInstance instance(aFrame, clipPathStyle); + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<Path> path = instance.CreateClipPath( + drawTarget, SVGUtils::GetCSSPxToDevPxMatrix(aFrame)); + float pixelRatio = float(AppUnitsPerCSSPixel()) / + aFrame->PresContext()->AppUnitsPerDevPixel(); + return path && path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix()); +} + +/* static */ +Maybe<Rect> CSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip( + nsIFrame* aFrame, const StyleClipPath& aClipPathStyle) { + MOZ_ASSERT(aClipPathStyle.IsShape() || aClipPathStyle.IsBox() || + aClipPathStyle.IsPath()); + + CSSClipPathInstance instance(aFrame, aClipPathStyle); + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<Path> path = instance.CreateClipPath( + drawTarget, SVGUtils::GetCSSPxToDevPxMatrix(aFrame)); + return path ? Some(path->GetBounds()) : Nothing(); +} + +already_AddRefed<Path> CSSClipPathInstance::CreateClipPath( + DrawTarget* aDrawTarget, const gfxMatrix& aTransform) { + if (mClipPathStyle.IsPath()) { + return CreateClipPathPath(aDrawTarget); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + + nsRect r; + if (mClipPathStyle.IsBox()) { + r = nsLayoutUtils::ComputeGeometryBox(mTargetFrame, mClipPathStyle.AsBox()); + } else { + r = nsLayoutUtils::ComputeGeometryBox(mTargetFrame, + mClipPathStyle.AsShape()._1); + } + + gfxRect rr(r.x, r.y, r.width, r.height); + rr.Scale(1.0 / AppUnitsPerCSSPixel()); + rr = aTransform.TransformRect(rr); + rr.Scale(appUnitsPerDevPixel); + rr.Round(); + + r = nsRect(int(rr.x), int(rr.y), int(rr.width), int(rr.height)); + + if (mClipPathStyle.IsBox()) { + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + AppendRectToPath(builder, NSRectToRect(r, appUnitsPerDevPixel), true); + return builder->Finish(); + } + + MOZ_ASSERT(mClipPathStyle.IsShape()); + + r = ToAppUnits(r.ToNearestPixels(appUnitsPerDevPixel), appUnitsPerDevPixel); + + const auto& basicShape = *mClipPathStyle.AsShape()._0; + switch (basicShape.tag) { + case StyleBasicShape::Tag::Circle: + return CreateClipPathCircle(aDrawTarget, r); + case StyleBasicShape::Tag::Ellipse: + return CreateClipPathEllipse(aDrawTarget, r); + case StyleBasicShape::Tag::Polygon: + return CreateClipPathPolygon(aDrawTarget, r); + case StyleBasicShape::Tag::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(); +} + +already_AddRefed<Path> CSSClipPathInstance::CreateClipPathCircle( + DrawTarget* aDrawTarget, const nsRect& aRefBox) { + const auto& basicShape = *mClipPathStyle.AsShape()._0; + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nsPoint center = + ShapeUtils::ComputeCircleOrEllipseCenter(basicShape, aRefBox); + nscoord r = ShapeUtils::ComputeCircleRadius(basicShape, center, aRefBox); + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + builder->Arc(Point(center.x, center.y) / appUnitsPerDevPixel, + r / appUnitsPerDevPixel, 0, Float(2 * M_PI)); + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> CSSClipPathInstance::CreateClipPathEllipse( + DrawTarget* aDrawTarget, const nsRect& aRefBox) { + const auto& basicShape = *mClipPathStyle.AsShape()._0; + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nsPoint center = + ShapeUtils::ComputeCircleOrEllipseCenter(basicShape, aRefBox); + nsSize radii = ShapeUtils::ComputeEllipseRadii(basicShape, center, aRefBox); + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + EllipseToBezier(builder.get(), + Point(center.x, center.y) / appUnitsPerDevPixel, + Size(radii.width, radii.height) / appUnitsPerDevPixel); + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> CSSClipPathInstance::CreateClipPathPolygon( + DrawTarget* aDrawTarget, const nsRect& aRefBox) { + const auto& basicShape = *mClipPathStyle.AsShape()._0; + auto fillRule = basicShape.AsPolygon().fill == StyleFillRule::Nonzero + ? FillRule::FILL_WINDING + : FillRule::FILL_EVEN_ODD; + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(fillRule); + + nsTArray<nsPoint> vertices = + ShapeUtils::ComputePolygonVertices(basicShape, aRefBox); + if (vertices.IsEmpty()) { + MOZ_ASSERT_UNREACHABLE( + "ComputePolygonVertices() should've given us some vertices!"); + } else { + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + builder->MoveTo(NSPointToPoint(vertices[0], appUnitsPerDevPixel)); + for (size_t i = 1; i < vertices.Length(); ++i) { + builder->LineTo(NSPointToPoint(vertices[i], appUnitsPerDevPixel)); + } + } + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> CSSClipPathInstance::CreateClipPathInset( + DrawTarget* aDrawTarget, const nsRect& aRefBox) { + const auto& basicShape = *mClipPathStyle.AsShape()._0; + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + + const nsRect insetRect = ShapeUtils::ComputeInsetRect(basicShape, aRefBox); + const Rect insetRectPixels = NSRectToRect(insetRect, appUnitsPerDevPixel); + nscoord appUnitsRadii[8]; + + if (ShapeUtils::ComputeInsetRadii(basicShape, aRefBox, insetRect, + appUnitsRadii)) { + RectCornerRadii corners; + nsCSSRendering::ComputePixelRadii(appUnitsRadii, appUnitsPerDevPixel, + &corners); + + AppendRoundedRectToPath(builder, insetRectPixels, corners, true); + } else { + AppendRectToPath(builder, insetRectPixels, true); + } + return builder->Finish(); +} + +already_AddRefed<Path> CSSClipPathInstance::CreateClipPathPath( + DrawTarget* aDrawTarget) { + const auto& path = mClipPathStyle.AsPath(); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder( + path.fill == StyleFillRule::Nonzero ? FillRule::FILL_WINDING + : FillRule::FILL_EVEN_ODD); + float scale = float(AppUnitsPerCSSPixel()) / + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + return SVGPathData::BuildPath(path.path._0.AsSpan(), builder, + StyleStrokeLinecap::Butt, 0.0, scale); +} + +} // namespace mozilla diff --git a/layout/svg/CSSClipPathInstance.h b/layout/svg/CSSClipPathInstance.h new file mode 100644 index 0000000000..90ef52a6fb --- /dev/null +++ b/layout/svg/CSSClipPathInstance.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_CSSCLIPPATHINSTANCE_H_ +#define LAYOUT_SVG_CSSCLIPPATHINSTANCE_H_ + +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "mozilla/gfx/2D.h" +#include "nsRect.h" +#include "nsStyleStruct.h" + +class nsIFrame; +class gfxContext; + +namespace mozilla { + +class MOZ_STACK_CLASS CSSClipPathInstance { + using DrawTarget = gfx::DrawTarget; + using Path = gfx::Path; + using Rect = gfx::Rect; + + public: + static void ApplyBasicShapeOrPathClip(gfxContext& aContext, nsIFrame* aFrame, + const gfxMatrix& aTransform); + static RefPtr<Path> CreateClipPathForFrame(gfx::DrawTarget* aDt, + nsIFrame* aFrame, + const gfxMatrix& aTransform); + // aPoint is in CSS pixels. + static bool HitTestBasicShapeOrPathClip(nsIFrame* aFrame, + const gfxPoint& aPoint); + + static Maybe<Rect> GetBoundingRectForBasicShapeOrPathClip( + nsIFrame* aFrame, const StyleClipPath&); + + private: + explicit CSSClipPathInstance(nsIFrame* aFrame, const StyleClipPath& aClipPath) + : mTargetFrame(aFrame), mClipPathStyle(aClipPath) {} + + already_AddRefed<Path> CreateClipPath(DrawTarget* aDrawTarget, + const gfxMatrix& aTransform); + + 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); + + already_AddRefed<Path> CreateClipPathPath(DrawTarget* aDrawTarget); + + /** + * The frame for the element that is currently being clipped. + */ + nsIFrame* mTargetFrame; + const StyleClipPath& mClipPathStyle; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_CSSCLIPPATHINSTANCE_H_ diff --git a/layout/svg/CSSFilterInstance.cpp b/layout/svg/CSSFilterInstance.cpp new file mode 100644 index 0000000000..02ec3907bd --- /dev/null +++ b/layout/svg/CSSFilterInstance.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "CSSFilterInstance.h" + +// Keep others in (case-insensitive) order: +#include "FilterDescription.h" +#include "gfx2DGlue.h" +#include "gfxUtils.h" +#include "nsIFrame.h" +#include "nsStyleStruct.h" +#include "nsTArray.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +static float ClampFactor(float aFactor) { + if (aFactor > 1) { + return 1; + } + if (aFactor < 0) { + MOZ_ASSERT_UNREACHABLE("A negative value should not have been parsed."); + return 0; + } + + return aFactor; +} + +CSSFilterInstance::CSSFilterInstance( + const StyleFilter& aFilter, nscolor aShadowFallbackColor, + const nsIntRect& aTargetBoundsInFilterSpace, + const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform) + : mFilter(aFilter), + mShadowFallbackColor(aShadowFallbackColor), + mTargetBoundsInFilterSpace(aTargetBoundsInFilterSpace), + mFrameSpaceInCSSPxToFilterSpaceTransform( + aFrameSpaceInCSSPxToFilterSpaceTransform) {} + +nsresult CSSFilterInstance::BuildPrimitives( + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + bool aInputIsTainted) { + FilterPrimitiveDescription descr = + CreatePrimitiveDescription(aPrimitiveDescrs, aInputIsTainted); + nsresult result; + switch (mFilter.tag) { + case StyleFilter::Tag::Blur: + result = SetAttributesForBlur(descr); + break; + case StyleFilter::Tag::Brightness: + result = SetAttributesForBrightness(descr); + break; + case StyleFilter::Tag::Contrast: + result = SetAttributesForContrast(descr); + break; + case StyleFilter::Tag::DropShadow: + result = SetAttributesForDropShadow(descr); + break; + case StyleFilter::Tag::Grayscale: + result = SetAttributesForGrayscale(descr); + break; + case StyleFilter::Tag::HueRotate: + result = SetAttributesForHueRotate(descr); + break; + case StyleFilter::Tag::Invert: + result = SetAttributesForInvert(descr); + break; + case StyleFilter::Tag::Opacity: + result = SetAttributesForOpacity(descr); + break; + case StyleFilter::Tag::Saturate: + result = SetAttributesForSaturate(descr); + break; + case StyleFilter::Tag::Sepia: + result = SetAttributesForSepia(descr); + break; + default: + MOZ_ASSERT_UNREACHABLE("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(std::move(descr)); + return NS_OK; +} + +FilterPrimitiveDescription CSSFilterInstance::CreatePrimitiveDescription( + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + bool aInputIsTainted) { + FilterPrimitiveDescription descr; + 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 CSSFilterInstance::SetAttributesForBlur( + FilterPrimitiveDescription& aDescr) { + const Length& radiusInFrameSpace = mFilter.AsBlur(); + Size radiusInFilterSpace = + BlurRadiusToFilterSpace(radiusInFrameSpace.ToAppUnits()); + GaussianBlurAttributes atts; + atts.mStdDeviation = radiusInFilterSpace; + aDescr.Attributes() = AsVariant(atts); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForBrightness( + FilterPrimitiveDescription& aDescr) { + float value = mFilter.AsBrightness(); + float intercept = 0.0f; + ComponentTransferAttributes atts; + + // Set transfer functions for RGB. + atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR; + atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R; + atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R; + float slopeIntercept[2]; + slopeIntercept[kComponentTransferSlopeIndex] = value; + slopeIntercept[kComponentTransferInterceptIndex] = intercept; + atts.mValues[kChannelROrRGB].AppendElements(slopeIntercept, 2); + + atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForContrast( + FilterPrimitiveDescription& aDescr) { + float value = mFilter.AsContrast(); + float intercept = -(0.5 * value) + 0.5; + ComponentTransferAttributes atts; + + // Set transfer functions for RGB. + atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR; + atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R; + atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R; + float slopeIntercept[2]; + slopeIntercept[kComponentTransferSlopeIndex] = value; + slopeIntercept[kComponentTransferInterceptIndex] = intercept; + atts.mValues[kChannelROrRGB].AppendElements(slopeIntercept, 2); + + atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForDropShadow( + FilterPrimitiveDescription& aDescr) { + const auto& shadow = mFilter.AsDropShadow(); + + DropShadowAttributes atts; + + // Set drop shadow blur radius. + Size radiusInFilterSpace = BlurRadiusToFilterSpace(shadow.blur.ToAppUnits()); + atts.mStdDeviation = radiusInFilterSpace; + + // Set offset. + IntPoint offsetInFilterSpace = OffsetToFilterSpace( + shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits()); + atts.mOffset = offsetInFilterSpace; + + // Set color. If unspecified, use the CSS color property. + nscolor shadowColor = shadow.color.CalcColor(mShadowFallbackColor); + atts.mColor = ToAttributeColor(shadowColor); + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForGrayscale( + FilterPrimitiveDescription& aDescr) { + ColorMatrixAttributes atts; + // Set color matrix type. + atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE; + + // Set color matrix values. + float value = 1 - ClampFactor(mFilter.AsGrayscale()); + atts.mValues.AppendElements(&value, 1); + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForHueRotate( + FilterPrimitiveDescription& aDescr) { + ColorMatrixAttributes atts; + // Set color matrix type. + atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_HUE_ROTATE; + + // Set color matrix values. + float value = mFilter.AsHueRotate().ToDegrees(); + atts.mValues.AppendElements(&value, 1); + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForInvert( + FilterPrimitiveDescription& aDescr) { + ComponentTransferAttributes atts; + float value = ClampFactor(mFilter.AsInvert()); + + // Set transfer functions for RGB. + float invertTableValues[2]; + invertTableValues[0] = value; + invertTableValues[1] = 1 - value; + + // Set transfer functions for RGB. + atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE; + atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R; + atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R; + atts.mValues[kChannelROrRGB].AppendElements(invertTableValues, 2); + + atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForOpacity( + FilterPrimitiveDescription& aDescr) { + OpacityAttributes atts; + float value = ClampFactor(mFilter.AsOpacity()); + + atts.mOpacity = value; + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForSaturate( + FilterPrimitiveDescription& aDescr) { + ColorMatrixAttributes atts; + // Set color matrix type. + atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE; + + // Set color matrix values. + float value = mFilter.AsSaturate(); + atts.mValues.AppendElements(&value, 1); + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +nsresult CSSFilterInstance::SetAttributesForSepia( + FilterPrimitiveDescription& aDescr) { + ColorMatrixAttributes atts; + // Set color matrix type. + atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_SEPIA; + + // Set color matrix values. + float value = ClampFactor(mFilter.AsSepia()); + atts.mValues.AppendElements(&value, 1); + + aDescr.Attributes() = AsVariant(std::move(atts)); + return NS_OK; +} + +Size CSSFilterInstance::BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace) { + float radiusInFrameSpaceInCSSPx = + nsPresContext::AppUnitsToFloatCSSPixels(aRadiusInFrameSpace); + + // Convert the radius to filter space. + Size radiusInFilterSpace(radiusInFrameSpaceInCSSPx, + radiusInFrameSpaceInCSSPx); + // Narrow the scale factors. They will only be used with types containing + // floating point types. + auto frameSpaceInCSSPxToFilterSpaceScale = + mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors() + .ConvertTo<float>(); + radiusInFilterSpace = + radiusInFilterSpace * frameSpaceInCSSPxToFilterSpaceScale; + + // Check the radius limits. + if (radiusInFilterSpace.width < 0 || radiusInFilterSpace.height < 0) { + MOZ_ASSERT_UNREACHABLE( + "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 CSSFilterInstance::OffsetToFilterSpace(nscoord aXOffsetInFrameSpace, + nscoord aYOffsetInFrameSpace) { + gfxPoint offsetInFilterSpace( + nsPresContext::AppUnitsToFloatCSSPixels(aXOffsetInFrameSpace), + nsPresContext::AppUnitsToFloatCSSPixels(aYOffsetInFrameSpace)); + + // Convert the radius to filter space. + auto frameSpaceInCSSPxToFilterSpaceScale = + mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors(); + offsetInFilterSpace.x *= frameSpaceInCSSPxToFilterSpaceScale.xScale; + offsetInFilterSpace.y *= frameSpaceInCSSPxToFilterSpaceScale.yScale; + + return IntPoint(int32_t(offsetInFilterSpace.x), + int32_t(offsetInFilterSpace.y)); +} + +sRGBColor CSSFilterInstance::ToAttributeColor(nscolor aColor) { + return sRGBColor(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 CSSFilterInstance::GetLastResultIndex( + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) { + uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); + return !numPrimitiveDescrs + ? FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic + : numPrimitiveDescrs - 1; +} + +void CSSFilterInstance::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); +} + +} // namespace mozilla diff --git a/layout/svg/CSSFilterInstance.h b/layout/svg/CSSFilterInstance.h new file mode 100644 index 0000000000..cdf21ee2bf --- /dev/null +++ b/layout/svg/CSSFilterInstance.h @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_CSSFILTERINSTANCE_H_ +#define LAYOUT_SVG_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 "mozilla/ServoStyleConsts.h" + +namespace mozilla { + +/** + * This class helps FilterInstance 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 CSSFilterInstance { + using sRGBColor = gfx::sRGBColor; + using FilterPrimitiveDescription = gfx::FilterPrimitiveDescription; + using IntPoint = gfx::IntPoint; + using Size = gfx::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 CSSFilterInstance. + * @param aShadowFallbackColor The color that should be used for + * drop-shadow() filters that don't specify a shadow color. + * @param aTargetBoundsInFilterSpace The pre-filter ink 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. + */ + CSSFilterInstance(const StyleFilter& 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( + 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. + */ + sRGBColor 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 StyleFilter& 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; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_CSSFILTERINSTANCE_H_ diff --git a/layout/svg/FilterInstance.cpp b/layout/svg/FilterInstance.cpp new file mode 100644 index 0000000000..bdc6e9b3bc --- /dev/null +++ b/layout/svg/FilterInstance.cpp @@ -0,0 +1,903 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "FilterInstance.h" + +// MFBT headers next: +#include "mozilla/UniquePtr.h" + +// Keep others in (case-insensitive) order: +#include "FilterSupport.h" +#include "ImgDrawResult.h" +#include "SVGContentUtils.h" +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" + +#include "gfxUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/gfx/Filters.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/Document.h" +#include "nsLayoutUtils.h" +#include "CSSFilterInstance.h" +#include "SVGIntegrationUtils.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +FilterDescription FilterInstance::GetFilterDescription( + nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain, + bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics, + const gfxRect& aBBox, + nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) { + gfxMatrix identity; + FilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain, + aFilterInputIsTainted, nullptr, identity, nullptr, + nullptr, nullptr, &aBBox); + if (!instance.IsInitialized()) { + return FilterDescription(); + } + return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages); +} + +static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) { + if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) { + return MakeUnique<SVGElementMetrics>(element); + } + return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame); +} + +void FilterInstance::PaintFilteredFrame( + nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilterChain, + gfxContext* aCtx, const SVGFilterPaintCallback& aPaintCallback, + const nsRegion* aDirtyArea, imgDrawingParams& aImgParams, float aOpacity) { + UniquePtr<UserSpaceMetrics> metrics = + UserSpaceMetricsForFrame(aFilteredFrame); + + gfxContextMatrixAutoSaveRestore autoSR(aCtx); + auto scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors(); + if (scaleFactors.xScale == 0 || scaleFactors.yScale == 0) { + return; + } + + gfxMatrix scaleMatrix(scaleFactors.xScale, 0.0f, 0.0f, scaleFactors.yScale, + 0.0f, 0.0f); + + gfxMatrix reverseScaleMatrix = scaleMatrix; + DebugOnly<bool> invertible = reverseScaleMatrix.Invert(); + MOZ_ASSERT(invertible); + + gfxMatrix scaleMatrixInDevUnits = + scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame); + + // Hardcode InputIsTainted to true because we don't want JS to be able to + // read the rendered contents of aFilteredFrame. + FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), + *metrics, aFilterChain, /* InputIsTainted */ true, + aPaintCallback, scaleMatrixInDevUnits, aDirtyArea, + nullptr, nullptr, nullptr); + if (instance.IsInitialized()) { + // Pull scale vector out of aCtx's transform, put all scale factors, which + // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits. + aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble()); + + instance.Render(aCtx, aImgParams, aOpacity); + } else { + // Render the unfiltered contents. + aPaintCallback(*aCtx, aImgParams, nullptr, nullptr); + } +} + +static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) { + MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R); + switch (aFuncType) { + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: + return mozilla::wr::ComponentTransferFuncType::Table; + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: + return mozilla::wr::ComponentTransferFuncType::Discrete; + case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: + return mozilla::wr::ComponentTransferFuncType::Linear; + case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: + return mozilla::wr::ComponentTransferFuncType::Gamma; + case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY: + default: + return mozilla::wr::ComponentTransferFuncType::Identity; + } + MOZ_ASSERT_UNREACHABLE("all func types not handled?"); + return mozilla::wr::ComponentTransferFuncType::Identity; +} + +bool FilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame, + Span<const StyleFilter> aFilters, + WrFiltersHolder& aWrFilters, + bool& aInitialized) { + bool status = BuildWebRenderFiltersImpl(aFilteredFrame, aFilters, aWrFilters, + aInitialized); + if (!status) { + aFilteredFrame->PresContext()->Document()->SetUseCounter( + eUseCounter_custom_WrFilterFallback); + } + + return status; +} + +bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame* aFilteredFrame, + Span<const StyleFilter> aFilters, + WrFiltersHolder& aWrFilters, + bool& aInitialized) { + aWrFilters.filters.Clear(); + aWrFilters.filter_datas.Clear(); + aWrFilters.values.Clear(); + + UniquePtr<UserSpaceMetrics> metrics = + UserSpaceMetricsForFrame(aFilteredFrame); + + // TODO: simply using an identity matrix here, was pulling the scale from a + // gfx context for the non-wr path. + gfxMatrix scaleMatrix; + gfxMatrix scaleMatrixInDevUnits = + scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame); + + // Hardcode inputIsTainted to true because we don't want JS to be able to + // read the rendered contents of aFilteredFrame. + bool inputIsTainted = true; + FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), + *metrics, aFilters, inputIsTainted, nullptr, + scaleMatrixInDevUnits, nullptr, nullptr, nullptr, + nullptr); + + if (!instance.IsInitialized()) { + aInitialized = false; + return true; + } + + // If there are too many filters to render, then just pretend that we + // succeeded, and don't render any of them. + if (instance.mFilterDescription.mPrimitives.Length() > + StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) { + return true; + } + + Maybe<IntRect> finalClip; + bool srgb = true; + // We currently apply the clip on the stacking context after applying filters, + // but primitive subregions imply clipping after each filter and not just the + // end of the chain. For some types of filter it doesn't matter, but for those + // which sample outside of the location of the destination pixel like blurs, + // only clipping after could produce incorrect results, so we bail out in this + // case. + // We can lift this restriction once we have added support for primitive + // subregions to WebRender's filters. + for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length(); + i++) { + const auto& primitive = instance.mFilterDescription.mPrimitives[i]; + + // WebRender only supports filters with one input. + if (primitive.NumberOfInputs() != 1) { + return false; + } + // The first primitive must have the source graphic as the input, all + // other primitives must have the prior primitive as the input, otherwise + // it's not supported by WebRender. + if (i == 0) { + if (primitive.InputPrimitiveIndex(0) != + FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) { + return false; + } + } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) { + return false; + } + + bool previousSrgb = srgb; + bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB; + if (srgb && !primNeedsSrgb) { + aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear()); + } else if (!srgb && primNeedsSrgb) { + aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb()); + } + srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB; + + const PrimitiveAttributes& attr = primitive.Attributes(); + + bool filterIsNoop = false; + + if (attr.is<OpacityAttributes>()) { + float opacity = attr.as<OpacityAttributes>().mOpacity; + aWrFilters.filters.AppendElement(wr::FilterOp::Opacity( + wr::PropertyBinding<float>::Value(opacity), opacity)); + } else if (attr.is<ColorMatrixAttributes>()) { + const ColorMatrixAttributes& attributes = + attr.as<ColorMatrixAttributes>(); + + float transposed[20]; + if (gfx::ComputeColorMatrix(attributes, transposed)) { + float matrix[20] = { + transposed[0], transposed[5], transposed[10], transposed[15], + transposed[1], transposed[6], transposed[11], transposed[16], + transposed[2], transposed[7], transposed[12], transposed[17], + transposed[3], transposed[8], transposed[13], transposed[18], + transposed[4], transposed[9], transposed[14], transposed[19]}; + + aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix)); + } else { + filterIsNoop = true; + } + } else if (attr.is<GaussianBlurAttributes>()) { + if (finalClip) { + // There's a clip that needs to apply before the blur filter, but + // WebRender only lets us apply the clip at the end of the filter + // chain. Clipping after a blur is not equivalent to clipping before + // a blur, so bail out. + return false; + } + + const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>(); + + const Size& stdDev = blur.mStdDeviation; + if (stdDev.width != 0.0 || stdDev.height != 0.0) { + aWrFilters.filters.AppendElement( + wr::FilterOp::Blur(stdDev.width, stdDev.height)); + } else { + filterIsNoop = true; + } + } else if (attr.is<DropShadowAttributes>()) { + if (finalClip) { + // We have to bail out for the same reason we would with a blur filter. + return false; + } + + const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>(); + + const Size& stdDev = shadow.mStdDeviation; + if (stdDev.width != stdDev.height) { + return false; + } + + sRGBColor color = shadow.mColor; + if (!primNeedsSrgb) { + color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)], + gsRGBToLinearRGBMap[uint8_t(color.g * 255)], + gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a); + } + wr::Shadow wrShadow; + wrShadow.offset = {shadow.mOffset.x, shadow.mOffset.y}; + wrShadow.color = wr::ToColorF(ToDeviceColor(color)); + wrShadow.blur_radius = stdDev.width; + wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow); + + aWrFilters.filters.AppendElement(filterOp); + } else if (attr.is<ComponentTransferAttributes>()) { + const ComponentTransferAttributes& attributes = + attr.as<ComponentTransferAttributes>(); + + size_t numValues = + attributes.mValues[0].Length() + attributes.mValues[1].Length() + + attributes.mValues[2].Length() + attributes.mValues[3].Length(); + if (numValues > 1024) { + // Depending on how the wr shaders are implemented we may need to + // limit the total number of values. + return false; + } + + wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer}; + wr::WrFilterData filterData; + aWrFilters.values.AppendElement(nsTArray<float>()); + nsTArray<float>* values = + &aWrFilters.values[aWrFilters.values.Length() - 1]; + values->SetCapacity(numValues); + + filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]); + size_t R_startindex = values->Length(); + values->AppendElements(attributes.mValues[0]); + filterData.R_values_count = attributes.mValues[0].Length(); + + size_t indexToUse = + attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1; + filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]); + size_t G_startindex = values->Length(); + values->AppendElements(attributes.mValues[indexToUse]); + filterData.G_values_count = attributes.mValues[indexToUse].Length(); + + indexToUse = + attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2; + filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]); + size_t B_startindex = values->Length(); + values->AppendElements(attributes.mValues[indexToUse]); + filterData.B_values_count = attributes.mValues[indexToUse].Length(); + + filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]); + size_t A_startindex = values->Length(); + values->AppendElements(attributes.mValues[3]); + filterData.A_values_count = attributes.mValues[3].Length(); + + filterData.R_values = + filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr; + filterData.G_values = + filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr; + filterData.B_values = + filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr; + filterData.A_values = + filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr; + + aWrFilters.filters.AppendElement(filterOp); + aWrFilters.filter_datas.AppendElement(filterData); + } else { + return false; + } + + if (filterIsNoop && aWrFilters.filters.Length() > 0 && + (aWrFilters.filters.LastElement().tag == + wr::FilterOp::Tag::SrgbToLinear || + aWrFilters.filters.LastElement().tag == + wr::FilterOp::Tag::LinearToSrgb)) { + // We pushed a color space conversion filter in prevision of applying + // another filter which turned out to be a no-op, so the conversion is + // unnecessary. Remove it from the filter list. + // This is both an optimization and a way to pass the wptest + // css/filter-effects/filter-scale-001.html for which the needless + // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we + // cannot add fuzziness to the test. + Unused << aWrFilters.filters.PopLastElement(); + srgb = previousSrgb; + } + + if (!filterIsNoop) { + if (finalClip.isNothing()) { + finalClip = Some(primitive.PrimitiveSubregion()); + } else { + finalClip = + Some(primitive.PrimitiveSubregion().Intersect(finalClip.value())); + } + } + } + + if (!srgb) { + aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb()); + } + + if (finalClip) { + aWrFilters.post_filters_clip = + Some(instance.FilterSpaceToFrameSpace(finalClip.value())); + } + return true; +} + +nsRegion FilterInstance::GetPreFilterNeededArea( + nsIFrame* aFilteredFrame, const nsRegion& aPostFilterDirtyRegion) { + gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame); + auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan(); + 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. + FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), + *metrics, filterChain, /* InputIsTainted */ true, + nullptr, tm, &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(); +} + +Maybe<nsRect> FilterInstance::GetPostFilterBounds( + nsIFrame* aFilteredFrame, const gfxRect* aOverrideBBox, + const nsRect* aPreFilterBounds) { + MOZ_ASSERT(!aFilteredFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) || + !aFilteredFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "Non-display SVG do not maintain ink overflow rects"); + + nsRegion preFilterRegion; + nsRegion* preFilterRegionPtr = nullptr; + if (aPreFilterBounds) { + preFilterRegion = *aPreFilterBounds; + preFilterRegionPtr = &preFilterRegion; + } + + gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame); + auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan(); + 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. + FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), + *metrics, filterChain, /* InputIsTainted */ true, + nullptr, tm, nullptr, preFilterRegionPtr, + aPreFilterBounds, aOverrideBBox); + if (!instance.IsInitialized()) { + return Nothing(); + } + + return Some(instance.ComputePostFilterExtents()); +} + +FilterInstance::FilterInstance( + nsIFrame* aTargetFrame, nsIContent* aTargetContent, + const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain, + bool aFilterInputIsTainted, const SVGFilterPaintCallback& aPaintCallback, + const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion, + const nsRegion* aPreFilterDirtyRegion, + const nsRect* aPreFilterInkOverflowRectOverride, + 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 = + SVGUtils::GetBBox(mTargetFrame, SVGUtils::eUseFrameBoundsForOuterSVG | + SVGUtils::eBBoxIncludeFillGeometry); + } + + // Compute user space to filter space transforms. + if (!ComputeUserSpaceToFilterSpaceScale()) { + return; + } + + if (!ComputeTargetBBoxInFilterSpace()) { + return; + } + + // Get various transforms: + gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.xScale, 0.0f, 0.0f, + mFilterSpaceToUserSpaceScale.yScale, 0.0f, 0.0f); + + mFilterSpaceToFrameSpaceInCSSPxTransform = + filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform(); + // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible + mFrameSpaceInCSSPxToFilterSpaceTransform = + mFilterSpaceToFrameSpaceInCSSPxTransform; + mFrameSpaceInCSSPxToFilterSpaceTransform.Invert(); + + nsIntRect targetBounds; + if (aPreFilterInkOverflowRectOverride) { + targetBounds = FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride); + } else if (mTargetFrame) { + nsRect preFilterVOR = mTargetFrame->PreEffectsInkOverflowRect(); + targetBounds = FrameSpaceToFilterSpace(&preFilterVOR); + } + mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds); + + // Build the filter graph. + if (NS_FAILED( + BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) { + return; + } + + // Convert the passed in rects from frame space to filter space: + mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion); + mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion); + + mInitialized = true; +} + +bool FilterInstance::ComputeTargetBBoxInFilterSpace() { + gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox); + targetBBoxInFilterSpace.RoundOut(); + + return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace, + &mTargetBBoxInFilterSpace); +} + +bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() { + if (mTargetFrame) { + mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(); + if (mUserSpaceToFilterSpaceScale.xScale <= 0.0f || + mUserSpaceToFilterSpaceScale.yScale <= 0.0f) { + // Nothing should be rendered. + return false; + } + } else { + mUserSpaceToFilterSpaceScale = MatrixScalesDouble(); + } + + mFilterSpaceToUserSpaceScale = + MatrixScalesDouble(1.0f / mUserSpaceToFilterSpaceScale.xScale, + 1.0f / mUserSpaceToFilterSpaceScale.yScale); + + return true; +} + +gfxRect FilterInstance::UserSpaceToFilterSpace( + const gfxRect& aUserSpaceRect) const { + gfxRect filterSpaceRect = aUserSpaceRect; + filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale); + return filterSpaceRect; +} + +gfxRect FilterInstance::FilterSpaceToUserSpace( + const gfxRect& aFilterSpaceRect) const { + gfxRect userSpaceRect = aFilterSpaceRect; + userSpaceRect.Scale(mFilterSpaceToUserSpaceScale); + return userSpaceRect; +} + +nsresult FilterInstance::BuildPrimitives(Span<const StyleFilter> aFilterChain, + nsIFrame* aTargetFrame, + bool aFilterInputIsTainted) { + nsTArray<FilterPrimitiveDescription> primitiveDescriptions; + + for (uint32_t i = 0; i < aFilterChain.Length(); i++) { + bool inputIsTainted = primitiveDescriptions.IsEmpty() + ? aFilterInputIsTainted + : primitiveDescriptions.LastElement().IsTainted(); + nsresult rv = BuildPrimitivesForFilter( + aFilterChain[i], aTargetFrame, inputIsTainted, primitiveDescriptions); + if (NS_FAILED(rv)) { + return rv; + } + } + + mFilterDescription = FilterDescription(std::move(primitiveDescriptions)); + + return NS_OK; +} + +nsresult FilterInstance::BuildPrimitivesForFilter( + const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted, + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) { + NS_ASSERTION(mUserSpaceToFilterSpaceScale.xScale > 0.0f && + mFilterSpaceToUserSpaceScale.yScale > 0.0f, + "scale factors between spaces should be positive values"); + + if (aFilter.IsUrl()) { + // Build primitives for an SVG filter. + SVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, mTargetContent, + mMetrics, mTargetBBox, + mUserSpaceToFilterSpaceScale); + if (!svgFilterInstance.IsInitialized()) { + return NS_ERROR_FAILURE; + } + + return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions, + 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->StyleText()->mColor.ToColor() + : NS_RGB(0, 0, 0); + + CSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor, + mTargetBounds, + mFrameSpaceInCSSPxToFilterSpaceTransform); + return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions, + aInputIsTainted); +} + +static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) { + aBounds = aRegion.GetBounds(); + + bool overflow; + IntSize surfaceSize = + SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow); + if (overflow) { + aBounds.SizeTo(surfaceSize); + } +} + +void FilterInstance::ComputeNeededBoxes() { + if (mFilterDescription.mPrimitives.IsEmpty()) { + return; + } + + nsIntRegion sourceGraphicNeededRegion; + nsIntRegion fillPaintNeededRegion; + nsIntRegion strokePaintNeededRegion; + + FilterSupport::ComputeSourceNeededRegions( + mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion, + fillPaintNeededRegion, strokePaintNeededRegion); + + sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds); + + UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds); + UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds); + UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds); +} + +void FilterInstance::BuildSourcePaint(SourceInfo* aSource, + imgDrawingParams& aImgParams) { + MOZ_ASSERT(mTargetFrame); + nsIntRect neededRect = aSource->mNeededBounds; + if (neededRect.IsEmpty()) { + return; + } + + RefPtr<DrawTarget> offscreenDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + neededRect.Size(), SurfaceFormat::B8G8R8A8); + if (!offscreenDT || !offscreenDT->IsValid()) { + return; + } + + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT); + MOZ_ASSERT(ctx); // already checked the draw target above + gfxContextAutoSaveRestore saver(ctx); + + ctx->SetMatrixDouble(mPaintTransform * + gfxMatrix::Translation(-neededRect.TopLeft())); + GeneralPattern pattern; + if (aSource == &mFillPaint) { + SVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern, aImgParams); + } else if (aSource == &mStrokePaint) { + SVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern, aImgParams); + } + + if (pattern.GetPattern()) { + offscreenDT->FillRect( + ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern); + } + + aSource->mSourceSurface = offscreenDT->Snapshot(); + aSource->mSurfaceRect = neededRect; +} + +void FilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) { + if (!mFillPaint.mNeededBounds.IsEmpty()) { + BuildSourcePaint(&mFillPaint, aImgParams); + } + + if (!mStrokePaint.mNeededBounds.IsEmpty()) { + BuildSourcePaint(&mStrokePaint, aImgParams); + } +} + +void FilterInstance::BuildSourceImage(DrawTarget* aDest, + imgDrawingParams& aImgParams, + FilterNode* aFilter, FilterNode* aSource, + const Rect& aSourceRect) { + MOZ_ASSERT(mTargetFrame); + + nsIntRect neededRect = mSourceGraphic.mNeededBounds; + if (neededRect.IsEmpty()) { + return; + } + + RefPtr<DrawTarget> offscreenDT; + SurfaceFormat format = SurfaceFormat::B8G8R8A8; + if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) { + offscreenDT = aDest->CreateSimilarDrawTargetForFilter( + neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0)); + } + if (!offscreenDT || !offscreenDT->IsValid()) { + return; + } + + gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect)); + r.RoundOut(); + nsIntRect dirty; + if (!gfxUtils::GfxRectToIntRect(r, &dirty)) { + return; + } + + // 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.) + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT); + MOZ_ASSERT(ctx); // already checked the draw target above + gfxMatrix devPxToCssPxTM = SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame); + DebugOnly<bool> invertible = devPxToCssPxTM.Invert(); + MOZ_ASSERT(invertible); + ctx->SetMatrixDouble(devPxToCssPxTM * mPaintTransform * + gfxMatrix::Translation(-neededRect.TopLeft())); + + auto imageFlags = aImgParams.imageFlags; + if (mTargetFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + // We're coming from a mask or pattern instance. Patterns + // are painted into a separate surface and it seems we can't + // handle the differently sized surface that might be returned + // with FLAG_HIGH_QUALITY_SCALING + imageFlags &= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + imgDrawingParams imgParams(imageFlags); + mPaintCallback(*ctx, imgParams, &mPaintTransform, &dirty); + aImgParams.result = imgParams.result; + + mSourceGraphic.mSourceSurface = offscreenDT->Snapshot(); + mSourceGraphic.mSurfaceRect = neededRect; +} + +void FilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams, + float aOpacity) { + MOZ_ASSERT(mTargetFrame, "Need a frame for rendering"); + + if (mFilterDescription.mPrimitives.IsEmpty()) { + // An filter without any primitive. Treat it as success and paint nothing. + return; + } + + nsIntRect filterRect = + mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds()); + if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) { + return; + } + + gfxContextMatrixAutoSaveRestore autoSR(aCtx); + aCtx->SetMatrix( + aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y)); + + ComputeNeededBoxes(); + + Rect renderRect = IntRectToRect(filterRect); + RefPtr<DrawTarget> dt = aCtx->GetDrawTarget(); + + MOZ_ASSERT(dt); + if (!dt->IsValid()) { + return; + } + + BuildSourcePaints(aImgParams); + RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint; + if (mFillPaint.mSourceSurface) { + fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface, + mFillPaint.mSurfaceRect.TopLeft()); + } + if (mStrokePaint.mSourceSurface) { + strokePaint = FilterWrappers::ForSurface( + dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft()); + } + + // We make the sourceGraphic filter but don't set its inputs until after so + // that we can make the sourceGraphic size depend on the filter chain + sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM); + if (sourceGraphic) { + // Make sure we set the translation before calling BuildSourceImage + // so that CreateSimilarDrawTargetForFilter works properly + IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft(); + sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX, + Matrix::Translation(offset.x, offset.y)); + } + + RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription( + dt, mFilterDescription, renderRect, sourceGraphic, + mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages); + + if (!resultFilter) { + gfxWarning() << "Filter is NULL."; + return; + } + + BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect); + if (sourceGraphic) { + if (mSourceGraphic.mSourceSurface) { + sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface); + } else { + RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget()); + sourceGraphic->SetInput(IN_TRANSFORM_IN, clear); + } + } + + dt->DrawFilter(resultFilter, renderRect, Point(0, 0), DrawOptions(aOpacity)); +} + +nsRegion FilterInstance::ComputePostFilterDirtyRegion() { + if (mPreFilterDirtyRegion.IsEmpty() || + mFilterDescription.mPrimitives.IsEmpty()) { + return nsRegion(); + } + + nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion( + mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion()); + return FilterSpaceToFrameSpace(resultChangeRegion); +} + +nsRect FilterInstance::ComputePostFilterExtents() { + if (mFilterDescription.mPrimitives.IsEmpty()) { + return nsRect(); + } + + nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents( + mFilterDescription, mTargetBounds); + return FilterSpaceToFrameSpace(postFilterExtents.GetBounds()); +} + +nsRect FilterInstance::ComputeSourceNeededRect() { + ComputeNeededBoxes(); + return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds); +} + +nsIntRect FilterInstance::OutputFilterSpaceBounds() const { + uint32_t numPrimitives = mFilterDescription.mPrimitives.Length(); + if (numPrimitives <= 0) { + return nsIntRect(); + } + + return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion(); +} + +nsIntRect FilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const { + nsIntRect rect = OutputFilterSpaceBounds(); + if (aRect) { + if (aRect->IsEmpty()) { + return nsIntRect(); + } + gfxRect rectInCSSPx = + nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel()); + gfxRect rectInFilterSpace = + mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx); + rectInFilterSpace.RoundOut(); + nsIntRect intRect; + if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) { + rect = intRect; + } + } + return rect; +} + +nsRect FilterInstance::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, AppUnitsPerCSSPixel()); +} + +nsIntRegion FilterInstance::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. + nsRect rect = iter.Get(); + result.Or(result, FrameSpaceToFilterSpace(&rect)); + } + return result; +} + +nsRegion FilterInstance::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 FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const { + if (!mTargetFrame) { + return gfxMatrix(); + } + return gfxMatrix::Translation( + -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame)); +} + +} // namespace mozilla diff --git a/layout/svg/FilterInstance.h b/layout/svg/FilterInstance.h new file mode 100644 index 0000000000..de59f2aaf7 --- /dev/null +++ b/layout/svg/FilterInstance.h @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_FILTERINSTANCE_H_ +#define LAYOUT_SVG_FILTERINSTANCE_H_ + +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "nsCOMPtr.h" +#include "FilterDescription.h" +#include "nsHashKeys.h" +#include "nsPoint.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsTArray.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/SVGIntegrationUtils.h" + +class gfxContext; +class nsIContent; +class nsIFrame; +struct WrFiltersHolder; + +namespace mozilla { + +namespace dom { +class UserSpaceMetrics; +} // namespace dom + +namespace image { +struct imgDrawingParams; +} + +/** + * 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 FilterInstance { + using IntRect = gfx::IntRect; + using SourceSurface = gfx::SourceSurface; + using DrawTarget = gfx::DrawTarget; + using FilterPrimitiveDescription = gfx::FilterPrimitiveDescription; + using FilterDescription = gfx::FilterDescription; + using UserSpaceMetrics = dom::UserSpaceMetrics; + using imgDrawingParams = image::imgDrawingParams; + using SVGFilterPaintCallback = SVGIntegrationUtils::SVGFilterPaintCallback; + + 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, Span<const StyleFilter> 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 void PaintFilteredFrame(nsIFrame* aFilteredFrame, + Span<const StyleFilter> aFilterChain, + gfxContext* aCtx, + const SVGFilterPaintCallback& aPaintCallback, + const nsRegion* aDirtyArea, + imgDrawingParams& aImgParams, + float aOpacity = 1.0f); + + /** + * 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 ink 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 ink overflow rect of + * aFilteredFrame, if non-null. + */ + static Maybe<nsRect> GetPostFilterBounds( + nsIFrame* aFilteredFrame, const gfxRect* aOverrideBBox = nullptr, + const nsRect* aPreFilterBounds = nullptr); + + /** + * Try to build WebRender filters for a frame if the filters applied to it are + * supported. aInitialized is set to true if the filter has been initialized + * and false otherwise (e.g. a bad url). If aInitialized is false the filter + * the filter contents should not be drawn. + */ + static bool BuildWebRenderFilters( + nsIFrame* aFilteredFrame, + mozilla::Span<const mozilla::StyleFilter> aFilters, + WrFiltersHolder& aWrFilters, bool& aInitialized); + + private: + /** + * @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 aPreFilterInkOverflowRectOverride [optional] Use a different + * ink 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. + */ + FilterInstance( + nsIFrame* aTargetFrame, nsIContent* aTargetContent, + const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain, + bool aFilterInputIsTainted, + const SVGIntegrationUtils::SVGFilterPaintCallback& aPaintCallback, + const gfxMatrix& aPaintTransform, + const nsRegion* aPostFilterDirtyRegion = nullptr, + const nsRegion* aPreFilterDirtyRegion = nullptr, + const nsRect* aPreFilterInkOverflowRectOverride = nullptr, + const gfxRect* aOverrideBBox = nullptr); + + static bool BuildWebRenderFiltersImpl( + nsIFrame* aFilteredFrame, + mozilla::Span<const mozilla::StyleFilter> aFilters, + WrFiltersHolder& aWrFilters, bool& aInitialized); + + /** + * 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 + * FilterInstance constructor. + */ + void Render(gfxContext* aCtx, imgDrawingParams& aImgParams, + float aOpacity = 1.0f); + + const FilterDescription& ExtractDescriptionAndAdditionalImages( + nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) { + aOutAdditionalImages = std::move(mInputImages); + 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 FilterInstance 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 FilterInstance constructor. + */ + nsRect ComputeSourceNeededRect(); + + 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 + */ + void BuildSourcePaint(SourceInfo* aSource, imgDrawingParams& aImgParams); + + /** + * Creates a SourceSurface for either the FillPaint and StrokePaint graph + * nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and + * mStrokePaint.mSourceSurface respectively. + */ + void BuildSourcePaints(imgDrawingParams& aImgParams); + + /** + * Creates the SourceSurface for the SourceGraphic graph node, paints its + * contents, and assigns it to mSourceGraphic.mSourceSurface. + */ + void BuildSourceImage(DrawTarget* aDest, imgDrawingParams& aImgParams, + mozilla::gfx::FilterNode* aFilter, + mozilla::gfx::FilterNode* aSource, + const mozilla::gfx::Rect& aSourceRect); + + /** + * 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(Span<const StyleFilter> 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 StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted, + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions); + + /** + * 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. + */ + bool 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; + + bool ComputeTargetBBoxInFilterSpace(); + + /** + * 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; + + const SVGFilterPaintCallback& 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; + + /** + * Transform rects between filter space and frame space in CSS pixels. + */ + gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform; + gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform; + + /** + * The scale factors between user space and filter space. + */ + gfx::MatrixScalesDouble mUserSpaceToFilterSpaceScale; + gfx::MatrixScalesDouble 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; + FilterDescription mFilterDescription; + bool mInitialized; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_FILTERINSTANCE_H_ diff --git a/layout/svg/ISVGDisplayableFrame.h b/layout/svg/ISVGDisplayableFrame.h new file mode 100644 index 0000000000..90446561a1 --- /dev/null +++ b/layout/svg/ISVGDisplayableFrame.h @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_ISVGDISPLAYABLEFRAME_H_ +#define LAYOUT_SVG_ISVGDISPLAYABLEFRAME_H_ + +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "nsQueryFrame.h" +#include "nsRect.h" +#include "mozilla/gfx/MatrixFwd.h" + +class gfxContext; +class nsIFrame; + +namespace mozilla { +class SVGAnimatedLengthList; +class SVGAnimatedNumberList; +class SVGBBox; +class SVGLengthList; +class SVGNumberList; +class SVGUserUnitList; + +namespace image { +struct imgDrawingParams; +} // namespace image + +/** + * This class is used for elements that can be part of a directly displayable + * section of a document. This includes SVGGeometryFrame and SVGGFrame. + * (Even though the latter doesn't display anything itself, if it contains + * SVGGeometryFrame descendants it is can still be part of a displayable + * section of a document) This class is not used for elements that can never + * display directly, including SVGGradientFrame and SVGPatternFrame. (The + * latter may contain displayable content, but it and its content are never + * *directly* displayed in a document. It can only end up being displayed by + * means of a reference from other content.) + * + * Note specifically that SVG frames that inherit SVGContainerFrame do *not* + * implement this class (only those that inherit SVGDisplayContainerFrame + * do.) + */ +class ISVGDisplayableFrame : public nsQueryFrame { + public: + using imgDrawingParams = image::imgDrawingParams; + + NS_DECL_QUERYFRAME_TARGET(ISVGDisplayableFrame) + + /** + * 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 + * SVGOuterSVGFrame::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 SVGOuterSVGFrame::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 aImgParams imagelib parameters that may be used when painting + * feImage. + * + * @param aDirtyRect The area being redrawn, in frame offset pixel + * coordinates. + */ + virtual void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + 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; + + // 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 SVGOuterSVGFrame has had its initial reflow + // (i.e. once the SVG viewport dimensions are known). It should also only + // be called by SVGOuterSVGFrame during its reflow. + virtual void ReflowSVG() = 0; + + // Flags to pass to NotifySVGChange: + // + // 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) + // FULL_ZOOM_CHANGED: + // the page's zoom level has changed + 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 gfx::Matrix& aToBBoxUserspace, + uint32_t aFlags) = 0; + + // Are we a container frame? + virtual bool IsDisplayContainer() = 0; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_ISVGDISPLAYABLEFRAME_H_ diff --git a/layout/svg/ISVGSVGFrame.h b/layout/svg/ISVGSVGFrame.h new file mode 100644 index 0000000000..ce034b5338 --- /dev/null +++ b/layout/svg/ISVGSVGFrame.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_ISVGSVGFRAME_H_ +#define LAYOUT_SVG_ISVGSVGFRAME_H_ + +#include "nsQueryFrame.h" + +namespace mozilla { + +class ISVGSVGFrame { + public: + NS_DECL_QUERYFRAME_TARGET(ISVGSVGFrame) + + /** + * 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 + * ISVGDisplayableFrame::COORD_CONTEXT_CHANGED and/or + * ISVGDisplayableFrame::TRANSFORM_CHANGED. + */ + virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) = 0; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_ISVGSVGFRAME_H_ diff --git a/layout/svg/SVGAFrame.cpp b/layout/svg/SVGAFrame.cpp new file mode 100644 index 0000000000..cbfdf69e62 --- /dev/null +++ b/layout/svg/SVGAFrame.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/PresShell.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/dom/SVGAElement.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "SVGLengthList.h" + +nsIFrame* NS_NewSVGAFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGAFrame final : public SVGDisplayContainerFrame { + friend nsIFrame* ::NS_NewSVGAFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGAFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGAFrame) + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + // nsIFrame: + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGA"_ns, aResult); + } +#endif +}; + +} // namespace mozilla + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGAFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGAFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGAFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods +#ifdef DEBUG +void SVGAFrame::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"); + + SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGAFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* 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); + } + + // Currently our SMIL implementation does not modify the DOM attributes. Once + // we implement the SVG 2 SMIL behaviour this can be removed + // SVGAElement::SetAttr/UnsetAttr's ResetLinkState() call will be sufficient. + if (aModType == dom::MutationEvent_Binding::SMIL && + aAttribute == nsGkAtoms::href && + (aNameSpaceID == kNameSpaceID_None || + aNameSpaceID == kNameSpaceID_XLink)) { + auto* content = static_cast<dom::SVGAElement*>(GetContent()); + + // SMIL may change whether an <a> element is a link, in which case we will + // need to update the link state. + content->ResetLinkState(true, content->ElementHasHref()); + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/layout/svg/SVGClipPathFrame.cpp b/layout/svg/SVGClipPathFrame.cpp new file mode 100644 index 0000000000..157d4becfb --- /dev/null +++ b/layout/svg/SVGClipPathFrame.cpp @@ -0,0 +1,489 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGClipPathFrame.h" + +// Keep others in (case-insensitive) order: +#include "AutoReferenceChainGuard.h" +#include "ImgDrawResult.h" +#include "gfxContext.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGClipPathElement.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "nsGkAtoms.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGClipPathFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame) + +void SVGClipPathFrame::ApplyClipPath(gfxContext& aContext, + nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix) { + MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask"); + + const DrawTarget* drawTarget = aContext.GetDrawTarget(); + + // No need for AutoReferenceChainGuard since simple clip paths by definition + // don't reference another clip path. + + // Restore current transform after applying clip path: + gfxContextMatrixAutoSaveRestore autoRestore(&aContext); + + RefPtr<Path> clipPath; + + ISVGDisplayableFrame* singleClipPathChild = nullptr; + IsTrivial(&singleClipPathChild); + + if (singleClipPathChild) { + SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild); + if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) { + SVGGeometryElement* pathElement = + static_cast<SVGGeometryElement*>(pathFrame->GetContent()); + + gfxMatrix toChildsUserSpace = + SVGUtils::GetTransformMatrixInUserSpace(pathFrame) * + (GetClipPathTransform(aClippedFrame) * aMatrix); + + gfxMatrix newMatrix = aContext.CurrentMatrixDouble() + .PreMultiply(toChildsUserSpace) + .NudgeToIntegers(); + if (!newMatrix.IsSingular()) { + aContext.SetMatrixDouble(newMatrix); + FillRule clipRule = + SVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule); + clipPath = pathElement->GetOrBuildPath(drawTarget, 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()); + } +} + +static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask) { + MOZ_ASSERT(aExtraMask); + + Matrix origin = aTarget->GetTransform(); + aTarget->SetTransform(Matrix()); + aTarget->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)), + aExtraMask, Point(0, 0), + DrawOptions(1.0, CompositionOp::OP_IN)); + aTarget->SetTransform(origin); +} + +void SVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext, + nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, + SourceSurface* aExtraMask) { + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + + // A clipPath can reference another clipPath, creating a chain of clipPaths + // that must all be applied. We re-enter this method for each clipPath in a + // chain, so we need to protect against reference chain related crashes etc.: + AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + return; // Break reference chain + } + + DrawTarget* maskDT = aMaskContext.GetDrawTarget(); + MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8); + + // Paint this clipPath's contents into aMaskDT: + // 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: + SVGClipPathFrame* clipPathThatClipsClipPath; + // XXX check return value? + SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath); + SVGUtils::MaskUsage maskUsage; + SVGUtils::DetermineMaskUsage(this, true, maskUsage); + + if (maskUsage.shouldApplyClipPath) { + clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame, + aMatrix); + } else if (maskUsage.shouldGenerateClipMaskLayer) { + RefPtr<SourceSurface> maskSurface = clipPathThatClipsClipPath->GetClipMask( + aMaskContext, aClippedFrame, aMatrix); + // We want the mask to be untransformed so use the inverse of the current + // transform as the maskTransform to compensate. + Matrix maskTransform = aMaskContext.CurrentMatrix(); + maskTransform.Invert(); + aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface, + maskTransform); + // The corresponding PopGroupAndBlend call below will mask the + // blend using |maskSurface|. + } + + // Paint our children into the mask: + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + PaintFrameIntoMask(kid, aClippedFrame, aMaskContext); + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + aMaskContext.PopGroupAndBlend(); + } else if (maskUsage.shouldApplyClipPath) { + aMaskContext.PopClip(); + } + + if (aExtraMask) { + ComposeExtraMask(maskDT, aExtraMask); + } +} + +void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame, + nsIFrame* aClippedFrame, + gfxContext& aTarget) { + ISVGDisplayableFrame* frame = do_QueryFrame(aFrame); + if (!frame) { + return; + } + + // The CTM of each frame referencing us can be different. + frame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED); + + // Children of this clipPath may themselves be clipped. + SVGClipPathFrame* clipPathThatClipsChild; + // XXX check return value? + if (SVGObserverUtils::GetAndObserveClipPath(aFrame, + &clipPathThatClipsChild) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return; + } + + SVGUtils::MaskUsage maskUsage; + SVGUtils::DetermineMaskUsage(aFrame, true, maskUsage); + if (maskUsage.shouldApplyClipPath) { + clipPathThatClipsChild->ApplyClipPath(aTarget, aClippedFrame, + mMatrixForChildren); + } else if (maskUsage.shouldGenerateClipMaskLayer) { + RefPtr<SourceSurface> maskSurface = clipPathThatClipsChild->GetClipMask( + aTarget, aClippedFrame, mMatrixForChildren); + + // We want the mask to be untransformed so use the inverse of the current + // transform as the maskTransform to compensate. + Matrix maskTransform = aTarget.CurrentMatrix(); + maskTransform.Invert(); + aTarget.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface, + maskTransform); + // The corresponding PopGroupAndBlend call below will mask the + // blend using |maskSurface|. + } + + gfxMatrix toChildsUserSpace = mMatrixForChildren; + nsIFrame* child = do_QueryFrame(frame); + nsIContent* childContent = child->GetContent(); + if (childContent->IsSVGElement()) { + toChildsUserSpace = + SVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren; + } + + // clipPath does not result in any image rendering, so we just use a dummy + // imgDrawingParams instead of requiring our caller to pass one. + image::imgDrawingParams imgParams; + + // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and + // SVGGeometryFrame::Render checks for that state bit and paints + // only the geometry (opaque black) if set. + frame->PaintSVG(aTarget, toChildsUserSpace, imgParams); + + if (maskUsage.shouldGenerateClipMaskLayer) { + aTarget.PopGroupAndBlend(); + } else if (maskUsage.shouldApplyClipPath) { + aTarget.PopClip(); + } +} + +already_AddRefed<SourceSurface> SVGClipPathFrame::GetClipMask( + gfxContext& aReferenceContext, nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, SourceSurface* aExtraMask) { + RefPtr<DrawTarget> maskDT = + aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget( + Rect(), SurfaceFormat::A8); + if (!maskDT) { + return nullptr; + } + + RefPtr<gfxContext> maskContext = + gfxContext::CreatePreservingTransformOrNull(maskDT); + if (!maskContext) { + gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT); + return nullptr; + } + + PaintClipMask(*maskContext, aClippedFrame, aMatrix, aExtraMask); + + RefPtr<SourceSurface> surface = maskDT->Snapshot(); + return surface.forget(); +} + +bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, + const gfxPoint& aPoint) { + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + + // A clipPath can reference another clipPath, creating a chain of clipPaths + // that must all be applied. We re-enter this method for each clipPath in a + // chain, so we need to protect against reference chain related crashes etc.: + AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + return false; // Break reference chain + } + + gfxMatrix matrix = GetClipPathTransform(aClippedFrame); + if (!matrix.Invert()) { + return false; + } + gfxPoint point = matrix.TransformPoint(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 receiving events at aPoint: + SVGClipPathFrame* clipPathFrame; + // XXX check return value? + SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame); + if (clipPathFrame && + !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) { + return false; + } + + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + gfxPoint pointForChild = point; + + gfxMatrix m = SVGUtils::GetTransformMatrixInUserSpace(kid); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return false; + } + pointForChild = m.TransformPoint(point); + } + if (SVGFrame->GetFrameForPoint(pointForChild)) { + return true; + } + } + } + + return false; +} + +bool SVGClipPathFrame::IsTrivial(ISVGDisplayableFrame** aSingleChild) { + // If the clip path is clipped then it's non-trivial + if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) == + SVGObserverUtils::eHasRefsAllValid) { + return false; + } + + if (aSingleChild) { + *aSingleChild = nullptr; + } + + ISVGDisplayableFrame* foundChild = nullptr; + + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + ISVGDisplayableFrame* 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 (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) == + SVGObserverUtils::eHasRefsAllValid) { + return false; + } + + foundChild = svgChild; + } + } + if (aSingleChild) { + *aSingleChild = foundChild; + } + return true; +} + +bool SVGClipPathFrame::IsValid() { + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + + // A clipPath can reference another clipPath, creating a chain of clipPaths + // that must all be applied. We re-enter this method for each clipPath in a + // chain, so we need to protect against reference chain related crashes etc.: + AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + return false; // Break reference chain + } + + if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return false; + } + + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + LayoutFrameType kidType = kid->Type(); + + if (kidType == LayoutFrameType::SVGUse) { + for (nsIFrame* grandKid : kid->PrincipalChildList()) { + LayoutFrameType grandKidType = grandKid->Type(); + + if (grandKidType != LayoutFrameType::SVGGeometry && + grandKidType != LayoutFrameType::SVGText) { + return false; + } + } + continue; + } + + if (kidType != LayoutFrameType::SVGGeometry && + kidType != LayoutFrameType::SVGText) { + return false; + } + } + + return true; +} + +nsresult SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::transform) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + SVGUtils::NotifyChildrenOfSVGChange( + this, ISVGDisplayableFrame::TRANSFORM_CHANGED); + } + if (aAttribute == nsGkAtoms::clipPathUnits) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + } + + return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +void SVGClipPathFrame::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); + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} + +gfxMatrix SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; } + +gfxMatrix SVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) { + SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent()); + + gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) * + SVGUtils::GetTransformMatrixInUserSpace(this); + + SVGAnimatedEnumeration* clipPathUnits = + &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS]; + + uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry | + (aClippedFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone + ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement + : 0); + + return SVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame, + flags); +} + +SVGBBox SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox, + const gfxMatrix& aMatrix, + uint32_t aFlags) { + SVGClipPathFrame* clipPathThatClipsClipPath; + if (SVGObserverUtils::GetAndObserveClipPath(this, + &clipPathThatClipsClipPath) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return SVGBBox(); + } + + nsIContent* node = GetContent()->GetFirstChild(); + SVGBBox unionBBox, tmpBBox; + for (; node; node = node->GetNextSibling()) { + SVGElement* svgNode = static_cast<SVGElement*>(node); + nsIFrame* frame = svgNode->GetPrimaryFrame(); + if (frame) { + ISVGDisplayableFrame* svg = do_QueryFrame(frame); + if (svg) { + gfxMatrix matrix = + SVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix; + tmpBBox = svg->GetBBoxContribution(gfx::ToMatrix(matrix), + SVGUtils::eBBoxIncludeFill); + SVGClipPathFrame* clipPathFrame; + if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) != + SVGObserverUtils::eHasRefsSomeInvalid && + clipPathFrame) { + tmpBBox = + clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags); + } + if (!(aFlags & SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) { + tmpBBox.Intersect(aBBox); + } + unionBBox.UnionEdges(tmpBBox); + } + } + } + + if (clipPathThatClipsClipPath) { + tmpBBox = clipPathThatClipsClipPath->GetBBoxForClipPathFrame(aBBox, aMatrix, + aFlags); + unionBBox.Intersect(tmpBBox); + } + return unionBBox; +} + +bool SVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms, + Matrix* aFromParentTransforms) const { + const auto* e = static_cast<SVGElement const*>(GetContent()); + Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent)); + + if (m.IsIdentity()) { + return false; + } + + if (aOwnTransforms) { + *aOwnTransforms = m; + } + + return true; +} + +} // namespace mozilla diff --git a/layout/svg/SVGClipPathFrame.h b/layout/svg/SVGClipPathFrame.h new file mode 100644 index 0000000000..3ef5b11cca --- /dev/null +++ b/layout/svg/SVGClipPathFrame.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGCLIPPATHFRAME_H_ +#define LAYOUT_SVG_SVGCLIPPATHFRAME_H_ + +#include "gfxMatrix.h" +#include "mozilla/Attributes.h" +#include "mozilla/SVGContainerFrame.h" + +class gfxContext; + +namespace mozilla { +class ISVGDisplayableFrame; +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGClipPathFrame final : public SVGContainerFrame { + friend nsIFrame* ::NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + using Matrix = gfx::Matrix; + using SourceSurface = gfx::SourceSurface; + using imgDrawingParams = image::imgDrawingParams; + + protected: + explicit SVGClipPathFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGContainerFrame(aStyle, aPresContext, kClassID), + mIsBeingProcessed(false) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGClipPathFrame) + + // nsIFrame methods: + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + + bool IsSVGTransformed(Matrix* aOwnTransforms, + Matrix* aFromParentTransforms) const override; + + // SVGClipPathFrame 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 [in, optional] aExtraMask An extra surface that the returned + * surface should be masked with. + */ + already_AddRefed<SourceSurface> GetClipMask( + gfxContext& aReferenceContext, nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, SourceSurface* aExtraMask = nullptr); + + /** + * Paint mask directly onto a given context(aMaskContext). + * + * @param aMaskContext The target of mask been painting on. + * @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 + * current transform. + * @param [in, optional] aExtraMask An extra surface that the returned + * surface should be masked with. + */ + void PaintClipMask(gfxContext& aMaskContext, nsIFrame* aClippedFrame, + const gfxMatrix& aMatrix, SourceSurface* aExtraMask); + + /** + * 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(ISVGDisplayableFrame** aSingleChild = nullptr); + + bool IsValid(); + + // nsIFrame interface: + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGClipPath"_ns, aResult); + } +#endif + + SVGBBox GetBBoxForClipPathFrame(const SVGBBox& aBBox, + const gfxMatrix& aMatrix, uint32_t aFlags); + + /** + * 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: + // SVGContainerFrame methods: + gfxMatrix GetCanvasTM() override; + + already_AddRefed<DrawTarget> CreateClipMask(gfxContext& aReferenceContext, + gfx::IntPoint& aOffset); + + void PaintFrameIntoMask(nsIFrame* aFrame, nsIFrame* aClippedFrame, + gfxContext& aTarget); + + // 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 to indicate whether a methods that may reenter due to + // following a reference to another instance is currently executing. + bool mIsBeingProcessed; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGCLIPPATHFRAME_H_ diff --git a/layout/svg/SVGContainerFrame.cpp b/layout/svg/SVGContainerFrame.cpp new file mode 100644 index 0000000000..3ff429c632 --- /dev/null +++ b/layout/svg/SVGContainerFrame.cpp @@ -0,0 +1,427 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGContainerFrame.h" + +// Keep others in (case-insensitive) order: +#include "ImgDrawResult.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "nsCSSFrameConstructor.h" +#include "SVGAnimatedTransformList.h" + +using namespace mozilla::dom; +using namespace mozilla::image; + +nsIFrame* NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + nsIFrame* frame = new (aPresShell) + mozilla::SVGContainerFrame(aStyle, aPresShell->GetPresContext(), + mozilla::SVGContainerFrame::kClassID); + // 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; +} + +namespace mozilla { + +NS_QUERYFRAME_HEAD(SVGContainerFrame) + NS_QUERYFRAME_ENTRY(SVGContainerFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +NS_QUERYFRAME_HEAD(SVGDisplayContainerFrame) + NS_QUERYFRAME_ENTRY(SVGDisplayContainerFrame) + NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame) + +NS_IMPL_FRAMEARENA_HELPERS(SVGContainerFrame) + +void SVGContainerFrame::AppendFrames(ChildListID aListID, + nsFrameList&& aFrameList) { + InsertFrames(aListID, mFrames.LastChild(), nullptr, std::move(aFrameList)); +} + +void SVGContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + + mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList)); +} + +void SVGContainerFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + + mFrames.DestroyFrame(aOldFrame); +} + +bool SVGContainerFrame::ComputeCustomOverflow(OverflowAreas& 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. + * + * However, we only need to do this work if we were reflowed with + * NS_FRAME_IS_DIRTY, which implies that all descendants are dirty. 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 SVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) { + if (!aContainer->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { + return; + } + MOZ_ASSERT(aContainer->HasAnyStateBits(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 or not SVG"); + for (nsIFrame* kid : aContainer->PrincipalChildList()) { + LayoutFrameType type = kid->Type(); + if (type == LayoutFrameType::SVGText) { + static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText(); + } else { + if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) || + type == LayoutFrameType::SVGForeignObject || + !kid->IsFrameOfType(nsIFrame::eSVG)) { + ReflowSVGNonDisplayText(kid); + } + } + } +} + +void SVGDisplayContainerFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + if (!IsSVGOuterSVGFrame()) { + AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); + } + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} + +void SVGDisplayContainerFrame::BuildDisplayList( + nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { + // mContent could be a XUL element so check for an SVG element before casting + if (mContent->IsSVGElement() && + !static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) { + return; + } + DisplayOutline(aBuilder, aLists); + return BuildDisplayListForNonBlockChildren(aBuilder, aLists); +} + +void SVGDisplayContainerFrame::InsertFrames( + ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, 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 + SVGContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, + std::move(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 (!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN | + NS_FRAME_IS_NONDISPLAY)) { + for (nsIFrame* kid = firstNewFrame; kid != nextFrame; + kid = kid->GetNextSibling()) { + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + MOZ_ASSERT(!kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "Check for this explicitly in the |if|, then"); + bool isFirstReflow = kid->HasAnyStateBits(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 + // SVGUtils::ScheduleBoundsUpdate. + SVGUtils::ScheduleReflowSVG(kid); + if (isFirstReflow) { + // Add back the NS_FRAME_FIRST_REFLOW bit: + kid->AddStateBits(NS_FRAME_FIRST_REFLOW); + } + } + } + } +} + +void SVGDisplayContainerFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) { + SVGObserverUtils::InvalidateRenderingObservers(aOldFrame); + + // SVGContainerFrame::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(), RestyleHint{0}, nsChangeHint_UpdateOverflow); + + SVGContainerFrame::RemoveFrame(aListID, aOldFrame); +} + +bool SVGDisplayContainerFrame::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<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform( + aFromParentTransform); + } + + // mContent could be a XUL element so check for an SVG element before casting + if (mContent->IsSVGElement()) { + SVGElement* content = static_cast<SVGElement*>(GetContent()); + SVGAnimatedTransformList* transformList = + content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent)); + } + foundTransform = true; + } + } + return foundTransform; +} + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods + +void SVGDisplayContainerFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect) { + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (mState & NS_FRAME_IS_NONDISPLAY) || + PresContext()->Document()->IsSVGGlyphsDocument(), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + if (StyleEffects()->mOpacity == 0.0) { + return; + } + + gfxMatrix matrix = aTransform; + if (GetContent()->IsSVGElement()) { // must check before cast + matrix = static_cast<const SVGElement*>(GetContent()) + ->PrependLocalTransformsTo(matrix, eChildToUserSpace); + if (matrix.IsSingular()) { + return; + } + } + + 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 SVGElement* element = static_cast<const SVGElement*>(content); + if (!element->HasValidDimensions()) { + continue; // nothing to paint for kid + } + + m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m; + if (m.IsSingular()) { + continue; + } + } + SVGUtils::PaintFrameWithEffects(kid, aContext, m, aImgParams, aDirtyRect); + } +} + +nsIFrame* SVGDisplayContainerFrame::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 SVGUtils::HitTestChildren(this, aPoint); +} + +void SVGDisplayContainerFrame::ReflowSVG() { + MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>"); + + if (!SVGUtils::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()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + if (outerSVGHasHadFirstReflow) { + RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children + } + + OverflowAreas overflowRects; + + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + MOZ_ASSERT(!kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "Check for this explicitly in the |if|, then"); + 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. + MOZ_ASSERT( + kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || + !kid->IsFrameOfType(nsIFrame::eSVG), + "expected kid to be a NS_FRAME_IS_NONDISPLAY frame or not SVG"); + if (kid->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { + SVGContainerFrame* 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->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol) || + (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): + SVGObserverUtils::UpdateEffects(this); + } + + FinishAndStoreOverflow(overflowRects, mRect.Size()); + + // Remove state bits after FinishAndStoreOverflow so that it doesn't + // invalidate on first reflow: + RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void SVGDisplayContainerFrame::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; + } + + SVGUtils::NotifyChildrenOfSVGChange(this, aFlags); +} + +SVGBBox SVGDisplayContainerFrame::GetBBoxContribution( + const Matrix& aToBBoxUserspace, uint32_t aFlags) { + SVGBBox bboxUnion; + + nsIFrame* kid = mFrames.FirstChild(); + while (kid) { + nsIContent* content = kid->GetContent(); + ISVGDisplayableFrame* 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 SVGElement*>(content)->HasValidDimensions())) { + gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace); + if (content->IsSVGElement()) { + transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo( + {}, eChildToUserSpace) * + SVGUtils::GetTransformMatrixInUserSpace(kid) * 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; +} + +gfxMatrix SVGDisplayContainerFrame::GetCanvasTM() { + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + + auto* parent = static_cast<SVGContainerFrame*>(GetParent()); + auto* content = static_cast<SVGElement*>(GetContent()); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = MakeUnique<gfxMatrix>(tm); + } + + return *mCanvasTM; +} + +} // namespace mozilla diff --git a/layout/svg/SVGContainerFrame.h b/layout/svg/SVGContainerFrame.h new file mode 100644 index 0000000000..a78a7ceb3f --- /dev/null +++ b/layout/svg/SVGContainerFrame.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGCONTAINERFRAME_H_ +#define LAYOUT_SVG_SVGCONTAINERFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/UniquePtr.h" +#include "nsContainerFrame.h" +#include "nsIFrame.h" +#include "nsQueryFrame.h" +#include "nsRect.h" + +class gfxContext; +class nsFrameList; +class nsIContent; + +struct nsRect; + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +/** + * 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 SVGDisplayContainerFrame below). + */ +class SVGContainerFrame : public nsContainerFrame { + friend nsIFrame* ::NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + SVGContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID) + : nsContainerFrame(aStyle, aPresContext, aID) { + AddStateBits(NS_FRAME_SVG_LAYOUT); + } + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGContainerFrame) + + // 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: + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsContainerFrame::IsFrameOfType( + aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer)); + } + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + + bool ComputeCustomOverflow(mozilla::OverflowAreas& 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 + * SVGGenericContainerFrame which is used for unrecognized elements in the + * SVG namespace. Do *not* blindly cast to SVG element types. + */ +class SVGDisplayContainerFrame : public SVGContainerFrame, + public ISVGDisplayableFrame { + protected: + SVGDisplayContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + nsIFrame::ClassID aID) + : SVGContainerFrame(aStyle, aPresContext, aID) { + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + } + + public: + NS_DECL_QUERYFRAME + NS_DECL_QUERYFRAME_TARGET(SVGDisplayContainerFrame) + NS_DECL_ABSTRACT_FRAME(SVGDisplayContainerFrame) + + // nsIFrame: + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override; + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + bool IsSVGTransformed(Matrix* aOwnTransform = nullptr, + Matrix* aFromParentTransform = nullptr) const override; + + // ISVGDisplayableFrame interface: + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + void ReflowSVG() override; + void NotifySVGChanged(uint32_t aFlags) override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + bool IsDisplayContainer() override { return true; } + gfxMatrix GetCanvasTM() override; + + protected: + /** + * Cached canvasTM value. + */ + UniquePtr<gfxMatrix> mCanvasTM; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGCONTAINERFRAME_H_ diff --git a/layout/svg/SVGContextPaint.cpp b/layout/svg/SVGContextPaint.cpp new file mode 100644 index 0000000000..f6579ed7c1 --- /dev/null +++ b/layout/svg/SVGContextPaint.cpp @@ -0,0 +1,370 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGDocument.h" +#include "mozilla/extensions/WebExtensionPolicy.h" +#include "mozilla/StaticPrefs_svg.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "SVGPaintServerFrame.h" + +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +using image::imgDrawingParams; + +/* static */ +bool SVGContextPaint::IsAllowedForImageFromURI(nsIURI* aURI) { + if (StaticPrefs::svg_context_properties_content_enabled()) { + return true; + } + + // Context paint is pref'ed off for Web content. Ideally we'd have some + // easy means to determine whether the frame that has linked to the image + // is a frame for a content node that originated from Web content. + // Unfortunately different types of anonymous content, about: documents + // such as about:reader, etc. that are "our" code that we ship are + // sometimes hard to distinguish from real Web content. As a result, + // instead of trying to figure out what content is "ours" we instead let + // any content provide image context paint, but only if the image is + // chrome:// or resource:// do we return true. This should be sufficient + // to stop the image context paint feature being useful to (and therefore + // used by and relied upon by) Web content. (We don't want Web content to + // use this feature because we're not sure that image context paint is a + // good mechanism for wider use, or suitable for specification.) + // + // Because the default favicon used in the browser UI needs context paint, we + // also allow it for page-icon:<page-url>. This exposes context paint to + // 3rd-party favicons, but only for history and bookmark items. Other places + // such as the tab bar don't use the page-icon protocol to load favicons. + // + // One case that is not covered by chrome:// or resource:// are WebExtensions, + // specifically ones that are "ours". WebExtensions are moz-extension:// + // regardless if the extension is in-tree or not. Since we don't want + // extension developers coming to rely on image context paint either, we only + // enable context-paint for extensions that are owned by Mozilla + // (based on the extension permission "internal:svgContextPropertiesAllowed"). + // + // We also allow this for browser UI icons that are served up from + // Mozilla-controlled domains listed in the + // svg.context-properties.content.allowed-domains pref. + // + nsAutoCString scheme; + if (NS_SUCCEEDED(aURI->GetScheme(scheme)) && + (scheme.EqualsLiteral("chrome") || scheme.EqualsLiteral("resource") || + scheme.EqualsLiteral("page-icon"))) { + return true; + } + RefPtr<BasePrincipal> principal = + BasePrincipal::CreateContentPrincipal(aURI, OriginAttributes()); + + RefPtr<extensions::WebExtensionPolicy> addonPolicy = principal->AddonPolicy(); + if (addonPolicy) { + // Only allowed for extensions that have the + // internal:svgContextPropertiesAllowed permission (added internally from + // to Mozilla-owned extensions, see `isMozillaExtension` function + // defined in Extension.jsm for the exact criteria). + return addonPolicy->HasPermission( + nsGkAtoms::svgContextPropertiesAllowedPermission); + } + + bool isInAllowList = false; + principal->IsURIInPrefList("svg.context-properties.content.allowed-domains", + &isInAllowList); + return isInAllowList; +} + +/** + * 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 + */ +static void SetupInheritablePaint(const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsIFrame* aFrame, float& aOpacity, + SVGContextPaint* aOuterContextPaint, + SVGContextPaintImpl::Paint& aTargetPaint, + StyleSVGPaint nsStyleSVG::*aFillOrStroke, + nscolor aDefaultFallbackColor, + imgDrawingParams& aImgParams) { + const nsStyleSVG* style = aFrame->StyleSVG(); + SVGPaintServerFrame* ps = + SVGObserverUtils::GetAndObservePaintServer(aFrame, aFillOrStroke); + + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, aDrawTarget, aContextMatrix, + aFillOrStroke, aOpacity, aImgParams); + + if (pattern) { + aTargetPaint.SetPaintServer(aFrame, aContextMatrix, ps); + return; + } + } + + if (aOuterContextPaint) { + RefPtr<gfxPattern> pattern; + auto tag = SVGContextPaintImpl::Paint::Tag::None; + switch ((style->*aFillOrStroke).kind.tag) { + case StyleSVGPaintKind::Tag::ContextFill: + tag = SVGContextPaintImpl::Paint::Tag::ContextFill; + pattern = aOuterContextPaint->GetFillPattern( + aDrawTarget, aOpacity, aContextMatrix, aImgParams); + break; + case StyleSVGPaintKind::Tag::ContextStroke: + tag = SVGContextPaintImpl::Paint::Tag::ContextStroke; + pattern = aOuterContextPaint->GetStrokePattern( + aDrawTarget, aOpacity, aContextMatrix, aImgParams); + break; + default:; + } + if (pattern) { + aTargetPaint.SetContextPaint(aOuterContextPaint, tag); + return; + } + } + + nscolor color = SVGUtils::GetFallbackOrPaintColor( + *aFrame->Style(), aFillOrStroke, aDefaultFallbackColor); + aTargetPaint.SetColor(color); +} + +DrawMode SVGContextPaintImpl::Init(const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + nsIFrame* aFrame, + SVGContextPaint* aOuterContextPaint, + imgDrawingParams& aImgParams) { + DrawMode toDraw = DrawMode(0); + + const nsStyleSVG* style = aFrame->StyleSVG(); + + // fill: + if (style->mFill.kind.IsNone()) { + SetFillOpacity(0.0f); + } else { + float opacity = + SVGUtils::GetOpacity(style->mFillOpacity, aOuterContextPaint); + + SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame, opacity, + aOuterContextPaint, mFillPaint, &nsStyleSVG::mFill, + NS_RGB(0, 0, 0), aImgParams); + + SetFillOpacity(opacity); + + toDraw |= DrawMode::GLYPH_FILL; + } + + // stroke: + if (style->mStroke.kind.IsNone()) { + SetStrokeOpacity(0.0f); + } else { + float opacity = + SVGUtils::GetOpacity(style->mStrokeOpacity, aOuterContextPaint); + + SetupInheritablePaint( + aDrawTarget, aContextMatrix, aFrame, opacity, aOuterContextPaint, + mStrokePaint, &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0), aImgParams); + + 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; +} + +SVGContextPaint* SVGContextPaint::GetContextPaint(nsIContent* aContent) { + dom::Document* ownerDoc = aContent->OwnerDoc(); + if (!ownerDoc->IsSVGDocument()) { + return nullptr; + } + + const auto* contextPaint = + ownerDoc->AsSVGDocument()->GetCurrentContextPaint(); + MOZ_ASSERT_IF(contextPaint, ownerDoc->IsBeingUsedAsImage()); + + // XXX The SVGContextPaint that SVGDocument keeps around is const. We could + // and should keep that constness to the SVGContextPaint that we get here + // (SVGImageContext is never changed after it is initialized). + // + // Unfortunately lazy initialization of SVGContextPaint (which is a member of + // SVGImageContext, and also conceptually never changes after construction) + // prevents some of SVGContextPaint's conceptually const methods from being + // const. Trying to fix SVGContextPaint (perhaps by using |mutable|) is a + // bit of a headache so for now we punt on that, don't reapply the constness + // to the SVGContextPaint here, and trust that no one will add code that + // actually modifies the object. + return const_cast<SVGContextPaint*>(contextPaint); +} + +already_AddRefed<gfxPattern> SVGContextPaintImpl::GetFillPattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + return mFillPaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mFill, aCTM, + aImgParams); +} + +already_AddRefed<gfxPattern> SVGContextPaintImpl::GetStrokePattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + return mStrokePaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mStroke, + aCTM, aImgParams); +} + +already_AddRefed<gfxPattern> SVGContextPaintImpl::Paint::GetPattern( + const DrawTarget* aDrawTarget, float aOpacity, + StyleSVGPaint nsStyleSVG::*aFillOrStroke, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + 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 Tag::None: + pattern = new gfxPattern(DeviceColor()); + mPatternMatrix = gfxMatrix(); + break; + case Tag::Color: { + DeviceColor color = ToDeviceColor(mPaintDefinition.mColor); + color.a *= aOpacity; + pattern = new gfxPattern(color); + mPatternMatrix = gfxMatrix(); + break; + } + case Tag::PaintServer: + pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern( + mFrame, aDrawTarget, mContextMatrix, aFillOrStroke, aOpacity, + aImgParams); + { + // 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 Tag::ContextFill: + pattern = mPaintDefinition.mContextPaint->GetFillPattern( + aDrawTarget, aOpacity, aCTM, aImgParams); + // 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 Tag::ContextStroke: + pattern = mPaintDefinition.mContextPaint->GetStrokePattern( + aDrawTarget, aOpacity, aCTM, aImgParams); + // 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.InsertOrUpdate(aOpacity, RefPtr{pattern}); + return pattern.forget(); +} + +AutoSetRestoreSVGContextPaint::AutoSetRestoreSVGContextPaint( + const SVGContextPaint& aContextPaint, dom::SVGDocument& aSVGDocument) + : mSVGDocument(aSVGDocument), + mOuterContextPaint(aSVGDocument.GetCurrentContextPaint()) { + MOZ_ASSERT(aSVGDocument.IsBeingUsedAsImage(), + "SVGContextPaint::GetContextPaint assumes this"); + + mSVGDocument.SetCurrentContextPaint(&aContextPaint); +} + +AutoSetRestoreSVGContextPaint::~AutoSetRestoreSVGContextPaint() { + mSVGDocument.SetCurrentContextPaint(mOuterContextPaint); +} + +// SVGEmbeddingContextPaint + +already_AddRefed<gfxPattern> SVGEmbeddingContextPaint::GetFillPattern( + const DrawTarget* aDrawTarget, float aFillOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + if (!mFill) { + return nullptr; + } + // The gfxPattern that we create below depends on aFillOpacity, and since + // different elements in the SVG image may pass in different values for + // fill opacities we don't try to cache the gfxPattern that we create. + DeviceColor fill = *mFill; + fill.a *= aFillOpacity; + return do_AddRef(new gfxPattern(fill)); +} + +already_AddRefed<gfxPattern> SVGEmbeddingContextPaint::GetStrokePattern( + const DrawTarget* aDrawTarget, float aStrokeOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + if (!mStroke) { + return nullptr; + } + DeviceColor stroke = *mStroke; + stroke.a *= aStrokeOpacity; + return do_AddRef(new gfxPattern(stroke)); +} + +uint32_t SVGEmbeddingContextPaint::Hash() const { + uint32_t hash = 0; + + if (mFill) { + hash = HashGeneric(hash, mFill->ToABGR()); + } else { + // Arbitrary number, just to avoid trivial hash collisions between pairs of + // instances where one embedding context has fill set to the same value as + // another context has stroke set to. + hash = 1; + } + + if (mStroke) { + hash = HashGeneric(hash, mStroke->ToABGR()); + } + + if (mFillOpacity != 1.0f) { + hash = HashGeneric(hash, mFillOpacity); + } + + if (mStrokeOpacity != 1.0f) { + hash = HashGeneric(hash, mStrokeOpacity); + } + + return hash; +} + +} // namespace mozilla diff --git a/layout/svg/SVGContextPaint.h b/layout/svg/SVGContextPaint.h new file mode 100644 index 0000000000..d42066d682 --- /dev/null +++ b/layout/svg/SVGContextPaint.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGCONTEXTPAINT_H_ +#define LAYOUT_SVG_SVGCONTEXTPAINT_H_ + +#include "DrawMode.h" +#include "gfxMatrix.h" +#include "gfxPattern.h" +#include "gfxTypes.h" +#include "gfxUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/gfx/2D.h" +#include "nsColor.h" +#include "nsStyleStruct.h" +#include "nsTArray.h" +#include "ImgDrawResult.h" +#include "nsRefPtrHashtable.h" + +class gfxContext; + +namespace mozilla { +class SVGPaintServerFrame; + +namespace dom { +class SVGDocument; +} + +/** + * 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. + * + * This class is reference counted so that it can be shared among many similar + * SVGImageContext objects. (SVGImageContext objects are frequently + * copy-constructed with small modifications, and we'd like for those copies to + * be able to share their context-paint data cheaply.) However, in most cases, + * SVGContextPaint instances are stored in a local RefPtr and only last for the + * duration of a function call. + * XXX Note: SVGImageContext doesn't actually have a SVGContextPaint member yet, + * but it will in a later patch in the patch series that added this comment. + */ +class SVGContextPaint : public RefCounted<SVGContextPaint> { + protected: + using DrawTarget = mozilla::gfx::DrawTarget; + using Float = mozilla::gfx::Float; + using imgDrawingParams = mozilla::image::imgDrawingParams; + + SVGContextPaint() : mDashOffset(0.0f), mStrokeWidth(0.0f) {} + + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(SVGContextPaint) + + virtual ~SVGContextPaint() = default; + + virtual already_AddRefed<gfxPattern> GetFillPattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) = 0; + virtual already_AddRefed<gfxPattern> GetStrokePattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) = 0; + virtual float GetFillOpacity() const = 0; + virtual float GetStrokeOpacity() const = 0; + + already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget, + const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + return GetFillPattern(aDrawTarget, GetFillOpacity(), aCTM, aImgParams); + } + + already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget, + const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) { + return GetStrokePattern(aDrawTarget, GetStrokeOpacity(), aCTM, aImgParams); + } + + 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); + + const FallibleTArray<Float>& GetStrokeDashArray() const { return mDashes; } + + Float GetStrokeDashOffset() const { return mDashOffset; } + + Float GetStrokeWidth() const { return mStrokeWidth; } + + virtual uint32_t Hash() const { + MOZ_ASSERT_UNREACHABLE( + "Only VectorImage needs to hash, and that should " + "only be operating on our SVGEmbeddingContextPaint " + "subclass"); + return 0; + } + + /** + * Returns true if image context paint is allowed to be used in an image that + * has the given URI, else returns false. + */ + static bool IsAllowedForImageFromURI(nsIURI* aURI); + + private: + // Member-vars are initialized in InitStrokeGeometry. + FallibleTArray<Float> mDashes; + MOZ_INIT_OUTSIDE_CTOR Float mDashOffset; + MOZ_INIT_OUTSIDE_CTOR Float 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(const SVGContextPaint& aContextPaint, + dom::SVGDocument& aSVGDocument); + ~AutoSetRestoreSVGContextPaint(); + + private: + dom::SVGDocument& mSVGDocument; + // The context paint that needs to be restored by our dtor after it removes + // aContextPaint: + const SVGContextPaint* mOuterContextPaint; +}; + +/** + * This class should be flattened into SVGContextPaint once we get rid of the + * other sub-class (SimpleTextContextPaint). + */ +struct SVGContextPaintImpl : public SVGContextPaint { + protected: + using DrawTarget = mozilla::gfx::DrawTarget; + + public: + DrawMode Init(const DrawTarget* aDrawTarget, const gfxMatrix& aContextMatrix, + nsIFrame* aFrame, SVGContextPaint* aOuterContextPaint, + imgDrawingParams& aImgParams); + + already_AddRefed<gfxPattern> GetFillPattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) override; + already_AddRefed<gfxPattern> GetStrokePattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) 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 { + enum class Tag : uint8_t { + None, + Color, + PaintServer, + ContextFill, + ContextStroke, + }; + + Paint() : mPaintDefinition{}, mPaintType(Tag::None) {} + + void SetPaintServer(nsIFrame* aFrame, const gfxMatrix& aContextMatrix, + SVGPaintServerFrame* aPaintServerFrame) { + mPaintType = Tag::PaintServer; + mPaintDefinition.mPaintServerFrame = aPaintServerFrame; + mFrame = aFrame; + mContextMatrix = aContextMatrix; + } + + void SetColor(const nscolor& aColor) { + mPaintType = Tag::Color; + mPaintDefinition.mColor = aColor; + } + + void SetContextPaint(SVGContextPaint* aContextPaint, Tag aTag) { + MOZ_ASSERT(aTag == Tag::ContextFill || aTag == Tag::ContextStroke); + mPaintType = aTag; + mPaintDefinition.mContextPaint = aContextPaint; + } + + union { + SVGPaintServerFrame* 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; + Tag mPaintType; + + // Device-space-to-pattern-space + gfxMatrix mPatternMatrix; + nsRefPtrHashtable<nsFloatHashKey, gfxPattern> mPatternCache; + + already_AddRefed<gfxPattern> GetPattern( + const DrawTarget* aDrawTarget, float aOpacity, + StyleSVGPaint nsStyleSVG::*aFillOrStroke, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams); + }; + + Paint mFillPaint; + Paint mStrokePaint; + + float mFillOpacity; + float mStrokeOpacity; +}; + +/** + * This class is used to pass context paint to an SVG image when an element + * references that image (e.g. via HTML <img> or SVG <image>, or by referencing + * it from a CSS property such as 'background-image'). In this case we only + * support context colors and not paint servers. + */ +class SVGEmbeddingContextPaint : public SVGContextPaint { + using DeviceColor = gfx::DeviceColor; + + public: + SVGEmbeddingContextPaint() : mFillOpacity(1.0f), mStrokeOpacity(1.0f) {} + + bool operator==(const SVGEmbeddingContextPaint& aOther) const { + MOZ_ASSERT(GetStrokeWidth() == aOther.GetStrokeWidth() && + GetStrokeDashOffset() == aOther.GetStrokeDashOffset() && + GetStrokeDashArray() == aOther.GetStrokeDashArray(), + "We don't currently include these in the context information " + "from an embedding element"); + return mFill == aOther.mFill && mStroke == aOther.mStroke && + mFillOpacity == aOther.mFillOpacity && + mStrokeOpacity == aOther.mStrokeOpacity; + } + + void SetFill(nscolor aFill) { mFill.emplace(gfx::ToDeviceColor(aFill)); } + const Maybe<DeviceColor>& GetFill() const { return mFill; } + void SetStroke(nscolor aStroke) { + mStroke.emplace(gfx::ToDeviceColor(aStroke)); + } + const Maybe<DeviceColor>& GetStroke() const { return mStroke; } + + /** + * Returns a pattern of type PatternType::COLOR, or else nullptr. + */ + already_AddRefed<gfxPattern> GetFillPattern( + const DrawTarget* aDrawTarget, float aFillOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) override; + + /** + * Returns a pattern of type PatternType::COLOR, or else nullptr. + */ + already_AddRefed<gfxPattern> GetStrokePattern( + const DrawTarget* aDrawTarget, float aStrokeOpacity, + const gfxMatrix& aCTM, imgDrawingParams& aImgParams) 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; }; + + uint32_t Hash() const override; + + private: + Maybe<DeviceColor> mFill; + Maybe<DeviceColor> mStroke; + float mFillOpacity; + float mStrokeOpacity; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGCONTEXTPAINT_H_ diff --git a/layout/svg/SVGFEContainerFrame.cpp b/layout/svg/SVGFEContainerFrame.cpp new file mode 100644 index 0000000000..0df17c7814 --- /dev/null +++ b/layout/svg/SVGFEContainerFrame.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/SVGFilters.h" +#include "nsContainerFrame.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsLiteralString.h" + +using namespace mozilla::dom; + +nsIFrame* NS_NewSVGFEContainerFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +/* + * This frame is used by filter primitive elements that + * have special child elements that provide parameters. + */ +class SVGFEContainerFrame final : public nsContainerFrame { + friend nsIFrame* ::NS_NewSVGFEContainerFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGFEContainerFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsContainerFrame(aStyle, aPresContext, kClassID) { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGFEContainerFrame) + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsContainerFrame::IsFrameOfType( + aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGFEContainer"_ns, aResult); + } +#endif + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override { + // We don't maintain a ink overflow rect + return false; + } +}; + +} // namespace mozilla + +nsIFrame* NS_NewSVGFEContainerFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGFEContainerFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGFEContainerFrame) + +#ifdef DEBUG +void SVGFEContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + nsCOMPtr<SVGFE> filterPrimitive = do_QueryInterface(aContent); + NS_ASSERTION(filterPrimitive, + "Trying to construct an SVGFEContainerFrame for a " + "content element that doesn't support the right interfaces"); + + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGFEContainerFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + dom::SVGFE* element = static_cast<dom::SVGFE*>(GetContent()); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT( + GetParent()->IsSVGFilterFrame(), + "Observers observe the filter, so that's what we must invalidate"); + SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent()); + } + + return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +} // namespace mozilla diff --git a/layout/svg/SVGFEImageFrame.cpp b/layout/svg/SVGFEImageFrame.cpp new file mode 100644 index 0000000000..ac60a5a791 --- /dev/null +++ b/layout/svg/SVGFEImageFrame.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/SVGFEImageElement.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "nsContainerFrame.h" +#include "nsIFrame.h" +#include "nsGkAtoms.h" +#include "nsLiteralString.h" + +using namespace mozilla::dom; + +nsIFrame* NS_NewSVGFEImageFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGFEImageFrame final : public nsIFrame { + friend nsIFrame* ::NS_NewSVGFEImageFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGFEImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : nsIFrame(aStyle, aPresContext, kClassID) { + 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(SVGFEImageFrame) + + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + virtual void DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGFEImage"_ns, aResult); + } +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + void OnVisibilityChange( + Visibility aNewVisibility, + const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override; + + bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override { + // We don't maintain a ink overflow rect + return false; + } +}; + +} // namespace mozilla + +nsIFrame* NS_NewSVGFEImageFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGFEImageFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGFEImageFrame) + +/* virtual */ +void SVGFEImageFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + DecApproximateVisibleCount(); + + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsIFrame::mContent); + if (imageLoader) { + imageLoader->FrameDestroyed(this); + } + + nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +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"); + + nsIFrame::Init(aContent, aParent, aPrevInFlow); + + // We assume that feImage's are always visible. + // This call must happen before the FrameCreated. This is because the + // primary frame pointer on our content node isn't set until after this + // function ends, so there is no way for the resulting OnVisibilityChange + // notification to get a frame. FrameCreated has a workaround for this in + // that it passes our frame around so it can be accessed. OnVisibilityChange + // doesn't have that workaround. + IncApproximateVisibleCount(); + + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsIFrame::mContent); + if (imageLoader) { + imageLoader->FrameCreated(this); + } +} + +nsresult SVGFEImageFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + SVGFEImageElement* element = static_cast<SVGFEImageElement*>(GetContent()); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT( + GetParent()->IsSVGFilterFrame(), + "Observers observe the filter, so that's what we must invalidate"); + SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent()); + } + + // Currently our SMIL implementation does not modify the DOM attributes. Once + // we implement the SVG 2 SMIL behaviour this can be removed + // SVGFEImageElement::AfterSetAttr's implementation will be sufficient. + if (aModType == MutationEvent_Binding::SMIL && + aAttribute == nsGkAtoms::href && + (aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None)) { + bool hrefIsSet = + element->mStringAttributes[SVGFEImageElement::HREF].IsExplicitlySet() || + element->mStringAttributes[SVGFEImageElement::XLINK_HREF] + .IsExplicitlySet(); + if (hrefIsSet) { + element->LoadSVGImage(true, true); + } else { + element->CancelImageRequests(true); + } + } + + return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +void SVGFEImageFrame::OnVisibilityChange( + Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) { + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsIFrame::mContent); + if (!imageLoader) { + MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent"); + nsIFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); + return; + } + + imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); + + nsIFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); +} + +} // namespace mozilla diff --git a/layout/svg/SVGFELeafFrame.cpp b/layout/svg/SVGFELeafFrame.cpp new file mode 100644 index 0000000000..c49ae429b4 --- /dev/null +++ b/layout/svg/SVGFELeafFrame.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/SVGFilters.h" +#include "ComputedStyle.h" +#include "nsContainerFrame.h" +#include "nsIFrame.h" +#include "nsGkAtoms.h" + +using namespace mozilla::dom; + +nsIFrame* NS_NewSVGFELeafFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); +namespace mozilla { + +/* + * This frame is used by filter primitive elements that don't + * have special child elements that provide parameters. + */ +class SVGFELeafFrame final : public nsIFrame { + friend nsIFrame* ::NS_NewSVGFELeafFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGFELeafFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : nsIFrame(aStyle, aPresContext, kClassID) { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGFELeafFrame) + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGFELeaf"_ns, aResult); + } +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override { + // We don't maintain a ink overflow rect + return false; + } +}; + +} // namespace mozilla + +nsIFrame* NS_NewSVGFELeafFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGFELeafFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGFELeafFrame) + +#ifdef DEBUG +void SVGFELeafFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + nsCOMPtr<SVGFE> filterPrimitive = do_QueryInterface(aContent); + NS_ASSERTION(filterPrimitive, + "Trying to construct an SVGFELeafFrame for a " + "content element that doesn't support the right interfaces"); + + nsIFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGFELeafFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + auto* element = static_cast<mozilla::dom::SVGFE*>(GetContent()); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT( + GetParent()->IsSVGFilterFrame(), + "Observers observe the filter, so that's what we must invalidate"); + SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent()); + } + + return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +} // namespace mozilla diff --git a/layout/svg/SVGFEUnstyledLeafFrame.cpp b/layout/svg/SVGFEUnstyledLeafFrame.cpp new file mode 100644 index 0000000000..3d103cb920 --- /dev/null +++ b/layout/svg/SVGFEUnstyledLeafFrame.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/dom/SVGFilters.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "nsContainerFrame.h" +#include "nsIFrame.h" +#include "nsGkAtoms.h" + +nsIFrame* NS_NewSVGFEUnstyledLeafFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGFEUnstyledLeafFrame final : public nsIFrame { + friend nsIFrame* ::NS_NewSVGFEUnstyledLeafFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + protected: + explicit SVGFEUnstyledLeafFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsIFrame(aStyle, aPresContext, kClassID) { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGFEUnstyledLeafFrame) + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGFEUnstyledLeaf"_ns, aResult); + } +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override { + // We don't maintain a ink overflow rect + return false; + } +}; + +} // namespace mozilla + +nsIFrame* NS_NewSVGFEUnstyledLeafFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGFEUnstyledLeafFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGFEUnstyledLeafFrame) + +nsresult SVGFEUnstyledLeafFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + auto* element = + static_cast<mozilla::dom::SVGFEUnstyledElement*>(GetContent()); + if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) { + MOZ_ASSERT( + GetParent()->GetParent()->IsSVGFilterFrame(), + "Observers observe the filter, so that's what we must invalidate"); + SVGObserverUtils::InvalidateDirectRenderingObservers( + GetParent()->GetParent()); + } + + return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +} // namespace mozilla diff --git a/layout/svg/SVGFilterFrame.cpp b/layout/svg/SVGFilterFrame.cpp new file mode 100644 index 0000000000..d134e16e30 --- /dev/null +++ b/layout/svg/SVGFilterFrame.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGFilterFrame.h" + +// Keep others in (case-insensitive) order: +#include "AutoReferenceChainGuard.h" +#include "gfxUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "nsGkAtoms.h" +#include "SVGObserverUtils.h" +#include "SVGElement.h" +#include "SVGFilterInstance.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsIFrame* NS_NewSVGFilterFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGFilterFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGFilterFrame) + +uint16_t SVGFilterFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) { + SVGAnimatedEnumeration& thisEnum = + static_cast<SVGFilterElement*>(GetContent())->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) { + return thisEnum.GetAnimValue(); + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<SVGFilterElement*>(aDefault) + ->mEnumAttributes[aIndex] + .GetAnimValue(); + } + + SVGFilterFrame* next = GetReferencedFilter(); + + return next ? next->GetEnumValue(aIndex, aDefault) + : static_cast<SVGFilterElement*>(aDefault) + ->mEnumAttributes[aIndex] + .GetAnimValue(); +} + +const SVGAnimatedLength* SVGFilterFrame::GetLengthValue(uint32_t aIndex, + nsIContent* aDefault) { + const SVGAnimatedLength* thisLength = + &static_cast<SVGFilterElement*>(GetContent())->mLengthAttributes[aIndex]; + + if (thisLength->IsExplicitlySet()) { + return thisLength; + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return &static_cast<SVGFilterElement*>(aDefault)->mLengthAttributes[aIndex]; + } + + SVGFilterFrame* next = GetReferencedFilter(); + + return next ? next->GetLengthValue(aIndex, aDefault) + : &static_cast<SVGFilterElement*>(aDefault) + ->mLengthAttributes[aIndex]; +} + +const SVGFilterElement* SVGFilterFrame::GetFilterContent(nsIContent* aDefault) { + for (nsIContent* child = mContent->GetFirstChild(); child; + child = child->GetNextSibling()) { + RefPtr<SVGFE> primitive; + CallQueryInterface(child, (SVGFE**)getter_AddRefs(primitive)); + if (primitive) { + return static_cast<SVGFilterElement*>(GetContent()); + } + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<SVGFilterElement*>(aDefault); + } + + SVGFilterFrame* next = GetReferencedFilter(); + + return next ? next->GetFilterContent(aDefault) + : static_cast<SVGFilterElement*>(aDefault); +} + +SVGFilterFrame* SVGFilterFrame::GetReferencedFilter() { + if (mNoHRefURI) { + return nullptr; + } + + auto GetHref = [this](nsAString& aHref) { + SVGFilterElement* filter = static_cast<SVGFilterElement*>(GetContent()); + if (filter->mStringAttributes[SVGFilterElement::HREF].IsExplicitlySet()) { + filter->mStringAttributes[SVGFilterElement::HREF].GetAnimValue(aHref, + filter); + } else { + filter->mStringAttributes[SVGFilterElement::XLINK_HREF].GetAnimValue( + aHref, filter); + } + this->mNoHRefURI = aHref.IsEmpty(); + }; + + nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref); + if (tframe) { + LayoutFrameType frameType = tframe->Type(); + if (frameType == LayoutFrameType::SVGFilter) { + return static_cast<SVGFilterFrame*>(tframe); + } + // We don't call SVGObserverUtils::RemoveTemplateObserver and set + // `mNoHRefURI = false` here since we want to be invalidated if the ID + // specified by our href starts resolving to a different/valid element. + } + + return nullptr; +} + +nsresult SVGFilterFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } else if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // Blow away our reference, if any + SVGObserverUtils::RemoveTemplateObserver(this); + mNoHRefURI = false; + // And update whoever references us + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +#ifdef DEBUG +void SVGFilterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::filter), + "Content is not an SVG filter"); + + SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +} // namespace mozilla diff --git a/layout/svg/SVGFilterFrame.h b/layout/svg/SVGFilterFrame.h new file mode 100644 index 0000000000..d6839c3ff4 --- /dev/null +++ b/layout/svg/SVGFilterFrame.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGFILTERFRAME_H_ +#define LAYOUT_SVG_SVGFILTERFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SVGContainerFrame.h" +#include "nsQueryFrame.h" + +class nsAtom; +class nsIContent; +class nsIFrame; + +struct nsRect; + +namespace mozilla { +class SVGAnimatedLength; +class SVGFilterInstance; +class PresShell; + +namespace dom { +class SVGFilterElement; +} // namespace dom +} // namespace mozilla + +nsIFrame* NS_NewSVGFilterFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGFilterFrame final : public SVGContainerFrame { + friend nsIFrame* ::NS_NewSVGFilterFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGFilterFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGContainerFrame(aStyle, aPresContext, kClassID), + mLoopFlag(false), + mNoHRefURI(false) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGFilterFrame) + + // nsIFrame methods: + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + private: + friend class SVGFilterInstance; + + /** + * Parses this frame's href and - if it references another filter - returns + * it. It also makes this frame a rendering observer of the specified ID. + */ + SVGFilterFrame* GetReferencedFilter(); + + // Accessors to lookup filter attributes + uint16_t GetEnumValue(uint32_t aIndex, nsIContent* aDefault); + uint16_t GetEnumValue(uint32_t aIndex) { + return GetEnumValue(aIndex, mContent); + } + const mozilla::SVGAnimatedLength* GetLengthValue(uint32_t aIndex, + nsIContent* aDefault); + const mozilla::SVGAnimatedLength* 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; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGFILTERFRAME_H_ diff --git a/layout/svg/SVGFilterInstance.cpp b/layout/svg/SVGFilterInstance.cpp new file mode 100644 index 0000000000..34885052a9 --- /dev/null +++ b/layout/svg/SVGFilterInstance.cpp @@ -0,0 +1,447 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGFilterInstance.h" + +// Keep others in (case-insensitive) order: +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/IDTracker.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "SVGFilterFrame.h" +#include "FilterSupport.h" +#include "gfx2DGlue.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGUnitTypes_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +SVGFilterInstance::SVGFilterInstance( + const StyleFilter& aFilter, nsIFrame* aTargetFrame, + nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics, + const gfxRect& aTargetBBox, + const MatrixScalesDouble& aUserSpaceToFilterSpaceScale) + : mFilter(aFilter), + mTargetContent(aTargetContent), + mMetrics(aMetrics), + mTargetBBox(aTargetBBox), + mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), + mSourceAlphaAvailable(false), + mInitialized(false) { + // Get the filter frame. + mFilterFrame = GetFilterFrame(aTargetFrame); + if (!mFilterFrame) { + return; + } + + // Get the filter element. + mFilterElement = mFilterFrame->GetFilterContent(); + if (!mFilterElement) { + MOZ_ASSERT_UNREACHABLE("filter frame should have a related element"); + return; + } + + mPrimitiveUnits = + mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS); + + if (!ComputeBounds()) { + return; + } + + mInitialized = true; +} + +bool SVGFilterInstance::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). + SVGAnimatedLength 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 = + SVGUtils::GetRelativeRect(filterUnits, XYWH, mTargetBBox, mMetrics); + + // Transform the user space bounds to filter space, so we + // can align them with the pixel boundaries 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 false; + } + + // Set the filter space bounds. + if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds, &mFilterSpaceBounds)) { + // The filter region is way too big if there is float -> int overflow. + return false; + } + + return true; +} + +SVGFilterFrame* SVGFilterInstance::GetFilterFrame(nsIFrame* aTargetFrame) { + if (!mFilter.IsUrl()) { + // 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; + if (aTargetFrame) { + RefPtr<URLAndReferrerInfo> urlExtraReferrer = + SVGObserverUtils::GetFilterURI(aTargetFrame, mFilter); + + // urlExtraReferrer might be null when mFilter has an invalid url + if (!urlExtraReferrer) { + return nullptr; + } + + url = urlExtraReferrer->GetURI(); + } else { + url = mFilter.AsUrl().ResolveLocalRef(mTargetContent); + } + + if (!url) { + return nullptr; + } + + // Look up the filter element by URL. + IDTracker idTracker; + bool watch = false; + idTracker.ResetToURIFragmentID( + mTargetContent, url, mFilter.AsUrl().ExtraData().ReferrerInfo(), watch); + Element* element = idTracker.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->IsSVGFilterFrame()) { + // 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<SVGFilterFrame*>(frame); +} + +float SVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, + float aValue) const { + SVGAnimatedLength val; + val.Init(aCtxType, 0xff, aValue, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); + + float value; + if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + value = SVGUtils::ObjectSpace(mTargetBBox, &val); + } else { + value = SVGUtils::UserSpace(mMetrics, &val); + } + + switch (aCtxType) { + case SVGContentUtils::X: + return value * static_cast<float>(mUserSpaceToFilterSpaceScale.xScale); + case SVGContentUtils::Y: + return value * static_cast<float>(mUserSpaceToFilterSpaceScale.yScale); + case SVGContentUtils::XY: + default: + return value * SVGContentUtils::ComputeNormalizedHypotenuse( + mUserSpaceToFilterSpaceScale.xScale, + mUserSpaceToFilterSpaceScale.yScale); + } +} + +Point3D SVGFilterInstance::ConvertLocation(const Point3D& aPoint) const { + SVGAnimatedLength val[4]; + val[0].Init(SVGContentUtils::X, 0xff, aPoint.x, + SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); + val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y, + SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); + // Dummy width/height values + val[2].Init(SVGContentUtils::X, 0xff, 0, + SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); + val[3].Init(SVGContentUtils::Y, 0xff, 0, + SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); + + gfxRect feArea = + SVGUtils::GetRelativeRect(mPrimitiveUnits, val, mTargetBBox, mMetrics); + gfxRect r = UserSpaceToFilterSpace(feArea); + return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z)); +} + +gfxRect SVGFilterInstance::UserSpaceToFilterSpace( + const gfxRect& aUserSpaceRect) const { + gfxRect filterSpaceRect = aUserSpaceRect; + filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale); + return filterSpaceRect; +} + +IntRect SVGFilterInstance::ComputeFilterPrimitiveSubregion( + SVGFE* aFilterElement, + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTArray<int32_t>& aInputIndices) { + SVGFE* 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 = SVGUtils::GetRelativeRect( + mPrimitiveUnits, &fE->mLengthAttributes[SVGFE::ATTR_X], mTargetBBox, + mMetrics); + Rect region = ToRect(UserSpaceToFilterSpace(feArea)); + + if (!fE->mLengthAttributes[SVGFE::ATTR_X].IsExplicitlySet()) + region.x = defaultFilterSubregion.X(); + if (!fE->mLengthAttributes[SVGFE::ATTR_Y].IsExplicitlySet()) + region.y = defaultFilterSubregion.Y(); + if (!fE->mLengthAttributes[SVGFE::ATTR_WIDTH].IsExplicitlySet()) + region.width = defaultFilterSubregion.Width(); + if (!fE->mLengthAttributes[SVGFE::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 SVGFilterInstance::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 SVGFilterInstance::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(AsVariant(ToAlphaAttributes())); + 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(std::move(descr)); + mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1; + mSourceAlphaAvailable = true; + return mSourceAlphaIndex; +} + +nsresult SVGFilterInstance::GetSourceIndices( + SVGFE* aPrimitiveElement, + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTHashMap<nsStringHashKey, int32_t>& aImageTable, + nsTArray<int32_t>& aSourceIndices) { + AutoTArray<SVGStringInfo, 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) { + sourceIndex = GetLastResultIndex(aPrimitiveDescrs); + } + } + + aSourceIndices.AppendElement(sourceIndex); + } + return NS_OK; +} + +nsresult SVGFilterInstance::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<SVGFE>> primitives; + for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + RefPtr<SVGFE> primitive; + CallQueryInterface(child, (SVGFE**)getter_AddRefs(primitive)); + if (primitive) { + primitives.AppendElement(primitive); + } + } + + // Maps source image name to source index. + nsTHashMap<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) { + SVGFE* 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(std::move(descr)); + uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; + + nsAutoString str; + filter->GetResultImageName().GetAnimValue(str, filter); + imageTable.InsertOrUpdate(str, primitiveDescrIndex); + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/layout/svg/SVGFilterInstance.h b/layout/svg/SVGFilterInstance.h new file mode 100644 index 0000000000..d7cda4ae87 --- /dev/null +++ b/layout/svg/SVGFilterInstance.h @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGFILTERINSTANCE_H_ +#define LAYOUT_SVG_SVGFILTERINSTANCE_H_ + +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGFilters.h" +#include "mozilla/ServoStyleConsts.h" + +namespace mozilla { +class SVGFilterFrame; + +namespace dom { +class SVGFilterElement; +} // namespace dom + +/** + * This class helps FilterInstance 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 SVGFilterInstance { + using Point3D = gfx::Point3D; + using IntRect = gfx::IntRect; + using SourceSurface = gfx::SourceSurface; + using FilterPrimitiveDescription = gfx::FilterPrimitiveDescription; + using SVGFE = dom::SVGFE; + using UserSpaceMetrics = dom::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 SVGFilterInstance. + * @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. + */ + SVGFilterInstance( + const StyleFilter& aFilter, nsIFrame* aTargetFrame, + nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics, + const gfxRect& aTargetBBox, + const gfx::MatrixScalesDouble& aUserSpaceToFilterSpaceScale); + + /** + * 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); + + float GetPrimitiveNumber(uint8_t aCtxType, + const SVGAnimatedNumber* aNumber) const { + return GetPrimitiveNumber(aCtxType, aNumber->GetAnimValue()); + } + float GetPrimitiveNumber(uint8_t aCtxType, + const SVGAnimatedNumberPair* aNumberPair, + SVGAnimatedNumberPair::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. + */ + SVGFilterFrame* GetFilterFrame(nsIFrame* aTargetFrame); + + /** + * Computes the filter primitive subregion for the given primitive. + */ + IntRect ComputeFilterPrimitiveSubregion( + SVGFE* 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; + + /** + * 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( + SVGFE* aPrimitiveElement, + nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, + const nsTHashMap<nsStringHashKey, int32_t>& aImageTable, + nsTArray<int32_t>& aSourceIndices); + + /** + * Compute the filter region in user space, filter space, and filter + * space. + */ + bool ComputeBounds(); + + /** + * The SVG reference filter originally from the style system. + */ + const StyleFilter& 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 dom::SVGFilterElement* mFilterElement; + + /** + * The frame for the SVG filter element. + */ + SVGFilterFrame* mFilterFrame; + + /** + * The SVG bbox of the element that is being filtered, in user space. + */ + gfxRect mTargetBBox; + + /** + * The "filter region" in various spaces. + */ + nsIntRect mFilterSpaceBounds; + + /** + * The scale factors between user space and filter space. + */ + gfx::MatrixScalesDouble mUserSpaceToFilterSpaceScale; + + /** + * 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; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGFILTERINSTANCE_H_ diff --git a/layout/svg/SVGForeignObjectFrame.cpp b/layout/svg/SVGForeignObjectFrame.cpp new file mode 100644 index 0000000000..fd89176991 --- /dev/null +++ b/layout/svg/SVGForeignObjectFrame.cpp @@ -0,0 +1,563 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGForeignObjectFrame.h" + +// Keep others in (case-insensitive) order: +#include "ImgDrawResult.h" +#include "gfxContext.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGOuterSVGFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGForeignObjectElement.h" +#include "nsDisplayList.h" +#include "nsGkAtoms.h" +#include "nsNameSpaceManager.h" +#include "nsLayoutUtils.h" +#include "nsRegion.h" +#include "SVGGeometryProperty.h" + +using namespace mozilla::dom; +using namespace mozilla::image; +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// Implementation + +nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGForeignObjectFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGForeignObjectFrame) + +SVGForeignObjectFrame::SVGForeignObjectFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsContainerFrame(aStyle, aPresContext, kClassID), mInReflow(false) { + AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED | + NS_FRAME_SVG_LAYOUT); +} + +//---------------------------------------------------------------------- +// nsIFrame methods + +NS_QUERYFRAME_HEAD(SVGForeignObjectFrame) + NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +void SVGForeignObjectFrame::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); + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + if (!(mState & NS_FRAME_IS_NONDISPLAY)) { + SVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this); + } +} + +void SVGForeignObjectFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + // Only unregister if we registered in the first place: + if (!(mState & NS_FRAME_IS_NONDISPLAY)) { + SVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this); + } + nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +nsresult SVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None) { + 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(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + } + } + + return NS_OK; +} + +void SVGForeignObjectFrame::DidSetComputedStyle( + ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + + if (aOldComputedStyle) { + if (StyleSVGReset()->mX != aOldComputedStyle->StyleSVGReset()->mX || + StyleSVGReset()->mY != aOldComputedStyle->StyleSVGReset()->mY) { + // Invalidate cached transform matrix. + mCanvasTM = nullptr; + SVGUtils::ScheduleReflowSVG(this); + } + } +} + +void SVGForeignObjectFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + MOZ_ASSERT(!HasAnyStateBits(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(!HasAnyStateBits(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(); +} + +void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) { + return; + } + nsDisplayList newList(aBuilder); + nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList, + &newList); + DisplayOutline(aBuilder, set); + BuildDisplayListForNonBlockChildren(aBuilder, set); + aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this, + &newList); +} + +bool SVGForeignObjectFrame::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<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform( + aFromParentTransform); + } + + SVGElement* content = static_cast<SVGElement*>(GetContent()); + SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent)); + } + foundTransform = true; + } + return foundTransform; +} + +void SVGForeignObjectFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + 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; + } + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + if (aTransform.IsSingular()) { + NS_WARNING("Can't render foreignObject element!"); + return; + } + + nsRect kidDirtyRect = kid->InkOverflowRect(); + + /* 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, 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; + } + } + + aContext.Save(); + + if (StyleDisplay()->IsScrollableOverflow()) { + float x, y, width, height; + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, + SVGT::Height>( + static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height); + + gfxRect clipRect = + SVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height); + SVGUtils::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 = nsPresContext::AppUnitsToFloatCSSPixels( + PresContext()->AppUnitsPerDevPixel()); + gfxMatrix canvasTMForChildren = aTransform; + canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx); + + aContext.Multiply(canvasTMForChildren); + + using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; + PaintFrameFlags flags = PaintFrameFlags::InTransform; + if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) { + flags |= PaintFrameFlags::ToWindow; + } + if (aImgParams.imageFlags & imgIContainer::FLAG_SYNC_DECODE) { + flags |= PaintFrameFlags::SyncDecodeImages; + } + if (aImgParams.imageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) { + flags |= PaintFrameFlags::UseHighQualityScaling; + } + nsLayoutUtils::PaintFrame(&aContext, kid, nsRegion(kidDirtyRect), + NS_RGBA(0, 0, 0, 0), + nsDisplayListBuilderMode::Painting, flags); + + aContext.Restore(); +} + +nsIFrame* SVGForeignObjectFrame::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() || HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return nullptr; + } + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return nullptr; + } + + float x, y, width, height; + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height); + + if (!gfxRect(x, y, width, height).Contains(aPoint) || + !SVGUtils::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)) * AppUnitsPerCSSPixel(); + nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y)); + + return nsLayoutUtils::GetFrameForPoint(RelativeTo{kid}, point); +} + +void SVGForeignObjectFrame::ReflowSVG() { + NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!SVGUtils::NeedsReflowSVG(this)) { + return; + } + + // We update mRect before the DoReflow call so that DoReflow uses the + // correct dimensions: + + float x, y, w, h; + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + static_cast<SVGElement*>(GetContent()), &x, &y, &w, &h); + + // 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), + 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->MarkSubtreeDirty(); + + // 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): + SVGObserverUtils::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(); + } + + auto* anonKid = PrincipalChildList().FirstChild(); + nsRect overflow = anonKid->InkOverflowRect(); + + OverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + // Now unset the various reflow bits: + RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void SVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags) { + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + bool needNewBounds = false; // i.e. mRect or ink overflow rect + bool needReflow = false; + bool needNewCanvasTM = false; + + if (aFlags & COORD_CONTEXT_CHANGED) { + // Coordinate context changes affect mCanvasTM if we have a + // percentage 'x' or 'y' + if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) { + 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 (StylePosition()->mWidth.HasPercent() || + StylePosition()->mHeight.HasPercent()) { + 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. + SVGUtils::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 SVGOuterSVGFrame'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, SVGOuterSVGFrame::DidReflow will take care of reflowing us + // synchronously, so there's no need. + if (needReflow && !PresShell()->IsReflowLocked()) { + RequestReflow(IntrinsicDirty::None); + } + + if (needNewCanvasTM) { + // Do this after calling InvalidateAndScheduleBoundsUpdate in case we + // change the code and it needs to use it. + mCanvasTM = nullptr; + } +} + +SVGBBox SVGForeignObjectFrame::GetBBoxContribution( + const Matrix& aToBBoxUserspace, uint32_t aFlags) { + SVGForeignObjectElement* content = + static_cast<SVGForeignObjectElement*>(GetContent()); + + float x, y, w, h; + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + content, &x, &y, &w, &h); + + 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 SVGForeignObjectFrame::GetCanvasTM() { + if (!mCanvasTM) { + NS_ASSERTION(GetParent(), "null parent"); + + auto* parent = static_cast<SVGContainerFrame*>(GetParent()); + auto* content = static_cast<SVGForeignObjectElement*>(GetContent()); + + gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); + + mCanvasTM = MakeUnique<gfxMatrix>(tm); + } + return *mCanvasTM; +} + +//---------------------------------------------------------------------- +// Implementation helpers + +void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType) { + if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + // If we haven't had a ReflowSVG() yet, nothing to do. + return; + } + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY); +} + +void SVGForeignObjectFrame::DoReflow() { + MarkInReflow(); + // Skip reflow if we're zero-sized, unless this is our first reflow. + if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + return; + } + + nsPresContext* presContext = PresContext(); + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + // initiate a synchronous reflow here and now: + RefPtr<gfxContext> 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 input made child wrong size"); + reflowInput.SetComputedBSize(BSize(wm)); + + ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0, + ReflowChildFlags::NoMoveFrame, status); + NS_ASSERTION(mRect.width == desiredSize.Width() && + mRect.height == desiredSize.Height(), + "unexpected size"); + FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0, + ReflowChildFlags::NoMoveFrame); + + mInReflow = false; +} + +nsRect SVGForeignObjectFrame::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 / AppUnitsPerCSSPixel()); + nsRect rect = SVGUtils::ToCanvasBounds(r, GetCanvasTM(), PresContext()); + rect = SVGUtils::GetPostFilterInkOverflowRect(this, rect); + return rect; + } + return nsRect(); +} + +void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box"); + aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild())); +} + +} // namespace mozilla diff --git a/layout/svg/SVGForeignObjectFrame.h b/layout/svg/SVGForeignObjectFrame.h new file mode 100644 index 0000000000..4fcd6fe963 --- /dev/null +++ b/layout/svg/SVGForeignObjectFrame.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGFOREIGNOBJECTFRAME_H_ +#define LAYOUT_SVG_SVGFOREIGNOBJECTFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/PresShellForwards.h" +#include "mozilla/UniquePtr.h" +#include "nsContainerFrame.h" +#include "nsRegion.h" + +class gfxContext; + +nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGForeignObjectFrame final : public nsContainerFrame, + public ISVGDisplayableFrame { + friend nsContainerFrame* ::NS_NewSVGForeignObjectFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + protected: + explicit SVGForeignObjectFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext); + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGForeignObjectFrame) + + // nsIFrame: + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + void DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) override; + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsContainerFrame::IsFrameOfType(aFlags & ~nsIFrame::eSVG); + } + + bool IsSVGTransformed(Matrix* aOwnTransform, + Matrix* aFromParentTransform) const override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGForeignObject"_ns, aResult); + } +#endif + + // ISVGDisplayableFrame interface: + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + void ReflowSVG() override; + void NotifySVGChanged(uint32_t aFlags) override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + bool IsDisplayContainer() override { return true; } + + gfxMatrix GetCanvasTM(); + + nsRect GetInvalidRegion(); + + // Return our ::-moz-svg-foreign-content anonymous box. + void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override; + + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + protected: + // implementation helpers: + void DoReflow(); + void RequestReflow(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; } + + UniquePtr<gfxMatrix> mCanvasTM; + + bool mInReflow; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGFOREIGNOBJECTFRAME_H_ diff --git a/layout/svg/SVGGFrame.cpp b/layout/svg/SVGGFrame.cpp new file mode 100644 index 0000000000..92df721ffb --- /dev/null +++ b/layout/svg/SVGGFrame.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGGFrame.h" + +// Keep others in (case-insensitive) order: +#include "mozilla/dom/SVGElement.h" +#include "mozilla/PresShell.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" + +using namespace mozilla::dom; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGGFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGGFrame) + +#ifdef DEBUG +void SVGGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement() && + static_cast<SVGElement*>(aContent)->IsTransformable(), + "The element is not transformable"); + + SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods + +nsresult SVGGFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* 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; +} + +} // namespace mozilla diff --git a/layout/svg/SVGGFrame.h b/layout/svg/SVGGFrame.h new file mode 100644 index 0000000000..68e01ec8b9 --- /dev/null +++ b/layout/svg/SVGGFrame.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGGFRAME_H_ +#define LAYOUT_SVG_SVGGFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SVGContainerFrame.h" +#include "gfxMatrix.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGGFrame : public SVGDisplayContainerFrame { + friend nsIFrame* ::NS_NewSVGGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + explicit SVGGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGGFrame(aStyle, aPresContext, kClassID) {} + + protected: + SVGGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + nsIFrame::ClassID aID) + : SVGDisplayContainerFrame(aStyle, aPresContext, aID) {} + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGGFrame) + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGG"_ns, aResult); + } +#endif + + // nsIFrame interface: + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGGFRAME_H_ diff --git a/layout/svg/SVGGeometryFrame.cpp b/layout/svg/SVGGeometryFrame.cpp new file mode 100644 index 0000000000..7d52bd4ff5 --- /dev/null +++ b/layout/svg/SVGGeometryFrame.cpp @@ -0,0 +1,898 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGGeometryFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGGraphicsElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "nsDisplayList.h" +#include "nsGkAtoms.h" +#include "nsLayoutUtils.h" +#include "SVGAnimatedTransformList.h" +#include "SVGMarkerFrame.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGGeometryFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame) + +//---------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(SVGGeometryFrame) + NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame) + NS_QUERYFRAME_ENTRY(SVGGeometryFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsIFrame) + +void DisplaySVGGeometry::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + SVGGeometryFrame* frame = static_cast<SVGGeometryFrame*>(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) / + AppUnitsPerCSSPixel(); + if (frame->GetFrameForPoint(userSpacePt)) { + aOutFrames->AppendElement(frame); + } +} + +void DisplaySVGGeometry::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* 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 = SVGUtils::GetCSSPxToDevPxMatrix(mFrame) * + gfxMatrix::Translation(devPixelOffset); + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx, tm, imgParams); +} + +//---------------------------------------------------------------------- +// nsIFrame methods + +void SVGGeometryFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); + nsIFrame::Init(aContent, aParent, aPrevInFlow); + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); +} + +nsresult SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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<SVGGeometryElement*>(GetContent()) + ->AttributeDefinesGeometry(aAttribute))) { + nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(this); + } + return NS_OK; +} + +/* virtual */ +void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + nsIFrame::DidSetComputedStyle(aOldComputedStyle); + auto* element = static_cast<SVGGeometryElement*>(GetContent()); + if (!aOldComputedStyle) { + element->ClearAnyCachedPath(); + return; + } + + const auto* oldStyleSVG = aOldComputedStyle->StyleSVG(); + if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) { + 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 (HasAnyStateBits(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(); + } + } + } + + if (element->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle)) { + element->ClearAnyCachedPath(); + } +} + +bool SVGGeometryFrame::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<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform( + aFromParentTransform); + } + + SVGElement* content = static_cast<SVGElement*>(GetContent()); + SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent)); + } + foundTransform = true; + } + return foundTransform; +} + +void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) { + return; + } + + if (aBuilder->IsForPainting()) { + if (!IsVisibleForPainting()) { + return; + } + if (StyleEffects()->mOpacity == 0.0f) { + return; + } + const auto* styleSVG = StyleSVG(); + if (Type() != LayoutFrameType::SVGImage && styleSVG->mFill.kind.IsNone() && + styleSVG->mStroke.kind.IsNone() && styleSVG->mMarkerEnd.IsNone() && + styleSVG->mMarkerMid.IsNone() && styleSVG->mMarkerStart.IsNone()) { + return; + } + + aBuilder->BuildCompositorHitTestInfoIfNeeded(this, + aLists.BorderBackground()); + } + + DisplayOutline(aBuilder, aLists); + aLists.Content()->AppendNewToTop<DisplaySVGGeometry>(aBuilder, this); +} + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods + +void SVGGeometryFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect) { + if (!StyleVisibility()->IsVisible()) { + return; + } + + // Matrix to the geometry's user space: + gfxMatrix newMatrix = + aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers(); + if (newMatrix.IsSingular()) { + return; + } + + uint32_t paintOrder = StyleSVG()->mPaintOrder; + if (!paintOrder) { + Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams); + PaintMarkers(aContext, aTransform, aImgParams); + } else { + while (paintOrder) { + auto component = StylePaintOrder(paintOrder & kPaintOrderMask); + switch (component) { + case StylePaintOrder::Fill: + Render(&aContext, eRenderFill, newMatrix, aImgParams); + break; + case StylePaintOrder::Stroke: + Render(&aContext, eRenderStroke, newMatrix, aImgParams); + break; + case StylePaintOrder::Markers: + PaintMarkers(aContext, aTransform, aImgParams); + break; + default: + MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?"); + case StylePaintOrder::Normal: + break; + } + paintOrder >>= kPaintOrderShift; + } + } +} + +nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) { + FillRule fillRule; + uint16_t hitTestFlags; + if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { + hitTestFlags = SVG_HIT_TEST_FILL; + fillRule = SVGUtils::ToFillRule(StyleSVG()->mClipRule); + } else { + hitTestFlags = GetHitTestFlags(); + if (!hitTestFlags) { + return nullptr; + } + if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) { + gfxRect rect = nsLayoutUtils::RectToGfxRect(mRect, AppUnitsPerCSSPixel()); + if (!rect.Contains(aPoint)) { + return nullptr; + } + } + fillRule = SVGUtils::ToFillRule(StyleSVG()->mFillRule); + } + + bool isHit = false; + + SVGGeometryElement* content = static_cast<SVGGeometryElement*>(GetContent()); + + // 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, Style(), nullptr); + gfxMatrix userToOuterSVG; + if (SVGUtils::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 && SVGUtils::HitTestClip(this, aPoint)) { + return this; + } + + return nullptr; +} + +void SVGGeometryFrame::ReflowSVG() { + NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!SVGUtils::NeedsReflowSVG(this)) { + return; + } + + uint32_t flags = SVGUtils::eBBoxIncludeFill | SVGUtils::eBBoxIncludeStroke | + SVGUtils::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 |= SVGUtils::eBBoxIncludeFillGeometry; + } + if ((hitTestFlags & SVG_HIT_TEST_STROKE)) { + flags |= SVGUtils::eBBoxIncludeStrokeGeometry; + } + + gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect(); + mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, 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): + SVGObserverUtils::UpdateEffects(this); + } + + nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size()); + OverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + RemoveStateBits(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()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + InvalidateFrame(); + } +} + +void SVGGeometryFrame::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) { + auto* geom = static_cast<SVGGeometryElement*>(GetContent()); + // 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. + const auto& strokeWidth = StyleSVG()->mStrokeWidth; + if (geom->GeometryDependsOnCoordCtx() || + (strokeWidth.IsLengthPercentage() && + strokeWidth.AsLengthPercentage().HasPercent())) { + geom->ClearAnyCachedPath(); + SVGUtils::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|. + SVGUtils::ScheduleReflowSVG(this); + } +} + +SVGBBox SVGGeometryFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) { + SVGBBox bbox; + + if (aToBBoxUserspace.IsSingular()) { + // XXX ReportToConsole + return bbox; + } + + if ((aFlags & SVGUtils::eForGetClientRects) && + aToBBoxUserspace.PreservesAxisAlignedRectangles()) { + Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel()); + bbox = aToBBoxUserspace.TransformBounds(rect); + return bbox; + } + + SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent()); + + bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) || + ((aFlags & SVGUtils::eBBoxIncludeFill) && + !StyleSVG()->mFill.kind.IsNone()); + + bool getStroke = + (aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) || + ((aFlags & SVGUtils::eBBoxIncludeStroke) && SVGUtils::HasStroke(this)); + + SVGContentUtils::AutoStrokeOptions strokeOptions; + if (getStroke) { + SVGContentUtils::GetStrokeOptions(&strokeOptions, element, Style(), 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 && + SVGUtils::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; + tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + + FillRule fillRule = SVGUtils::ToFillRule( + HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule + : 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 SVGGeometryFrame::Reflow() inserts a scale + // into aToBBoxUserspace and then scales the bounds that we return. + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, element, + Style(), nullptr, + SVGContentUtils::eIgnoreStrokeDashing); + Rect strokeBBoxExtents; + gfxMatrix userToOuterSVG; + if (SVGUtils::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 SVGUtils::PathExtentsToMaxStrokeExtents: + gfxRect strokeBBoxExtents = SVGUtils::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 & SVGUtils::eBBoxIncludeMarkers) != 0 && element->IsMarkable()) { + SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; + if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) { + nsTArray<SVGMark> marks; + element->GetMarkPoints(&marks); + if (uint32_t num = marks.Length()) { + float strokeWidth = SVGUtils::GetStrokeWidth(this); + for (uint32_t i = 0; i < num; i++) { + const SVGMark& mark = marks[i]; + SVGMarkerFrame* frame = markerFrames[mark.type]; + if (frame) { + SVGBBox mbbox = frame->GetMarkBBoxContribution( + aToBBoxUserspace, aFlags, this, mark, strokeWidth); + MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad"); + bbox.UnionEdges(mbbox); + } + } + } + } + } + + return bbox; +} + +//---------------------------------------------------------------------- +// SVGGeometryFrame methods: + +gfxMatrix SVGGeometryFrame::GetCanvasTM() { + NS_ASSERTION(GetParent(), "null parent"); + + auto* parent = static_cast<SVGContainerFrame*>(GetParent()); + auto* content = static_cast<SVGGraphicsElement*>(GetContent()); + + return content->PrependLocalTransformsTo(parent->GetCanvasTM()); +} + +void SVGGeometryFrame::Render(gfxContext* aContext, uint32_t aRenderComponents, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams) { + MOZ_ASSERT(!aTransform.IsSingular()); + + DrawTarget* drawTarget = aContext->GetDrawTarget(); + + MOZ_ASSERT(drawTarget); + if (!drawTarget->IsValid()) { + return; + } + + FillRule fillRule = SVGUtils::ToFillRule( + HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule + : StyleSVG()->mFillRule); + + SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent()); + + AntialiasMode aaMode = + (StyleSVG()->mShapeRendering == StyleShapeRendering::Optimizespeed || + StyleSVG()->mShapeRendering == StyleShapeRendering::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->SetMatrixDouble(aTransform); + + if (HasAnyStateBits(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(sRGBColor(1.0f, 1.0f, 1.0f, 1.0f))); + drawTarget->Fill(path, white, + DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode)); + } + return; + } + + SVGGeometryElement::SimplePath simplePath; + RefPtr<Path> path; + + element->GetAsSimplePath(&simplePath); + if (!simplePath.IsPath()) { + path = element->GetOrBuildPath(drawTarget, fillRule); + if (!path) { + return; + } + } + + SVGContextPaint* contextPaint = + SVGContextPaint::GetContextPaint(GetContent()); + + if (aRenderComponents & eRenderFill) { + GeneralPattern fillPattern; + SVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams, + 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) && + SVGUtils::HasStroke(this, contextPaint)) { + // Account for vector-effect:non-scaling-stroke: + gfxMatrix userToOuterSVG; + if (SVGUtils::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; + SVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, aImgParams, + contextPaint); + + if (strokePattern.GetPattern()) { + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, + static_cast<SVGElement*>(GetContent()), + Style(), 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); + } + } + } +} + +bool SVGGeometryFrame::IsInvisible() const { + if (!StyleVisibility()->IsVisible()) { + return false; + } + + const nsStyleSVG* style = StyleSVG(); + SVGContextPaint* contextPaint = + SVGContextPaint::GetContextPaint(GetContent()); + + // Anything below will round to zero later down the pipeline. + float opacity_threshold = 1.0 / 128.0; + + float elemOpacity = StyleEffects()->mOpacity; + if (elemOpacity <= opacity_threshold) { + return true; + } + + if (IsSVGImageFrame()) { + return false; + } + + if (!style->mFill.kind.IsNone()) { + float opacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint); + if (opacity > opacity_threshold) { + return false; + } + } + + if (!style->mStroke.kind.IsNone()) { + float opacity = SVGUtils::GetOpacity(style->mStrokeOpacity, contextPaint); + if (opacity > opacity_threshold) { + return false; + } + } + + if (style->mMarkerStart.IsUrl() || style->mMarkerMid.IsUrl() || + style->mMarkerEnd.IsUrl()) { + return false; + } + + return true; +} + +bool SVGGeometryFrame::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem, + bool aDryRun) { + if (!StyleVisibility()->IsVisible()) { + return true; + } + + SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent()); + + SVGGeometryElement::SimplePath simplePath; + element->GetAsSimplePath(&simplePath); + + if (!simplePath.IsRect()) { + return false; + } + + const nsStyleSVG* style = StyleSVG(); + MOZ_ASSERT(style); + + if (!style->mFill.kind.IsColor()) { + return false; + } + + switch (style->mFill.kind.tag) { + case StyleSVGPaintKind::Tag::Color: + break; + default: + return false; + } + + if (!style->mStroke.kind.IsNone()) { + return false; + } + + if (StyleEffects()->mMixBlendMode != StyleBlend::Normal) { + // FIXME: not implemented + return false; + } + + SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; + if (element->IsMarkable() && + SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) { + // Markers aren't suppported yet. + return false; + } + + if (!aDryRun) { + auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); + float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx; + + auto rect = simplePath.AsRect(); + rect.Scale(scale); + + auto offset = LayoutDevicePoint::FromAppUnits( + aItem->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx); + rect.MoveBy(offset.x, offset.y); + + auto wrRect = wr::ToLayoutRect(rect); + + SVGContextPaint* contextPaint = + SVGContextPaint::GetContextPaint(GetContent()); + // At the moment this code path doesn't support strokes so it fine to + // combine the rectangle's opacity (which has to be applied on the result) + // of (filling + stroking) with the fill opacity. + float elemOpacity = StyleEffects()->mOpacity; + float fillOpacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint); + float opacity = elemOpacity * fillOpacity; + + auto c = nsLayoutUtils::GetColor(this, &nsStyleSVG::mFill); + wr::ColorF color{ + ((float)NS_GET_R(c)) / 255.0f, ((float)NS_GET_G(c)) / 255.0f, + ((float)NS_GET_B(c)) / 255.0f, ((float)NS_GET_A(c)) / 255.0f * opacity}; + + aBuilder.PushRect(wrRect, wrRect, !aItem->BackfaceIsHidden(), true, false, + color); + } + + return true; +} + +void SVGGeometryFrame::PaintMarkers(gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams) { + auto* element = static_cast<SVGGeometryElement*>(GetContent()); + if (!element->IsMarkable()) { + return; + } + SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; + if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) { + return; + } + nsTArray<SVGMark> marks; + element->GetMarkPoints(&marks); + if (marks.IsEmpty()) { + return; + } + float strokeWidth = GetStrokeWidthForMarkers(); + for (const SVGMark& mark : marks) { + if (auto* frame = markerFrames[mark.type]) { + frame->PaintMark(aContext, aTransform, this, mark, strokeWidth, + aImgParams); + } + } +} + +float SVGGeometryFrame::GetStrokeWidthForMarkers() { + float strokeWidth = SVGUtils::GetStrokeWidth( + this, SVGContextPaint::GetContextPaint(GetContent())); + gfxMatrix userToOuterSVG; + if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { + // We're not interested in any translation here so we can treat this as + // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us + // sx and sy values as the X and Y scales. The value we want is the XY + // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) / + // sqrt(2). If we use the formulae from + // https://scicomp.stackexchange.com/a/14103, we discover that the + // normalised hypotenuse is simply the square root of the sum of the squares + // of all the 2D matrix elements divided by sqrt(2). + // + // Note that this may need adjusting to support 3D transforms properly. + + strokeWidth /= float(sqrt(userToOuterSVG._11 * userToOuterSVG._11 + + userToOuterSVG._12 * userToOuterSVG._12 + + userToOuterSVG._21 * userToOuterSVG._21 + + userToOuterSVG._22 * userToOuterSVG._22) / + M_SQRT2); + } + return strokeWidth; +} + +uint16_t SVGGeometryFrame::GetHitTestFlags() { + return SVGUtils::GetGeometryHitTestFlags(this); +} +} // namespace mozilla diff --git a/layout/svg/SVGGeometryFrame.h b/layout/svg/SVGGeometryFrame.h new file mode 100644 index 0000000000..efc339eeff --- /dev/null +++ b/layout/svg/SVGGeometryFrame.h @@ -0,0 +1,208 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGGEOMETRYFRAME_H_ +#define LAYOUT_SVG_SVGGEOMETRYFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsDisplayList.h" +#include "nsIFrame.h" +#include "nsLiteralString.h" +#include "nsQueryFrame.h" + +namespace mozilla { + +class DisplaySVGGeometry; +class PresShell; +class SVGGeometryFrame; +class SVGMarkerObserver; + +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace image { +struct imgDrawingParams; +} // namespace image + +} // namespace mozilla + +class gfxContext; +class nsAtom; +class nsIFrame; + +struct nsRect; + +nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGGeometryFrame : public nsIFrame, public ISVGDisplayableFrame { + using DrawTarget = gfx::DrawTarget; + + friend nsIFrame* ::NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + friend class DisplaySVGGeometry; + + protected: + SVGGeometryFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + nsIFrame::ClassID aID = kClassID) + : nsIFrame(aStyle, aPresContext, aID) { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_MAY_BE_TRANSFORMED); + } + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGGeometryFrame) + + // nsIFrame interface: + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsIFrame::IsFrameOfType(aFlags & ~nsIFrame::eSVG); + } + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + bool IsSVGTransformed(Matrix* aOwnTransforms = nullptr, + Matrix* aFromParentTransforms = nullptr) const override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGGeometry"_ns, aResult); + } +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + // SVGGeometryFrame methods + gfxMatrix GetCanvasTM(); + + bool IsInvisible() const; + + protected: + // ISVGDisplayableFrame interface: + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + void ReflowSVG() override; + void NotifySVGChanged(uint32_t aFlags) override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + 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, imgDrawingParams& aImgParams); + + virtual bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem, + bool aDryRun); + /** + * @param aMatrix The transform that must be multiplied onto aContext to + * establish this frame's SVG user space. + */ + void PaintMarkers(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams); + + /* + * Get the stroke width that markers should use, accounting for + * non-scaling stroke. + */ + float GetStrokeWidthForMarkers(); +}; + +//---------------------------------------------------------------------- +// Display list item: + +class DisplaySVGGeometry final : public nsPaintedDisplayItem { + using imgDrawingParams = image::imgDrawingParams; + + public: + DisplaySVGGeometry(nsDisplayListBuilder* aBuilder, SVGGeometryFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(DisplaySVGGeometry); + MOZ_ASSERT(aFrame, "Must have a frame!"); + } + + MOZ_COUNTED_DTOR_OVERRIDE(DisplaySVGGeometry) + + NS_DISPLAY_DECL_NAME("DisplaySVGGeometry", TYPE_SVG_GEOMETRY) + + void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + + // Whether this part of the SVG should be natively handled by webrender, + // potentially becoming an "active layer" inside a blob image. + bool ShouldBeActive(mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + // We delegate this question to the parent frame to take advantage of + // the SVGGeometryFrame inheritance hierarchy which provides actual + // implementation details. The dryRun flag prevents serious side-effects. + auto* frame = static_cast<SVGGeometryFrame*>(mFrame); + return frame->CreateWebRenderCommands(aBuilder, aResources, aSc, aManager, + aDisplayListBuilder, this, + /*aDryRun=*/true); + } + + virtual bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override { + // We delegate this question to the parent frame to take advantage of + // the SVGGeometryFrame inheritance hierarchy which provides actual + // implementation details. + auto* frame = static_cast<SVGGeometryFrame*>(mFrame); + bool result = frame->CreateWebRenderCommands(aBuilder, aResources, aSc, + aManager, aDisplayListBuilder, + this, /*aDryRun=*/false); + MOZ_ASSERT(result, "ShouldBeActive inconsistent with CreateWRCommands?"); + return result; + } + + bool IsInvisible() const override { + auto* frame = static_cast<SVGGeometryFrame*>(mFrame); + return frame->IsInvisible(); + } +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGGEOMETRYFRAME_H_ diff --git a/layout/svg/SVGGradientFrame.cpp b/layout/svg/SVGGradientFrame.cpp new file mode 100644 index 0000000000..42116a9851 --- /dev/null +++ b/layout/svg/SVGGradientFrame.cpp @@ -0,0 +1,606 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGGradientFrame.h" +#include <algorithm> + +// Keep others in (case-insensitive) order: +#include "AutoReferenceChainGuard.h" +#include "gfxPattern.h" +#include "gfxUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGGradientElement.h" +#include "mozilla/dom/SVGGradientElementBinding.h" +#include "mozilla/dom/SVGStopElement.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "nsContentUtils.h" +#include "SVGAnimatedTransformList.h" + +// XXX Tight coupling with content classes ahead! + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGGradientElement_Binding; +using namespace mozilla::dom::SVGUnitTypes_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Implementation + +SVGGradientFrame::SVGGradientFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext, ClassID aID) + : SVGPaintServerFrame(aStyle, aPresContext, aID), + mSource(nullptr), + mLoopFlag(false), + mNoHRefURI(false) {} + +NS_QUERYFRAME_HEAD(SVGGradientFrame) + NS_QUERYFRAME_ENTRY(SVGGradientFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGPaintServerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult SVGGradientFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::gradientUnits || + aAttribute == nsGkAtoms::gradientTransform || + aAttribute == nsGkAtoms::spreadMethod)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } else if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // Blow away our reference, if any + SVGObserverUtils::RemoveTemplateObserver(this); + mNoHRefURI = false; + // And update whoever references us + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + return SVGPaintServerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +//---------------------------------------------------------------------- + +uint16_t SVGGradientFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) { + const SVGAnimatedEnumeration& thisEnum = + static_cast<dom::SVGGradientElement*>(GetContent()) + ->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) { + return thisEnum.GetAnimValue(); + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<dom::SVGGradientElement*>(aDefault) + ->mEnumAttributes[aIndex] + .GetAnimValue(); + } + + SVGGradientFrame* next = GetReferencedGradient(); + + return next ? next->GetEnumValue(aIndex, aDefault) + : static_cast<dom::SVGGradientElement*>(aDefault) + ->mEnumAttributes[aIndex] + .GetAnimValue(); +} + +uint16_t SVGGradientFrame::GetGradientUnits() { + // This getter is called every time the others are called - maybe cache it? + return GetEnumValue(dom::SVGGradientElement::GRADIENTUNITS); +} + +uint16_t SVGGradientFrame::GetSpreadMethod() { + return GetEnumValue(dom::SVGGradientElement::SPREADMETHOD); +} + +const SVGAnimatedTransformList* SVGGradientFrame::GetGradientTransformList( + nsIContent* aDefault) { + SVGAnimatedTransformList* thisTransformList = + static_cast<dom::SVGGradientElement*>(GetContent()) + ->GetAnimatedTransformList(); + + if (thisTransformList && thisTransformList->IsExplicitlySet()) + return thisTransformList; + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<const dom::SVGGradientElement*>(aDefault) + ->mGradientTransform.get(); + } + + SVGGradientFrame* next = GetReferencedGradient(); + + return next ? next->GetGradientTransformList(aDefault) + : static_cast<const dom::SVGGradientElement*>(aDefault) + ->mGradientTransform.get(); +} + +gfxMatrix SVGGradientFrame::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 + : SVGUtils::GetBBox( + aSource, SVGUtils::eUseFrameBoundsForOuterSVG | + SVGUtils::eBBoxIncludeFillGeometry); + bboxMatrix = + gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y()); + } + + const SVGAnimatedTransformList* animTransformList = + GetGradientTransformList(GetContent()); + if (!animTransformList) { + return bboxMatrix; + } + + gfxMatrix gradientTransform = + animTransformList->GetAnimValue().GetConsolidationMatrix(); + return bboxMatrix.PreMultiply(gradientTransform); +} + +dom::SVGLinearGradientElement* SVGGradientFrame::GetLinearGradientWithLength( + uint32_t aIndex, dom::SVGLinearGradientElement* aDefault) { + // If this was a linear gradient with the required length, we would have + // already found it in SVGLinearGradientFrame::GetLinearGradientWithLength. + // Since we didn't find the length, continue looking down the chain. + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return aDefault; + } + + SVGGradientFrame* next = GetReferencedGradient(); + return next ? next->GetLinearGradientWithLength(aIndex, aDefault) : aDefault; +} + +dom::SVGRadialGradientElement* SVGGradientFrame::GetRadialGradientWithLength( + uint32_t aIndex, dom::SVGRadialGradientElement* aDefault) { + // If this was a radial gradient with the required length, we would have + // already found it in SVGRadialGradientFrame::GetRadialGradientWithLength. + // Since we didn't find the length, continue looking down the chain. + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return aDefault; + } + + SVGGradientFrame* next = GetReferencedGradient(); + return next ? next->GetRadialGradientWithLength(aIndex, aDefault) : aDefault; +} + +//---------------------------------------------------------------------- +// SVGPaintServerFrame 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); + + const nsStyleSVGReset* styleSVGReset = aStopFrame->StyleSVGReset(); + *aOffset = mozilla::clamped(*aOffset, 0.0f, 1.0f); + *aStopColor = styleSVGReset->mStopColor.CalcColor(aStopFrame); + *aStopOpacity = styleSVGReset->mStopOpacity; +} + +already_AddRefed<gfxPattern> SVGGradientFrame::GetPaintServerPattern( + nsIFrame* aSource, const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, imgDrawingParams& aImgParams, + 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()->IsText() ? 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(DeviceColor()); + return do_AddRef(new gfxPattern(DeviceColor())); + } + + if (nStops == 1 || GradientVectorLengthIsZero()) { + auto* lastStopFrame = stopFrames[nStops - 1]; + const auto* svgReset = lastStopFrame->StyleSVGReset(); + // The gradient paints a single colour, using the stop-color of the last + // gradient step if there are more than one. + float stopOpacity = svgReset->mStopOpacity; + nscolor stopColor = svgReset->mStopColor.CalcColor(lastStopFrame); + + sRGBColor stopColor2 = sRGBColor::FromABGR(stopColor); + stopColor2.a *= stopOpacity * aGraphicOpacity; + return do_AddRef(new gfxPattern(ToDeviceColor(stopColor2))); + } + + // 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 (SVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) { + patternMatrix *= userToOuterSVG; + } + } + + if (!patternMatrix.Invert()) { + return nullptr; + } + + RefPtr<gfxPattern> gradient = CreateGradient(); + if (!gradient) { + 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; + + sRGBColor stopColor2 = sRGBColor::FromABGR(stopColor); + stopColor2.a *= stopOpacity * aGraphicOpacity; + gradient->AddColorStop(offset, ToDeviceColor(stopColor2)); + } + + return gradient.forget(); +} + +// Private (helper) methods + +SVGGradientFrame* SVGGradientFrame::GetReferencedGradient() { + if (mNoHRefURI) { + return nullptr; + } + + auto GetHref = [this](nsAString& aHref) { + dom::SVGGradientElement* grad = + static_cast<dom::SVGGradientElement*>(this->GetContent()); + if (grad->mStringAttributes[dom::SVGGradientElement::HREF] + .IsExplicitlySet()) { + grad->mStringAttributes[dom::SVGGradientElement::HREF].GetAnimValue(aHref, + grad); + } else { + grad->mStringAttributes[dom::SVGGradientElement::XLINK_HREF].GetAnimValue( + aHref, grad); + } + this->mNoHRefURI = aHref.IsEmpty(); + }; + + nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref); + if (tframe) { + return static_cast<SVGGradientFrame*>(do_QueryFrame(tframe)); + } + // We don't call SVGObserverUtils::RemoveTemplateObserver and set + // `mNoHRefURI = false` here since we want to be invalidated if the ID + // specified by our href starts resolving to a different/valid element. + + return nullptr; +} + +void SVGGradientFrame::GetStopFrames(nsTArray<nsIFrame*>* aStopFrames) { + nsIFrame* stopFrame = nullptr; + for (stopFrame = mFrames.FirstChild(); stopFrame; + stopFrame = stopFrame->GetNextSibling()) { + if (stopFrame->IsSVGStopFrame()) { + aStopFrames->AppendElement(stopFrame); + } + } + if (aStopFrames->Length() > 0) { + return; + } + + // Our gradient element doesn't have stops - try to "inherit" them + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return; + } + + SVGGradientFrame* next = GetReferencedGradient(); + if (next) { + next->GetStopFrames(aStopFrames); + } +} + +// ------------------------------------------------------------------------- +// Linear Gradients +// ------------------------------------------------------------------------- + +NS_QUERYFRAME_HEAD(SVGLinearGradientFrame) + NS_QUERYFRAME_ENTRY(SVGLinearGradientFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGGradientFrame) + +#ifdef DEBUG +void SVGLinearGradientFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::linearGradient), + "Content is not an SVG linearGradient"); + + SVGGradientFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGLinearGradientFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x1 || aAttribute == nsGkAtoms::y1 || + aAttribute == nsGkAtoms::x2 || aAttribute == nsGkAtoms::y2)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + return SVGGradientFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +//---------------------------------------------------------------------- + +float SVGLinearGradientFrame::GetLengthValue(uint32_t aIndex) { + dom::SVGLinearGradientElement* lengthElement = GetLinearGradientWithLength( + aIndex, static_cast<dom::SVGLinearGradientElement*>(GetContent())); + // 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 SVGAnimatedLength& 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 SVGUtils::UserSpace(mSource, &length); + } + + NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); + + return length.GetAnimValue(static_cast<SVGViewportElement*>(nullptr)); +} + +dom::SVGLinearGradientElement* +SVGLinearGradientFrame::GetLinearGradientWithLength( + uint32_t aIndex, dom::SVGLinearGradientElement* aDefault) { + dom::SVGLinearGradientElement* thisElement = + static_cast<dom::SVGLinearGradientElement*>(GetContent()); + const SVGAnimatedLength& length = thisElement->mLengthAttributes[aIndex]; + + if (length.IsExplicitlySet()) { + return thisElement; + } + + return SVGGradientFrame::GetLinearGradientWithLength(aIndex, aDefault); +} + +bool SVGLinearGradientFrame::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> SVGLinearGradientFrame::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 +// ------------------------------------------------------------------------- + +NS_QUERYFRAME_HEAD(SVGRadialGradientFrame) + NS_QUERYFRAME_ENTRY(SVGRadialGradientFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGGradientFrame) + +#ifdef DEBUG +void SVGRadialGradientFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::radialGradient), + "Content is not an SVG radialGradient"); + + SVGGradientFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGRadialGradientFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::r || aAttribute == nsGkAtoms::cx || + aAttribute == nsGkAtoms::cy || aAttribute == nsGkAtoms::fx || + aAttribute == nsGkAtoms::fy)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + return SVGGradientFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +//---------------------------------------------------------------------- + +float SVGRadialGradientFrame::GetLengthValue(uint32_t aIndex) { + dom::SVGRadialGradientElement* lengthElement = GetRadialGradientWithLength( + aIndex, static_cast<dom::SVGRadialGradientElement*>(GetContent())); + // 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 SVGRadialGradientFrame::GetLengthValue(uint32_t aIndex, + float aDefaultValue) { + dom::SVGRadialGradientElement* lengthElement = + GetRadialGradientWithLength(aIndex, nullptr); + + return lengthElement ? GetLengthValueFromElement(aIndex, *lengthElement) + : aDefaultValue; +} + +float SVGRadialGradientFrame::GetLengthValueFromElement( + uint32_t aIndex, dom::SVGRadialGradientElement& aElement) { + const SVGAnimatedLength& 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 SVGUtils::UserSpace(mSource, &length); + } + + NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); + + return length.GetAnimValue(static_cast<SVGViewportElement*>(nullptr)); +} + +dom::SVGRadialGradientElement* +SVGRadialGradientFrame::GetRadialGradientWithLength( + uint32_t aIndex, dom::SVGRadialGradientElement* aDefault) { + dom::SVGRadialGradientElement* thisElement = + static_cast<dom::SVGRadialGradientElement*>(GetContent()); + const SVGAnimatedLength& length = thisElement->mLengthAttributes[aIndex]; + + if (length.IsExplicitlySet()) { + return thisElement; + } + + return SVGGradientFrame::GetRadialGradientWithLength(aIndex, aDefault); +} + +bool SVGRadialGradientFrame::GradientVectorLengthIsZero() { + return GetLengthValue(dom::SVGRadialGradientElement::ATTR_R) == 0; +} + +already_AddRefed<gfxPattern> SVGRadialGradientFrame::CreateGradient() { + float cx, cy, r, fx, fy, fr; + + 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); + fr = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FR); + + RefPtr<gfxPattern> pattern = new gfxPattern(fx, fy, fr, cx, cy, r); + return pattern.forget(); +} + +} // namespace mozilla + +// ------------------------------------------------------------------------- +// Public functions +// ------------------------------------------------------------------------- + +nsIFrame* NS_NewSVGLinearGradientFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGLinearGradientFrame(aStyle, aPresShell->GetPresContext()); +} + +nsIFrame* NS_NewSVGRadialGradientFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGRadialGradientFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGLinearGradientFrame) +NS_IMPL_FRAMEARENA_HELPERS(SVGRadialGradientFrame) + +} // namespace mozilla diff --git a/layout/svg/SVGGradientFrame.h b/layout/svg/SVGGradientFrame.h new file mode 100644 index 0000000000..50b78fb06c --- /dev/null +++ b/layout/svg/SVGGradientFrame.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGGRADIENTFRAME_H_ +#define LAYOUT_SVG_SVGGRADIENTFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SVGPaintServerFrame.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsLiteralString.h" + +class gfxPattern; +class nsAtom; +class nsIContent; + +namespace mozilla { +class PresShell; +class SVGAnimatedTransformList; + +namespace dom { +class SVGLinearGradientElement; +class SVGRadialGradientElement; +} // namespace dom +} // namespace mozilla + +nsIFrame* NS_NewSVGLinearGradientFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); +nsIFrame* NS_NewSVGRadialGradientFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGGradientFrame : public SVGPaintServerFrame { + using ExtendMode = gfx::ExtendMode; + + protected: + SVGGradientFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID); + + public: + NS_DECL_ABSTRACT_FRAME(SVGGradientFrame) + NS_DECL_QUERYFRAME + NS_DECL_QUERYFRAME_TARGET(SVGGradientFrame) + + // SVGPaintServerFrame methods: + already_AddRefed<gfxPattern> GetPaintServerPattern( + nsIFrame* aSource, const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, imgDrawingParams& aImgParams, + const gfxRect* aOverrideBounds) override; + + // nsIFrame interface: + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGGradient"_ns, aResult); + } +#endif // DEBUG + + private: + /** + * Parses this frame's href and - if it references another gradient - returns + * it. It also makes this frame a rendering observer of the specified ID. + */ + SVGGradientFrame* GetReferencedGradient(); + + // Optionally get a stop frame (returns stop index/count) + void GetStopFrames(nsTArray<nsIFrame*>* aStopFrames); + + const SVGAnimatedTransformList* 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; + + // 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 dom::SVGLinearGradientElement* GetLinearGradientWithLength( + uint32_t aIndex, dom::SVGLinearGradientElement* aDefault); + virtual dom::SVGRadialGradientElement* GetRadialGradientWithLength( + uint32_t aIndex, 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 SVGLinearGradientFrame final : public SVGGradientFrame { + friend nsIFrame* ::NS_NewSVGLinearGradientFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + protected: + explicit SVGLinearGradientFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : SVGGradientFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGLinearGradientFrame) + + // nsIFrame interface: +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGLinearGradient"_ns, aResult); + } +#endif // DEBUG + + protected: + float GetLengthValue(uint32_t aIndex); + mozilla::dom::SVGLinearGradientElement* GetLinearGradientWithLength( + uint32_t aIndex, + mozilla::dom::SVGLinearGradientElement* aDefault) override; + bool GradientVectorLengthIsZero() override; + already_AddRefed<gfxPattern> CreateGradient() override; +}; + +// ------------------------------------------------------------------------- +// Radial Gradients +// ------------------------------------------------------------------------- + +class SVGRadialGradientFrame final : public SVGGradientFrame { + friend nsIFrame* ::NS_NewSVGRadialGradientFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + protected: + explicit SVGRadialGradientFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : SVGGradientFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGRadialGradientFrame) + + // nsIFrame interface: +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGRadialGradient"_ns, 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); + mozilla::dom::SVGRadialGradientElement* GetRadialGradientWithLength( + uint32_t aIndex, + mozilla::dom::SVGRadialGradientElement* aDefault) override; + bool GradientVectorLengthIsZero() override; + already_AddRefed<gfxPattern> CreateGradient() override; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGGRADIENTFRAME_H_ diff --git a/layout/svg/SVGImageContext.cpp b/layout/svg/SVGImageContext.cpp new file mode 100644 index 0000000000..ff84e9e8c6 --- /dev/null +++ b/layout/svg/SVGImageContext.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGImageContext.h" + +// Keep others in (case-insensitive) order: +#include "gfxUtils.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/StaticPrefs_svg.h" +#include "mozilla/dom/Document.h" +#include "nsIFrame.h" +#include "nsPresContext.h" +#include "nsStyleStruct.h" + +namespace mozilla { + +/* static */ +void SVGImageContext::MaybeStoreContextPaint(SVGImageContext& aContext, + nsIFrame* aFromFrame, + imgIContainer* aImgContainer) { + return MaybeStoreContextPaint(aContext, *aFromFrame->PresContext(), + *aFromFrame->Style(), aImgContainer); +} + +/* static */ +void SVGImageContext::MaybeStoreContextPaint(SVGImageContext& aContext, + const nsPresContext& aPresContext, + const ComputedStyle& aStyle, + imgIContainer* aImgContainer) { + if (aImgContainer->GetType() != imgIContainer::TYPE_VECTOR) { + // Avoid this overhead for raster images. + return; + } + + if (StaticPrefs::svg_embedder_prefers_color_scheme_content_enabled() || + aPresContext.Document()->IsDocumentURISchemeChrome()) { + auto scheme = LookAndFeel::ColorSchemeForStyle( + *aPresContext.Document(), aStyle.StyleUI()->mColorScheme.bits, + ColorSchemeMode::Preferred); + aContext.SetColorScheme(Some(scheme)); + } + + const nsStyleSVG* style = aStyle.StyleSVG(); + if (!style->ExposesContextProperties()) { + // Content must have '-moz-context-properties' set to the names of the + // properties it wants to expose to images it links to. + return; + } + + bool haveContextPaint = false; + + auto contextPaint = MakeRefPtr<SVGEmbeddingContextPaint>(); + + if ((style->mMozContextProperties.bits & StyleContextPropertyBits::FILL) && + style->mFill.kind.IsColor()) { + haveContextPaint = true; + contextPaint->SetFill(style->mFill.kind.AsColor().CalcColor(aStyle)); + } + if ((style->mMozContextProperties.bits & StyleContextPropertyBits::STROKE) && + style->mStroke.kind.IsColor()) { + haveContextPaint = true; + contextPaint->SetStroke(style->mStroke.kind.AsColor().CalcColor(aStyle)); + } + if (style->mMozContextProperties.bits & + StyleContextPropertyBits::FILL_OPACITY) { + haveContextPaint = true; + contextPaint->SetFillOpacity(style->mFillOpacity.IsOpacity() + ? style->mFillOpacity.AsOpacity() + : 1.0f); + } + if (style->mMozContextProperties.bits & + StyleContextPropertyBits::STROKE_OPACITY) { + haveContextPaint = true; + contextPaint->SetStrokeOpacity(style->mStrokeOpacity.IsOpacity() + ? style->mStrokeOpacity.AsOpacity() + : 1.0f); + } + + if (haveContextPaint) { + aContext.mContextPaint = std::move(contextPaint); + } +} + +} // namespace mozilla diff --git a/layout/svg/SVGImageContext.h b/layout/svg/SVGImageContext.h new file mode 100644 index 0000000000..e40e41d16b --- /dev/null +++ b/layout/svg/SVGImageContext.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGIMAGECONTEXT_H_ +#define LAYOUT_SVG_SVGIMAGECONTEXT_H_ + +#include "mozilla/Maybe.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGPreserveAspectRatio.h" +#include "Units.h" + +class nsIFrame; + +namespace mozilla { + +enum class ColorScheme : uint8_t; +class ComputedStyle; + +// SVG image-specific rendering context. For imgIContainer::Draw. +// Used to pass information such as +// - viewport and color-scheme 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() = default; + + /** + * Currently it seems that the aViewportSize parameter ends up being used + * for different things by different pieces of code, and probably in some + * cases being used incorrectly (specifically in the case of pixel snapping + * under the nsLayoutUtils::Draw*Image() methods). An unfortunate result of + * the messy code is that aViewportSize is currently a Maybe<T> since it + * is difficult to create a utility function that consumers can use up + * front to get the "correct" viewport size (i.e. which for compatibility + * with the current code (bugs and all) would mean the size including all + * the snapping and resizing magic that happens in various places under the + * nsLayoutUtils::Draw*Image() methods on the way to DrawImageInternal + * creating |fallbackContext|). Using Maybe<T> allows code to pass Nothing() + * in order to get the size that's created for |fallbackContext|. At some + * point we need to clean this code up, make our abstractions clear, create + * that utility and stop using Maybe for this parameter. + */ + explicit SVGImageContext( + const Maybe<CSSIntSize>& aViewportSize, + const Maybe<SVGPreserveAspectRatio>& aPreserveAspectRatio = Nothing(), + const Maybe<ColorScheme>& aColorScheme = Nothing()) + : mViewportSize(aViewportSize), + mPreserveAspectRatio(aPreserveAspectRatio), + mColorScheme(aColorScheme) {} + + static void MaybeStoreContextPaint(SVGImageContext& aContext, + nsIFrame* aFromFrame, + imgIContainer* aImgContainer); + + static void MaybeStoreContextPaint(SVGImageContext& aContext, + const nsPresContext&, const ComputedStyle&, + imgIContainer*); + + const Maybe<CSSIntSize>& GetViewportSize() const { return mViewportSize; } + + void SetViewportSize(const Maybe<CSSIntSize>& aSize) { + mViewportSize = aSize; + } + + const Maybe<ColorScheme>& GetColorScheme() const { return mColorScheme; } + + void SetColorScheme(const Maybe<ColorScheme>& aScheme) { + mColorScheme = aScheme; + } + + const Maybe<SVGPreserveAspectRatio>& GetPreserveAspectRatio() const { + return mPreserveAspectRatio; + } + + void SetPreserveAspectRatio(const Maybe<SVGPreserveAspectRatio>& aPAR) { + mPreserveAspectRatio = aPAR; + } + + const SVGEmbeddingContextPaint* GetContextPaint() const { + return mContextPaint.get(); + } + + void ClearContextPaint() { mContextPaint = nullptr; } + + bool operator==(const SVGImageContext& aOther) const { + bool contextPaintIsEqual = + // neither have context paint, or they have the same object: + (mContextPaint == aOther.mContextPaint) || + // or both have context paint that are different but equivalent objects: + (mContextPaint && aOther.mContextPaint && + *mContextPaint == *aOther.mContextPaint); + + return contextPaintIsEqual && mViewportSize == aOther.mViewportSize && + mPreserveAspectRatio == aOther.mPreserveAspectRatio && + mColorScheme == aOther.mColorScheme; + } + + bool operator!=(const SVGImageContext& aOther) const { + return !(*this == aOther); + } + + PLDHashNumber Hash() const { + PLDHashNumber hash = 0; + if (mContextPaint) { + hash = HashGeneric(hash, mContextPaint->Hash()); + } + return HashGeneric(hash, mViewportSize.map(HashSize).valueOr(0), + mPreserveAspectRatio.map(HashPAR).valueOr(0), + mColorScheme.map(HashColorScheme).valueOr(0)); + } + + private: + static PLDHashNumber HashSize(const CSSIntSize& aSize) { + return HashGeneric(aSize.width, aSize.height); + } + static PLDHashNumber HashPAR(const SVGPreserveAspectRatio& aPAR) { + return aPAR.Hash(); + } + static PLDHashNumber HashColorScheme(ColorScheme aScheme) { + return HashGeneric(uint8_t(aScheme)); + } + + // NOTE: When adding new member-vars, remember to update Hash() & operator==. + RefPtr<SVGEmbeddingContextPaint> mContextPaint; + Maybe<CSSIntSize> mViewportSize; + Maybe<SVGPreserveAspectRatio> mPreserveAspectRatio; + Maybe<ColorScheme> mColorScheme; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGIMAGECONTEXT_H_ diff --git a/layout/svg/SVGImageFrame.cpp b/layout/svg/SVGImageFrame.cpp new file mode 100644 index 0000000000..67975ac122 --- /dev/null +++ b/layout/svg/SVGImageFrame.cpp @@ -0,0 +1,880 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGImageFrame.h" + +// Keep in (case-insensitive) order: +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/image/WebRenderImageProvider.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "imgIContainer.h" +#include "ImageRegion.h" +#include "nsContainerFrame.h" +#include "nsIImageLoadingContent.h" +#include "nsLayoutUtils.h" +#include "imgINotificationObserver.h" +#include "SVGGeometryProperty.h" +#include "SVGGeometryFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_image.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGImageContext.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/SVGImageElement.h" +#include "nsIReflowCallback.h" +#include "mozilla/Unused.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; +using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; +namespace SVGT = SVGGeometryProperty::Tags; + +namespace mozilla { + +class SVGImageListener final : public imgINotificationObserver { + public: + explicit SVGImageListener(SVGImageFrame* aFrame); + + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + + void SetFrame(SVGImageFrame* frame) { mFrame = frame; } + + private: + ~SVGImageListener() = default; + + SVGImageFrame* mFrame; +}; + +// --------------------------------------------------------------------- +// nsQueryFrame methods +NS_QUERYFRAME_HEAD(SVGImageFrame) + NS_QUERYFRAME_ENTRY(SVGImageFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGGeometryFrame) + +} // namespace mozilla + +nsIFrame* NS_NewSVGImageFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGImageFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGImageFrame) + +SVGImageFrame::~SVGImageFrame() { + // set the frame to null so we don't send messages to a dead object. + if (mListener) { + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(GetContent()); + if (imageLoader) { + imageLoader->RemoveNativeObserver(mListener); + } + reinterpret_cast<SVGImageListener*>(mListener.get())->SetFrame(nullptr); + } + mListener = nullptr; +} + +void SVGImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image), + "Content is not an SVG image!"); + + SVGGeometryFrame::Init(aContent, aParent, aPrevInFlow); + + if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + // Non-display frames are likely to be patterns, masks or the like. + // Treat them as always visible. + // This call must happen before the FrameCreated. This is because the + // primary frame pointer on our content node isn't set until after this + // function ends, so there is no way for the resulting OnVisibilityChange + // notification to get a frame. FrameCreated has a workaround for this in + // that it passes our frame around so it can be accessed. OnVisibilityChange + // doesn't have that workaround. + IncApproximateVisibleCount(); + } + + mListener = new SVGImageListener(this); + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(GetContent()); + if (!imageLoader) { + MOZ_CRASH("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->AddNativeObserver(mListener); +} + +/* virtual */ +void SVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + DecApproximateVisibleCount(); + } + + if (mReflowCallbackPosted) { + PresShell()->CancelReflowCallback(this); + mReflowCallbackPosted = false; + } + + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(nsIFrame::mContent); + + if (imageLoader) { + imageLoader->FrameDestroyed(this); + } + + nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +/* virtual */ +void SVGImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { + SVGGeometryFrame::DidSetComputedStyle(aOldStyle); + + if (!mImageContainer || !aOldStyle) { + return; + } + + nsCOMPtr<imgIRequest> currentRequest; + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(GetContent()); + if (imageLoader) { + imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + } + + StyleImageOrientation newOrientation = + StyleVisibility()->UsedImageOrientation(currentRequest); + StyleImageOrientation oldOrientation = + aOldStyle->StyleVisibility()->UsedImageOrientation(currentRequest); + + if (oldOrientation != newOrientation) { + nsCOMPtr<imgIContainer> image(mImageContainer->Unwrap()); + mImageContainer = nsLayoutUtils::OrientImage(image, newOrientation); + } + + // TODO(heycam): We should handle aspect-ratio, like nsImageFrame does. +} + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult SVGImageFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None) { + 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; + } + } + + // Currently our SMIL implementation does not modify the DOM attributes. Once + // we implement the SVG 2 SMIL behaviour this can be removed + // SVGImageElement::AfterSetAttr's implementation will be sufficient. + if (aModType == MutationEvent_Binding::SMIL && + aAttribute == nsGkAtoms::href && + (aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None)) { + SVGImageElement* element = static_cast<SVGImageElement*>(GetContent()); + + bool hrefIsSet = + element->mStringAttributes[SVGImageElement::HREF].IsExplicitlySet() || + element->mStringAttributes[SVGImageElement::XLINK_HREF] + .IsExplicitlySet(); + if (hrefIsSet) { + element->LoadSVGImage(true, true); + } else { + element->CancelImageRequests(true); + } + } + + return SVGGeometryFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +void SVGImageFrame::OnVisibilityChange( + Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) { + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(GetContent()); + if (!imageLoader) { + SVGGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); + return; + } + + imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); + + SVGGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); +} + +gfx::Matrix SVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth, + int32_t aNativeHeight) { + float x, y, width, height; + SVGImageElement* element = static_cast<SVGImageElement*>(GetContent()); + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + element, &x, &y, &width, &height); + + Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform( + width, height, 0, 0, aNativeWidth, aNativeHeight, + element->mPreserveAspectRatio); + + return viewBoxTM * gfx::Matrix::Translation(x, y); +} + +gfx::Matrix SVGImageFrame::GetVectorImageTransform() { + float x, y; + SVGImageElement* element = static_cast<SVGImageElement*>(GetContent()); + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(element, &x, &y); + + // 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 SVGImageFrame::GetIntrinsicImageDimensions( + mozilla::gfx::Size& aSize, mozilla::AspectRatio& aAspectRatio) const { + if (!mImageContainer) { + return false; + } + + ImageResolution resolution = mImageContainer->GetResolution(); + + int32_t width, height; + if (NS_FAILED(mImageContainer->GetWidth(&width))) { + aSize.width = -1; + } else { + aSize.width = width; + resolution.ApplyXTo(aSize.width); + } + + if (NS_FAILED(mImageContainer->GetHeight(&height))) { + aSize.height = -1; + } else { + aSize.height = height; + resolution.ApplyYTo(aSize.height); + } + + Maybe<AspectRatio> asp = mImageContainer->GetIntrinsicRatio(); + aAspectRatio = asp.valueOr(AspectRatio{}); + + return true; +} + +bool SVGImageFrame::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; + } + mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight); + 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; +} + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods: +void SVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect) { + if (!StyleVisibility()->IsVisible()) { + return; + } + + float x, y, width, height; + SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent()); + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + imgElem, &x, &y, &width, &height); + 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(GetContent()); + if (imageLoader) + imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + + if (currentRequest) + currentRequest->GetImage(getter_AddRefs(mImageContainer)); + } + + if (mImageContainer) { + gfxContextAutoSaveRestore autoRestorer(&aContext); + + if (StyleDisplay()->IsScrollableOverflow()) { + gfxRect clipRect = + SVGUtils::GetClipRectForFrame(this, x, y, width, height); + SVGUtils::SetClipRect(&aContext, aTransform, clipRect); + } + + if (!TransformContextForPainting(&aContext, aTransform)) { + return; + } + + // 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 (SVGUtils::CanOptimizeOpacity(this)) { + opacity = StyleEffects()->mOpacity; + } + + if (opacity != 1.0f || + StyleEffects()->mMixBlendMode != StyleBlend::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); + + // dirtyRect is relative to the outer <svg>, we should transform it + // down to <image>. + Rect dir(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); + dir.Scale(1.f / AppUnitsPerCSSPixel()); + + // FIXME: This isn't correct if there is an inner <svg> enclosing + // the <image>. But that seems to be a quite obscure usecase, we can + // add a dedicated utility for that purpose to replace the GetCTM + // here if necessary. + auto mat = SVGContentUtils::GetCTM( + static_cast<SVGImageElement*>(GetContent()), false); + if (mat.IsSingular()) { + return; + } + + mat.Invert(); + dir = mat.TransformRect(dir); + + // x, y offset of <image> is not included in CTM. + dir.MoveBy(-x, -y); + + dir.Scale(AppUnitsPerCSSPixel()); + dir.Round(); + dirtyRect = nsRect(dir.x, dir.y, dir.width, dir.height); + } + + uint32_t flags = aImgParams.imageFlags; + if (mForceSyncDecoding) { + flags |= 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. + const SVGImageContext context( + Some(CSSIntSize::Truncate(width, height)), + Some(imgElem->mPreserveAspectRatio.GetAnimValue())); + + // 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. + aImgParams.result &= nsLayoutUtils::DrawSingleImage( + aContext, PresContext(), mImageContainer, + nsLayoutUtils::GetSamplingFilterForFrame(this), destRect, + aDirtyRect ? dirtyRect : destRect, context, flags); + } else { // mImageContainer->GetType() == TYPE_RASTER + aImgParams.result &= nsLayoutUtils::DrawSingleUnscaledImage( + aContext, PresContext(), mImageContainer, + nsLayoutUtils::GetSamplingFilterForFrame(this), nsPoint(0, 0), + aDirtyRect ? &dirtyRect : nullptr, SVGImageContext(), flags); + } + + if (opacity != 1.0f || + StyleEffects()->mMixBlendMode != StyleBlend::Normal) { + aContext.PopGroupAndBlend(); + } + // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext + } +} + +bool SVGImageFrame::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem, + bool aDryRun) { + if (!StyleVisibility()->IsVisible()) { + return true; + } + + float opacity = 1.0f; + if (SVGUtils::CanOptimizeOpacity(this)) { + opacity = StyleEffects()->mOpacity; + } + + if (opacity != 1.0f) { + // FIXME: not implemented, might be trivial + return false; + } + if (StyleEffects()->mMixBlendMode != StyleBlend::Normal) { + // FIXME: not implemented + return false; + } + + // try to setup the image + if (!mImageContainer) { + nsCOMPtr<imgIRequest> currentRequest; + nsCOMPtr<nsIImageLoadingContent> imageLoader = + do_QueryInterface(GetContent()); + if (imageLoader) { + imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + } + + if (currentRequest) { + currentRequest->GetImage(getter_AddRefs(mImageContainer)); + } + } + + if (!mImageContainer) { + // nothing to draw (yet) + return true; + } + + uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags(); + if (mForceSyncDecoding) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } + + // Compute bounds of the image + nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); + int32_t appUnitsPerCSSPixel = AppUnitsPerCSSPixel(); + + float x, y, width, height; + SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent()); + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + imgElem, &x, &y, &width, &height); + NS_ASSERTION(width > 0 && height > 0, + "Should only be painting things with valid width/height"); + + auto toReferenceFrame = aItem->ToReferenceFrame(); + auto appRect = nsLayoutUtils::RoundGfxRectToAppRect(Rect(0, 0, width, height), + appUnitsPerCSSPixel); + appRect += toReferenceFrame; + auto destRect = LayoutDeviceRect::FromAppUnits(appRect, appUnitsPerDevPx); + auto clipRect = destRect; + + if (StyleDisplay()->IsScrollableOverflow()) { + // Apply potential non-trivial clip + auto cssClip = SVGUtils::GetClipRectForFrame(this, 0, 0, width, height); + auto appClip = + nsLayoutUtils::RoundGfxRectToAppRect(cssClip, appUnitsPerCSSPixel); + appClip += toReferenceFrame; + clipRect = LayoutDeviceRect::FromAppUnits(appClip, appUnitsPerDevPx); + + // Apply preserveAspectRatio + 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) { + // Image has no size; nothing to draw + return true; + } + + mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight); + + auto preserveAspectRatio = imgElem->mPreserveAspectRatio.GetAnimValue(); + uint16_t align = preserveAspectRatio.GetAlign(); + uint16_t meetOrSlice = preserveAspectRatio.GetMeetOrSlice(); + + // default to the defaults + if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) { + align = SVG_PRESERVEASPECTRATIO_XMIDYMID; + } + if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) { + meetOrSlice = SVG_MEETORSLICE_MEET; + } + + // aspect > 1 is horizontal + // aspect < 1 is vertical + float nativeAspect = ((float)nativeWidth) / ((float)nativeHeight); + float viewAspect = width / height; + + // "Meet" is "fit image to view"; "Slice" is "cover view with image". + // + // Whether we meet or slice, one side of the destRect will always be + // perfectly spanned by our image. The only questions to answer are + // "which side won't span perfectly" and "should that side be grown + // or shrunk". + // + // Because we fit our image to the destRect, this all just reduces to: + // "if meet, shrink to fit. if slice, grow to fit." + if (align != SVG_PRESERVEASPECTRATIO_NONE && nativeAspect != viewAspect) { + // Slightly redundant bools, but they make the conditions clearer + bool tooTall = nativeAspect > viewAspect; + bool tooWide = nativeAspect < viewAspect; + if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooTall) || + (meetOrSlice == SVG_MEETORSLICE_SLICE && tooWide)) { + // Adjust height and realign y + auto oldHeight = destRect.height; + destRect.height = destRect.width / nativeAspect; + auto heightChange = oldHeight - destRect.height; + switch (align) { + case SVG_PRESERVEASPECTRATIO_XMINYMIN: + case SVG_PRESERVEASPECTRATIO_XMIDYMIN: + case SVG_PRESERVEASPECTRATIO_XMAXYMIN: + // align to top (no-op) + break; + case SVG_PRESERVEASPECTRATIO_XMINYMID: + case SVG_PRESERVEASPECTRATIO_XMIDYMID: + case SVG_PRESERVEASPECTRATIO_XMAXYMID: + // align to center + destRect.y += heightChange / 2.0f; + break; + case SVG_PRESERVEASPECTRATIO_XMINYMAX: + case SVG_PRESERVEASPECTRATIO_XMIDYMAX: + case SVG_PRESERVEASPECTRATIO_XMAXYMAX: + // align to bottom + destRect.y += heightChange; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooWide) || + (meetOrSlice == SVG_MEETORSLICE_SLICE && tooTall)) { + // Adjust width and realign x + auto oldWidth = destRect.width; + destRect.width = destRect.height * nativeAspect; + auto widthChange = oldWidth - destRect.width; + switch (align) { + case SVG_PRESERVEASPECTRATIO_XMINYMIN: + case SVG_PRESERVEASPECTRATIO_XMINYMID: + case SVG_PRESERVEASPECTRATIO_XMINYMAX: + // align to left (no-op) + break; + case SVG_PRESERVEASPECTRATIO_XMIDYMIN: + case SVG_PRESERVEASPECTRATIO_XMIDYMID: + case SVG_PRESERVEASPECTRATIO_XMIDYMAX: + // align to center + destRect.x += widthChange / 2.0f; + break; + case SVG_PRESERVEASPECTRATIO_XMAXYMIN: + case SVG_PRESERVEASPECTRATIO_XMAXYMID: + case SVG_PRESERVEASPECTRATIO_XMAXYMAX: + // align to right + destRect.x += widthChange; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } + } + } + } + + SVGImageContext svgContext; + if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { + if (StaticPrefs::image_svg_blob_image()) { + flags |= imgIContainer::FLAG_RECORD_BLOB; + } + // Forward preserveAspectRatio to inner SVGs + svgContext.SetViewportSize(Some(CSSIntSize::Truncate(width, height))); + svgContext.SetPreserveAspectRatio( + Some(imgElem->mPreserveAspectRatio.GetAnimValue())); + } + + Maybe<ImageIntRegion> region; + IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters( + mImageContainer, this, destRect, clipRect, aSc, flags, svgContext, + region); + + RefPtr<image::WebRenderImageProvider> provider; + ImgDrawResult drawResult = mImageContainer->GetImageProvider( + aManager->LayerManager(), decodeSize, svgContext, region, flags, + getter_AddRefs(provider)); + + // While we got a container, it may not contain a fully decoded surface. If + // that is the case, and we have an image we were previously displaying which + // has a fully decoded surface, then we should prefer the previous image. + switch (drawResult) { + case ImgDrawResult::NOT_READY: + case ImgDrawResult::TEMPORARY_ERROR: + // nothing to draw (yet) + return true; + case ImgDrawResult::NOT_SUPPORTED: + // things we haven't implemented for WR yet + return false; + default: + // image is ready to draw + break; + } + + // Don't do any actual mutations to state if we're doing a dry run + // (used to decide if we're making this into an active layer) + if (!aDryRun) { + // If the image container is empty, we don't want to fallback. Any other + // failure will be due to resource constraints and fallback is unlikely to + // help us. Hence we can ignore the return value from PushImage. + if (provider) { + aManager->CommandBuilder().PushImageProvider(aItem, provider, drawResult, + aBuilder, aResources, + destRect, clipRect); + } + } + + return true; +} + +nsIFrame* SVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) { + if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) && !GetHitTestFlags()) { + return nullptr; + } + + Rect rect; + SVGImageElement* element = static_cast<SVGImageElement*>(GetContent()); + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + element, &rect.x, &rect.y, &rect.width, &rect.height); + + 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; + } + mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight); + Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform( + rect.width, rect.height, 0, 0, nativeWidth, nativeHeight, + element->mPreserveAspectRatio); + if (!SVGUtils::HitTestRect(viewBoxTM, 0, 0, nativeWidth, nativeHeight, + aPoint.x - rect.x, aPoint.y - rect.y)) { + return nullptr; + } + } + } + + return this; +} + +//---------------------------------------------------------------------- +// SVGGeometryFrame methods: + +// Lie about our fill/stroke so that covered region and hit detection work +// properly + +void SVGImageFrame::ReflowSVG() { + NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!SVGUtils::NeedsReflowSVG(this)) { + return; + } + + float x, y, width, height; + SVGImageElement* element = static_cast<SVGImageElement*>(GetContent()); + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( + element, &x, &y, &width, &height); + + Rect extent(x, y, width, height); + + if (!extent.IsEmpty()) { + mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, 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): + SVGObserverUtils::UpdateEffects(this); + + if (!mReflowCallbackPosted) { + mReflowCallbackPosted = true; + PresShell()->PostReflowCallback(this); + } + } + + nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size()); + OverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + RemoveStateBits(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()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + InvalidateFrame(); + } +} + +bool SVGImageFrame::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 SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; } + +uint16_t SVGImageFrame::GetHitTestFlags() { + uint16_t flags = 0; + + switch (Style()->PointerEvents()) { + case StylePointerEvents::None: + break; + case StylePointerEvents::Visiblepainted: + case StylePointerEvents::Auto: + if (StyleVisibility()->IsVisible()) { + /* XXX: should check pixel transparency */ + flags |= SVG_HIT_TEST_FILL; + } + break; + case StylePointerEvents::Visiblefill: + case StylePointerEvents::Visiblestroke: + case StylePointerEvents::Visible: + if (StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL; + } + break; + case StylePointerEvents::Painted: + /* XXX: should check pixel transparency */ + flags |= SVG_HIT_TEST_FILL; + break; + case StylePointerEvents::Fill: + case StylePointerEvents::Stroke: + case StylePointerEvents::All: + flags |= SVG_HIT_TEST_FILL; + break; + default: + NS_ERROR("not reached"); + break; + } + + return flags; +} + +//---------------------------------------------------------------------- +// SVGImageListener implementation + +NS_IMPL_ISUPPORTS(SVGImageListener, imgINotificationObserver) + +SVGImageListener::SVGImageListener(SVGImageFrame* aFrame) : mFrame(aFrame) {} + +void SVGImageListener::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) { + if (!mFrame) { + return; + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + mFrame->InvalidateFrame(); + nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(), + RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(mFrame); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + // No new dimensions, so we don't need to call + // SVGUtils::InvalidateAndScheduleBoundsUpdate. + nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(), + RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + mFrame->InvalidateFrame(); + } + + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + // Called once the resource's dimensions have been obtained. + nsCOMPtr<imgIContainer> image; + aRequest->GetImage(getter_AddRefs(image)); + if (image) { + StyleImageOrientation orientation = + mFrame->StyleVisibility()->UsedImageOrientation(aRequest); + image = nsLayoutUtils::OrientImage(image, orientation); + image->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode()); + mFrame->mImageContainer = std::move(image); + } + mFrame->InvalidateFrame(); + nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(), + RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(mFrame); + } +} + +} // namespace mozilla diff --git a/layout/svg/SVGImageFrame.h b/layout/svg/SVGImageFrame.h new file mode 100644 index 0000000000..372f339778 --- /dev/null +++ b/layout/svg/SVGImageFrame.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGIMAGEFRAME_H_ +#define LAYOUT_SVG_SVGIMAGEFRAME_H_ + +// Keep in (case-insensitive) order: +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "imgIContainer.h" +#include "nsContainerFrame.h" +#include "imgINotificationObserver.h" +#include "mozilla/SVGGeometryFrame.h" +#include "nsIReflowCallback.h" +#include "mozilla/Unused.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGImageFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGImageFrame final : public SVGGeometryFrame, public nsIReflowCallback { + friend nsIFrame* ::NS_NewSVGImageFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + bool CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + const layers::StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + DisplaySVGGeometry* aItem, + bool aDryRun) override; + + protected: + explicit SVGImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGGeometryFrame(aStyle, aPresContext, kClassID), + mReflowCallbackPosted(false), + mForceSyncDecoding(false) { + EnableVisibilityTracking(); + } + + virtual ~SVGImageFrame(); + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGImageFrame) + + // ISVGDisplayableFrame interface: + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + void ReflowSVG() override; + + // SVGGeometryFrame methods: + uint16_t GetHitTestFlags() override; + + // nsIFrame interface: + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + void OnVisibilityChange( + Visibility aNewVisibility, + const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override; + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + void DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) override; + void DidSetComputedStyle(ComputedStyle* aOldStyle) final; + + bool GetIntrinsicImageDimensions(gfx::Size& aSize, + AspectRatio& aAspectRatio) const; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGImage"_ns, aResult); + } +#endif + + // nsIReflowCallback + bool ReflowFinished() override; + void ReflowCallbackCanceled() override; + + /// Always sync decode our image when painting if @aForce is true. + void SetForceSyncDecoding(bool aForce) { mForceSyncDecoding = aForce; } + + 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; + bool mForceSyncDecoding; + + friend class SVGImageListener; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGIMAGEFRAME_H_ diff --git a/layout/svg/SVGInnerSVGFrame.cpp b/layout/svg/SVGInnerSVGFrame.cpp new file mode 100644 index 0000000000..0a547ccc94 --- /dev/null +++ b/layout/svg/SVGInnerSVGFrame.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGInnerSVGFrame.h" + +#include "mozilla/PresShell.h" + +nsIFrame* NS_NewSVGInnerSVGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGInnerSVGFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGInnerSVGFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods + +NS_QUERYFRAME_HEAD(SVGInnerSVGFrame) + NS_QUERYFRAME_ENTRY(SVGInnerSVGFrame) + NS_QUERYFRAME_ENTRY(ISVGSVGFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGViewportFrame) + +#ifdef DEBUG +void SVGInnerSVGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), + "Content is not an SVG 'svg' element!"); + + SVGViewportFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +} // namespace mozilla diff --git a/layout/svg/SVGInnerSVGFrame.h b/layout/svg/SVGInnerSVGFrame.h new file mode 100644 index 0000000000..1db386f344 --- /dev/null +++ b/layout/svg/SVGInnerSVGFrame.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGINNERSVGFRAME_H_ +#define LAYOUT_SVG_SVGINNERSVGFRAME_H_ + +#include "SVGViewportFrame.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGInnerSVGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGInnerSVGFrame final : public SVGViewportFrame { + friend nsIFrame* ::NS_NewSVGInnerSVGFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGInnerSVGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGViewportFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGInnerSVGFrame) + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGInnerSVG"_ns, aResult); + } +#endif +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGINNERSVGFRAME_H_ diff --git a/layout/svg/SVGIntegrationUtils.cpp b/layout/svg/SVGIntegrationUtils.cpp new file mode 100644 index 0000000000..7e4a29c424 --- /dev/null +++ b/layout/svg/SVGIntegrationUtils.cpp @@ -0,0 +1,1256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGIntegrationUtils.h" + +// Keep others in (case-insensitive) order: +#include "gfxDrawable.h" + +#include "nsCSSAnonBoxes.h" +#include "nsCSSRendering.h" +#include "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "gfxContext.h" +#include "SVGPaintServerFrame.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/CSSClipPathInstance.h" +#include "mozilla/FilterInstance.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/SVGElement.h" + +using namespace mozilla::dom; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +/** + * This class is used to get the pre-effects ink overflow rect of a frame, + * or, in the case of a frame with continuations, to collect the union of the + * pre-effects ink 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 PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback { + public: + /** + * If the pre-effects ink overflow rect of the frame being examined + * happens to be known, it can be passed in as aCurrentFrame and its + * pre-effects ink overflow rect can be passed in as + * aCurrentFrameOverflowArea. This is just an optimization to save a + * frame property lookup - these arguments are optional. + */ + PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation, + nsIFrame* aCurrentFrame, + const nsRect& aCurrentFrameOverflowArea, + bool aInReflow) + : mFirstContinuation(aFirstContinuation), + mCurrentFrame(aCurrentFrame), + mCurrentFrameOverflowArea(aCurrentFrameOverflowArea), + mInReflow(aInReflow) { + NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(), + "We want the first continuation here"); + } + + void AddBox(nsIFrame* aFrame) override { + nsRect overflow = (aFrame == mCurrentFrame) + ? mCurrentFrameOverflowArea + : PreEffectsInkOverflowRect(aFrame, mInReflow); + mResult.UnionRect(mResult, + overflow + aFrame->GetOffsetTo(mFirstContinuation)); + } + + nsRect GetResult() const { return mResult; } + + private: + static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) { + nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty()); + if (r) { + return *r; + } + +#ifdef DEBUG + // Having PreTransformOverflowAreasProperty cached means + // InkOverflowRect() will return post-effect rect, which is not what + // we want. This function intentional reports pre-effect rect. But it does + // not matter if there is no SVG effect on this frame, since no effect + // means post-effect rect matches pre-effect rect. + // + // This function may be called during reflow or painting. We should only + // do this check in painting process since the PreEffectsBBoxProperty of + // continuations are not set correctly while reflowing. + if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) && + !aInReflow) { + OverflowAreas* preTransformOverflows = + aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty()); + + MOZ_ASSERT(!preTransformOverflows, + "InkOverflowRect() won't return the pre-effects rect!"); + } +#endif + return aFrame->InkOverflowRectRelativeToSelf(); + } + + nsIFrame* mFirstContinuation; + nsIFrame* mCurrentFrame; + const nsRect& mCurrentFrameOverflowArea; + nsRect mResult; + bool mInReflow; +}; + +/** + * Gets the union of the pre-effects ink overflow rects of all of a frame's + * continuations, in "user space". + */ +static nsRect GetPreEffectsInkOverflowUnion( + nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame, + const nsRect& aCurrentFramePreEffectsOverflow, + const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) { + NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), + "Need first continuation here"); + PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame, + aCurrentFramePreEffectsOverflow, + aInReflow); + // Compute union of all overflow areas relative to aFirstContinuation: + nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector); + // Return the result in user space: + return collector.GetResult() + aFirstContinuationToUserSpace; +} + +/** + * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space". + */ +static nsRect GetPreEffectsInkOverflow( + nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame, + const nsPoint& aFirstContinuationToUserSpace) { + NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), + "Need first continuation here"); + PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr, + nsRect(), false); + // Compute overflow areas of current frame relative to aFirstContinuation: + nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector); + // Return the result in user space: + return collector.GetResult() + aFirstContinuationToUserSpace; +} + +bool SVGIntegrationUtils::UsingOverflowAffectingEffects( + const nsIFrame* aFrame) { + // Currently overflow don't take account of SVG or other non-absolute + // positioned clipping, or masking. + return aFrame->StyleEffects()->HasFilters(); +} + +bool SVGIntegrationUtils::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(); + const nsStyleEffects* effects = aFrame->StyleEffects(); + // TODO(cbrewster): remove backdrop-filter from this list once it is supported + // in preserve-3d cases. + return effects->HasFilters() || effects->HasBackdropFilters() || + style->HasClipPath() || style->HasMask(); +} + +bool SVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) { + const nsStyleSVGReset* style = aFrame->StyleSVGReset(); + return style->HasClipPath() || style->HasMask(); +} + +bool SVGIntegrationUtils::UsingSimpleClipPathForFrame(const nsIFrame* aFrame) { + const nsStyleSVGReset* style = aFrame->StyleSVGReset(); + if (!style->HasClipPath() || style->HasMask()) { + return false; + } + + const auto& clipPath = style->mClipPath; + if (!clipPath.IsShape()) { + return false; + } + + return !clipPath.AsShape()._0->IsPolygon(); +} + +nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) { + if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the + // covered region relative to the SVGOuterSVGFrame, 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(); +} + +struct EffectOffsets { + // The offset between the reference frame and the bounding box of the + // target frame in app unit. + nsPoint offsetToBoundingBox; + // The offset between the reference frame and the bounding box of the + // target frame in app unit. + nsPoint offsetToUserSpace; + // The offset between the reference frame and the bounding box of the + // target frame in device unit. + gfxPoint offsetToUserSpaceInDevPx; +}; + +static EffectOffsets ComputeEffectOffset( + nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) { + EffectOffsets result; + + result.offsetToBoundingBox = + aParams.builder->ToReferenceFrame(aFrame) - + SVGIntegrationUtils::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. */ + result.offsetToBoundingBox = + nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels( + result.offsetToBoundingBox.x), + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels( + result.offsetToBoundingBox.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 aParams.ctx 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 = + SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); + nsPoint toUserSpace = + nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), + nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); + + result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace; + +#ifdef DEBUG + bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); + NS_ASSERTION( + hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace, + "For non-SVG frames there shouldn't be any additional offset"); +#endif + + result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint( + result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel()); + + return result; +} + +/** + * Setup transform matrix of a gfx context by a specific frame. Move the + * origin of aParams.ctx to the user space of aFrame. + */ +static EffectOffsets MoveContextOriginToUserSpace( + nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) { + EffectOffsets offset = ComputeEffectOffset(aFrame, aParams); + + aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate( + offset.offsetToUserSpaceInDevPx)); + + return offset; +} + +gfxPoint SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx( + nsIFrame* aFrame, const PaintFramesParams& aParams) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + EffectOffsets offset = ComputeEffectOffset(firstFrame, aParams); + return offset.offsetToUserSpaceInDevPx; +} + +/* static */ +nsSize SVGIntegrationUtils::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 SVGIntegrationUtils::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); + return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width), + nsPresContext::AppUnitsToFloatCSSPixels(r.height)); +} + +gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame( + nsIFrame* aNonSVGFrame, bool aUnionContinuations) { + // Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG + // frames at all. This function is for elements that are laid out using the + // CSS box model rules. + NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), + "Frames with SVG layout should not get here"); + MOZ_ASSERT(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG) || + aNonSVGFrame->IsSVGOuterSVGFrame()); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + // 'r' is in "user space": + nsRect r = (aUnionContinuations) + ? GetPreEffectsInkOverflowUnion( + firstFrame, nullptr, nsRect(), + GetOffsetToBoundingBox(firstFrame), false) + : GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame, + GetOffsetToBoundingBox(firstFrame)); + + return nsLayoutUtils::RectToGfxRect(r, 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 GetPreEffectsInkOverflowUnion() calls will +// obtain valid border boxes for all the continuations. As a result, we'll +// end up returning bogus post-filter ink 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 +// GetPreEffectsInkOverflowUnion 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 ink 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 ink 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 SVGIntegrationUtils::ComputePostEffectsInkOverflowRect( + nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) { + MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), + "Don't call this on SVG child frames"); + + MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(), + "We should only be called if the frame is filtered, since filters " + "are the only effect that affects overflow."); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + // Note: we do not return here for eHasNoRefs since we must still handle any + // CSS filter functions. + // TODO: We currently pass nullptr instead of an nsTArray* here, but we + // actually should get the filter frames and then pass them into + // GetPostFilterBounds below! See bug 1494263. + // TODO: we should really return an empty rect for eHasRefsSomeInvalid since + // in that case we disable painting of the element. + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + 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( + GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect, + firstFrameToBoundingBox, true), + AppUnitsPerCSSPixel()); + overrideBBox.RoundOut(); + + Maybe<nsRect> overflowRect = + FilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox); + if (!overflowRect) { + return aPreEffectsOverflowRect; + } + + // Return overflowRect relative to aFrame, rather than "user space": + return overflowRect.value() - + (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox); +} + +nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea( + nsIFrame* aFrame, const nsRect& aDirtyRect) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + + // If we have any filters to observe then we should have started doing that + // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving + // here to avoid needless work (or masking bugs by setting up observers at + // the wrong time). + if (!aFrame->StyleEffects()->HasFilters() || + SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + 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 FilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect) + .GetBounds() - + toUserSpace; +} + +bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, + const nsPoint& aPt) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + // Convert aPt to user space: + nsPoint toUserSpace; + if (aFrame->HasAnyStateBits(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) / AppUnitsPerCSSPixel(); + return SVGUtils::HitTestClip(firstFrame, userSpacePt); +} + +using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams; + +/** + * Paint css-positioned-mask onto a given target(aMaskDT). + * Return value indicates if mask is complete. + */ +static bool PaintMaskSurface(const PaintFramesParams& aParams, + DrawTarget* aMaskDT, float aOpacity, + const ComputedStyle* aSC, + const nsTArray<SVGMaskFrame*>& aMaskFrames, + const nsPoint& aOffsetToUserSpace) { + MOZ_ASSERT(aMaskFrames.Length() > 0); + MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8); + MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1); + + const nsStyleSVGReset* svgReset = aSC->StyleSVGReset(); + gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame); + + nsPresContext* presContext = aParams.frame->PresContext(); + gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint( + aOffsetToUserSpace, presContext->AppUnitsPerDevPixel()); + + RefPtr<gfxContext> maskContext = + gfxContext::CreatePreservingTransformOrNull(aMaskDT); + MOZ_ASSERT(maskContext); + + bool isMaskComplete = true; + + // 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--) { + SVGMaskFrame* 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) { + SVGMaskFrame::MaskParams params( + maskContext->GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, + aOpacity, svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams); + RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params); + if (svgMask) { + Matrix tmp = aMaskDT->GetTransform(); + aMaskDT->SetTransform(Matrix()); + aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)), + svgMask, Point(0, 0), + DrawOptions(1.0, compositionOp)); + aMaskDT->SetTransform(tmp); + } + } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) { + gfxContextMatrixAutoSaveRestore matRestore(maskContext); + + maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace)); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *presContext, aParams.dirtyRect, aParams.borderArea, + aParams.frame, + aParams.builder->GetBackgroundPaintFlags() | + nsCSSRendering::PAINTBG_MASK_IMAGE, + i, compositionOp, aOpacity); + + aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC( + params, *maskContext, aSC, *aParams.frame->StyleBorder()); + } else { + isMaskComplete = false; + } + } + + return isMaskComplete; +} + +struct MaskPaintResult { + RefPtr<SourceSurface> maskSurface; + Matrix maskTransform; + bool transparentBlackMask; + bool opacityApplied; + + MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {} +}; + +static MaskPaintResult CreateAndPaintMaskSurface( + const PaintFramesParams& aParams, float aOpacity, const ComputedStyle* aSC, + const nsTArray<SVGMaskFrame*>& aMaskFrames, + const nsPoint& aOffsetToUserSpace) { + const nsStyleSVGReset* svgReset = aSC->StyleSVGReset(); + MOZ_ASSERT(aMaskFrames.Length() > 0); + MaskPaintResult paintResult; + + gfxContext& ctx = aParams.ctx; + + // Optimization for single SVG mask. + if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) { + gfxMatrix cssPxToDevPxMatrix = + SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame); + paintResult.opacityApplied = true; + SVGMaskFrame::MaskParams params( + ctx.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, aOpacity, + svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams); + paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params); + paintResult.maskTransform = ctx.CurrentMatrix(); + paintResult.maskTransform.Invert(); + if (!paintResult.maskSurface) { + paintResult.transparentBlackMask = true; + } + + return paintResult; + } + + const LayoutDeviceRect& maskSurfaceRect = + aParams.maskRect.valueOr(LayoutDeviceRect()); + if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) { + // XXX: Is this ever true? + paintResult.transparentBlackMask = true; + return paintResult; + } + + RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget( + maskSurfaceRect.ToUnknownRect(), SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return paintResult; + } + + // We can paint mask along with opacity only if + // 1. There is only one mask, or + // 2. No overlap among masks. + // Collision detect in #2 is not that trivial, we only accept #1 here. + paintResult.opacityApplied = (aMaskFrames.Length() == 1); + + // Set context's matrix on maskContext, offset by the maskSurfaceRect's + // position. This makes sure that we combine the masks in device space. + Matrix maskSurfaceMatrix = ctx.CurrentMatrix(); + + bool isMaskComplete = PaintMaskSurface( + aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC, + aMaskFrames, aOffsetToUserSpace); + + if (!isMaskComplete || + (aParams.imgParams.result != ImgDrawResult::SUCCESS && + aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE && + aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) { + // Now we know the status of mask resource since we used it while painting. + // According to the return value of PaintMaskSurface, we know whether mask + // resource is resolvable or not. + // + // 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>. + // Set paintResult.transparentBlackMask as true, the caller should stop + // painting masked content as if this mask is a transparent black one. + // For a SVG doc: + // SVG 1.1 say that if we fail to resolve a mask, we should draw the + // object unmasked. + // Left paintResult.maskSurface empty, the caller should paint all + // masked content as if this mask is an opaque white one(no mask). + paintResult.transparentBlackMask = + !aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); + + MOZ_ASSERT(!paintResult.maskSurface); + return paintResult; + } + + paintResult.maskTransform = maskSurfaceMatrix; + if (!paintResult.maskTransform.Invert()) { + return paintResult; + } + + paintResult.maskSurface = maskDT->Snapshot(); + return paintResult; +} + +static bool ValidateSVGFrame(nsIFrame* aFrame) { +#ifdef DEBUG + NS_ASSERTION(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) || + (NS_SVGDisplayListPaintingEnabled() && + !aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)), + "Should not use SVGIntegrationUtils on this SVG frame"); +#endif + + bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout) { +#ifdef DEBUG + ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); + MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(), + "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?"); +#endif + + const nsIContent* content = aFrame->GetContent(); + if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) { + // The SVG spec says not to draw _anything_ + return false; + } + } + + return true; +} + +class AutoPopGroup { + public: + AutoPopGroup() : mContext(nullptr) {} + + ~AutoPopGroup() { + if (mContext) { + mContext->PopGroupAndBlend(); + } + } + + void SetContext(gfxContext* aContext) { mContext = aContext; } + + private: + gfxContext* mContext; +}; + +bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams, + bool& aOutIsMaskComplete) { + aOutIsMaskComplete = true; + + SVGUtils::MaskUsage maskUsage; + SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage); + if (!maskUsage.shouldDoSomething()) { + return false; + } + + nsIFrame* frame = aParams.frame; + if (!ValidateSVGFrame(frame)) { + return false; + } + + gfxContext& ctx = aParams.ctx; + RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget(); + + if (maskUsage.shouldGenerateMaskLayer && + (maskUsage.shouldGenerateClipMaskLayer || + maskUsage.shouldApplyClipPath)) { + // We will paint both mask of positioned mask and clip-path into + // maskTarget. + // + // Create one extra draw target for drawing positioned mask, so that we do + // not have to copy the content of maskTarget before painting + // clip-path into it. + maskTarget = maskTarget->CreateClippedDrawTarget(Rect(), SurfaceFormat::A8); + } + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsTArray<SVGMaskFrame*> maskFrames; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); + + AutoPopGroup autoPop; + bool shouldPushOpacity = + (maskUsage.opacity != 1.0) && (maskFrames.Length() != 1); + if (shouldPushOpacity) { + ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity); + autoPop.SetContext(&ctx); + } + + gfxContextMatrixAutoSaveRestore matSR; + + // Paint clip-path-basic-shape onto ctx + gfxContextAutoSaveRestore basicShapeSR; + if (maskUsage.shouldApplyBasicShapeOrPath) { + matSR.SetContext(&ctx); + + MoveContextOriginToUserSpace(firstFrame, aParams); + + basicShapeSR.SetContext(&ctx); + gfxMatrix mat = SVGUtils::GetCSSPxToDevPxMatrix(frame); + if (!maskUsage.shouldGenerateMaskLayer) { + // Only have basic-shape clip-path effect. Fill clipped region by + // opaque white. + ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite()); + RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame( + ctx.GetDrawTarget(), frame, mat); + if (path) { + ctx.SetPath(path); + ctx.Fill(); + } + + return true; + } + CSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame, mat); + } + + // Paint mask into maskTarget. + if (maskUsage.shouldGenerateMaskLayer) { + matSR.Restore(); + matSR.SetContext(&ctx); + + EffectOffsets offsets = ComputeEffectOffset(frame, aParams); + maskTarget->SetTransform(maskTarget->GetTransform().PreTranslate( + ToPoint(offsets.offsetToUserSpaceInDevPx))); + aOutIsMaskComplete = PaintMaskSurface( + aParams, maskTarget, shouldPushOpacity ? 1.0 : maskUsage.opacity, + firstFrame->Style(), maskFrames, offsets.offsetToUserSpace); + } + + // Paint clip-path onto ctx. + if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) { + matSR.Restore(); + matSR.SetContext(&ctx); + + MoveContextOriginToUserSpace(firstFrame, aParams); + Matrix clipMaskTransform; + gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame); + + SVGClipPathFrame* clipPathFrame; + // XXX check return value? + SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); + RefPtr<SourceSurface> maskSurface = + maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr; + clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface); + } + + return true; +} + +template <class T> +void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams, + const T& aPaintChild) { + MOZ_ASSERT(SVGIntegrationUtils::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; + if (!ValidateSVGFrame(frame)) { + return; + } + + SVGUtils::MaskUsage maskUsage; + SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage); + + if (maskUsage.opacity == 0.0f) { + return; + } + + gfxContext& context = aParams.ctx; + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + + SVGClipPathFrame* clipPathFrame; + // XXX check return value? + SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); + + nsTArray<SVGMaskFrame*> maskFrames; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); + + gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame); + + bool shouldGenerateMask = + (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer || + maskUsage.shouldGenerateMaskLayer); + bool shouldPushMask = false; + + /* Check if we need to do additional operations on this child's + * rendering, which necessitates rendering into another surface. */ + if (shouldGenerateMask) { + gfxContextMatrixAutoSaveRestore matSR; + + 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. + EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams); + MaskPaintResult paintResult = CreateAndPaintMaskSurface( + aParams, maskUsage.opacity, firstFrame->Style(), maskFrames, + offsets.offsetToUserSpace); + + if (paintResult.transparentBlackMask) { + return; + } + + maskSurface = paintResult.maskSurface; + if (maskSurface) { + shouldPushMask = true; + + opacityApplied = paintResult.opacityApplied; + } + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + matSR.Restore(); + matSR.SetContext(&context); + + MoveContextOriginToUserSpace(firstFrame, aParams); + RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask( + context, frame, cssPxToDevPxMatrix, maskSurface); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + } else { + // Either entire surface is clipped out, or gfx buffer allocation + // failure in SVGClipPathFrame::GetClipMask. + return; + } + + shouldPushMask = true; + } + + // opacity != 1.0f. + if (!maskUsage.shouldGenerateClipMaskLayer && + !maskUsage.shouldGenerateMaskLayer) { + MOZ_ASSERT(maskUsage.opacity != 1.0f); + + matSR.SetContext(&context); + MoveContextOriginToUserSpace(firstFrame, aParams); + shouldPushMask = true; + } + + if (shouldPushMask) { + // We want the mask to be untransformed so use the inverse of the + // current transform as the maskTransform to compensate. + Matrix maskTransform = context.CurrentMatrix(); + maskTransform.Invert(); + + 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.shouldApplyBasicShapeOrPath) { + gfxContextMatrixAutoSaveRestore matSR(&context); + + MoveContextOriginToUserSpace(firstFrame, aParams); + + MOZ_ASSERT(!maskUsage.shouldApplyClipPath || + !maskUsage.shouldApplyBasicShapeOrPath); + if (maskUsage.shouldApplyClipPath) { + clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix); + } else { + CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame, + cssPxToDevPxMatrix); + } + } + + /* Paint the child */ + context.SetMatrix(matrixAutoSaveRestore.Matrix()); + aPaintChild(); + + if (StaticPrefs::layers_draw_mask_debug()) { + gfxContextAutoSaveRestore saver(&context); + + context.NewPath(); + gfxRect drawingRect = nsLayoutUtils::RectToGfxRect( + aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel()); + context.SnappedRectangle(drawingRect); + sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f); + if (maskUsage.shouldGenerateMaskLayer) { + overlayColor.r = 1.0f; // red represents css positioned mask. + } + if (maskUsage.shouldApplyClipPath || + maskUsage.shouldGenerateClipMaskLayer) { + overlayColor.g = 1.0f; // green represents clip-path:<clip-source>. + } + if (maskUsage.shouldApplyBasicShapeOrPath) { + overlayColor.b = 1.0f; // blue represents + // clip-path:<basic-shape>||<geometry-box>. + } + + context.SetColor(overlayColor); + context.Fill(); + } + + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { + context.PopClip(); + } + + if (shouldPushMask) { + context.PopGroupAndBlend(); + } +} + +void SVGIntegrationUtils::PaintMaskAndClipPath( + const PaintFramesParams& aParams, + const std::function<void()>& aPaintChild) { + PaintMaskAndClipPathInternal(aParams, aPaintChild); +} + +void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams, + Span<const StyleFilter> aFilters, + const SVGFilterPaintCallback& aCallback) { + MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(), + "Filter effect is discarded while generating glyph mask."); + MOZ_ASSERT(!aFilters.IsEmpty(), + "Should not use this method when no filter effect on this frame"); + + nsIFrame* frame = aParams.frame; + if (!ValidateSVGFrame(frame)) { + return; + } + + float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity); + if (opacity == 0.0f) { + return; + } + + // 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); + // Note: we do not return here for eHasNoRefs since we must still handle any + // CSS filter functions. + // TODO: We currently pass nullptr instead of an nsTArray* here, but we + // actually should get the filter frames and then pass them into + // PaintFilteredFrame below! See bug 1494263. + // XXX: Do we need to check for eHasRefsSomeInvalid here given that + // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid? + // Or can we just assert !eHasRefsSomeInvalid? + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + aCallback(aParams.ctx, aParams.imgParams, nullptr, nullptr); + return; + } + + gfxContext& context = aParams.ctx; + + gfxContextAutoSaveRestore autoSR(&context); + EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams); + + /* Paint the child and apply filters */ + nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox; + + FilterInstance::PaintFilteredFrame(frame, aFilters, &context, aCallback, + &dirtyRegion, aParams.imgParams, opacity); +} + +bool SVGIntegrationUtils::CreateWebRenderCSSFilters( + Span<const StyleFilter> aFilters, nsIFrame* aFrame, + WrFiltersHolder& aWrFilters) { + // All CSS filters are supported by WebRender. SVG filters are not fully + // supported, those use NS_STYLE_FILTER_URL and are handled separately. + + // If there are too many filters to render, then just pretend that we + // succeeded, and don't render any of them. + if (aFilters.Length() > + StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) { + return true; + } + aWrFilters.filters.SetCapacity(aFilters.Length()); + auto& wrFilters = aWrFilters.filters; + for (const StyleFilter& filter : aFilters) { + switch (filter.tag) { + case StyleFilter::Tag::Brightness: + wrFilters.AppendElement( + wr::FilterOp::Brightness(filter.AsBrightness())); + break; + case StyleFilter::Tag::Contrast: + wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast())); + break; + case StyleFilter::Tag::Grayscale: + wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale())); + break; + case StyleFilter::Tag::Invert: + wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert())); + break; + case StyleFilter::Tag::Opacity: { + float opacity = filter.AsOpacity(); + wrFilters.AppendElement(wr::FilterOp::Opacity( + wr::PropertyBinding<float>::Value(opacity), opacity)); + break; + } + case StyleFilter::Tag::Saturate: + wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate())); + break; + case StyleFilter::Tag::Sepia: + wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia())); + break; + case StyleFilter::Tag::HueRotate: { + wrFilters.AppendElement( + wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees())); + break; + } + case StyleFilter::Tag::Blur: { + // TODO(emilio): we should go directly from css pixels -> device pixels. + float appUnitsPerDevPixel = + aFrame->PresContext()->AppUnitsPerDevPixel(); + float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(), + appUnitsPerDevPixel); + wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius)); + break; + } + case StyleFilter::Tag::DropShadow: { + float appUnitsPerDevPixel = + aFrame->PresContext()->AppUnitsPerDevPixel(); + const StyleSimpleShadow& shadow = filter.AsDropShadow(); + nscolor color = shadow.color.CalcColor(aFrame); + + wr::Shadow wrShadow; + wrShadow.offset = { + NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(), + appUnitsPerDevPixel), + NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(), + appUnitsPerDevPixel)}; + wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(), + appUnitsPerDevPixel); + wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f, + NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f}; + wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow)); + break; + } + default: + return false; + } + } + + return true; +} + +bool SVGIntegrationUtils::BuildWebRenderFilters( + nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters, + WrFiltersHolder& aWrFilters, bool& aInitialized) { + return FilterInstance::BuildWebRenderFilters(aFilteredFrame, aFilters, + aWrFilters, aInitialized); +} + +bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) { + WrFiltersHolder wrFilters; + auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan(); + bool initialized = true; + return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) || + BuildWebRenderFilters(aFrame, filterChain, wrFilters, initialized); +} + +bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor( + nsIFrame* aFrame) { + // WebRender supports masks / clip-paths and some filters in the compositor. + if (aFrame->StyleEffects()->HasFilters()) { + return !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame); + } + return false; +} + +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->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) { + return false; + } + + AutoSetRestorePaintServerState paintServer(mFrame); + + aContext->Save(); + + // Clip to aFillRect so that we don't paint outside. + aContext->Clip(aFillRect); + + 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 = SVGIntegrationUtils::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::InTransform; + if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) { + flags |= PaintFrameFlags::SyncDecodeImages; + } + nsLayoutUtils::PaintFrame(aContext, 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(aContext, currentFrame, dirty - offset, + NS_RGBA(0, 0, 0, 0), + nsDisplayListBuilderMode::Painting, flags); + + aContext->Restore(); + } + + aContext->Restore(); + + return true; +} + +/* static */ +already_AddRefed<gfxDrawable> SVGIntegrationUtils::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 (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) { + // 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. + gfxRect overrideBounds(0, 0, aPaintServerSize.width, + aPaintServerSize.height); + overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel()); + imgDrawingParams imgParams(aFlags); + RefPtr<gfxPattern> pattern = server->GetPaintServerPattern( + aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0, + imgParams, &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<ISVGDisplayableFrame*>(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(); +} + +} // namespace mozilla diff --git a/layout/svg/SVGIntegrationUtils.h b/layout/svg/SVGIntegrationUtils.h new file mode 100644 index 0000000000..c736727d2b --- /dev/null +++ b/layout/svg/SVGIntegrationUtils.h @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGINTEGRATIONUTILS_H_ +#define LAYOUT_SVG_SVGINTEGRATIONUTILS_H_ + +#include "ImgDrawResult.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsRegionFwd.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/webrender/WebRenderTypes.h" + +class gfxContext; +class gfxDrawable; +class nsIFrame; +struct nsPoint; +struct nsRect; +struct nsSize; + +struct WrFiltersHolder { + nsTArray<mozilla::wr::FilterOp> filters; + nsTArray<mozilla::wr::WrFilterData> filter_datas; + mozilla::Maybe<nsRect> post_filters_clip; + // This exists just to own the values long enough for them to be copied into + // rust. + nsTArray<nsTArray<float>> values; +}; + +namespace mozilla { +class nsDisplayList; +class nsDisplayListBuilder; + +namespace gfx { +class DrawTarget; +} // namespace gfx + +/** + * Integration of SVG effects (clipPath clipping, masking and filters) into + * regular display list based painting and hit-testing. + */ +class SVGIntegrationUtils final { + using DrawTarget = gfx::DrawTarget; + using IntRect = gfx::IntRect; + using imgDrawingParams = image::imgDrawingParams; + + public: + /** + * Returns true if SVG effects that affect the overflow of the given frame + * are currently applied to the frame. + */ + static bool UsingOverflowAffectingEffects(const nsIFrame* aFrame); + + /** + * 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 true if the element has a clippath that is simple enough to + * be represented without a mask in WebRender. + */ + static bool UsingSimpleClipPathForFrame(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 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 aUnionContinuations decide whether bbox contains the area of + * current frame only or 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, + bool aUnionContinuations); + + /** + * Used to adjust a frame's pre-effects ink 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 ComputePostEffectsInkOverflowRect( + nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect); + + /** + * Figure out which area of the source is needed given an area to + * repaint + */ + static nsRect GetRequiredSourceForInvalidArea(nsIFrame* aFrame, + const nsRect& aDirtyRect); + + /** + * 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 MOZ_STACK_CLASS PaintFramesParams { + gfxContext& ctx; + nsIFrame* frame; + nsRect dirtyRect; + nsRect borderArea; + nsDisplayListBuilder* builder; + bool handleOpacity; // If true, PaintMaskAndClipPath/ PaintFilter should + // apply css opacity. + Maybe<LayoutDeviceRect> maskRect; + imgDrawingParams& imgParams; + + explicit PaintFramesParams(gfxContext& aCtx, nsIFrame* aFrame, + const nsRect& aDirtyRect, + const nsRect& aBorderArea, + nsDisplayListBuilder* aBuilder, + bool aHandleOpacity, + imgDrawingParams& aImgParams) + : ctx(aCtx), + frame(aFrame), + dirtyRect(aDirtyRect), + borderArea(aBorderArea), + builder(aBuilder), + handleOpacity(aHandleOpacity), + imgParams(aImgParams) {} + }; + + // This should use FunctionRef instead of std::function because we don't need + // to take ownership of the function. See bug 1490781. + static void PaintMaskAndClipPath(const PaintFramesParams& aParams, + const std::function<void()>& aPaintChild); + + /** + * Paint mask of frame onto a given context, aParams.ctx. + * aParams.ctx must contain an A8 surface. Returns false if the mask + * didn't get painted and should be ignored at the call site. + * isMaskComplete is an outparameter returning whether the mask is complete. + * Incomplete masks should not be drawn and the proper fallback behaviour + * depends on if the masked element is html or svg. + */ + static bool PaintMask(const PaintFramesParams& aParams, + bool& aOutIsMaskComplete); + + /** + * 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 if passing aTransform/aDirtyRect, or untouched otherwise. + * @param aImgParams the params to draw with. + * @param aTransform the user-to-device space matrix, if painting with + * filters. + * @param aDirtyRect the dirty rect *in user space pixels* + */ + using SVGFilterPaintCallback = std::function<void( + gfxContext& aContext, imgDrawingParams&, const gfxMatrix* aTransform, + const nsIntRect* aDirtyRect)>; + + /** + * Paint non-SVG frame with filter and opacity effect. + */ + static void PaintFilter(const PaintFramesParams& aParams, + Span<const StyleFilter> aFilters, + const SVGFilterPaintCallback& aCallback); + + /** + * Build WebRender filters for a frame with CSS filters applied to it. + */ + static bool CreateWebRenderCSSFilters(Span<const StyleFilter> aFilters, + nsIFrame* aFrame, + WrFiltersHolder& aWrFilters); + + /** + * Try to build WebRender filters for a frame with SVG filters applied to it + * if the filters are supported. + */ + static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame, + Span<const StyleFilter> aFilters, + WrFiltersHolder& aWrFilters, + bool& aInitialized); + + /** + * Check if the filters present on |aFrame| are supported by WebRender. + */ + static bool CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame); + + /** + * Check if |aFrame| uses any SVG effects that cannot be rendered in the + * compositor. + */ + static bool UsesSVGEffectsNotSupportedInCompositor(nsIFrame* aFrame); + + /** + * @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 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); + + /** + * The offset between the reference frame and the bounding box of the + * target frame in device units. + */ + static gfxPoint GetOffsetToUserSpaceInDevPx(nsIFrame* aFrame, + const PaintFramesParams& aParams); +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGINTEGRATIONUTILS_H_ diff --git a/layout/svg/SVGMarkerFrame.cpp b/layout/svg/SVGMarkerFrame.cpp new file mode 100644 index 0000000000..b8df132fb7 --- /dev/null +++ b/layout/svg/SVGMarkerFrame.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGMarkerFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfxContext.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGMarkerElement.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +nsContainerFrame* NS_NewSVGMarkerFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGMarkerFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGMarkerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult SVGMarkerFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +#ifdef DEBUG +void SVGMarkerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::marker), + "Content is not an SVG marker"); + + SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +//---------------------------------------------------------------------- +// SVGContainerFrame methods: + +gfxMatrix SVGMarkerFrame::GetCanvasTM() { + NS_ASSERTION(mMarkedFrame, "null SVGGeometry frame"); + + if (mInUse2) { + // We're going to be bailing drawing the marker, so return an identity. + return gfxMatrix(); + } + + SVGMarkerElement* content = static_cast<SVGMarkerElement*>(GetContent()); + + mInUse2 = true; + gfxMatrix markedTM = mMarkedFrame->GetCanvasTM(); + mInUse2 = false; + + Matrix viewBoxTM = content->GetViewBoxTransform(); + + return ThebesMatrix(viewBoxTM * mMarkerTM) * markedTM; +} + +static nsIFrame* GetAnonymousChildFrame(nsIFrame* aFrame) { + nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); + MOZ_ASSERT(kid && kid->IsSVGMarkerAnonChildFrame(), + "expected to find anonymous child of marker frame"); + return kid; +} + +void SVGMarkerFrame::PaintMark(gfxContext& aContext, + const gfxMatrix& aToMarkedFrameUserSpace, + SVGGeometryFrame* aMarkedFrame, + const SVGMark& aMark, float aStrokeWidth, + imgDrawingParams& aImgParams) { + // 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; + } + + AutoMarkerReferencer markerRef(this, aMarkedFrame); + + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(GetContent()); + if (!marker->HasValidDimensions()) { + return; + } + + const SVGViewBox viewBox = marker->GetViewBox(); + + if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { + // We must disable rendering if the viewBox width or height are zero. + return; + } + + Matrix viewBoxTM = marker->GetViewBoxTransform(); + + mMarkerTM = marker->GetMarkerTransform(aStrokeWidth, aMark); + + gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(mMarkerTM) * + aToMarkedFrameUserSpace; + + if (StyleDisplay()->IsScrollableOverflow()) { + aContext.Save(); + gfxRect clipRect = SVGUtils::GetClipRectForFrame( + this, viewBox.x, viewBox.y, viewBox.width, viewBox.height); + SVGUtils::SetClipRect(&aContext, markTM, clipRect); + } + + nsIFrame* kid = GetAnonymousChildFrame(this); + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + // The CTM of each frame referencing us may be different. + SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED); + SVGUtils::PaintFrameWithEffects(kid, aContext, markTM, aImgParams); + + if (StyleDisplay()->IsScrollableOverflow()) aContext.Restore(); +} + +SVGBBox SVGMarkerFrame::GetMarkBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags, + SVGGeometryFrame* aMarkedFrame, + const SVGMark& 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*>(GetContent()); + if (!content->HasValidDimensions()) { + return bbox; + } + + const SVGViewBox viewBox = content->GetViewBox(); + + if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { + return bbox; + } + + mMarkerTM = content->GetMarkerTransform(aStrokeWidth, aMark); + Matrix viewBoxTM = content->GetViewBoxTransform(); + + Matrix tm = viewBoxTM * mMarkerTM * aToBBoxUserspace; + + ISVGDisplayableFrame* 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 SVGMarkerFrame::SetParentCoordCtxProvider(SVGViewportElement* aContext) { + SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(GetContent()); + marker->SetParentCoordCtxProvider(aContext); +} + +void SVGMarkerFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + aResult.AppendElement(OwnedAnonBox(GetAnonymousChildFrame(this))); +} + +//---------------------------------------------------------------------- +// helper class + +SVGMarkerFrame::AutoMarkerReferencer::AutoMarkerReferencer( + SVGMarkerFrame* aFrame, SVGGeometryFrame* aMarkedFrame) + : mFrame(aFrame) { + mFrame->mInUse = true; + mFrame->mMarkedFrame = aMarkedFrame; + + SVGViewportElement* ctx = + static_cast<SVGElement*>(aMarkedFrame->GetContent())->GetCtx(); + mFrame->SetParentCoordCtxProvider(ctx); +} + +SVGMarkerFrame::AutoMarkerReferencer::~AutoMarkerReferencer() { + mFrame->SetParentCoordCtxProvider(nullptr); + + mFrame->mMarkedFrame = nullptr; + mFrame->mInUse = false; +} + +} // namespace mozilla + +//---------------------------------------------------------------------- +// Implementation of SVGMarkerAnonChildFrame + +nsContainerFrame* NS_NewSVGMarkerAnonChildFrame( + mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGMarkerAnonChildFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGMarkerAnonChildFrame) + +#ifdef DEBUG +void SVGMarkerAnonChildFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + MOZ_ASSERT(aParent->IsSVGMarkerFrame(), "Unexpected parent"); + SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif + +} // namespace mozilla diff --git a/layout/svg/SVGMarkerFrame.h b/layout/svg/SVGMarkerFrame.h new file mode 100644 index 0000000000..7f4da9c5a5 --- /dev/null +++ b/layout/svg/SVGMarkerFrame.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGMARKERFRAME_H_ +#define LAYOUT_SVG_SVGMARKERFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SVGContainerFrame.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "nsIFrame.h" +#include "nsLiteralString.h" +#include "nsQueryFrame.h" + +class gfxContext; + +namespace mozilla { + +class PresShell; +class SVGGeometryFrame; + +struct SVGMark; + +namespace dom { +class SVGViewportElement; +} // namespace dom +} // namespace mozilla + +nsContainerFrame* NS_NewSVGMarkerFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); +nsContainerFrame* NS_NewSVGMarkerAnonChildFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGMarkerFrame final : public SVGContainerFrame { + using imgDrawingParams = image::imgDrawingParams; + + friend class SVGMarkerAnonChildFrame; + friend nsContainerFrame* ::NS_NewSVGMarkerFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + protected: + explicit SVGMarkerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGContainerFrame(aStyle, aPresContext, kClassID), + mMarkedFrame(nullptr), + mInUse(false), + mInUse2(false) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGMarkerFrame) + + // nsIFrame interface: +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGMarker"_ns, aResult); + } +#endif + + nsContainerFrame* GetContentInsertionFrame() override { + // Any children must be added to our single anonymous inner frame kid. + MOZ_ASSERT( + PrincipalChildList().FirstChild() && + PrincipalChildList().FirstChild()->IsSVGMarkerAnonChildFrame(), + "Where is our anonymous child?"); + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + // SVGMarkerFrame methods: + void PaintMark(gfxContext& aContext, const gfxMatrix& aToMarkedFrameUserSpace, + SVGGeometryFrame* aMarkedFrame, const SVGMark& aMark, + float aStrokeWidth, imgDrawingParams& aImgParams); + + SVGBBox GetMarkBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags, + SVGGeometryFrame* aMarkedFrame, + const SVGMark& aMark, float aStrokeWidth); + + // Return our anonymous box child. + void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override; + + private: + // stuff needed for callback + SVGGeometryFrame* mMarkedFrame; + Matrix mMarkerTM; + + // SVGContainerFrame methods: + 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(SVGMarkerFrame* aFrame, + SVGGeometryFrame* aMarkedFrame); + ~AutoMarkerReferencer(); + + private: + SVGMarkerFrame* mFrame; + }; + + // SVGMarkerFrame methods: + void SetParentCoordCtxProvider(dom::SVGViewportElement* aContext); + + // recursion prevention flag + bool mInUse; + + // second recursion prevention flag, for GetCanvasTM() + bool mInUse2; +}; + +//////////////////////////////////////////////////////////////////////// +// nsMarkerAnonChildFrame class + +class SVGMarkerAnonChildFrame final : public SVGDisplayContainerFrame { + friend nsContainerFrame* ::NS_NewSVGMarkerAnonChildFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + explicit SVGMarkerAnonChildFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGMarkerAnonChildFrame) + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGMarkerAnonChild"_ns, aResult); + } +#endif + + // SVGContainerFrame methods: + gfxMatrix GetCanvasTM() override { + return static_cast<SVGMarkerFrame*>(GetParent())->GetCanvasTM(); + } +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGMARKERFRAME_H_ diff --git a/layout/svg/SVGMaskFrame.cpp b/layout/svg/SVGMaskFrame.cpp new file mode 100644 index 0000000000..de192823d5 --- /dev/null +++ b/layout/svg/SVGMaskFrame.cpp @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGMaskFrame.h" + +// Keep others in (case-insensitive) order: +#include "AutoReferenceChainGuard.h" +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "mozilla/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGMaskElement.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGUnitTypes_Binding; +using namespace mozilla::gfx; +using namespace mozilla::image; + +nsIFrame* NS_NewSVGMaskFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGMaskFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGMaskFrame) + +already_AddRefed<SourceSurface> SVGMaskFrame::GetMaskForMaskedFrame( + MaskParams& aParams) { + // Make sure we break reference loops and over long reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mInUse, &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return nullptr; + } + + gfxRect maskArea = GetMaskArea(aParams.maskedFrame); + if (maskArea.IsEmpty()) { + return nullptr; + } + // Get the clip extents in device space: + // Minimizing the mask surface extents (using both the current clip extents + // and maskArea) is important for performance. + // + gfxRect maskSurfaceRectDouble = aParams.toUserSpace.TransformBounds(maskArea); + Rect maskSurfaceRect = ToRect(maskSurfaceRectDouble); + maskSurfaceRect.RoundOut(); + + StyleMaskType maskType; + if (aParams.maskMode == StyleMaskMode::MatchSource) { + maskType = StyleSVGReset()->mMaskType; + } else { + maskType = aParams.maskMode == StyleMaskMode::Luminance + ? StyleMaskType::Luminance + : StyleMaskType::Alpha; + } + + RefPtr<DrawTarget> maskDT; + if (maskType == StyleMaskType::Luminance) { + maskDT = aParams.dt->CreateClippedDrawTarget(maskSurfaceRect, + SurfaceFormat::B8G8R8A8); + } else { + maskDT = + aParams.dt->CreateClippedDrawTarget(maskSurfaceRect, SurfaceFormat::A8); + } + + if (!maskDT || !maskDT->IsValid()) { + return nullptr; + } + + RefPtr<gfxContext> tmpCtx = + gfxContext::CreatePreservingTransformOrNull(maskDT); + MOZ_ASSERT(tmpCtx); // already checked the draw target above + + mMatrixForChildren = + GetMaskTransform(aParams.maskedFrame) * aParams.toUserSpace; + + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + gfxMatrix m = mMatrixForChildren; + + // The CTM of each frame referencing us can be different + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED); + m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m; + } + + SVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m, aParams.imgParams); + } + + RefPtr<SourceSurface> surface; + if (maskType == StyleMaskType::Luminance) { + auto luminanceType = LuminanceType::LUMINANCE; + if (StyleSVG()->mColorInterpolation == StyleColorInterpolation::Linearrgb) { + luminanceType = LuminanceType::LINEARRGB; + } + + RefPtr<SourceSurface> maskSnapshot = + maskDT->IntoLuminanceSource(luminanceType, aParams.opacity); + if (!maskSnapshot) { + return nullptr; + } + surface = std::move(maskSnapshot); + } else { + maskDT->FillRect(maskSurfaceRect, + ColorPattern(DeviceColor::MaskWhite(aParams.opacity)), + DrawOptions(1, CompositionOp::OP_IN)); + RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot(); + if (!maskSnapshot) { + return nullptr; + } + surface = std::move(maskSnapshot); + } + + return surface.forget(); +} + +gfxRect SVGMaskFrame::GetMaskArea(nsIFrame* aMaskedFrame) { + SVGMaskElement* maskElem = static_cast<SVGMaskElement*>(GetContent()); + + uint16_t units = + maskElem->mEnumAttributes[SVGMaskElement::MASKUNITS].GetAnimValue(); + gfxRect bbox; + if (units == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + bbox = + SVGUtils::GetBBox(aMaskedFrame, SVGUtils::eUseFrameBoundsForOuterSVG | + SVGUtils::eBBoxIncludeFillGeometry); + } + + // Bounds in the user space of aMaskedFrame + gfxRect maskArea = SVGUtils::GetRelativeRect( + units, &maskElem->mLengthAttributes[SVGMaskElement::ATTR_X], bbox, + aMaskedFrame); + + return maskArea; +} + +nsresult SVGMaskFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +#ifdef DEBUG +void SVGMaskFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::mask), + "Content is not an SVG mask"); + + SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +gfxMatrix SVGMaskFrame::GetCanvasTM() { return mMatrixForChildren; } + +gfxMatrix SVGMaskFrame::GetMaskTransform(nsIFrame* aMaskedFrame) { + SVGMaskElement* content = static_cast<SVGMaskElement*>(GetContent()); + + SVGAnimatedEnumeration* maskContentUnits = + &content->mEnumAttributes[SVGMaskElement::MASKCONTENTUNITS]; + + uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry | + (aMaskedFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone + ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement + : 0); + + return SVGUtils::AdjustMatrixForUnits(gfxMatrix(), maskContentUnits, + aMaskedFrame, flags); +} + +} // namespace mozilla diff --git a/layout/svg/SVGMaskFrame.h b/layout/svg/SVGMaskFrame.h new file mode 100644 index 0000000000..ee97415459 --- /dev/null +++ b/layout/svg/SVGMaskFrame.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGMASKFRAME_H_ +#define LAYOUT_SVG_SVGMASKFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/gfx/2D.h" +#include "gfxPattern.h" +#include "gfxMatrix.h" + +class gfxContext; + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGMaskFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGMaskFrame final : public SVGContainerFrame { + friend nsIFrame* ::NS_NewSVGMaskFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + using Matrix = gfx::Matrix; + using SourceSurface = gfx::SourceSurface; + using imgDrawingParams = image::imgDrawingParams; + + protected: + explicit SVGMaskFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGContainerFrame(aStyle, aPresContext, kClassID), mInUse(false) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGMaskFrame) + + struct MaskParams { + gfx::DrawTarget* dt; + nsIFrame* maskedFrame; + const gfxMatrix& toUserSpace; + float opacity; + StyleMaskMode maskMode; + imgDrawingParams& imgParams; + + explicit MaskParams(gfx::DrawTarget* aDt, nsIFrame* aMaskedFrame, + const gfxMatrix& aToUserSpace, float aOpacity, + StyleMaskMode aMaskMode, imgDrawingParams& aImgParams) + : dt(aDt), + maskedFrame(aMaskedFrame), + toUserSpace(aToUserSpace), + opacity(aOpacity), + maskMode(aMaskMode), + imgParams(aImgParams) {} + }; + + // SVGMaskFrame method: + + /** + * Generate a mask surface for the target frame. + * + * The return surface can be null, it's the caller's responsibility to + * null-check before dereferencing. + */ + already_AddRefed<SourceSurface> GetMaskForMaskedFrame(MaskParams& aParams); + + gfxRect GetMaskArea(nsIFrame* aMaskedFrame); + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGMask"_ns, 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); + + gfxMatrix mMatrixForChildren; + // recursion prevention flag + bool mInUse; + + // SVGContainerFrame methods: + gfxMatrix GetCanvasTM() override; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGMASKFRAME_H_ diff --git a/layout/svg/SVGObserverUtils.cpp b/layout/svg/SVGObserverUtils.cpp new file mode 100644 index 0000000000..bd0602ebb8 --- /dev/null +++ b/layout/svg/SVGObserverUtils.cpp @@ -0,0 +1,1766 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGObserverUtils.h" + +// Keep others in (case-insensitive) order: +#include "mozilla/css/ImageLoader.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGTextPathElement.h" +#include "mozilla/dom/SVGUseElement.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" +#include "nsCSSFrameConstructor.h" +#include "nsCycleCollectionParticipant.h" +#include "nsHashKeys.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsInterfaceHashtable.h" +#include "nsIReflowCallback.h" +#include "nsISupportsImpl.h" +#include "nsLayoutUtils.h" +#include "nsNetUtil.h" +#include "nsTHashtable.h" +#include "nsURIHashKey.h" +#include "SVGFilterFrame.h" +#include "SVGMarkerFrame.h" +#include "SVGPaintServerFrame.h" + +using namespace mozilla::dom; + +namespace mozilla { + +bool URLAndReferrerInfo::operator==(const URLAndReferrerInfo& aRHS) const { + bool uriEqual = false, referrerEqual = false; + this->mURI->Equals(aRHS.mURI, &uriEqual); + this->mReferrerInfo->Equals(aRHS.mReferrerInfo, &referrerEqual); + + return uriEqual && referrerEqual; +} + +class URLAndReferrerInfoHashKey : public PLDHashEntryHdr { + public: + using KeyType = const URLAndReferrerInfo*; + using KeyTypePointer = const URLAndReferrerInfo*; + + explicit URLAndReferrerInfoHashKey(const URLAndReferrerInfo* aKey) noexcept + : mKey(aKey) { + MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey); + } + URLAndReferrerInfoHashKey(URLAndReferrerInfoHashKey&& aToMove) noexcept + : PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) { + MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey); + } + MOZ_COUNTED_DTOR(URLAndReferrerInfoHashKey) + + const URLAndReferrerInfo* GetKey() const { return mKey; } + + bool KeyEquals(const URLAndReferrerInfo* aKey) const { + if (!mKey) { + return !aKey; + } + return *mKey == *aKey; + } + + static const URLAndReferrerInfo* KeyToPointer( + const URLAndReferrerInfo* aKey) { + return aKey; + } + + static PLDHashNumber HashKey(const URLAndReferrerInfo* aKey) { + if (!aKey) { + // If the key is null, return hash for empty string. + return HashString(""_ns); + } + nsAutoCString urlSpec, referrerSpec; + // nsURIHashKey ignores GetSpec() failures, so we do too: + Unused << aKey->GetURI()->GetSpec(urlSpec); + return AddToHash( + HashString(urlSpec), + static_cast<ReferrerInfo*>(aKey->GetReferrerInfo())->Hash()); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + RefPtr<const URLAndReferrerInfo> mKey; +}; + +static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef( + nsIFrame* aFrame, const StyleComputedImageUrl& aURL) { + MOZ_ASSERT(aFrame); + + nsCOMPtr<nsIURI> uri = aURL.GetURI(); + + if (aURL.IsLocalRef()) { + uri = SVGObserverUtils::GetBaseURLForLocalRef(aFrame->GetContent(), uri); + uri = aURL.ResolveLocalRef(uri); + } + + if (!uri) { + return nullptr; + } + + RefPtr<URLAndReferrerInfo> info = + new URLAndReferrerInfo(uri, aURL.ExtraData()); + return info.forget(); +} + +static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef( + nsIFrame* aFrame, const nsAString& aURL, nsIReferrerInfo* aReferrerInfo) { + MOZ_ASSERT(aFrame); + + nsIContent* content = aFrame->GetContent(); + + // Like SVGObserverUtils::GetBaseURLForLocalRef, we want to resolve the + // URL against any <use> element shadow tree's source document. + // + // Unlike GetBaseURLForLocalRef, we are assuming that the URL was specified + // directly on mFrame's content (because this ResolveURLUsingLocalRef + // overload is used for href="" attributes and not CSS URL values), so there + // is no need to check whether the URL was specified / inherited from + // outside the shadow tree. + nsIURI* base = nullptr; + const Encoding* encoding = nullptr; + if (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) { + base = use->GetSourceDocURI(); + encoding = use->GetSourceDocCharacterSet(); + } + + if (!base) { + base = content->OwnerDoc()->GetDocumentURI(); + encoding = content->OwnerDoc()->GetDocumentCharacterSet(); + } + + nsCOMPtr<nsIURI> uri; + Unused << NS_NewURI(getter_AddRefs(uri), aURL, WrapNotNull(encoding), base); + + if (!uri) { + return nullptr; + } + + RefPtr<URLAndReferrerInfo> info = new URLAndReferrerInfo(uri, aReferrerInfo); + return info.forget(); +} + +class SVGFilterObserverList; + +/** + * A class used as a member of the "observer" classes below to help them + * avoid dereferencing their frame during presshell teardown when their frame + * may have been destroyed (leaving their pointer to their frame dangling). + * + * When a presshell is torn down, the properties for each frame may not be + * deleted until after the frames are destroyed. "Observer" objects (attached + * as frame properties) must therefore check whether the presshell is being + * torn down before using their pointer to their frame. + * + * mFramePresShell may be null, but when mFrame is non-null, mFramePresShell + * is guaranteed to be non-null, too. + */ +struct SVGFrameReferenceFromProperty { + explicit SVGFrameReferenceFromProperty(nsIFrame* aFrame) + : mFrame(aFrame), mFramePresShell(aFrame->PresShell()) {} + + // Clear our reference to the frame. + void Detach() { + mFrame = nullptr; + mFramePresShell = nullptr; + } + + // null if the frame has become invalid + nsIFrame* Get() { + if (mFramePresShell && mFramePresShell->IsDestroying()) { + Detach(); // mFrame is no longer valid. + } + return mFrame; + } + + private: + // The frame that our property is attached to (may be null). + nsIFrame* mFrame; + PresShell* mFramePresShell; +}; + +void SVGRenderingObserver::StartObserving() { + Element* target = GetReferencedElementWithoutObserving(); + if (target) { + target->AddMutationObserver(this); + } +} + +void SVGRenderingObserver::StopObserving() { + Element* target = GetReferencedElementWithoutObserving(); + + if (target) { + target->RemoveMutationObserver(this); + if (mInObserverSet) { + SVGObserverUtils::RemoveRenderingObserver(target, this); + mInObserverSet = false; + } + } + NS_ASSERTION(!mInObserverSet, "still in an observer set?"); +} + +Element* SVGRenderingObserver::GetAndObserveReferencedElement() { +#ifdef DEBUG + DebugObserverSet(); +#endif + Element* referencedElement = GetReferencedElementWithoutObserving(); + if (referencedElement && !mInObserverSet) { + SVGObserverUtils::AddRenderingObserver(referencedElement, this); + mInObserverSet = true; + } + return referencedElement; +} + +nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame() { + Element* referencedElement = GetAndObserveReferencedElement(); + return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr; +} + +nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame( + LayoutFrameType aFrameType, bool* aOK) { + nsIFrame* frame = GetAndObserveReferencedFrame(); + if (frame) { + if (frame->Type() == aFrameType) { + return frame; + } + if (aOK) { + *aOK = false; + } + } + return nullptr; +} + +void SVGRenderingObserver::OnNonDOMMutationRenderingChange() { + mInObserverSet = false; + OnRenderingChange(); +} + +void SVGRenderingObserver::NotifyEvictedFromRenderingObserverSet() { + mInObserverSet = false; // We've been removed from rendering-obs. set. + StopObserving(); // Stop observing mutations too. +} + +void SVGRenderingObserver::AttributeChanged(dom::Element* aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) { + if (aElement->IsInNativeAnonymousSubtree()) { + // Don't observe attribute changes in native-anonymous subtrees like + // scrollbars. + return; + } + + // 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. + + OnRenderingChange(); +} + +void SVGRenderingObserver::ContentAppended(nsIContent* aFirstNewContent) { + OnRenderingChange(); +} + +void SVGRenderingObserver::ContentInserted(nsIContent* aChild) { + OnRenderingChange(); +} + +void SVGRenderingObserver::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + OnRenderingChange(); +} + +/** + * SVG elements reference supporting resources by element ID. We need to + * track when those resources change and when the document 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 SVGIDRenderingObserver to manage the relationship. The + * property object is attached to the referencing frame. + */ +class SVGIDRenderingObserver : public SVGRenderingObserver { + public: + // Callback for checking if the element being observed is valid for this + // observer. Note that this may be called during construction, before the + // deriving class is fully constructed. + using TargetIsValidCallback = bool (*)(const Element&); + SVGIDRenderingObserver( + URLAndReferrerInfo* aURI, nsIContent* aObservingContent, + bool aReferenceImage, + TargetIsValidCallback aTargetIsValidCallback = nullptr); + + protected: + virtual ~SVGIDRenderingObserver() { + // This needs to call our GetReferencedElementWithoutObserving override, + // so must be called here rather than in our base class's dtor. + StopObserving(); + } + + void TargetChanged() { + mTargetIsValid = ([this] { + Element* observed = mObservedElementTracker.get(); + if (!observed) { + return false; + } + // If the content is observing an ancestor, then return the target is not + // valid. + // + // TODO(emilio): Should we allow content observing its own descendants? + // That seems potentially-bad as well. + if (observed->OwnerDoc() == mObservingContent->OwnerDoc() && + nsContentUtils::ContentIsHostIncludingDescendantOf(mObservingContent, + observed)) { + return false; + } + if (mTargetIsValidCallback) { + return mTargetIsValidCallback(*observed); + } + return true; + }()); + } + + Element* GetReferencedElementWithoutObserving() final { + return mTargetIsValid ? mObservedElementTracker.get() : nullptr; + } + + void OnRenderingChange() override; + + /** + * Helper that provides a reference to the element with the ID that our + * observer wants to observe, and that will invalidate our observer if the + * element that that ID identifies changes to a different element (or none). + */ + class ElementTracker final : public IDTracker { + public: + explicit ElementTracker(SVGIDRenderingObserver* aOwningObserver) + : mOwningObserver(aOwningObserver) {} + + protected: + void ElementChanged(Element* aFrom, Element* aTo) override { + // Call OnRenderingChange() before the target changes, so that + // mIsTargetValid reflects the right state. + mOwningObserver->OnRenderingChange(); + mOwningObserver->StopObserving(); + IDTracker::ElementChanged(aFrom, aTo); + mOwningObserver->TargetChanged(); + mOwningObserver->StartObserving(); + // And same after the target changes, for the same reason. + mOwningObserver->OnRenderingChange(); + } + /** + * Override IsPersistent because we want to keep tracking the element + * for the ID even when it changes. + */ + bool IsPersistent() override { return true; } + + private: + SVGIDRenderingObserver* mOwningObserver; + }; + + ElementTracker mObservedElementTracker; + RefPtr<Element> mObservingContent; + bool mTargetIsValid = false; + TargetIsValidCallback mTargetIsValidCallback; +}; + +/** + * Note that in the current setup there are two separate observer lists. + * + * In SVGIDRenderingObserver's ctor, the new object adds itself to the + * mutation observer list maintained by the referenced element. In this way the + * SVGIDRenderingObserver is notified if there are any attribute or content + * tree changes to the element or any of its *descendants*. + * + * In SVGIDRenderingObserver::GetAndObserveReferencedElement() the + * SVGIDRenderingObserver object also adds itself to an + * SVGRenderingObserverSet 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. + */ +SVGIDRenderingObserver::SVGIDRenderingObserver( + URLAndReferrerInfo* aURI, nsIContent* aObservingContent, + bool aReferenceImage, TargetIsValidCallback aTargetIsValidCallback) + : mObservedElementTracker(this), + mObservingContent(aObservingContent->AsElement()), + mTargetIsValidCallback(aTargetIsValidCallback) { + // Start watching the target element + nsIURI* uri = nullptr; + nsIReferrerInfo* referrerInfo = nullptr; + if (aURI) { + uri = aURI->GetURI(); + referrerInfo = aURI->GetReferrerInfo(); + } + + mObservedElementTracker.ResetToURIFragmentID( + aObservingContent, uri, referrerInfo, true, aReferenceImage); + TargetChanged(); + StartObserving(); +} + +void SVGIDRenderingObserver::OnRenderingChange() { + if (mObservedElementTracker.get() && mInObserverSet) { + SVGObserverUtils::RemoveRenderingObserver(mObservedElementTracker.get(), + this); + mInObserverSet = false; + } +} + +class SVGRenderingObserverProperty : public SVGIDRenderingObserver { + public: + NS_DECL_ISUPPORTS + + SVGRenderingObserverProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage), + mFrameReference(aFrame) {} + + protected: + virtual ~SVGRenderingObserverProperty() = default; // non-public + + void OnRenderingChange() override; + + SVGFrameReferenceFromProperty mFrameReference; +}; + +NS_IMPL_ISUPPORTS(SVGRenderingObserverProperty, nsIMutationObserver) + +void SVGRenderingObserverProperty::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (!mTargetIsValid) { + return; + } + + nsIFrame* frame = mFrameReference.Get(); + + if (frame && frame->HasAllStateBits(NS_FRAME_SVG_LAYOUT)) { + // We need to notify anything that is observing the referencing frame or + // any of its ancestors that the referencing frame has been invalidated. + // Since walking the parent chain checking for observers is expensive we + // do that using a change hint (multiple change hints of the same type are + // coalesced). + nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(), + RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + } +} + +class SVGTextPathObserver final : public SVGRenderingObserverProperty { + public: + SVGTextPathObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} + + protected: + void OnRenderingChange() override; +}; + +void SVGTextPathObserver::OnRenderingChange() { + SVGRenderingObserverProperty::OnRenderingChange(); + + if (!mTargetIsValid) { + return; + } + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) { + return; + } + + MOZ_ASSERT(frame->IsFrameOfType(nsIFrame::eSVG) || + SVGUtils::IsInSVGTextSubtree(frame), + "SVG frame expected"); + + MOZ_ASSERT(frame->GetContent()->IsSVGElement(nsGkAtoms::textPath), + "expected frame for a <textPath> element"); + + auto* text = static_cast<SVGTextFrame*>( + nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText)); + MOZ_ASSERT(text, "expected to find an ancestor SVGTextFrame"); + if (text) { + text->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | + NS_STATE_SVG_POSITIONING_DIRTY); + + if (SVGUtils::AnyOuterSVGIsCallingReflowSVG(text)) { + text->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); + if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + text->ReflowSVGNonDisplayText(); + } else { + text->ReflowSVG(); + } + } else { + text->ScheduleReflowSVG(); + } + } +} + +class SVGMarkerObserver final : public SVGRenderingObserverProperty { + public: + SVGMarkerObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} + + protected: + void OnRenderingChange() override; +}; + +void SVGMarkerObserver::OnRenderingChange() { + SVGRenderingObserverProperty::OnRenderingChange(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) { + return; + } + + MOZ_ASSERT(frame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); + + // Don't need to request ReflowFrame if we're being reflowed. + // Because mRect for SVG frames includes the bounds of any markers + // (see the comment for nsIFrame::GetRect), the referencing frame must be + // reflowed for any marker changes. + if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { + // XXXjwatt: We need to unify SVG into standard reflow so we can just use + // nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here. + // XXXSDL KILL THIS!!! + SVGUtils::ScheduleReflowSVG(frame); + } + frame->PresContext()->RestyleManager()->PostRestyleEvent( + frame->GetContent()->AsElement(), RestyleHint{0}, + nsChangeHint_RepaintFrame); +} + +class SVGPaintingProperty : public SVGRenderingObserverProperty { + public: + SVGPaintingProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} + + protected: + void OnRenderingChange() override; +}; + +void SVGPaintingProperty::OnRenderingChange() { + SVGRenderingObserverProperty::OnRenderingChange(); + + nsIFrame* frame = mFrameReference.Get(); + if (!frame) { + return; + } + + if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + frame->InvalidateFrameSubtree(); + } else { + for (nsIFrame* f = frame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + f->InvalidateFrame(); + } + } +} + +// Observer for -moz-element(#element). Note that the observed element does not +// have to be an SVG element. +class SVGMozElementObserver final : public SVGPaintingProperty { + public: + SVGMozElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGPaintingProperty(aURI, aFrame, aReferenceImage) {} + + // We only return true here because GetAndObserveBackgroundImage uses us + // to implement observing of arbitrary elements (including HTML elements) + // that may require us to repaint if the referenced element is reflowed. + // Bug 1496065 has been filed to remove that support though. + bool ObservesReflow() override { return true; } +}; + +/** + * For content with `background-clip: text`. + * + * This observer is unusual in that the observing frame and observed frame are + * the same frame. This is because the observing frame is observing for reflow + * of its descendant text nodes, since such reflows may not result in the + * frame's nsDisplayBackground changing. In other words, Display List Based + * Invalidation may not invalidate the frame's background, so we need this + * observer to make sure that happens. + * + * XXX: It's questionable whether we should even be [ab]using the SVG observer + * mechanism for `background-clip:text`. Since we know that the observed frame + * is the frame we need to invalidate, we could just check the computed style + * in the (one) place where we pass INVALIDATE_REFLOW and invalidate there... + */ +class BackgroundClipRenderingObserver : public SVGRenderingObserver { + public: + explicit BackgroundClipRenderingObserver(nsIFrame* aFrame) : mFrame(aFrame) {} + + NS_DECL_ISUPPORTS + + private: + // We do not call StopObserving() since the observing and observed element + // are the same element (and because we could crash - see bug 1556441). + virtual ~BackgroundClipRenderingObserver() = default; + + Element* GetReferencedElementWithoutObserving() final { + return mFrame->GetContent()->AsElement(); + } + + void OnRenderingChange() final; + + /** + * Observing for mutations is not enough. A new font loading and applying + * to the text content could cause it to reflow, and we need to invalidate + * for that. + */ + bool ObservesReflow() final { return true; } + + // The observer and observee! + nsIFrame* mFrame; +}; + +NS_IMPL_ISUPPORTS(BackgroundClipRenderingObserver, nsIMutationObserver) + +void BackgroundClipRenderingObserver::OnRenderingChange() { + for (nsIFrame* f = mFrame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + f->InvalidateFrame(); + } +} + +static bool IsSVGFilterElement(const Element& aObserved) { + return aObserved.IsSVGElement(nsGkAtoms::filter); +} + +/** + * 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 SVGFilterObserverList class manages a list of SVGFilterObservers. + */ +class SVGFilterObserver final : public SVGIDRenderingObserver { + public: + SVGFilterObserver(URLAndReferrerInfo* aURI, nsIContent* aObservingContent, + SVGFilterObserverList* aFilterChainObserver) + : SVGIDRenderingObserver(aURI, aObservingContent, false, + IsSVGFilterElement), + mFilterObserverList(aFilterChainObserver) {} + + void DetachFromChainObserver() { mFilterObserverList = nullptr; } + + /** + * @return the filter frame, or null if there is no filter frame + */ + SVGFilterFrame* GetAndObserveFilterFrame(); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserver) + + // SVGIDRenderingObserver + void OnRenderingChange() override; + + protected: + virtual ~SVGFilterObserver() = default; // non-public + + SVGFilterObserverList* mFilterObserverList; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserver) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserver) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservedElementTracker) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservingContent) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserver) + tmp->StopObserving(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservedElementTracker); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservingContent) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +SVGFilterFrame* SVGFilterObserver::GetAndObserveFilterFrame() { + return static_cast<SVGFilterFrame*>( + GetAndObserveReferencedFrame(LayoutFrameType::SVGFilter, nullptr)); +} + +/** + * This class manages a list of SVGFilterObservers, which correspond to + * reference to SVG filters in a list of filters in a given 'filter' property. + * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); + * + * In the above example, the SVGFilterObserverList will manage two + * SVGFilterObservers, one for each of the references to SVG filters. CSS + * filters like "blur(10px)" don't reference filter elements, so they don't + * need an SVGFilterObserver. The style system invalidates changes to CSS + * filters. + * + * FIXME(emilio): Why do we need this as opposed to the individual observers we + * create in the constructor? + */ +class SVGFilterObserverList : public nsISupports { + public: + SVGFilterObserverList(Span<const StyleFilter> aFilters, + nsIContent* aFilteredElement, + nsIFrame* aFilteredFrame = nullptr); + + const nsTArray<RefPtr<SVGFilterObserver>>& GetObservers() const { + return mObservers; + } + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList) + + virtual void OnRenderingChange() = 0; + + protected: + virtual ~SVGFilterObserverList(); + + void DetachObservers() { + for (auto& observer : mObservers) { + observer->DetachFromChainObserver(); + } + } + + nsTArray<RefPtr<SVGFilterObserver>> mObservers; +}; + +void SVGFilterObserver::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (mFilterObserverList) { + mFilterObserverList->OnRenderingChange(); + } + + if (!mTargetIsValid) { + return; + } + + nsIFrame* frame = mObservingContent->GetPrimaryFrame(); + if (!frame) { + return; + } + + // Repaint asynchronously in case the filter frame is being torn down + nsChangeHint changeHint = nsChangeHint(nsChangeHint_RepaintFrame); + + // Since we don't call SVGRenderingObserverProperty:: + // OnRenderingChange, we have to add this bit ourselves. + if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // 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->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { + changeHint |= nsChangeHint_UpdateOverflow; + } + frame->PresContext()->RestyleManager()->PostRestyleEvent( + mObservingContent, RestyleHint{0}, changeHint); +} + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserverList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserverList) + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserverList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserverList) + tmp->DetachObservers(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserverList) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +SVGFilterObserverList::SVGFilterObserverList(Span<const StyleFilter> aFilters, + nsIContent* aFilteredElement, + nsIFrame* aFilteredFrame) { + for (const auto& filter : aFilters) { + if (!filter.IsUrl()) { + continue; + } + + const auto& url = filter.AsUrl(); + + // aFilteredFrame can be null if this filter belongs to a + // CanvasRenderingContext2D. + RefPtr<URLAndReferrerInfo> filterURL; + if (aFilteredFrame) { + filterURL = ResolveURLUsingLocalRef(aFilteredFrame, url); + } else { + nsCOMPtr<nsIURI> resolvedURI = url.ResolveLocalRef(aFilteredElement); + if (resolvedURI) { + filterURL = new URLAndReferrerInfo(resolvedURI, url.ExtraData()); + } + } + + RefPtr<SVGFilterObserver> observer = + new SVGFilterObserver(filterURL, aFilteredElement, this); + mObservers.AppendElement(observer); + } +} + +SVGFilterObserverList::~SVGFilterObserverList() { DetachObservers(); } + +class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList { + public: + SVGFilterObserverListForCSSProp(Span<const StyleFilter> aFilters, + nsIFrame* aFilteredFrame) + : SVGFilterObserverList(aFilters, aFilteredFrame->GetContent(), + aFilteredFrame) {} + + protected: + void OnRenderingChange() override; + bool mInvalidating = false; +}; + +void SVGFilterObserverListForCSSProp::OnRenderingChange() { + if (mInvalidating) { + return; + } + AutoRestore<bool> guard(mInvalidating); + mInvalidating = true; + for (auto& observer : mObservers) { + observer->OnRenderingChange(); + } +} + +class SVGFilterObserverListForCanvasContext final + : public SVGFilterObserverList { + public: + SVGFilterObserverListForCanvasContext(CanvasRenderingContext2D* aContext, + Element* aCanvasElement, + Span<const StyleFilter> aFilters) + : SVGFilterObserverList(aFilters, aCanvasElement), mContext(aContext) {} + + void OnRenderingChange() override; + void DetachFromContext() { mContext = nullptr; } + + private: + CanvasRenderingContext2D* mContext; +}; + +void SVGFilterObserverListForCanvasContext::OnRenderingChange() { + if (!mContext) { + NS_WARNING( + "GFX: This should never be called without a context, except during " + "cycle collection (when DetachFromContext has been called)"); + return; + } + // Refresh the cached FilterDescription in mContext->CurrentState().filter. + // If this filter is not at the top of the state stack, we'll refresh the + // wrong filter, but that's ok, because we'll refresh the right filter + // when we pop the state stack in CanvasRenderingContext2D::Restore(). + RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext); + kungFuDeathGrip->UpdateFilter(); +} + +class SVGMaskObserverList final : public nsISupports { + public: + explicit SVGMaskObserverList(nsIFrame* aFrame); + + // nsISupports + NS_DECL_ISUPPORTS + + const nsTArray<RefPtr<SVGPaintingProperty>>& GetObservers() const { + return mProperties; + } + + void ResolveImage(uint32_t aIndex); + + private: + virtual ~SVGMaskObserverList() = default; // non-public + nsTArray<RefPtr<SVGPaintingProperty>> mProperties; + nsIFrame* mFrame; +}; + +NS_IMPL_ISUPPORTS(SVGMaskObserverList, nsISupports) + +SVGMaskObserverList::SVGMaskObserverList(nsIFrame* aFrame) : mFrame(aFrame) { + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + + for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) { + const StyleComputedImageUrl* data = + svgReset->mMask.mLayers[i].mImage.GetImageRequestURLValue(); + RefPtr<URLAndReferrerInfo> maskUri; + if (data) { + maskUri = ResolveURLUsingLocalRef(aFrame, *data); + } + + bool hasRef = false; + if (maskUri) { + maskUri->GetURI()->GetHasRef(&hasRef); + } + + // Accrording to maskUri, SVGPaintingProperty's ctor may trigger an + // external SVG resource download, so we should pass maskUri in only if + // maskUri has a chance pointing to an SVG mask resource. + // + // And, an URL may refer to an SVG mask resource if it consists of + // a fragment. + SVGPaintingProperty* prop = new SVGPaintingProperty( + hasRef ? maskUri.get() : nullptr, aFrame, false); + mProperties.AppendElement(prop); + } +} + +void SVGMaskObserverList::ResolveImage(uint32_t aIndex) { + const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset(); + MOZ_ASSERT(aIndex < svgReset->mMask.mImageCount); + + auto& image = const_cast<StyleImage&>(svgReset->mMask.mLayers[aIndex].mImage); + if (image.IsResolved()) { + return; + } + MOZ_ASSERT(image.IsImageRequestType()); + Document* doc = mFrame->PresContext()->Document(); + image.ResolveImage(*doc, nullptr); + if (imgRequestProxy* req = image.GetImageRequest()) { + // FIXME(emilio): What disassociates this request? + doc->StyleImageLoader()->AssociateRequestToFrame(req, mFrame); + } +} + +/** + * Used for gradient-to-gradient, pattern-to-pattern and filter-to-filter + * references to "template" elements (specified via the 'href' attributes). + * + * This is a special class for the case where we know we only want to call + * InvalidateDirectRenderingObservers (as opposed to + * InvalidateRenderingObservers). + * + * TODO(jwatt): If we added a new NS_FRAME_RENDERING_OBSERVER_CONTAINER state + * bit to clipPath, filter, gradients, marker, mask, pattern and symbol, and + * could have InvalidateRenderingObservers stop on reaching such an element, + * then we would no longer need this class (not to mention improving perf by + * significantly cutting down on ancestor traversal). + */ +class SVGTemplateElementObserver : public SVGIDRenderingObserver { + public: + NS_DECL_ISUPPORTS + + SVGTemplateElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + bool aReferenceImage) + : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage), + mFrameReference(aFrame) {} + + protected: + virtual ~SVGTemplateElementObserver() = default; // non-public + + void OnRenderingChange() override; + + SVGFrameReferenceFromProperty mFrameReference; +}; + +NS_IMPL_ISUPPORTS(SVGTemplateElementObserver, nsIMutationObserver) + +void SVGTemplateElementObserver::OnRenderingChange() { + SVGIDRenderingObserver::OnRenderingChange(); + + if (nsIFrame* frame = mFrameReference.Get()) { + // We know that we don't need to walk the parent chain notifying rendering + // observers since changes to a gradient etc. do not affect ancestor + // elements. So we only invalidate *direct* rendering observers here. + // Since we don't need to walk the parent chain, we don't need to worry + // about coalescing multiple invalidations by using a change hint as we do + // in SVGRenderingObserverProperty::OnRenderingChange. + SVGObserverUtils::InvalidateDirectRenderingObservers(frame); + } +} + +/** + * An instance of this class is stored on an observed frame (as a frame + * property) whenever the frame has active rendering observers. It is used to + * store pointers to the SVGRenderingObserver instances belonging to any + * observing frames, allowing invalidations from the observed frame to be sent + * to all observing frames. + * + * SVGRenderingObserver instances that are added are not strongly referenced, + * so they must remove themselves before they die. + * + * This class is "single-shot", which is to say that when something about the + * observed element changes, InvalidateAll() clears our hashtable of + * SVGRenderingObservers. SVGRenderingObserver objects will be added back + * again if/when the observing frame looks up our observed frame to use it. + * + * XXXjwatt: is this the best thing to do nowadays? Back when that mechanism + * landed in bug 330498 we had two pass, recursive invalidation up the frame + * tree, and I think reference loops were a problem. Nowadays maybe a flag + * on the SVGRenderingObserver objects to coalesce invalidations may work + * better? + * + * InvalidateAll must be called before this object is destroyed, i.e. + * before the referenced frame is destroyed. This should normally happen + * via SVGContainerFrame::RemoveFrame, since only frames in the frame + * tree should be referenced. + */ +class SVGRenderingObserverSet { + public: + SVGRenderingObserverSet() : mObservers(4) { + MOZ_COUNT_CTOR(SVGRenderingObserverSet); + } + + ~SVGRenderingObserverSet() { + InvalidateAll(); + MOZ_COUNT_DTOR(SVGRenderingObserverSet); + } + + void Add(SVGRenderingObserver* aObserver) { mObservers.Insert(aObserver); } + void Remove(SVGRenderingObserver* aObserver) { mObservers.Remove(aObserver); } +#ifdef DEBUG + bool Contains(SVGRenderingObserver* aObserver) { + return mObservers.Contains(aObserver); + } +#endif + bool IsEmpty() { return mObservers.IsEmpty(); } + + /** + * 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: + nsTHashSet<SVGRenderingObserver*> mObservers; +}; + +void SVGRenderingObserverSet::InvalidateAll() { + if (mObservers.IsEmpty()) { + return; + } + + const auto observers = std::move(mObservers); + + for (const auto& observer : observers) { + observer->OnNonDOMMutationRenderingChange(); + } +} + +void SVGRenderingObserverSet::InvalidateAllForReflow() { + if (mObservers.IsEmpty()) { + return; + } + + AutoTArray<SVGRenderingObserver*, 10> observers; + + for (auto it = mObservers.cbegin(), end = mObservers.cend(); it != end; + ++it) { + SVGRenderingObserver* obs = *it; + if (obs->ObservesReflow()) { + observers.AppendElement(obs); + mObservers.Remove(it); + } + } + + for (uint32_t i = 0; i < observers.Length(); ++i) { + observers[i]->OnNonDOMMutationRenderingChange(); + } +} + +void SVGRenderingObserverSet::RemoveAll() { + const auto observers = std::move(mObservers); + + // 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 (const auto& observer : observers) { + observer->NotifyEvictedFromRenderingObserverSet(); + } +} + +static SVGRenderingObserverSet* GetObserverSet(Element* aElement) { + return static_cast<SVGRenderingObserverSet*>( + aElement->GetProperty(nsGkAtoms::renderingobserverset)); +} + +#ifdef DEBUG +// Defined down here because we need SVGRenderingObserverSet's definition. +void SVGRenderingObserver::DebugObserverSet() { + Element* referencedElement = GetReferencedElementWithoutObserving(); + if (referencedElement) { + SVGRenderingObserverSet* observers = GetObserverSet(referencedElement); + bool inObserverSet = observers && observers->Contains(this); + MOZ_ASSERT(inObserverSet == mInObserverSet, + "failed to track whether we're in our referenced element's " + "observer set!"); + } else { + MOZ_ASSERT(!mInObserverSet, "In whose observer set are we, then?"); + } +} +#endif + +using URIObserverHashtable = + nsInterfaceHashtable<URLAndReferrerInfoHashKey, nsIMutationObserver>; + +using PaintingPropertyDescriptor = + const FramePropertyDescriptor<SVGPaintingProperty>*; + +static void DestroyFilterProperty(SVGFilterObserverListForCSSProp* aProp) { + aProp->Release(); +} + +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefToTemplateProperty, + SVGTemplateElementObserver) +NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty, + SVGFilterObserverListForCSSProp, + DestroyFilterProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, SVGMaskObserverList) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, SVGPaintingProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerStartProperty, SVGMarkerObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMidProperty, SVGMarkerObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, SVGMarkerObserver) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, SVGPaintingProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, SVGPaintingProperty) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty, + SVGTextPathObserver) +NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty, + URIObserverHashtable) +NS_DECLARE_FRAME_PROPERTY_RELEASABLE(BackgroundClipObserverProperty, + BackgroundClipRenderingObserver) + +template <class T> +static T* GetEffectProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame, + const FramePropertyDescriptor<T>* aProperty) { + if (!aURI) { + return nullptr; + } + + bool found; + T* prop = aFrame->GetProperty(aProperty, &found); + if (found) { + MOZ_ASSERT(prop, "this property should only store non-null values"); + return prop; + } + prop = new T(aURI, aFrame, false); + NS_ADDREF(prop); + aFrame->AddProperty(aProperty, prop); + return prop; +} + +static SVGPaintingProperty* GetPaintingProperty( + URLAndReferrerInfo* aURI, nsIFrame* aFrame, + const FramePropertyDescriptor<SVGPaintingProperty>* aProperty) { + return GetEffectProperty(aURI, aFrame, aProperty); +} + +static already_AddRefed<URLAndReferrerInfo> GetMarkerURI( + nsIFrame* aFrame, const StyleUrlOrNone nsStyleSVG::*aMarker) { + const StyleUrlOrNone& url = aFrame->StyleSVG()->*aMarker; + if (url.IsNone()) { + return nullptr; + } + return ResolveURLUsingLocalRef(aFrame, url.AsUrl()); +} + +bool SVGObserverUtils::GetAndObserveMarkers(nsIFrame* aMarkedFrame, + SVGMarkerFrame* (*aFrames)[3]) { + MOZ_ASSERT(!aMarkedFrame->GetPrevContinuation() && + aMarkedFrame->IsSVGGeometryFrame() && + static_cast<SVGGeometryElement*>(aMarkedFrame->GetContent()) + ->IsMarkable(), + "Bad frame"); + + bool foundMarker = false; + RefPtr<URLAndReferrerInfo> markerURL; + SVGMarkerObserver* observer; + nsIFrame* marker; + +#define GET_MARKER(type) \ + markerURL = GetMarkerURI(aMarkedFrame, &nsStyleSVG::mMarker##type); \ + observer = \ + GetEffectProperty(markerURL, aMarkedFrame, Marker##type##Property()); \ + marker = observer ? observer->GetAndObserveReferencedFrame( \ + LayoutFrameType::SVGMarker, nullptr) \ + : nullptr; \ + foundMarker = foundMarker || bool(marker); \ + (*aFrames)[SVGMark::e##type] = static_cast<SVGMarkerFrame*>(marker); + + GET_MARKER(Start) + GET_MARKER(Mid) + GET_MARKER(End) + +#undef GET_MARKER + + return foundMarker; +} + +// Note that the returned list will be empty in the case of a 'filter' property +// that only specifies CSS filter functions (no url()'s to SVG filters). +static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS( + nsIFrame* aFrame) { + MOZ_ASSERT(!aFrame->GetPrevContinuation(), "Require first continuation"); + + const nsStyleEffects* effects = aFrame->StyleEffects(); + if (!effects->HasFilters()) { + return nullptr; + } + + bool found; + SVGFilterObserverListForCSSProp* observers = + aFrame->GetProperty(FilterProperty(), &found); + if (found) { + MOZ_ASSERT(observers, "this property should only store non-null values"); + return observers; + } + observers = + new SVGFilterObserverListForCSSProp(effects->mFilters.AsSpan(), aFrame); + NS_ADDREF(observers); + aFrame->AddProperty(FilterProperty(), observers); + return observers; +} + +static SVGObserverUtils::ReferenceState GetAndObserveFilters( + SVGFilterObserverListForCSSProp* aObserverList, + nsTArray<SVGFilterFrame*>* aFilterFrames) { + if (!aObserverList) { + return SVGObserverUtils::eHasNoRefs; + } + + const nsTArray<RefPtr<SVGFilterObserver>>& observers = + aObserverList->GetObservers(); + if (observers.IsEmpty()) { + return SVGObserverUtils::eHasNoRefs; + } + + for (uint32_t i = 0; i < observers.Length(); i++) { + SVGFilterFrame* filter = observers[i]->GetAndObserveFilterFrame(); + if (!filter) { + if (aFilterFrames) { + aFilterFrames->Clear(); + } + return SVGObserverUtils::eHasRefsSomeInvalid; + } + if (aFilterFrames) { + aFilterFrames->AppendElement(filter); + } + } + + return SVGObserverUtils::eHasRefsAllValid; +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters( + nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames) { + SVGFilterObserverListForCSSProp* observerList = + GetOrCreateFilterObserverListForCSS(aFilteredFrame); + return mozilla::GetAndObserveFilters(observerList, aFilterFrames); +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetFiltersIfObserving( + nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames) { + SVGFilterObserverListForCSSProp* observerList = + aFilteredFrame->GetProperty(FilterProperty()); + return mozilla::GetAndObserveFilters(observerList, aFilterFrames); +} + +already_AddRefed<nsISupports> SVGObserverUtils::ObserveFiltersForCanvasContext( + CanvasRenderingContext2D* aContext, Element* aCanvasElement, + const Span<const StyleFilter> aFilters) { + return do_AddRef(new SVGFilterObserverListForCanvasContext( + aContext, aCanvasElement, aFilters)); +} + +void SVGObserverUtils::DetachFromCanvasContext(nsISupports* aAutoObserver) { + static_cast<SVGFilterObserverListForCanvasContext*>(aAutoObserver) + ->DetachFromContext(); +} + +static SVGPaintingProperty* GetOrCreateClipPathObserver( + nsIFrame* aClippedFrame) { + MOZ_ASSERT(!aClippedFrame->GetPrevContinuation(), + "Require first continuation"); + + const nsStyleSVGReset* svgStyleReset = aClippedFrame->StyleSVGReset(); + if (!svgStyleReset->mClipPath.IsUrl()) { + return nullptr; + } + const auto& url = svgStyleReset->mClipPath.AsUrl(); + RefPtr<URLAndReferrerInfo> pathURI = + ResolveURLUsingLocalRef(aClippedFrame, url); + return GetPaintingProperty(pathURI, aClippedFrame, ClipPathProperty()); +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveClipPath( + nsIFrame* aClippedFrame, SVGClipPathFrame** aClipPathFrame) { + if (aClipPathFrame) { + *aClipPathFrame = nullptr; + } + SVGPaintingProperty* observers = GetOrCreateClipPathObserver(aClippedFrame); + if (!observers) { + return eHasNoRefs; + } + bool frameTypeOK = true; + SVGClipPathFrame* frame = + static_cast<SVGClipPathFrame*>(observers->GetAndObserveReferencedFrame( + LayoutFrameType::SVGClipPath, &frameTypeOK)); + // Note that, unlike for filters, a reference to an ID that doesn't exist + // is not invalid for clip-path or mask. + if (!frameTypeOK || (frame && !frame->IsValid())) { + return eHasRefsSomeInvalid; + } + if (aClipPathFrame) { + *aClipPathFrame = frame; + } + return frame ? eHasRefsAllValid : eHasNoRefs; +} + +static SVGMaskObserverList* GetOrCreateMaskObserverList( + nsIFrame* aMaskedFrame) { + MOZ_ASSERT(!aMaskedFrame->GetPrevContinuation(), + "Require first continuation"); + + const nsStyleSVGReset* style = aMaskedFrame->StyleSVGReset(); + if (!style->HasMask()) { + return nullptr; + } + + MOZ_ASSERT(style->mMask.mImageCount > 0); + + bool found; + SVGMaskObserverList* prop = aMaskedFrame->GetProperty(MaskProperty(), &found); + if (found) { + MOZ_ASSERT(prop, "this property should only store non-null values"); + return prop; + } + prop = new SVGMaskObserverList(aMaskedFrame); + NS_ADDREF(prop); + aMaskedFrame->AddProperty(MaskProperty(), prop); + return prop; +} + +SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveMasks( + nsIFrame* aMaskedFrame, nsTArray<SVGMaskFrame*>* aMaskFrames) { + SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame); + if (!observerList) { + return eHasNoRefs; + } + + const nsTArray<RefPtr<SVGPaintingProperty>>& observers = + observerList->GetObservers(); + if (observers.IsEmpty()) { + return eHasNoRefs; + } + + ReferenceState state = eHasRefsAllValid; + + for (size_t i = 0; i < observers.Length(); i++) { + bool frameTypeOK = true; + SVGMaskFrame* maskFrame = + static_cast<SVGMaskFrame*>(observers[i]->GetAndObserveReferencedFrame( + LayoutFrameType::SVGMask, &frameTypeOK)); + MOZ_ASSERT(!maskFrame || frameTypeOK); + // XXXjwatt: this looks fishy + if (!frameTypeOK) { + // We can not find the specific SVG mask resource in the downloaded SVG + // document. There are two possibilities: + // 1. The given resource id is invalid. + // 2. The given resource id refers to a viewbox. + // + // Hand it over to the style image. + observerList->ResolveImage(i); + state = eHasRefsSomeInvalid; + } + if (aMaskFrames) { + aMaskFrames->AppendElement(maskFrame); + } + } + + return state; +} + +SVGGeometryElement* SVGObserverUtils::GetAndObserveTextPathsPath( + nsIFrame* aTextPathFrame) { + // Continuations can come and go during reflow, and we don't need to observe + // the referenced element more than once for a given node. + aTextPathFrame = aTextPathFrame->FirstContinuation(); + + SVGTextPathObserver* property = + aTextPathFrame->GetProperty(HrefAsTextPathProperty()); + + if (!property) { + nsIContent* content = aTextPathFrame->GetContent(); + nsAutoString href; + static_cast<SVGTextPathElement*>(content)->HrefAsString(href); + if (href.IsEmpty()) { + return nullptr; // no URL + } + + // There's no clear refererer policy spec about non-CSS SVG resource + // references Bug 1415044 to investigate which referrer we should use + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(content->OwnerDoc()); + RefPtr<URLAndReferrerInfo> target = + ResolveURLUsingLocalRef(aTextPathFrame, href, referrerInfo); + + property = + GetEffectProperty(target, aTextPathFrame, HrefAsTextPathProperty()); + if (!property) { + return nullptr; + } + } + + Element* element = property->GetAndObserveReferencedElement(); + return (element && element->IsNodeOfType(nsINode::eSHAPE)) + ? static_cast<SVGGeometryElement*>(element) + : nullptr; +} + +void SVGObserverUtils::InitiateResourceDocLoads(nsIFrame* aFrame) { + // We create observer objects and attach them to aFrame, but we do not + // make aFrame start observing the referenced frames. + Unused << GetOrCreateFilterObserverListForCSS(aFrame); + Unused << GetOrCreateClipPathObserver(aFrame); + Unused << GetOrCreateMaskObserverList(aFrame); +} + +void SVGObserverUtils::RemoveTextPathObserver(nsIFrame* aTextPathFrame) { + aTextPathFrame->RemoveProperty(HrefAsTextPathProperty()); +} + +nsIFrame* SVGObserverUtils::GetAndObserveTemplate( + nsIFrame* aFrame, HrefToTemplateCallback aGetHref) { + SVGTemplateElementObserver* observer = + aFrame->GetProperty(HrefToTemplateProperty()); + + if (!observer) { + nsAutoString href; + aGetHref(href); + if (href.IsEmpty()) { + return nullptr; // no URL + } + + // Convert href to an nsIURI + nsIContent* content = aFrame->GetContent(); + + nsCOMPtr<nsIURI> baseURI = content->GetBaseURI(); + if (nsContentUtils::IsLocalRefURL(href)) { + baseURI = GetBaseURLForLocalRef(content, baseURI); + } + nsCOMPtr<nsIURI> targetURI; + nsContentUtils::NewURIWithDocumentCharset( + getter_AddRefs(targetURI), href, content->GetUncomposedDoc(), baseURI); + + // There's no clear refererer policy spec about non-CSS SVG resource + // references. Bug 1415044 to investigate which referrer we should use. + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(content->OwnerDoc()); + RefPtr<URLAndReferrerInfo> target = + new URLAndReferrerInfo(targetURI, referrerInfo); + + observer = GetEffectProperty(target, aFrame, HrefToTemplateProperty()); + } + + return observer ? observer->GetAndObserveReferencedFrame() : nullptr; +} + +void SVGObserverUtils::RemoveTemplateObserver(nsIFrame* aFrame) { + aFrame->RemoveProperty(HrefToTemplateProperty()); +} + +Element* SVGObserverUtils::GetAndObserveBackgroundImage(nsIFrame* aFrame, + const nsAtom* aHref) { + bool found; + URIObserverHashtable* hashtable = + aFrame->GetProperty(BackgroundImageProperty(), &found); + if (!found) { + hashtable = new URIObserverHashtable(); + aFrame->AddProperty(BackgroundImageProperty(), hashtable); + } else { + MOZ_ASSERT(hashtable, "this property should only store non-null values"); + } + + nsAutoString elementId = u"#"_ns + nsDependentAtomString(aHref); + nsCOMPtr<nsIURI> targetURI; + nsContentUtils::NewURIWithDocumentCharset( + getter_AddRefs(targetURI), elementId, + aFrame->GetContent()->GetUncomposedDoc(), + aFrame->GetContent()->GetBaseURI()); + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(aFrame->GetContent()->OwnerDoc()); + RefPtr<URLAndReferrerInfo> url = + new URLAndReferrerInfo(targetURI, referrerInfo); + + return static_cast<SVGMozElementObserver*>( + hashtable + ->LookupOrInsertWith( + url, + [&] { + return MakeRefPtr<SVGMozElementObserver>( + url, aFrame, + /* aWatchImage */ true); + }) + .get()) + ->GetAndObserveReferencedElement(); +} + +Element* SVGObserverUtils::GetAndObserveBackgroundClip(nsIFrame* aFrame) { + bool found; + BackgroundClipRenderingObserver* obs = + aFrame->GetProperty(BackgroundClipObserverProperty(), &found); + if (!found) { + obs = new BackgroundClipRenderingObserver(aFrame); + NS_ADDREF(obs); + aFrame->AddProperty(BackgroundClipObserverProperty(), obs); + } + + return obs->GetAndObserveReferencedElement(); +} + +SVGPaintServerFrame* SVGObserverUtils::GetAndObservePaintServer( + nsIFrame* aPaintedFrame, StyleSVGPaint nsStyleSVG::*aPaint) { + // 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* paintedFrame = aPaintedFrame; + if (paintedFrame->GetContent()->IsText()) { + paintedFrame = paintedFrame->GetParent(); + nsIFrame* grandparent = paintedFrame->GetParent(); + if (grandparent && grandparent->IsSVGTextFrame()) { + paintedFrame = grandparent; + } + } + + const nsStyleSVG* svgStyle = paintedFrame->StyleSVG(); + if (!(svgStyle->*aPaint).kind.IsPaintServer()) { + return nullptr; + } + + RefPtr<URLAndReferrerInfo> paintServerURL = ResolveURLUsingLocalRef( + paintedFrame, (svgStyle->*aPaint).kind.AsPaintServer()); + + MOZ_ASSERT(aPaint == &nsStyleSVG::mFill || aPaint == &nsStyleSVG::mStroke); + PaintingPropertyDescriptor propDesc = + (aPaint == &nsStyleSVG::mFill) ? FillProperty() : StrokeProperty(); + SVGPaintingProperty* property = + GetPaintingProperty(paintServerURL, paintedFrame, propDesc); + if (!property) { + return nullptr; + } + nsIFrame* result = property->GetAndObserveReferencedFrame(); + if (!result) { + return nullptr; + } + + LayoutFrameType type = result->Type(); + if (type != LayoutFrameType::SVGLinearGradient && + type != LayoutFrameType::SVGRadialGradient && + type != LayoutFrameType::SVGPattern) { + return nullptr; + } + + return static_cast<SVGPaintServerFrame*>(result); +} + +void SVGObserverUtils::UpdateEffects(nsIFrame* aFrame) { + NS_ASSERTION(aFrame->GetContent()->IsElement(), + "aFrame's content should be an element"); + + aFrame->RemoveProperty(FilterProperty()); + aFrame->RemoveProperty(MaskProperty()); + aFrame->RemoveProperty(ClipPathProperty()); + aFrame->RemoveProperty(MarkerStartProperty()); + aFrame->RemoveProperty(MarkerMidProperty()); + aFrame->RemoveProperty(MarkerEndProperty()); + aFrame->RemoveProperty(FillProperty()); + aFrame->RemoveProperty(StrokeProperty()); + aFrame->RemoveProperty(BackgroundImageProperty()); + + // Ensure that the filter is repainted correctly + // We can't do that in OnRenderingChange as the referenced frame may + // not be valid + GetOrCreateFilterObserverListForCSS(aFrame); + + if (aFrame->IsSVGGeometryFrame() && + static_cast<SVGGeometryElement*>(aFrame->GetContent())->IsMarkable()) { + // Set marker properties here to avoid reference loops + RefPtr<URLAndReferrerInfo> markerURL = + GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart); + GetEffectProperty(markerURL, aFrame, MarkerStartProperty()); + markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid); + GetEffectProperty(markerURL, aFrame, MarkerMidProperty()); + markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd); + GetEffectProperty(markerURL, aFrame, MarkerEndProperty()); + } +} + +void SVGObserverUtils::AddRenderingObserver(Element* aElement, + SVGRenderingObserver* aObserver) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (!observers) { + observers = new SVGRenderingObserverSet(); + aElement->SetProperty(nsGkAtoms::renderingobserverset, observers, + nsINode::DeleteProperty<SVGRenderingObserverSet>); + } + aElement->SetHasRenderingObservers(true); + observers->Add(aObserver); +} + +void SVGObserverUtils::RemoveRenderingObserver( + Element* aElement, SVGRenderingObserver* aObserver) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (observers) { + NS_ASSERTION(observers->Contains(aObserver), + "removing observer from an element we're not observing?"); + observers->Remove(aObserver); + if (observers->IsEmpty()) { + aElement->SetHasRenderingObservers(false); + } + } +} + +void SVGObserverUtils::RemoveAllRenderingObservers(Element* aElement) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (observers) { + observers->RemoveAll(); + aElement->SetHasRenderingObservers(false); + } +} + +void SVGObserverUtils::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->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty()); + + SVGRenderingObserverSet* observers = GetObserverSet(content->AsElement()); + if (observers) { + observers->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()) { + observers = GetObserverSet(f->GetContent()->AsElement()); + if (observers) { + observers->InvalidateAll(); + return; + } + } + } +} + +void SVGObserverUtils::InvalidateDirectRenderingObservers( + Element* aElement, uint32_t aFlags /* = 0 */) { + if (nsIFrame* frame = aElement->GetPrimaryFrame()) { + // If the rendering has changed, the bounds may well have changed too: + frame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty()); + } + + if (aElement->HasRenderingObservers()) { + SVGRenderingObserverSet* observers = GetObserverSet(aElement); + if (observers) { + if (aFlags & INVALIDATE_REFLOW) { + observers->InvalidateAllForReflow(); + } else { + observers->InvalidateAll(); + } + } + } +} + +void SVGObserverUtils::InvalidateDirectRenderingObservers( + nsIFrame* aFrame, uint32_t aFlags /* = 0 */) { + nsIContent* content = aFrame->GetContent(); + if (content && content->IsElement()) { + InvalidateDirectRenderingObservers(content->AsElement(), aFlags); + } +} + +already_AddRefed<nsIURI> SVGObserverUtils::GetBaseURLForLocalRef( + nsIContent* content, nsIURI* aDocURI) { + MOZ_ASSERT(content); + + // Content is in a shadow tree. If this URL was specified in the subtree + // referenced by the <use>, 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 (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) { + if (nsIURI* originalURI = use->GetSourceDocURI()) { + bool isEqualsExceptRef = false; + aDocURI->EqualsExceptRef(originalURI, &isEqualsExceptRef); + if (isEqualsExceptRef) { + return do_AddRef(originalURI); + } + } + } + + // For a local-reference URL, resolve that fragment against the current + // document that relative URLs are resolved against. + return do_AddRef(content->OwnerDoc()->GetDocumentURI()); +} + +already_AddRefed<URLAndReferrerInfo> SVGObserverUtils::GetFilterURI( + nsIFrame* aFrame, const StyleFilter& aFilter) { + MOZ_ASSERT(!aFrame->StyleEffects()->mFilters.IsEmpty() || + !aFrame->StyleEffects()->mBackdropFilters.IsEmpty() || + !aFrame->GetContent()->GetParent()); + MOZ_ASSERT(aFilter.IsUrl()); + return ResolveURLUsingLocalRef(aFrame, aFilter.AsUrl()); +} + +} // namespace mozilla diff --git a/layout/svg/SVGObserverUtils.h b/layout/svg/SVGObserverUtils.h new file mode 100644 index 0000000000..17bd012078 --- /dev/null +++ b/layout/svg/SVGObserverUtils.h @@ -0,0 +1,420 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGOBSERVERUTILS_H_ +#define LAYOUT_SVG_SVGOBSERVERUTILS_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/IDTracker.h" +#include "FrameProperties.h" +#include "nsID.h" +#include "nsIFrame.h" // only for LayoutFrameType +#include "nsIMutationObserver.h" +#include "nsISupports.h" +#include "nsISupportsImpl.h" +#include "nsIReferrerInfo.h" +#include "nsStringFwd.h" +#include "nsStubMutationObserver.h" +#include "nsStyleStruct.h" +#include "nsCycleCollectionParticipant.h" + +class nsAtom; +class nsIFrame; +class nsIURI; + +namespace mozilla { +class SVGClipPathFrame; +class SVGFilterFrame; +class SVGMarkerFrame; +class SVGMaskFrame; +class SVGPaintServerFrame; + +namespace dom { +class CanvasRenderingContext2D; +class Element; +class SVGGeometryElement; +} // namespace dom +} // namespace mozilla + +namespace mozilla { + +/* + * This class contains URL and referrer information (referrer and referrer + * policy). + * We use it to pass to svg system instead of nsIURI. The object brings referrer + * and referrer policy so we can send correct Referer headers. + */ +class URLAndReferrerInfo { + public: + URLAndReferrerInfo(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo) + : mURI(aURI), mReferrerInfo(aReferrerInfo) { + MOZ_ASSERT(aURI); + } + + URLAndReferrerInfo(nsIURI* aURI, const URLExtraData& aExtraData) + : mURI(aURI), mReferrerInfo(aExtraData.ReferrerInfo()) { + MOZ_ASSERT(aURI); + } + + NS_INLINE_DECL_REFCOUNTING(URLAndReferrerInfo) + + nsIURI* GetURI() const { return mURI; } + nsIReferrerInfo* GetReferrerInfo() const { return mReferrerInfo; } + + bool operator==(const URLAndReferrerInfo& aRHS) const; + + private: + ~URLAndReferrerInfo() = default; + + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIReferrerInfo> mReferrerInfo; +}; + +/** + * This interface allows us to be notified when a piece of SVG content is + * re-rendered. + * + * Concrete implementations of this base class need to implement + * GetReferencedElementWithoutObserving to specify the SVG element that + * they'd like to monitor for rendering changes, and they need to implement + * OnRenderingChange to specify how we'll react when that content gets + * re-rendered. They also need to implement a constructor and destructor, + * which should call StartObserving and StopObserving, respectively. + * + * The referenced element is generally looked up and stored during + * construction. If the referenced element is in an extenal SVG resource + * document, the lookup code will initiate loading of the external resource and + * OnRenderingChange will be called once the element in the external resource + * is available. + * + * Although the referenced element may be found and stored during construction, + * observing for rendering changes does not start until requested. + */ +class SVGRenderingObserver : public nsStubMutationObserver { + protected: + virtual ~SVGRenderingObserver() = default; + + public: + using Element = dom::Element; + + SVGRenderingObserver() : mInObserverSet(false) {} + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + /** + * Called when non-DOM-mutation changes to the observed element should likely + * cause the rendering of our observer to change. This includes changes to + * CSS computed values, but also changes to rendering observers that the + * observed element itself may have (for example, when we're being used to + * observe an SVG pattern, and an element in that pattern references and + * observes a gradient that has changed). + */ + void OnNonDOMMutationRenderingChange(); + + // When a SVGRenderingObserver list gets forcibly cleared, it uses this + // callback to notify every observer that's cleared from it, so they can + // react. + void NotifyEvictedFromRenderingObserverSet(); + + nsIFrame* GetAndObserveReferencedFrame(); + /** + * @param aOK this is only for the convenience of callers. We set *aOK to + * false if the frame is the wrong type + */ + nsIFrame* GetAndObserveReferencedFrame(mozilla::LayoutFrameType aFrameType, + bool* aOK); + + Element* GetAndObserveReferencedElement(); + + virtual bool ObservesReflow() { return false; } + + protected: + void StartObserving(); + void StopObserving(); + + /** + * Called whenever the rendering of the observed element may have changed. + * + * More specifically, this method is called whenever DOM mutation occurs in + * the observed element's subtree, or whenever + * SVGObserverUtils::InvalidateRenderingObservers or + * SVGObserverUtils::InvalidateDirectRenderingObservers is called for the + * observed element's frame. + * + * Subclasses should override this method to handle rendering changes + * appropriately. + */ + virtual void OnRenderingChange() = 0; + + virtual Element* GetReferencedElementWithoutObserving() = 0; + +#ifdef DEBUG + void DebugObserverSet(); +#endif + + // Whether we're in our observed element's observer set at this time. + bool mInObserverSet; +}; + +class SVGObserverUtils { + public: + using CanvasRenderingContext2D = dom::CanvasRenderingContext2D; + using Element = dom::Element; + using SVGGeometryElement = dom::SVGGeometryElement; + using HrefToTemplateCallback = const std::function<void(nsAString&)>&; + + /** + * Ensures that that if the given frame requires any resources that are in + * SVG resource documents that the loading of those documents is initiated. + * This does not make aFrame start to observe any elements that it + * references. + */ + static void InitiateResourceDocLoads(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 must be a first-continuation. + */ + static void AddRenderingObserver(Element* aElement, + SVGRenderingObserver* aObserver); + /** + * @param aFrame must be a first-continuation. + */ + static void RemoveRenderingObserver(Element* aElement, + SVGRenderingObserver* 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. + * MutationObservers::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 + * SVGRenderingObservers) 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 }; + + enum ReferenceState { + /// Has no references to SVG filters (may still have CSS filter functions!) + eHasNoRefs, + eHasRefsAllValid, + eHasRefsSomeInvalid, + }; + + /** + * 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 the paint server for aPaintedFrame. + */ + static SVGPaintServerFrame* GetAndObservePaintServer( + nsIFrame* aPaintedFrame, mozilla::StyleSVGPaint nsStyleSVG::*aPaint); + + /** + * Get the start/mid/end-markers for the given frame, and add the frame as + * an observer to those markers. Returns true if at least one marker type is + * found, false otherwise. + */ + static bool GetAndObserveMarkers(nsIFrame* aMarkedFrame, + SVGMarkerFrame* (*aFrames)[3]); + + /** + * Get the frames of the SVG filters applied to the given frame, and add the + * frame as an observer to those filter frames. + * + * NOTE! A return value of eHasNoRefs does NOT mean that there are no filters + * to be applied, only that there are no references to SVG filter elements. + * + * XXX Callers other than ComputePostEffectsInkOverflowRect and + * SVGUtils::GetPostFilterInkOverflowRect should not need to initiate + * observing. If we have a bug that causes invalidation (which would remove + * observers) between reflow and painting, then we don't really want to + * re-add abservers during painting. That has the potential to hide logic + * bugs, or cause later invalidation problems. However, let's not change + * that behavior just yet due to the regression potential. + */ + static ReferenceState GetAndObserveFilters( + nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames); + + /** + * If the given frame is already observing SVG filters, this function gets + * those filters. If the frame is not already observing filters this + * function assumes that it doesn't have anything to observe. + */ + static ReferenceState GetFiltersIfObserving( + nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames); + + /** + * Starts observing filters for a <canvas> element's CanvasRenderingContext2D. + * + * Returns a RAII object that the caller should make sure is released once + * the CanvasRenderingContext2D is no longer using them (that is, when the + * CanvasRenderingContext2D "drawing style state" on which the filters were + * set is destroyed or has its filter style reset). + * + * XXXjwatt: It's a bit unfortunate that both we and + * CanvasRenderingContext2D::UpdateFilter process the list of StyleFilter + * objects separately. It would be better to refactor things so that we only + * do that work once. + */ + static already_AddRefed<nsISupports> ObserveFiltersForCanvasContext( + CanvasRenderingContext2D* aContext, Element* aCanvasElement, + Span<const StyleFilter> aFilters); + + /** + * Called when cycle collecting CanvasRenderingContext2D, and requires the + * RAII object returned from ObserveFiltersForCanvasContext to be passed in. + * + * XXXjwatt: I don't think this is doing anything useful. All we do under + * this function is clear a raw C-style (i.e. not strong) pointer. That's + * clearly not helping in breaking any cycles. The fact that we MOZ_CRASH + * in OnRenderingChange if that pointer is null indicates that this isn't + * even doing anything useful in terms of preventing further invalidation + * from any observed filters. + */ + static void DetachFromCanvasContext(nsISupports* aAutoObserver); + + /** + * Get the frame of the SVG clipPath applied to aClippedFrame, if any, and + * set up aClippedFrame as a rendering observer of the clipPath's frame, to + * be invalidated if it changes. + * + * Currently we only have support for 'clip-path' with a single item, but the + * spec. now says 'clip-path' can be set to an arbitrary number of items. + * Once we support that, aClipPathFrame will need to be an nsTArray as it + * is for 'filter' and 'mask'. Currently a return value of eHasNoRefs means + * that there is no clipping at all, but once we support more than one item + * then - as for filter and mask - we could still have basic shape clipping + * to apply even if there are no references to SVG clipPath elements. + * + * Note that, unlike for filters, a reference to an ID that doesn't exist + * is not invalid for clip-path or mask. We will return eHasNoRefs in that + * case. + */ + static ReferenceState GetAndObserveClipPath( + nsIFrame* aClippedFrame, SVGClipPathFrame** aClipPathFrame); + + /** + * If masking is applied to aMaskedFrame, gets an array of any SVG masks + * that are referenced, setting up aMaskFrames as a rendering observer of + * those masks (if any). + * + * NOTE! A return value of eHasNoRefs does NOT mean that there are no masks + * to be applied, only that there are no references to SVG mask elements. + * + * Note that, unlike for filters, a reference to an ID that doesn't exist + * is not invalid for clip-path or mask. We will return eHasNoRefs in that + * case. + */ + static ReferenceState GetAndObserveMasks( + nsIFrame* aMaskedFrame, nsTArray<SVGMaskFrame*>* aMaskFrames); + + /** + * Get the SVGGeometryElement that is referenced by aTextPathFrame, and make + * aTextPathFrame start observing rendering changes to that element. + */ + static SVGGeometryElement* GetAndObserveTextPathsPath( + nsIFrame* aTextPathFrame); + + /** + * Make aTextPathFrame stop observing rendering changes to the + * SVGGeometryElement that it references, if any. + */ + static void RemoveTextPathObserver(nsIFrame* aTextPathFrame); + + /** + * Gets the nsIFrame of a referenced SVG "template" element, if any, and + * makes aFrame start observing rendering changes to the template element. + * + * Template elements: some elements like gradients, pattern or filter can + * reference another element of the same type using their 'href' attribute, + * and use that element as a template that provides attributes or content + * that is missing from the referring element. + * + * The frames that this function is called for do not have a common base + * class, which is why it is necessary to pass in a function that can be + * used as a callback to lazily get the href value, if necessary. + */ + static nsIFrame* GetAndObserveTemplate(nsIFrame* aFrame, + HrefToTemplateCallback aGetHref); + + static void RemoveTemplateObserver(nsIFrame* aFrame); + + /** + * Gets an arbitrary element and starts observing it. Used to implement + * '-moz-element'. + * + * Note that bug 1496065 has been filed to remove support for referencing + * arbitrary elements using '-moz-element'. + */ + static Element* GetAndObserveBackgroundImage(nsIFrame* aFrame, + const nsAtom* aHref); + + /** + * Gets an arbitrary element and starts observing it. Used to detect + * invalidation changes for background-clip:text. + */ + static Element* GetAndObserveBackgroundClip(nsIFrame* aFrame); + + /** + * A helper function to resolve filter URL. + */ + static already_AddRefed<URLAndReferrerInfo> GetFilterURI( + nsIFrame* aFrame, const StyleFilter& aFilter); + + /** + * Return a baseURL for resolving a local-ref URL. + * + * @param aContent an element which uses a local-ref property. Here are some + * examples: + * <rect fill=url(#foo)> + * <circle clip-path=url(#foo)> + * <use xlink:href="#foo"> + */ + static already_AddRefed<nsIURI> GetBaseURLForLocalRef(nsIContent* aContent, + nsIURI* aDocURI); +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGOBSERVERUTILS_H_ diff --git a/layout/svg/SVGOuterSVGFrame.cpp b/layout/svg/SVGOuterSVGFrame.cpp new file mode 100644 index 0000000000..c49e5769e6 --- /dev/null +++ b/layout/svg/SVGOuterSVGFrame.cpp @@ -0,0 +1,1033 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGOuterSVGFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfxContext.h" +#include "nsDisplayList.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsLayoutUtils.h" +#include "nsObjectLoadingContent.h" +#include "nsSubDocumentFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGForeignObjectFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGViewElement.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Implementation helpers + +void SVGOuterSVGFrame::RegisterForeignObject(SVGForeignObjectFrame* aFrame) { + NS_ASSERTION(aFrame, "Who on earth is calling us?!"); + + if (!mForeignObjectHash) { + mForeignObjectHash = MakeUnique<nsTHashSet<SVGForeignObjectFrame*>>(); + } + + NS_ASSERTION(!mForeignObjectHash->Contains(aFrame), + "SVGForeignObjectFrame already registered!"); + + mForeignObjectHash->Insert(aFrame); + + NS_ASSERTION(mForeignObjectHash->Contains(aFrame), + "Failed to register SVGForeignObjectFrame!"); +} + +void SVGOuterSVGFrame::UnregisterForeignObject(SVGForeignObjectFrame* aFrame) { + NS_ASSERTION(aFrame, "Who on earth is calling us?!"); + NS_ASSERTION(mForeignObjectHash && mForeignObjectHash->Contains(aFrame), + "SVGForeignObjectFrame not in registry!"); + return mForeignObjectHash->Remove(aFrame); +} + +} // namespace mozilla + +//---------------------------------------------------------------------- +// Implementation + +nsContainerFrame* NS_NewSVGOuterSVGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGOuterSVGFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGFrame) + +SVGOuterSVGFrame::SVGOuterSVGFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID), + mCallingReflowSVG(false), + mFullZoom(PresContext()->GetFullZoom()), + mViewportInitialized(false), + mIsRootContent(false), + mIsInObjectOrEmbed(false), + mIsInIframe(false) { + // Outer-<svg> has CSS layout, so remove this bit: + RemoveStateBits(NS_FRAME_SVG_LAYOUT); + AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); +} + +// The CSS Containment spec says that size-contained replaced elements must be +// treated as having an intrinsic width and height of 0. That's applicable to +// outer SVG frames, unless they're the outermost element (in which case +// they're not really "replaced", and there's no outer context to contain sizes +// from leaking into). Hence, we check for a parent element before we bother +// testing for 'contain:size'. +static inline ContainSizeAxes ContainSizeAxesIfApplicable( + const SVGOuterSVGFrame* aFrame) { + if (!aFrame->GetContent()->GetParent()) { + return ContainSizeAxes(false, false); + } + return aFrame->GetContainSizeAxes(); +} + +void SVGOuterSVGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), + "Content is not an SVG 'svg' element!"); + + AddStateBits(NS_FRAME_REFLOW_ROOT | 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 SVGContainerFrame. + // We don't create other SVG frames if PassesConditionalProcessingTests + // returns false, but since we do create SVGOuterSVGFrame 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. + auto* svg = static_cast<SVGSVGElement*>(aContent); + if (!svg->PassesConditionalProcessingTests()) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); + + Document* doc = mContent->GetUncomposedDoc(); + mIsRootContent = doc && doc->GetRootElement() == mContent; + + if (mIsRootContent) { + if (nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell()) { + RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext(); + if (const Maybe<nsString>& type = bc->GetEmbedderElementType()) { + mIsInObjectOrEmbed = + nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type); + mIsInIframe = nsGkAtoms::iframe->Equals(*type); + } + } + } + + MaybeSendIntrinsicSizeAndRatioToEmbedder(); +} + +//---------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(SVGOuterSVGFrame) + NS_QUERYFRAME_ENTRY(SVGOuterSVGFrame) + NS_QUERYFRAME_ENTRY(ISVGSVGFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods +//---------------------------------------------------------------------- +// reflowing + +/* virtual */ +nscoord SVGOuterSVGFrame::GetMinISize(gfxContext* aRenderingContext) { + nscoord result; + DISPLAY_MIN_INLINE_SIZE(this, result); + + // If this ever changes to return something other than zero, then + // nsSubDocumentFrame::GetMinISize will also need to change. + result = nscoord(0); + + return result; +} + +/* virtual */ +nscoord SVGOuterSVGFrame::GetPrefISize(gfxContext* aRenderingContext) { + nscoord result; + DISPLAY_PREF_INLINE_SIZE(this, result); + + SVGSVGElement* svg = static_cast<SVGSVGElement*>(GetContent()); + WritingMode wm = GetWritingMode(); + const SVGAnimatedLength& isize = + wm.IsVertical() ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT] + : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + + if (Maybe<nscoord> containISize = + ContainSizeAxesIfApplicable(this).ContainIntrinsicISize(*this)) { + result = *containISize; + } else if (isize.IsPercentage()) { + // If we are here, our inline size attribute is a percentage either + // explicitly (via an attribute value) or implicitly (by being unset, which + // is treated as 100%). The following if-condition, deciding to return + // either the fallback intrinsic size or zero, is made to match blink and + // webkit's behavior for webcompat. + if (isize.IsExplicitlySet() || StylePosition()->ISize(wm).HasPercent() || + !GetAspectRatio()) { + result = wm.IsVertical() ? kFallbackIntrinsicSize.height + : kFallbackIntrinsicSize.width; + } else { + result = nscoord(0); + } + } else { + result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg)); + if (result < 0) { + result = nscoord(0); + } + } + + return result; +} + +/* virtual */ +IntrinsicSize SVGOuterSVGFrame::GetIntrinsicSize() { + // XXXjwatt Note that here we want to return the CSS width/height if they're + // specified and we're embedded inside an nsIObjectLoadingContent. + + const auto containAxes = ContainSizeAxesIfApplicable(this); + if (containAxes.IsBoth()) { + // Intrinsic size of 'contain:size' replaced elements is determined by + // contain-intrinsic-size, defaulting to 0x0. + return containAxes.ContainIntrinsicSize(IntrinsicSize(0, 0), *this); + } + + SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent()); + const SVGAnimatedLength& width = + content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + const SVGAnimatedLength& height = + content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + + IntrinsicSize intrinsicSize; + + if (!width.IsPercentage()) { + nscoord val = + nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)); + intrinsicSize.width.emplace(std::max(val, 0)); + } + + if (!height.IsPercentage()) { + nscoord val = + nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content)); + intrinsicSize.height.emplace(std::max(val, 0)); + } + + return containAxes.ContainIntrinsicSize(intrinsicSize, *this); +} + +/* virtual */ +AspectRatio SVGOuterSVGFrame::GetIntrinsicRatio() const { + if (ContainSizeAxesIfApplicable(this).IsAny()) { + return AspectRatio(); + } + + // 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: https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS + + auto* content = static_cast<SVGSVGElement*>(GetContent()); + const SVGAnimatedLength& width = + content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + const SVGAnimatedLength& height = + content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + if (!width.IsPercentage() && !height.IsPercentage()) { + // Use width/height ratio only if + // 1. it's not a degenerate ratio, and + // 2. width and height are non-negative numbers. + // Otherwise, we use the viewbox rect. + // https://github.com/w3c/csswg-drafts/issues/6286 + const float w = width.GetAnimValue(content); + const float h = height.GetAnimValue(content); + if (w > 0.0f && h > 0.0f) { + return AspectRatio::FromSize(w, h); + } + } + + const auto& viewBox = content->GetViewBoxInternal(); + if (viewBox.HasRect()) { + const auto& anim = viewBox.GetAnimValue(); + return AspectRatio::FromSize(anim.width, anim.height); + } + + return SVGDisplayContainerFrame::GetIntrinsicRatio(); +} + +/* virtual */ +nsIFrame::SizeComputationResult SVGOuterSVGFrame::ComputeSize( + gfxContext* aRenderingContext, WritingMode aWritingMode, + const LogicalSize& aCBSize, nscoord aAvailableISize, + const LogicalSize& aMargin, const LogicalSize& aBorderPadding, + const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { + if (IsRootOfImage() || mIsInObjectOrEmbed) { + // 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, AspectRatioUsage::None}; + } + + 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(aWritingMode) != NS_UNCONSTRAINEDSIZE && + aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE, + "root should not have auto-width/height containing block"); + + if (!mIsInIframe) { + cbSize.ISize(aWritingMode) *= PresContext()->GetFullZoom(); + cbSize.BSize(aWritingMode) *= 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*>(GetContent()); + + const SVGAnimatedLength& width = + content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + if (width.IsPercentage()) { + MOZ_ASSERT(!intrinsicSize.width, + "GetIntrinsicSize should have reported no intrinsic width"); + float val = width.GetAnimValInSpecifiedUnits() / 100.0f; + intrinsicSize.width.emplace(std::max(val, 0.0f) * + cbSize.Width(aWritingMode)); + } + + const SVGAnimatedLength& height = + content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + NS_ASSERTION(aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE, + "root should not have auto-height containing block"); + if (height.IsPercentage()) { + MOZ_ASSERT(!intrinsicSize.height, + "GetIntrinsicSize should have reported no intrinsic height"); + float val = height.GetAnimValInSpecifiedUnits() / 100.0f; + intrinsicSize.height.emplace(std::max(val, 0.0f) * + cbSize.Height(aWritingMode)); + } + MOZ_ASSERT(intrinsicSize.height && intrinsicSize.width, + "We should have just handled the only situation where" + "we lack an intrinsic height or width."); + } + + return {ComputeSizeWithIntrinsicDimensions( + aRenderingContext, aWritingMode, intrinsicSize, GetAspectRatio(), + cbSize, aMargin, aBorderPadding, aSizeOverrides, aFlags), + AspectRatioUsage::None}; +} + +void SVGOuterSVGFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("SVGOuterSVGFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + NS_FRAME_TRACE( + NS_FRAME_TRACE_CALLS, + ("enter SVGOuterSVGFrame::Reflow: availSize=%d,%d", + aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); + + MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); + + 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*>(GetContent()); + + auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( + 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->MarkSubtreeDirty(); + for (nsIFrame* child : anonChild->PrincipalChildList()) { + child->MarkSubtreeDirty(); + } + } + changeBits |= COORD_CONTEXT_CHANGED; + svgElem->SetViewportSize(newViewportSize); + } + if (mFullZoom != PresContext()->GetFullZoom() && !mIsInIframe) { + 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 (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + ReflowSVGNonDisplayText(this); + } else { + // Update the mRects and ink overflow rects of all our descendants, + // including our anonymous wrapper kid: + 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 ink 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 ink 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 ink 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(); + + // An outer SVG will be here as a nondisplay if it fails the conditional + // processing test. In that case, we don't maintain its overflow. + if (!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + if (!mIsRootContent) { + aDesiredSize.mOverflowAreas.InkOverflow().UnionRect( + aDesiredSize.mOverflowAreas.InkOverflow(), + anonKid->InkOverflowRect() + anonKid->GetPosition()); + } + FinishAndStoreOverflow(&aDesiredSize); + } + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit SVGOuterSVGFrame::Reflow: size=%d,%d", + aDesiredSize.Width(), aDesiredSize.Height())); +} + +void SVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput) { + SVGDisplayContainerFrame::DidReflow(aPresContext, aReflowInput); + + // Make sure elements styled by :hover get updated if script/animation moves + // them under or out from under the pointer: + PresShell()->SynthesizeMouseMove(false); +} + +/* virtual */ +void SVGOuterSVGFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) { + // See the comments in Reflow above. + + // WARNING!! Keep this in sync with Reflow above! + + if (!mIsRootContent) { + nsIFrame* anonKid = PrincipalChildList().FirstChild(); + aOverflowAreas.InkOverflow().UnionRect( + aOverflowAreas.InkOverflow(), + anonKid->InkOverflowRect() + anonKid->GetPosition()); + } +} + +//---------------------------------------------------------------------- +// container methods + +/** + * Used to paint/hit-test SVG when SVG display lists are disabled. + */ +class nsDisplayOuterSVG final : public nsPaintedDisplayItem { + public: + nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder, SVGOuterSVGFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayOuterSVG); + } + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOuterSVG) + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) override; + virtual void Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aContext) override; + + virtual void ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + + NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG) +}; + +void nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + SVGOuterSVGFrame* outerSVGFrame = static_cast<SVGOuterSVGFrame*>(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) / + AppUnitsPerCSSPixel(); + + auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( + outerSVGFrame->PrincipalChildList().FirstChild()); + + nsIFrame* frame = + SVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint); + if (frame) { + aOutFrames->AppendElement(frame); + } +} + +void nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* 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 = GetPaintRect(aBuilder, aContext).Intersect(viewportRect); + + uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + nsIntRect contentAreaDirtyRect = + (clipRect - viewportRect.TopLeft()).ToOutsidePixels(appUnitsPerDevPixel); + + gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( + viewportRect.TopLeft(), appUnitsPerDevPixel); + + aContext->Save(); + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + // 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 = SVGUtils::GetCSSPxToDevPxMatrix(mFrame) * + gfxMatrix::Translation(devPixelOffset); + SVGUtils::PaintFrameWithEffects(mFrame, *aContext, tm, imgParams, + &contentAreaDirtyRect); + aContext->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 SVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren( + nsIFrame* aFrame) { + nsRegion result; + if (mForeignObjectHash && mForeignObjectHash->Count()) { + for (const auto& key : *mForeignObjectHash) { + result.Or(result, key->GetInvalidRegion()); + } + } + return result; +} + +void nsDisplayOuterSVG::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* frame = static_cast<SVGOuterSVGFrame*>(mFrame); + frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame)); + + nsRegion result = frame->GetInvalidRegion(); + result.MoveBy(ToReferenceFrame()); + frame->ClearInvalidRegion(); + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); + aInvalidRegion->Or(*aInvalidRegion, result); +} + +nsresult SVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None && + !HasAnyStateBits(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; + + SVGUtils::NotifyChildrenOfSVGChange( + PrincipalChildList().FirstChild(), + aAttribute == nsGkAtoms::viewBox + ? TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED + : TRANSFORM_CHANGED); + + if (aAttribute != nsGkAtoms::transform) { + static_cast<SVGSVGElement*>(GetContent()) + ->ChildrenOnlyTransformChanged(); + } + } + if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::viewBox) { + // Don't call ChildrenOnlyTransformChanged() here, since we call it + // under Reflow if the width/height/viewBox actually changed. + + MaybeSendIntrinsicSizeAndRatioToEmbedder(); + + if (!mIsInObjectOrEmbed) { + // We are not embedded by reference, so our 'width' and 'height' + // attributes are not overridden (and viewBox may influence our + // intrinsic aspect ratio). We need to reflow. + PresShell()->FrameNeedsReflow( + this, IntrinsicDirty::FrameAncestorsAndDescendants, + NS_FRAME_IS_DIRTY); + } + } + } + + return NS_OK; +} + +bool SVGOuterSVGFrame::IsSVGTransformed(Matrix* aOwnTransform, + Matrix* aFromParentTransform) const { + // Our anonymous child's HasChildrenOnlyTransform() implementation makes sure + // our children-only transforms are applied to our children. We only care + // about transforms that transform our own frame here. + + bool foundTransform = false; + + SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent()); + SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList(); + if ((transformList && transformList->HasTransform()) || + content->GetAnimateMotionTransform()) { + if (aOwnTransform) { + *aOwnTransform = gfx::ToMatrix( + content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent)); + } + foundTransform = true; + } + + return foundTransform; +} + +//---------------------------------------------------------------------- +// painting + +void SVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return; + } + + DisplayBorderBackgroundOutline(aBuilder, aLists); + + nsRect visibleRect = aBuilder->GetVisibleRect(); + nsRect dirtyRect = aBuilder->GetDirtyRect(); + + // Per-spec, we always clip root-<svg> even when 'overflow' has its initial + // value of 'visible'. See also the "ink overflow" comments in Reflow. + DisplayListClipState::AutoSaveRestore autoSR(aBuilder); + if (mIsRootContent || StyleDisplay()->IsScrollableOverflow()) { + autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this); + visibleRect = visibleRect.Intersect(GetContentRectRelativeToSelf()); + dirtyRect = dirtyRect.Intersect(GetContentRectRelativeToSelf()); + } + + nsDisplayListBuilder::AutoBuildingDisplayList building( + aBuilder, this, visibleRect, dirtyRect); + + if ((aBuilder->IsForEventDelivery() && + NS_SVGDisplayListHitTestingEnabled()) || + (!aBuilder->IsForEventDelivery() && NS_SVGDisplayListPaintingEnabled())) { + nsDisplayList* contentList = aLists.Content(); + nsDisplayListSet set(contentList, contentList, contentList, contentList, + contentList, contentList); + BuildDisplayListForNonBlockChildren(aBuilder, set); + } else if (IsVisibleForPainting() || !aBuilder->IsForPainting()) { + aBuilder->BuildCompositorHitTestInfoIfNeeded(this, + aLists.BorderBackground()); + aLists.Content()->AppendNewToTop<nsDisplayOuterSVG>(aBuilder, this); + } +} + +//---------------------------------------------------------------------- +// ISVGSVGFrame methods: + +void SVGOuterSVGFrame::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*>(GetContent()); + + if (aFlags & COORD_CONTEXT_CHANGED) { + if (content->HasViewBox()) { + // 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); + } + } + + SVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), + aFlags); +} + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods: + +void SVGOuterSVGFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect) { + NS_ASSERTION( + PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && + !PrincipalChildList().FirstChild()->GetNextSibling(), + "We should have a single, anonymous, child"); + auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( + PrincipalChildList().FirstChild()); + anonKid->PaintSVG(aContext, aTransform, aImgParams, aDirtyRect); +} + +SVGBBox SVGOuterSVGFrame::GetBBoxContribution( + const gfx::Matrix& aToBBoxUserspace, uint32_t aFlags) { + NS_ASSERTION( + PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && + !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. + auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( + PrincipalChildList().FirstChild()); + return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags); +} + +//---------------------------------------------------------------------- +// SVGContainerFrame methods: + +gfxMatrix SVGOuterSVGFrame::GetCanvasTM() { + if (!mCanvasTM) { + SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent()); + + float devPxPerCSSPx = 1.0f / nsPresContext::AppUnitsToFloatCSSPixels( + PresContext()->AppUnitsPerDevPixel()); + + gfxMatrix tm = content->PrependLocalTransformsTo( + gfxMatrix::Scaling(devPxPerCSSPx, devPxPerCSSPx)); + mCanvasTM = MakeUnique<gfxMatrix>(tm); + } + return *mCanvasTM; +} + +//---------------------------------------------------------------------- +// Implementation helpers + +bool SVGOuterSVGFrame::IsRootOfImage() { + if (!mContent->GetParent()) { + // Our content is the document element + Document* doc = mContent->GetUncomposedDoc(); + if (doc && doc->IsBeingUsedAsImage()) { + // Our document is being used as an image + return true; + } + } + + return false; +} + +bool SVGOuterSVGFrame::VerticalScrollbarNotNeeded() const { + const SVGAnimatedLength& height = + static_cast<SVGSVGElement*>(GetContent()) + ->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; + return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100; +} + +void SVGOuterSVGFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + nsIFrame* anonKid = PrincipalChildList().FirstChild(); + MOZ_ASSERT(anonKid->IsSVGOuterSVGAnonChildFrame()); + aResult.AppendElement(OwnedAnonBox(anonKid)); +} + +void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() { + MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()), + Some(GetAspectRatio())); +} + +void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder( + Maybe<IntrinsicSize> aIntrinsicSize, Maybe<AspectRatio> aIntrinsicRatio) { + if (!mIsInObjectOrEmbed) { + return; + } + + nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell(); + if (!docShell) { + return; + } + + BrowsingContext* bc = docShell->GetBrowsingContext(); + MOZ_ASSERT(bc->IsContentSubframe()); + + if (bc->GetParent()->IsInProcess()) { + if (Element* embedder = bc->GetEmbedderElement()) { + if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(embedder)) { + static_cast<nsObjectLoadingContent*>(olc.get()) + ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize, + aIntrinsicRatio); + } + return; + } + } + + if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { + Unused << browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize, + aIntrinsicRatio); + } +} + +void SVGOuterSVGFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle); + + if (!aOldComputedStyle) { + return; + } + + if (aOldComputedStyle->StylePosition()->mAspectRatio != + StylePosition()->mAspectRatio) { + // Our aspect-ratio property value changed, and an embedding <object> or + // <embed> might care about that. + MaybeSendIntrinsicSizeAndRatioToEmbedder(); + } +} + +void SVGOuterSVGFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + // This handles both the case when the root <svg> element is made display:none + // (and thus loses its intrinsic size and aspect ratio), and when the frame + // is navigated elsewhere & we need to reset parent <object>/<embed>'s + // recorded intrinsic size/ratio values. + MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing()); + + SVGDisplayContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +} // namespace mozilla + +//---------------------------------------------------------------------- +// Implementation of SVGOuterSVGAnonChildFrame + +nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame( + mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGOuterSVGAnonChildFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGAnonChildFrame) + +#ifdef DEBUG +void SVGOuterSVGAnonChildFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + MOZ_ASSERT(aParent->IsSVGOuterSVGFrame(), "Unexpected parent"); + SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif + +void SVGOuterSVGAnonChildFrame::BuildDisplayList( + nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { + // Wrap our contents into an nsDisplaySVGWrapper. + // We wrap this frame instead of the SVGOuterSVGFrame so that the wrapper + // doesn't contain the <svg> element's CSS styles, like backgrounds or + // borders. Creating the nsDisplaySVGWrapper here also means that it'll be + // inside the nsDisplayTransform for our viewbox transform. The + // nsDisplaySVGWrapper's reference frame is this frame, because this frame + // always returns true from IsSVGTransformed. + nsDisplayList newList(aBuilder); + nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList, + &newList); + BuildDisplayListForNonBlockChildren(aBuilder, set); + aLists.Content()->AppendNewToTop<nsDisplaySVGWrapper>(aBuilder, this, + &newList); +} + +static Matrix ComputeOuterSVGAnonChildFrameTransform( + const SVGOuterSVGAnonChildFrame* aFrame) { + // Our elements 'transform' attribute is applied to our SVGOuterSVGFrame + // parent, and the element's children-only transforms are applied to us, the + // anonymous child frame. Since we are the child frame, we apply the + // children-only transforms as if they are our own transform. + SVGSVGElement* content = static_cast<SVGSVGElement*>(aFrame->GetContent()); + + if (!content->HasChildrenOnlyTransform()) { + return Matrix(); + } + + // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here. + gfxMatrix ownMatrix = + content->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); + + if (ownMatrix.HasNonTranslation()) { + // viewBox, currentScale and currentTranslate should only produce a + // rectilinear transform. + MOZ_ASSERT(ownMatrix.IsRectilinear(), + "Non-rectilinear transform will break the following logic"); + + // The nsDisplayTransform code will apply this transform to our frame, + // including to our frame position. We don't want our frame position to + // be scaled though, so we need to correct for that in the transform. + // XXX Yeah, this is a bit hacky. + CSSPoint pos = CSSPixel::FromAppUnits(aFrame->GetPosition()); + CSSPoint scaledPos = CSSPoint(ownMatrix._11 * pos.x, ownMatrix._22 * pos.y); + CSSPoint deltaPos = scaledPos - pos; + ownMatrix *= gfxMatrix::Translation(-deltaPos.x, -deltaPos.y); + } + + return gfx::ToMatrix(ownMatrix); +} + +// We want this frame to be a reference frame. An easy way to achieve that is +// to always return true from this method, even for identity transforms. +// This frame being a reference frame ensures that the offset between this +// <svg> element and the parent reference frame is completely absorbed by the +// nsDisplayTransform that's created for this frame, and that this offset does +// not affect our descendants' transforms. Consequently, if the <svg> element +// moves, e.g. during scrolling, the transform matrices of our contents are +// unaffected. This simplifies invalidation. +bool SVGOuterSVGAnonChildFrame::IsSVGTransformed( + Matrix* aOwnTransform, Matrix* aFromParentTransform) const { + if (aOwnTransform) { + *aOwnTransform = ComputeOuterSVGAnonChildFrameTransform(this); + } + + return true; +} + +} // namespace mozilla diff --git a/layout/svg/SVGOuterSVGFrame.h b/layout/svg/SVGOuterSVGFrame.h new file mode 100644 index 0000000000..03a839e4e5 --- /dev/null +++ b/layout/svg/SVGOuterSVGFrame.h @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGOUTERSVGFRAME_H_ +#define LAYOUT_SVG_SVGOUTERSVGFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/ISVGSVGFrame.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/UniquePtr.h" +#include "nsRegion.h" +#include "nsTHashSet.h" + +class gfxContext; + +namespace mozilla { +class AutoSVGViewHandler; +class SVGForeignObjectFrame; +class SVGFragmentIdentifier; +class PresShell; +} // namespace mozilla + +nsContainerFrame* NS_NewSVGOuterSVGFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); +nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame( + mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +//////////////////////////////////////////////////////////////////////// +// SVGOuterSVGFrame class + +class SVGOuterSVGFrame final : public SVGDisplayContainerFrame, + public ISVGSVGFrame { + using imgDrawingParams = image::imgDrawingParams; + + friend nsContainerFrame* ::NS_NewSVGOuterSVGFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + friend class AutoSVGViewHandler; + friend class SVGFragmentIdentifier; + + protected: + explicit SVGOuterSVGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext); + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGOuterSVGFrame) + +#ifdef DEBUG + ~SVGOuterSVGFrame() { + NS_ASSERTION(!mForeignObjectHash || mForeignObjectHash->Count() == 0, + "foreignObject(s) still registered!"); + } +#endif + + // nsIFrame: + nscoord GetMinISize(gfxContext* aRenderingContext) override; + nscoord GetPrefISize(gfxContext* aRenderingContext) override; + + IntrinsicSize GetIntrinsicSize() override; + AspectRatio GetIntrinsicRatio() const override; + + SizeComputationResult ComputeSize( + gfxContext* aRenderingContext, WritingMode aWritingMode, + const LogicalSize& aCBSize, nscoord aAvailableISize, + const LogicalSize& aMargin, const LogicalSize& aBorderPadding, + const mozilla::StyleSizeOverrides& aSizeOverrides, + ComputeSizeFlags aFlags) override; + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput) override; + + void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas) override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + return SVGDisplayContainerFrame::IsFrameOfType( + aFlags & + ~(eSupportsContainLayoutAndPaint | eReplaced | eReplacedSizing)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGOuterSVG"_ns, aResult); + } +#endif + + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + void DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) override; + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + nsContainerFrame* GetContentInsertionFrame() override { + // Any children must be added to our single anonymous inner frame kid. + MOZ_ASSERT( + PrincipalChildList().FirstChild() && + PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame(), + "Where is our anonymous child?"); + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + bool IsSVGTransformed(Matrix* aOwnTransform, + Matrix* aFromParentTransform) const override; + + // Return our anonymous box child. + void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override; + + // ISVGSVGFrame interface: + void NotifyViewportOrTransformChanged(uint32_t aFlags) override; + + // ISVGDisplayableFrame methods: + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + + // SVGContainerFrame methods: + gfxMatrix GetCanvasTM() override; + + /* Methods to allow descendant SVGForeignObjectFrame frames to register and + * unregister themselves with their nearest SVGOuterSVGFrame 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(SVGForeignObjectFrame* aFrame); + void UnregisterForeignObject(SVGForeignObjectFrame* aFrame); + + 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 + * being used as an image. + */ + bool IsRootOfImage(); + + void MaybeSendIntrinsicSizeAndRatioToEmbedder(); + void MaybeSendIntrinsicSizeAndRatioToEmbedder(Maybe<IntrinsicSize>, + Maybe<AspectRatio>); + + // This is temporary until display list based invalidation is implemented for + // SVG. + // A hash-set containing our SVGForeignObjectFrame 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). + UniquePtr<nsTHashSet<SVGForeignObjectFrame*>> mForeignObjectHash; + + nsRegion mInvalidRegion; + + float mFullZoom; + + bool mViewportInitialized; + bool mIsRootContent; + bool mIsInObjectOrEmbed; + bool mIsInIframe; +}; + +//////////////////////////////////////////////////////////////////////// +// SVGOuterSVGAnonChildFrame class + +/** + * SVGOuterSVGFrames 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 SVGOuterSVGFrame, + * since an SVGOuterSVGFrame 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 SVGOuterSVGFrame's children in an instance of this class + * with its GetPosition() set to its SVGOuterSVGFrame's border/padding offset + * keeps both nsDisplayTransform and nsIFrame::GetOffsetTo happy. + * + * The reason that this class inherit from SVGDisplayContainerFrame 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 + * SVGContainerFrame instances all the way up to the SVGOuterSVGFrame. + */ +class SVGOuterSVGAnonChildFrame final : public SVGDisplayContainerFrame { + friend nsContainerFrame* ::NS_NewSVGOuterSVGAnonChildFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + explicit SVGOuterSVGAnonChildFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGOuterSVGAnonChildFrame) + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGOuterSVGAnonChild"_ns, aResult); + } +#endif + + bool IsSVGTransformed(Matrix* aOwnTransform, + Matrix* aFromParentTransform) const override; + + // SVGContainerFrame methods: + gfxMatrix GetCanvasTM() override { + // GetCanvasTM returns the transform from an SVG frame to the frame's + // SVGOuterSVGFrame's content box, so we do not include any x/y offset + // set on us for any CSS border or padding on our SVGOuterSVGFrame. + return static_cast<SVGOuterSVGFrame*>(GetParent())->GetCanvasTM(); + } +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGOUTERSVGFRAME_H_ diff --git a/layout/svg/SVGPaintServerFrame.cpp b/layout/svg/SVGPaintServerFrame.cpp new file mode 100644 index 0000000000..6a2b337871 --- /dev/null +++ b/layout/svg/SVGPaintServerFrame.cpp @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGPaintServerFrame.h" + +namespace mozilla { + +NS_QUERYFRAME_HEAD(SVGPaintServerFrame) + NS_QUERYFRAME_ENTRY(SVGPaintServerFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame) + +} // namespace mozilla diff --git a/layout/svg/SVGPaintServerFrame.h b/layout/svg/SVGPaintServerFrame.h new file mode 100644 index 0000000000..cebed16441 --- /dev/null +++ b/layout/svg/SVGPaintServerFrame.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGPAINTSERVERFRAME_H_ +#define LAYOUT_SVG_SVGPAINTSERVERFRAME_H_ + +#include "gfxRect.h" +#include "mozilla/Attributes.h" +#include "mozilla/SVGContainerFrame.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsIFrame.h" +#include "nsQueryFrame.h" + +class gfxContext; +class gfxPattern; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx + +/** + * RAII class used to temporarily set and remove the + * NS_FRAME_DRAWING_AS_PAINTSERVER frame state bit while a frame is being + * drawn as a paint server. + */ +class MOZ_RAII AutoSetRestorePaintServerState { + public: + explicit AutoSetRestorePaintServerState(nsIFrame* aFrame) : mFrame(aFrame) { + mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + } + ~AutoSetRestorePaintServerState() { + mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + } + + private: + nsIFrame* mFrame; +}; + +class SVGPaintServerFrame : public SVGContainerFrame { + protected: + using DrawTarget = gfx::DrawTarget; + + SVGPaintServerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID) + : SVGContainerFrame(aStyle, aPresContext, aID) { + AddStateBits(NS_FRAME_IS_NONDISPLAY); + } + + public: + using imgDrawingParams = image::imgDrawingParams; + + NS_DECL_ABSTRACT_FRAME(SVGPaintServerFrame) + NS_DECL_QUERYFRAME + NS_DECL_QUERYFRAME_TARGET(SVGPaintServerFrame) + + /** + * 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, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aOpacity, imgDrawingParams& aImgParams, + const gfxRect* aOverrideBounds = nullptr) = 0; + + // nsIFrame methods: + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGPAINTSERVERFRAME_H_ diff --git a/layout/svg/SVGPatternFrame.cpp b/layout/svg/SVGPatternFrame.cpp new file mode 100644 index 0000000000..9ba9139e36 --- /dev/null +++ b/layout/svg/SVGPatternFrame.cpp @@ -0,0 +1,714 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGPatternFrame.h" + +// Keep others in (case-insensitive) order: +#include "AutoReferenceChainGuard.h" +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxMatrix.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGPatternElement.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "mozilla/gfx/2D.h" +#include "nsGkAtoms.h" +#include "nsIFrameInlines.h" +#include "SVGAnimatedTransformList.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGUnitTypes_Binding; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Implementation + +SVGPatternFrame::SVGPatternFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : SVGPaintServerFrame(aStyle, aPresContext, kClassID), + mSource(nullptr), + mLoopFlag(false), + mNoHRefURI(false) {} + +NS_IMPL_FRAMEARENA_HELPERS(SVGPatternFrame) + +NS_QUERYFRAME_HEAD(SVGPatternFrame) + NS_QUERYFRAME_ENTRY(SVGPatternFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGPaintServerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult SVGPatternFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // Blow away our reference, if any + SVGObserverUtils::RemoveTemplateObserver(this); + mNoHRefURI = false; + // And update whoever references us + SVGObserverUtils::InvalidateDirectRenderingObservers(this); + } + + return SVGPaintServerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +#ifdef DEBUG +void SVGPatternFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::pattern), + "Content is not an SVG pattern"); + + SVGPaintServerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +//---------------------------------------------------------------------- +// SVGContainerFrame 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 SVGPatternFrame::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 SVGAnimatedViewBox& 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 SVGAnimatedViewBox& aViewBox, + uint16_t aPatternContentUnits, + uint16_t aPatternUnits, nsIFrame* aTarget, + const Matrix& aContextMatrix, + const gfxRect* aOverrideBounds) { + *aBBox = + aOverrideBounds + ? *aOverrideBounds + : SVGUtils::GetBBox(aTarget, SVGUtils::eUseFrameBoundsForOuterSVG | + SVGUtils::eBBoxIncludeFillGeometry); + + // 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> SVGPatternFrame::PaintPattern( + const DrawTarget* aDrawTarget, Matrix* patternMatrix, + const Matrix& aContextMatrix, nsIFrame* aSource, + StyleSVGPaint nsStyleSVG::*aFillOrStroke, float aGraphicOpacity, + const gfxRect* aOverrideBounds, imgDrawingParams& aImgParams) { + /* + * 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 + */ + + SVGPatternFrame* patternWithChildren = GetPatternWithChildren(); + if (!patternWithChildren) { + // Either no kids or a bad reference + return nullptr; + } + nsIFrame* firstKid = patternWithChildren->mFrames.FirstChild(); + + const SVGAnimatedViewBox& 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 = MakeUnique<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 (SVGUtils::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 = + SVGUtils::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->CreateSimilarDrawTargetWithBacking( + surfaceSize, SurfaceFormat::B8G8R8A8); + if (!dt || !dt->IsValid()) { + return nullptr; + } + dt->ClearRect(Rect(0, 0, surfaceSize.width, surfaceSize.height)); + + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(ctx); // already checked the draw target above + + if (aGraphicOpacity != 1.0f) { + ctx->Save(); + ctx->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->IsSVGGeometryFrameOrSubclass()) { + // Set the geometrical parent of the pattern we are rendering + patternWithChildren->mSource = static_cast<SVGGeometryFrame*>(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->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) { + AutoSetRestorePaintServerState paintServer(patternWithChildren); + for (nsIFrame* kid = firstKid; kid; kid = kid->GetNextSibling()) { + gfxMatrix tm = *(patternWithChildren->mCTM); + + // The CTM of each frame referencing us can be different + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED); + tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm; + } + + SVGUtils::PaintFrameWithEffects(kid, *ctx, tm, aImgParams); + } + } + + patternWithChildren->mSource = nullptr; + + if (aGraphicOpacity != 1.0f) { + ctx->PopGroupAndBlend(); + ctx->Restore(); + } + + // caller now owns the surface + return dt->GetBackingSurface(); +} + +/* 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? +SVGPatternFrame* SVGPatternFrame::GetPatternWithChildren() { + // Do we have any children ourselves? + if (!mFrames.IsEmpty()) { + return this; + } + + // No, see if we chain to someone who does + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return nullptr; + } + + SVGPatternFrame* next = GetReferencedPattern(); + if (!next) { + return nullptr; + } + + return next->GetPatternWithChildren(); +} + +uint16_t SVGPatternFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) { + SVGAnimatedEnumeration& thisEnum = + static_cast<SVGPatternElement*>(GetContent())->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) { + return thisEnum.GetAnimValue(); + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<SVGPatternElement*>(aDefault) + ->mEnumAttributes[aIndex] + .GetAnimValue(); + } + + SVGPatternFrame* next = GetReferencedPattern(); + return next ? next->GetEnumValue(aIndex, aDefault) + : static_cast<SVGPatternElement*>(aDefault) + ->mEnumAttributes[aIndex] + .GetAnimValue(); +} + +SVGAnimatedTransformList* SVGPatternFrame::GetPatternTransformList( + nsIContent* aDefault) { + SVGAnimatedTransformList* thisTransformList = + static_cast<SVGPatternElement*>(GetContent())->GetAnimatedTransformList(); + + if (thisTransformList && thisTransformList->IsExplicitlySet()) + return thisTransformList; + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<SVGPatternElement*>(aDefault)->mPatternTransform.get(); + } + + SVGPatternFrame* next = GetReferencedPattern(); + return next ? next->GetPatternTransformList(aDefault) + : static_cast<SVGPatternElement*>(aDefault) + ->mPatternTransform.get(); +} + +gfxMatrix SVGPatternFrame::GetPatternTransform() { + SVGAnimatedTransformList* animTransformList = + GetPatternTransformList(GetContent()); + if (!animTransformList) { + return gfxMatrix(); + } + + return animTransformList->GetAnimValue().GetConsolidationMatrix(); +} + +const SVGAnimatedViewBox& SVGPatternFrame::GetViewBox(nsIContent* aDefault) { + const SVGAnimatedViewBox& thisViewBox = + static_cast<SVGPatternElement*>(GetContent())->mViewBox; + + if (thisViewBox.IsExplicitlySet()) { + return thisViewBox; + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<SVGPatternElement*>(aDefault)->mViewBox; + } + + SVGPatternFrame* next = GetReferencedPattern(); + return next ? next->GetViewBox(aDefault) + : static_cast<SVGPatternElement*>(aDefault)->mViewBox; +} + +const SVGAnimatedPreserveAspectRatio& SVGPatternFrame::GetPreserveAspectRatio( + nsIContent* aDefault) { + const SVGAnimatedPreserveAspectRatio& thisPar = + static_cast<SVGPatternElement*>(GetContent())->mPreserveAspectRatio; + + if (thisPar.IsExplicitlySet()) { + return thisPar; + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return static_cast<SVGPatternElement*>(aDefault)->mPreserveAspectRatio; + } + + SVGPatternFrame* next = GetReferencedPattern(); + return next ? next->GetPreserveAspectRatio(aDefault) + : static_cast<SVGPatternElement*>(aDefault)->mPreserveAspectRatio; +} + +const SVGAnimatedLength* SVGPatternFrame::GetLengthValue(uint32_t aIndex, + nsIContent* aDefault) { + const SVGAnimatedLength* thisLength = + &static_cast<SVGPatternElement*>(GetContent())->mLengthAttributes[aIndex]; + + if (thisLength->IsExplicitlySet()) { + return thisLength; + } + + // Before we recurse, make sure we'll break reference loops and over long + // reference chains: + static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; + AutoReferenceChainGuard refChainGuard(this, &mLoopFlag, + &sRefChainLengthCounter); + if (MOZ_UNLIKELY(!refChainGuard.Reference())) { + // Break reference chain + return &static_cast<SVGPatternElement*>(aDefault) + ->mLengthAttributes[aIndex]; + } + + SVGPatternFrame* next = GetReferencedPattern(); + return next ? next->GetLengthValue(aIndex, aDefault) + : &static_cast<SVGPatternElement*>(aDefault) + ->mLengthAttributes[aIndex]; +} + +// Private (helper) methods + +SVGPatternFrame* SVGPatternFrame::GetReferencedPattern() { + if (mNoHRefURI) { + return nullptr; + } + + auto GetHref = [this](nsAString& aHref) { + SVGPatternElement* pattern = + static_cast<SVGPatternElement*>(this->GetContent()); + if (pattern->mStringAttributes[SVGPatternElement::HREF].IsExplicitlySet()) { + pattern->mStringAttributes[SVGPatternElement::HREF].GetAnimValue(aHref, + pattern); + } else { + pattern->mStringAttributes[SVGPatternElement::XLINK_HREF].GetAnimValue( + aHref, pattern); + } + this->mNoHRefURI = aHref.IsEmpty(); + }; + + nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref); + if (tframe) { + LayoutFrameType frameType = tframe->Type(); + if (frameType == LayoutFrameType::SVGPattern) { + return static_cast<SVGPatternFrame*>(tframe); + } + // We don't call SVGObserverUtils::RemoveTemplateObserver and set + // `mNoHRefURI = false` here since we want to be invalidated if the ID + // specified by our href starts resolving to a different/valid element. + } + + return nullptr; +} + +gfxRect SVGPatternFrame::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 SVGAnimatedLength *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 = SVGUtils::ObjectSpace(aTargetBBox, tmpX); + y = SVGUtils::ObjectSpace(aTargetBBox, tmpY); + width = SVGUtils::ObjectSpace(aTargetBBox, tmpWidth); + height = SVGUtils::ObjectSpace(aTargetBBox, tmpHeight); + } else { + float scale = MaxExpansion(aTargetCTM); + x = SVGUtils::UserSpace(aTarget, tmpX) * scale; + y = SVGUtils::UserSpace(aTarget, tmpY) * scale; + width = SVGUtils::UserSpace(aTarget, tmpWidth) * scale; + height = SVGUtils::UserSpace(aTarget, tmpHeight) * scale; + } + + return gfxRect(x, y, width, height); +} + +gfxMatrix SVGPatternFrame::ConstructCTM(const SVGAnimatedViewBox& aViewBox, + uint16_t aPatternContentUnits, + uint16_t aPatternUnits, + const gfxRect& callerBBox, + const Matrix& callerCTM, + nsIFrame* aTarget) { + SVGViewportElement* 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<SVGElement*>(targetContent)->GetCtx(); + } + scaleX = scaleY = MaxExpansion(callerCTM); + } + + if (!aViewBox.IsExplicitlySet()) { + return gfxMatrix(scaleX, 0.0, 0.0, scaleY, 0.0, 0.0); + } + const SVGViewBox& viewBox = aViewBox.GetAnimValue(); + + if (viewBox.height <= 0.0f || viewBox.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, viewBox.x, viewBox.y, + viewBox.width, viewBox.height, GetPreserveAspectRatio()); + + return ThebesMatrix(tm); +} + +//---------------------------------------------------------------------- +// SVGPaintServerFrame methods: +already_AddRefed<gfxPattern> SVGPatternFrame::GetPaintServerPattern( + nsIFrame* aSource, const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, imgDrawingParams& aImgParams, + const gfxRect* aOverrideBounds) { + if (aGraphicOpacity == 0.0f) { + return do_AddRef(new gfxPattern(DeviceColor())); + } + + // Paint it! + Matrix pMatrix; + RefPtr<SourceSurface> surface = + PaintPattern(aDrawTarget, &pMatrix, ToMatrix(aContextMatrix), aSource, + aFillOrStroke, aGraphicOpacity, aOverrideBounds, aImgParams); + + if (!surface) { + return nullptr; + } + + RefPtr<gfxPattern> pattern = new gfxPattern(surface, pMatrix); + + if (!pattern) { + return nullptr; + } + + pattern->SetExtend(ExtendMode::REPEAT); + return pattern.forget(); +} + +} // namespace mozilla + +// ------------------------------------------------------------------------- +// Public functions +// ------------------------------------------------------------------------- + +nsIFrame* NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGPatternFrame(aStyle, aPresShell->GetPresContext()); +} diff --git a/layout/svg/SVGPatternFrame.h b/layout/svg/SVGPatternFrame.h new file mode 100644 index 0000000000..4110b51289 --- /dev/null +++ b/layout/svg/SVGPatternFrame.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGPATTERNFRAME_H_ +#define LAYOUT_SVG_SVGPATTERNFRAME_H_ + +#include "mozilla/Attributes.h" +#include "gfxMatrix.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGPaintServerFrame.h" +#include "mozilla/UniquePtr.h" + +class nsIFrame; + +namespace mozilla { +class PresShell; +class SVGAnimatedLength; +class SVGAnimatedPreserveAspectRatio; +class SVGAnimatedTransformList; +class SVGAnimatedViewBox; +class SVGGeometryFrame; +} // namespace mozilla + +nsIFrame* NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGPatternFrame final : public SVGPaintServerFrame { + using SourceSurface = gfx::SourceSurface; + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGPatternFrame) + NS_DECL_QUERYFRAME + + friend nsIFrame* ::NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + explicit SVGPatternFrame(ComputedStyle* aStyle, nsPresContext* aPresContext); + + // SVGPaintServerFrame methods: + already_AddRefed<gfxPattern> GetPaintServerPattern( + nsIFrame* aSource, const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + float aGraphicOpacity, imgDrawingParams& aImgParams, + const gfxRect* aOverrideBounds) override; + + public: + // SVGContainerFrame methods: + gfxMatrix GetCanvasTM() override; + + // nsIFrame interface: + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGPattern"_ns, aResult); + } +#endif // DEBUG + + protected: + /** + * Parses this frame's href and - if it references another pattern - returns + * it. It also makes this frame a rendering observer of the specified ID. + */ + SVGPatternFrame* GetReferencedPattern(); + + // Accessors to lookup pattern attributes + uint16_t GetEnumValue(uint32_t aIndex, nsIContent* aDefault); + uint16_t GetEnumValue(uint32_t aIndex) { + return GetEnumValue(aIndex, mContent); + } + SVGAnimatedTransformList* GetPatternTransformList(nsIContent* aDefault); + gfxMatrix GetPatternTransform(); + const SVGAnimatedViewBox& GetViewBox(nsIContent* aDefault); + const SVGAnimatedViewBox& GetViewBox() { return GetViewBox(mContent); } + const SVGAnimatedPreserveAspectRatio& GetPreserveAspectRatio( + nsIContent* aDefault); + const SVGAnimatedPreserveAspectRatio& GetPreserveAspectRatio() { + return GetPreserveAspectRatio(mContent); + } + const SVGAnimatedLength* GetLengthValue(uint32_t aIndex, + nsIContent* aDefault); + const SVGAnimatedLength* GetLengthValue(uint32_t aIndex) { + return GetLengthValue(aIndex, mContent); + } + + already_AddRefed<SourceSurface> PaintPattern( + const DrawTarget* aDrawTarget, Matrix* patternMatrix, + const Matrix& aContextMatrix, nsIFrame* aSource, + StyleSVGPaint nsStyleSVG::*aFillOrStroke, float aGraphicOpacity, + const gfxRect* aOverrideBounds, imgDrawingParams& aImgParams); + + /** + * 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 SVGPatternFrame or the first pattern along the + * reference chain (if there is one) to have children. + */ + SVGPatternFrame* GetPatternWithChildren(); + + gfxRect GetPatternRect(uint16_t aPatternUnits, const gfxRect& bbox, + const Matrix& aTargetCTM, nsIFrame* aTarget); + gfxMatrix ConstructCTM(const SVGAnimatedViewBox& 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 + SVGGeometryFrame* mSource; + UniquePtr<gfxMatrix> mCTM; + + protected: + // This flag is used to detect loops in xlink:href processing + bool mLoopFlag; + bool mNoHRefURI; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGPATTERNFRAME_H_ diff --git a/layout/svg/SVGStopFrame.cpp b/layout/svg/SVGStopFrame.cpp new file mode 100644 index 0000000000..b6a3ab69a6 --- /dev/null +++ b/layout/svg/SVGStopFrame.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsIFrame.h" +#include "nsGkAtoms.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGGradientFrame.h" +#include "mozilla/SVGObserverUtils.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 SVGGradientFrame, which is the parent for this frame + +nsIFrame* NS_NewSVGStopFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGStopFrame : public nsIFrame { + friend nsIFrame* ::NS_NewSVGStopFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGStopFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : nsIFrame(aStyle, aPresContext, kClassID) { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGStopFrame) + + // nsIFrame interface: +#ifdef DEBUG + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override {} + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGStop"_ns, aResult); + } +#endif +}; + +//---------------------------------------------------------------------- +// Implementation + +NS_IMPL_FRAMEARENA_HELPERS(SVGStopFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +#ifdef DEBUG +void SVGStopFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::stop), + "Content is not a stop element"); + + nsIFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGStopFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::offset) { + MOZ_ASSERT( + static_cast<SVGGradientFrame*>(do_QueryFrame(GetParent())), + "Observers observe the gradient, so that's what we must invalidate"); + SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent()); + } + + return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +} // namespace mozilla + +// ------------------------------------------------------------------------- +// Public functions +// ------------------------------------------------------------------------- + +nsIFrame* NS_NewSVGStopFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGStopFrame(aStyle, aPresShell->GetPresContext()); +} diff --git a/layout/svg/SVGSwitchFrame.cpp b/layout/svg/SVGSwitchFrame.cpp new file mode 100644 index 0000000000..e846b1eb0f --- /dev/null +++ b/layout/svg/SVGSwitchFrame.cpp @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGGFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGSwitchElement.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGSwitchFrame final : public SVGGFrame { + friend nsIFrame* ::NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGSwitchFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGGFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGSwitchFrame) + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGSwitch"_ns, aResult); + } +#endif + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + // ISVGDisplayableFrame interface: + virtual void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + void ReflowSVG() override; + virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + + private: + nsIFrame* GetActiveChildFrame(); + void ReflowAllSVGTextFramesInsideNonActiveChildren(nsIFrame* aActiveChild); + static void AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid); +}; + +} // namespace mozilla + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGSwitchFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGSwitchFrame) + +#ifdef DEBUG +void SVGSwitchFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch), + "Content is not an SVG switch"); + + SVGGFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +void SVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + nsIFrame* kid = GetActiveChildFrame(); + if (kid) { + BuildDisplayListForChild(aBuilder, kid, aLists); + } +} + +void SVGSwitchFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + 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; + } + + nsIFrame* kid = GetActiveChildFrame(); + if (kid) { + gfxMatrix tm = aTransform; + if (kid->GetContent()->IsSVGElement()) { + tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm; + } + SVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams, aDirtyRect); + } +} + +nsIFrame* SVGSwitchFrame::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(); + ISVGDisplayableFrame* 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 SVGElement*>(GetContent()) + ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); + m = static_cast<const SVGElement*>(kid->GetContent()) + ->PrependLocalTransformsTo(m, eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.TransformPoint(point); + } + return svgFrame->GetFrameForPoint(point); + } + + return nullptr; +} + +static bool shouldReflowSVGTextFrameInside(nsIFrame* aFrame) { + return aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) || + aFrame->IsSVGForeignObjectFrame() || + !aFrame->IsFrameOfType(nsIFrame::eSVG); +} + +void SVGSwitchFrame::AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid) { + if (!aKid->IsSubtreeDirty()) { + return; + } + + if (aKid->IsSVGTextFrame()) { + MOZ_ASSERT(!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "A non-display SVGTextFrame directly contained in a display " + "container?"); + static_cast<SVGTextFrame*>(aKid)->ReflowSVG(); + } else if (shouldReflowSVGTextFrameInside(aKid)) { + if (!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + for (nsIFrame* kid : aKid->PrincipalChildList()) { + AlwaysReflowSVGTextFrameDoForOneKid(kid); + } + } else { + // This child is in a nondisplay context, something like: + // <switch> + // ... + // <g><mask><text></text></mask></g> + // </switch> + // We should not call ReflowSVG on it. + SVGContainerFrame::ReflowSVGNonDisplayText(aKid); + } + } +} + +void SVGSwitchFrame::ReflowAllSVGTextFramesInsideNonActiveChildren( + nsIFrame* aActiveChild) { + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { + if (aActiveChild == kid) { + continue; + } + + AlwaysReflowSVGTextFrameDoForOneKid(kid); + } +} + +void SVGSwitchFrame::ReflowSVG() { + NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), + "This call is probably a wasteful mistake"); + + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!SVGUtils::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()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + if (outerSVGHasHadFirstReflow) { + RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children + } + + OverflowAreas overflowRects; + + nsIFrame* child = GetActiveChildFrame(); + ReflowAllSVGTextFramesInsideNonActiveChildren(child); + + ISVGDisplayableFrame* svgChild = do_QueryFrame(child); + if (svgChild) { + MOZ_ASSERT(!child->HasAnyStateBits(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); + } else if (child && shouldReflowSVGTextFrameInside(child)) { + MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || + !child->IsFrameOfType(nsIFrame::eSVG), + "Check for this explicitly in the |if|, then"); + ReflowSVGNonDisplayText(child); + } + + if (isFirstReflow) { + // Make sure we have our filter property (if any) before calling + // FinishAndStoreOverflow (subsequent filter changes are handled off + // nsChangeHint_UpdateEffects): + SVGObserverUtils::UpdateEffects(this); + } + + FinishAndStoreOverflow(overflowRects, mRect.Size()); + + // Remove state bits after FinishAndStoreOverflow so that it doesn't + // invalidate on first reflow: + RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +SVGBBox SVGSwitchFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) { + nsIFrame* kid = GetActiveChildFrame(); + ISVGDisplayableFrame* svgKid = do_QueryFrame(kid); + if (svgKid) { + nsIContent* content = kid->GetContent(); + gfxMatrix transform = ThebesMatrix(aToBBoxUserspace); + if (content->IsSVGElement()) { + transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo( + {}, eChildToUserSpace) * + SVGUtils::GetTransformMatrixInUserSpace(kid) * transform; + } + return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags); + } + return SVGBBox(); +} + +nsIFrame* SVGSwitchFrame::GetActiveChildFrame() { + nsIContent* activeChild = + static_cast<dom::SVGSwitchElement*>(GetContent())->GetActiveChild(); + + if (activeChild) { + for (nsIFrame* kid = mFrames.FirstChild(); kid; + kid = kid->GetNextSibling()) { + if (activeChild == kid->GetContent()) { + return kid; + } + } + } + return nullptr; +} + +} // namespace mozilla diff --git a/layout/svg/SVGSymbolFrame.cpp b/layout/svg/SVGSymbolFrame.cpp new file mode 100644 index 0000000000..63cb31af5b --- /dev/null +++ b/layout/svg/SVGSymbolFrame.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGSymbolFrame.h" + +#include "mozilla/PresShell.h" + +nsIFrame* NS_NewSVGSymbolFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGSymbolFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGSymbolFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods + +NS_QUERYFRAME_HEAD(SVGSymbolFrame) + NS_QUERYFRAME_ENTRY(SVGSymbolFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGViewportFrame) + +#ifdef DEBUG +void SVGSymbolFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::symbol), + "Content is not an SVG 'symbol' element!"); + + SVGViewportFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +} // namespace mozilla diff --git a/layout/svg/SVGSymbolFrame.h b/layout/svg/SVGSymbolFrame.h new file mode 100644 index 0000000000..044e70eec7 --- /dev/null +++ b/layout/svg/SVGSymbolFrame.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGSYMBOLFRAME_H_ +#define LAYOUT_SVG_SVGSYMBOLFRAME_H_ + +#include "SVGViewportFrame.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGSymbolFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGSymbolFrame final : public SVGViewportFrame { + friend nsIFrame* ::NS_NewSVGSymbolFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGSymbolFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGViewportFrame(aStyle, aPresContext, kClassID) {} + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGSymbolFrame) + +#ifdef DEBUG + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGSymbol"_ns, aResult); + } +#endif +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGSYMBOLFRAME_H_ diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp new file mode 100644 index 0000000000..be8248ab68 --- /dev/null +++ b/layout/svg/SVGTextFrame.cpp @@ -0,0 +1,5413 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "gfxContext.h" +#include "gfxFont.h" +#include "gfxSkipChars.h" +#include "gfxTypes.h" +#include "gfxUtils.h" +#include "LookAndFeel.h" +#include "nsAlgorithm.h" +#include "nsBidiPresUtils.h" +#include "nsBlockFrame.h" +#include "nsCaret.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsQuickSort.h" +#include "SVGPaintServerFrame.h" +#include "nsTArray.h" +#include "nsTextFrame.h" +#include "SVGAnimatedNumberList.h" +#include "SVGContentUtils.h" +#include "SVGContextPaint.h" +#include "SVGLengthList.h" +#include "SVGNumberList.h" +#include "nsLayoutUtils.h" +#include "nsFrameSelection.h" +#include "nsStyleStructInlines.h" +#include "mozilla/Likely.h" +#include "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGOuterSVGFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/DOMPointBinding.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGRect.h" +#include "mozilla/dom/SVGTextContentElementBinding.h" +#include "mozilla/dom/SVGTextPathElement.h" +#include "mozilla/dom/Text.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "nsDisplayList.h" +#include <algorithm> +#include <cmath> +#include <limits> + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGTextContentElement_Binding; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +// ============================================================================ +// 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(nsPresContext::AppUnitsToFloatCSSPixels(aRect.x), + nsPresContext::AppUnitsToFloatCSSPixels(aRect.y), + nsPresContext::AppUnitsToFloatCSSPixels(aRect.width), + nsPresContext::AppUnitsToFloatCSSPixels(aRect.height)); +} + +/** + * 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()); + + textRun->GetLineHeightMetrics(range, aAscent, aDescent); +} + +/** + * 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); + } + + return aContent->IsAnyOfSVGElements(nsGkAtoms::a, nsGkAtoms::tspan); +} + +/** + * 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 Text. + * + * @param aFrame The frame to look at. + * @param aTextFrame aFrame as an nsTextFrame (output). + * @param aTextNode The Text content of aFrame (output). + * @return true if aFrame is a non-empty text frame, false otherwise. + */ +static bool GetNonEmptyTextFrameAndNode(nsIFrame* aFrame, + nsTextFrame*& aTextFrame, + Text*& aTextNode) { + nsTextFrame* text = do_QueryFrame(aFrame); + bool isNonEmptyTextFrame = text && text->GetContentLength() != 0; + + if (isNonEmptyTextFrame) { + nsIContent* content = text->GetContent(); + NS_ASSERTION(content && content->IsText(), + "unexpected content type for nsTextFrame"); + + Text* node = content->AsText(); + 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(nsAtom* 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, + StyleDominantBaseline aDominantBaseline, + float aFontSizeScaleFactor) { + WritingMode writingMode = aFrame->GetWritingMode(); + gfxFloat ascent, descent; + aTextRun->GetLineHeightMetrics(ascent, descent); + + auto convertIfVerticalRL = [&](gfxFloat dominantBaseline) { + return writingMode.IsVerticalRL() ? ascent + descent - dominantBaseline + : dominantBaseline; + }; + + switch (aDominantBaseline) { + case StyleDominantBaseline::Hanging: + return convertIfVerticalRL(ascent * 0.2); + case StyleDominantBaseline::TextBeforeEdge: + return convertIfVerticalRL(0); + + case StyleDominantBaseline::Alphabetic: + return writingMode.IsVerticalRL() + ? ascent * 0.5 + : aFrame->GetLogicalBaseline(writingMode); + + case StyleDominantBaseline::Auto: + return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode)); + + case StyleDominantBaseline::Middle: + return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode) - + SVGContentUtils::GetFontXHeight(aFrame) / 2.0 * + AppUnitsPerCSSPixel() * + aFontSizeScaleFactor); + + case StyleDominantBaseline::TextAfterEdge: + case StyleDominantBaseline::Ideographic: + return writingMode.IsVerticalLR() ? 0 : ascent + descent; + + case StyleDominantBaseline::Central: + return (ascent + descent) / 2.0; + case StyleDominantBaseline::Mathematical: + return convertIfVerticalRL(ascent / 2.0); + } + + MOZ_ASSERT_UNREACHABLE("unexpected dominant-baseline value"); + return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode)); +} + +/** + * 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) { + MOZ_ASSERT(aFrame, "aFrame must not be null"); + nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + 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.TransformPoint(gfxPoint(1, 1)) - + aMatrix.TransformPoint(gfxPoint(0, 0)); + return SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y); +} + +// ============================================================================ +// Utility classes + +// ---------------------------------------------------------------------------- +// 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 { + using Range = gfxTextRun::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. + */ + gfxMatrix GetTransformFromUserSpaceForPainting( + nsPresContext* aContext, const nscoord aVisIStartEdge, + const nscoord aVisIEndEdge) 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 Text 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 nscoord aVisIStartEdge, + const nscoord aVisIEndEdge) 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 = + nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + // Glyph position in user space. + m.PreTranslate(mPosition / cssPxPerDevPx); + + // Take into account any font size scaling and scaling due to textLength="". + m.PreScale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor); + + // Rotation due to rotate="" or a <textPath>. + m.PreRotate(mRotate); + + m.PreScale(mLengthAdjustScaleFactor, 1.0); + + // Translation to get the text frame in the right place. + nsPoint t; + + if (IsVertical()) { + t = nsPoint(-mBaseline, IsRightToLeft() + ? -mFrame->GetRect().height + aVisIEndEdge + : -aVisIStartEdge); + } else { + t = nsPoint(IsRightToLeft() ? -mFrame->GetRect().width + aVisIEndEdge + : -aVisIStartEdge, + -mBaseline); + } + m.PreTranslate(AppUnitsToGfxUnits(t, aContext)); + + return m; +} + +gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace( + nsPresContext* aContext) const { + gfxMatrix m; + if (!mFrame) { + return m; + } + + float cssPxPerDevPx = + nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + nscoord start, end; + GetClipEdges(start, end); + + // Glyph position in user space. + m.PreTranslate(mPosition); + + // Rotation due to rotate="" or a <textPath>. + m.PreRotate(mRotate); + + // Scale due to textLength="". + m.PreScale(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.PreTranslate(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 = AppUnitsPerCSSPixel(); + gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx) + : gfxPoint(start / appPerCssPx, 0); + return m.PreTranslate(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 InkOverflowRectRelativeToSelf 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->InkOverflowRectRelativeToSelf(); + 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); + gfxSkipCharsIterator start = it; + 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; + } + + // FIXME(heycam): We could create a single PropertyProvider for all + // TextRenderedRuns that correspond to the text frame, rather than recreate + // it each time here. + nsTextFrame::PropertyProvider provider(mFrame, start); + + // Measure that range. + gfxTextRun::Metrics metrics = textRun->MeasureText( + range, gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider); + // 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 = + NSToCoordRoundWithClamp(metrics.mBoundingBox.y + metrics.mAscent); + gfxFloat x, width; + if (aFlags & eNoHorizontalOverflow) { + x = 0.0; + width = textRun->GetAdvanceWidth(range, &provider); + if (width < 0.0) { + x = width; + width = -width; + } + } else { + x = metrics.mBoundingBox.x; + width = metrics.mBoundingBox.width; + } + nsRect fillInAppUnits( + NSToCoordRoundWithClamp(x), baseline - above, + NSToCoordRoundWithClamp(width), + NSToCoordRoundWithClamp(metrics.mBoundingBox.height) + above + below); + if (textRun->IsVertical()) { + // Swap line-relative textMetrics dimensions to physical coordinates. + std::swap(fillInAppUnits.x, fillInAppUnits.y); + std::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. + fill.Scale(1.0 / mFontSizeScaleFactor); + + // Include the fill if requested. + if (aFlags & eIncludeFill) { + r = fill; + } + + // Include the stroke if requested. + if ((aFlags & eIncludeStroke) && !fill.IsEmpty() && + SVGUtils::GetStrokeWidth(mFrame) > 0) { + r.UnionEdges( + SVGUtils::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); + nsTextFrame::PropertyProvider provider(mFrame, it); + + // 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->TextFragment()); + 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), &provider); + + // 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), &provider); + + 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); + nsTextFrame::PropertyProvider provider(mFrame, it); + + Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, + mTextFrameContentLength); + + return textRun->GetAdvanceWidth(range, &provider); +} + +int32_t TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext, + const gfxPoint& aPoint) const { + if (mTextFrameContentLength == 0) { + return -1; + } + + float cssPxPerDevPx = + nsPresContext::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.TransformPoint(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); + nsTextFrame::PropertyProvider provider(mFrame, it); + + // 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, &provider)); + + 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, &provider)); + if ((rtl && pos < runAdvance - advance) || (!rtl && pos >= advance)) { + return i; + } + } + return -1; +} + +// ---------------------------------------------------------------------------- +// TextNodeIterator + +enum SubtreePosition { eBeforeSubtree, eWithinSubtree, eAfterSubtree }; + +/** + * An iterator class for Text 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->IsText()) { + Next(); + } + } + + /** + * Returns the current Text, or null if the iterator has finished. + */ + Text* Current() const { return mCurrent ? mCurrent->AsText() : nullptr; } + + /** + * Advances to the next Text and returns it, or null if the end of + * iteration has been reached. + */ + Text* 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 Text will be iterated over. + */ + nsIContent* const mRoot; + + /** + * The node rooting the subtree to track. + */ + nsIContent* const mSubtree; + + /** + * The current node during iteration. + */ + nsIContent* mCurrent; + + /** + * The current iterator position relative to mSubtree. + */ + SubtreePosition mSubtreePosition; +}; + +Text* TextNodeIterator::Next() { + // Starting from mCurrent, we do a non-recursive traversal to the next + // Text 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->IsText()); + } + + return mCurrent ? mCurrent->AsText() : nullptr; +} + +// ---------------------------------------------------------------------------- +// 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->GetProperty(TextNodeCorrespondenceProperty()); + TextNodeCorrespondence* correspondence = + static_cast<TextNodeCorrespondence*>(value); + if (!correspondence) { + // FIXME bug 903785 + NS_ERROR( + "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 Text and nsTextFrames and noting when Text (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 Text. + */ + Text* NextNode(); + + /** + * The iterator over the Text that we use as we simultaneously + * iterate over the nsTextFrames. + */ + TextNodeIterator mNodeIterator; + + /** + * The previous Text we iterated over. + */ + Text* mPreviousNode; + + /** + * The index into the current Text's character content. + */ + uint32_t mNodeCharIndex; +}; + +/* static */ +void TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) { + if (aRoot->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)) { + // Resolve bidi so that continuation frames are created if necessary: + aRoot->MaybeResolveBidiForAnonymousBlockChild(); + TextNodeCorrespondenceRecorder recorder(aRoot); + recorder.Record(aRoot); + aRoot->RemoveStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY); + } +} + +void TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) { + if (!mNodeIterator.Current()) { + // If there are no Text nodes 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 a Text node. 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 Text that we iterate must also be undisplayed. + for (Text* textNode = mNodeIterator.Current(); textNode; + textNode = NextNode()) { + undisplayed += textNode->TextLength(); + } + } + + // Record the trailing number of undisplayed characters on the + // SVGTextFrame. + aRoot->mTrailingUndisplayedCharacters = undisplayed; +} + +Text* TextNodeCorrespondenceRecorder::NextNode() { + mPreviousNode = mNodeIterator.Current(); + Text* 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. + Text* 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()) { + MOZ_ASSERT_UNREACHABLE( + "incorrect tracking of correspondence between " + "text frames and text nodes"); + } else { + // Each whole Text 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 Text are + // undisplayed. + undisplayed = mPreviousNode->TextLength() - mNodeCharIndex; + } + // Each whole Text we find before we get to the text node for + // the current text frame must be undisplayed. + while (mNodeIterator.Current() && 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->SetProperty(TextNodeCorrespondenceProperty(), + new TextNodeCorrespondence(undisplayed)); + + // Remember how far into the current Text 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 MOZ_STACK_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, + const 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. + */ + StyleDominantBaseline 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->StyleSVG()->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* const mRootFrame; + + /** + * The frame for the subtree we are also interested in tracking. + */ + const nsIFrame* const 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<StyleDominantBaseline, 8> mBaselines; + + /** + * The iterator's current position relative to mSubtree. + */ + SubtreePosition mSubtreePosition; +}; + +uint32_t TextFrameIterator::UndisplayedCharacters() const { + MOZ_ASSERT( + !mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY), + "Text correspondence must be up to date"); + + 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.RemoveLastElement(); + } + // 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) { + StyleDominantBaseline baseline = aNextFrame->StyleSVG()->mDominantBaseline; + mBaselines.AppendElement(baseline); +} + +void TextFrameIterator::PopBaseline() { + NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines"); + mBaselines.RemoveLastElement(); +} + +// ----------------------------------------------------------------------------- +// 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, + const 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 Text. + 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->TextFragment()); + 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 MOZ_STACK_CLASS CharIterator { + using Range = gfxTextRun::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 not skipped characters. + eUnskipped, + // 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, + }; + + /** + * 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, bool aPostReflow = true); + + /** + * 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 the glyph run for the current character. + */ + const gfxTextRun::GlyphRun& GlyphRun() 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; + } + + /** + * 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; + + /** + * Returns the frame corresponding to the <textPath> that the current + * character is within. + */ + nsIFrame* TextPathFrame() const { return mFrameIterator.TextPathFrame(); } + +#ifdef DEBUG + /** + * Returns the subtree we were constructed with. + */ + nsIContent* GetSubtree() const { return mSubtree; } + + /** + * Returns the CharacterFilter mode in use. + */ + CharacterFilter Filter() const { return mFilter; } +#endif + + 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; + } + } + + /** + * The filter to use. + */ + CharacterFilter mFilter; + + /** + * The iterator for text frames. + */ + TextFrameIterator mFrameIterator; + +#ifdef DEBUG + /** + * The subtree we were constructed with. + */ + nsIContent* const mSubtree; +#endif + + /** + * 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; + + /** + * The scale factor to apply to glyph advances returned by + * GetAdvance etc. to take into account textLength="". + */ + float mLengthAdjustScaleFactor; + + /** + * Whether the instance of this class is being used after reflow has occurred + * or not. + */ + bool mPostReflow; +}; + +CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame, + CharIterator::CharacterFilter aFilter, + nsIContent* aSubtree, bool aPostReflow) + : mFilter(aFilter), + mFrameIterator(aSVGTextFrame, aSubtree), +#ifdef DEBUG + mSubtree(aSubtree), +#endif + mFrameForTrimCheck(nullptr), + mTrimmedOffset(0), + mTrimmedLength(0), + mTextRun(nullptr), + mTextElementCharIndex(0), + mGlyphStartTextElementCharIndex(0), + mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor), + mPostReflow(aPostReflow) { + 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()); +} + +const gfxTextRun::GlyphRun& CharIterator::GlyphRun() const { + uint32_t numRuns; + const gfxTextRun::GlyphRun* glyphRuns = mTextRun->GetGlyphRuns(&numRuns); + uint32_t runIndex = mTextRun->FindFirstGlyphRunContaining( + mSkipCharsIterator.GetSkippedOffset()); + MOZ_ASSERT(runIndex < numRuns); + return glyphRuns[runIndex]; +} + +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(); + nsTextFrame::TrimmedOffsets trim = mFrameForTrimCheck->GetTrimmedOffsets( + mFrameForTrimCheck->TextFragment(), + (mPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default + : nsTextFrame::TrimmedOffsetFlags::NotPostReflow)); + 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->TextFragment()->CharAt(index) == '\n')); +} + +gfxFloat CharIterator::GetAdvance(nsPresContext* aContext) const { + float cssPxPerDevPx = + nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); + + gfxSkipCharsIterator start = + TextFrame()->EnsureTextRun(nsTextFrame::eInflated); + nsTextFrame::PropertyProvider provider(TextFrame(), start); + + uint32_t offset = mSkipCharsIterator.GetSkippedOffset(); + gfxFloat advance = + mTextRun->GetAdvanceWidth(Range(offset, offset + 1), &provider); + 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(); + 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 { + switch (mFilter) { + case eOriginal: + return true; + case eUnskipped: + return !IsOriginalCharSkipped(); + case eAddressable: + return !IsOriginalCharSkipped() && !IsOriginalCharUnaddressable(); + } + MOZ_ASSERT_UNREACHABLE("Invalid mFilter value"); + return true; +} + +// ----------------------------------------------------------------------------- +// 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 final : public nsTextFrame::DrawPathCallbacks { + using imgDrawingParams = image::imgDrawingParams; + + public: + /** + * Constructs an SVGTextDrawPathCallbacks. + * + * @param aSVGTextFrame The ancestor text frame. + * @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 aImgParams Whether we need to synchronously decode images. + * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted. + */ + SVGTextDrawPathCallbacks(SVGTextFrame* aSVGTextFrame, gfxContext& aContext, + nsTextFrame* aFrame, const gfxMatrix& aCanvasTM, + imgDrawingParams& aImgParams, + bool aShouldPaintSVGGlyphs) + : DrawPathCallbacks(aShouldPaintSVGGlyphs), + mSVGTextFrame(aSVGTextFrame), + mContext(aContext), + mFrame(aFrame), + mCanvasTM(aCanvasTM), + mImgParams(aImgParams), + mColor(0) {} + + 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 mSVGTextFrame->HasAnyStateBits(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(); + + SVGTextFrame* const mSVGTextFrame; + gfxContext& mContext; + nsTextFrame* const mFrame; + const gfxMatrix& mCanvasTM; + imgDrawingParams& mImgParams; + + /** + * 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(); + mContext.NewPath(); +} + +void SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() { + HandleTextGeometry(); + mContext.NewPath(); +} + +void SVGTextDrawPathCallbacks::NotifyAfterText() { mContext.Restore(); } + +void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, nscolor aColor) { + mColor = aColor; + AntialiasMode aaMode = + SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); + + mContext.Save(); + mContext.NewPath(); + mContext.SetAntialiasMode(aaMode); + mContext.Rectangle(ThebesRect(aPath)); + HandleTextGeometry(); + mContext.NewPath(); + mContext.Restore(); +} + +void SVGTextDrawPathCallbacks::PaintSelectionDecorationLine(Rect aPath, + nscolor aColor) { + if (IsClipPathChild()) { + // Don't paint selection decorations when in a clip path. + return; + } + + mColor = aColor; + + mContext.Save(); + mContext.NewPath(); + mContext.Rectangle(ThebesRect(aPath)); + FillAndStrokeGeometry(); + mContext.Restore(); +} + +void SVGTextDrawPathCallbacks::SetupContext() { + mContext.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 StyleTextRendering::Optimizespeed: + mContext.SetAntialiasMode(AntialiasMode::NONE); + break; + default: + mContext.SetAntialiasMode(AntialiasMode::SUBPIXEL); + break; + } +} + +void SVGTextDrawPathCallbacks::HandleTextGeometry() { + if (IsClipPathChild()) { + RefPtr<Path> path = mContext.GetPath(); + ColorPattern white( + DeviceColor(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor + mContext.GetDrawTarget()->Fill(path, white); + } else { + // Normal painting. + gfxContextMatrixAutoSaveRestore saveMatrix(&mContext); + mContext.SetMatrixDouble(mCanvasTM); + + FillAndStrokeGeometry(); + } +} + +void SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) { + if (mColor == NS_SAME_AS_FOREGROUND_COLOR || + mColor == NS_40PERCENT_FOREGROUND_COLOR) { + SVGUtils::MakeFillPatternFor(mFrame, &mContext, aOutPattern, mImgParams); + return; + } + + if (mColor == NS_TRANSPARENT) { + return; + } + + aOutPattern->InitColorPattern(ToDeviceColor(mColor)); +} + +void SVGTextDrawPathCallbacks::FillAndStrokeGeometry() { + bool pushedGroup = false; + if (mColor == NS_40PERCENT_FOREGROUND_COLOR) { + pushedGroup = true; + mContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f); + } + + uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder; + if (!paintOrder) { + FillGeometry(); + StrokeGeometry(); + } else { + while (paintOrder) { + auto component = StylePaintOrder(paintOrder & kPaintOrderMask); + switch (component) { + case StylePaintOrder::Fill: + FillGeometry(); + break; + case StylePaintOrder::Stroke: + StrokeGeometry(); + break; + default: + MOZ_FALLTHROUGH_ASSERT("Unknown paint-order value"); + case StylePaintOrder::Markers: + case StylePaintOrder::Normal: + break; + } + paintOrder >>= kPaintOrderShift; + } + } + + if (pushedGroup) { + mContext.PopGroupAndBlend(); + } +} + +void SVGTextDrawPathCallbacks::FillGeometry() { + GeneralPattern fillPattern; + MakeFillPattern(&fillPattern); + if (fillPattern.GetPattern()) { + RefPtr<Path> path = mContext.GetPath(); + FillRule fillRule = + SVGUtils::ToFillRule(IsClipPathChild() ? mFrame->StyleSVG()->mClipRule + : mFrame->StyleSVG()->mFillRule); + if (fillRule != path->GetFillRule()) { + RefPtr<PathBuilder> builder = path->CopyToBuilder(fillRule); + path = builder->Finish(); + } + mContext.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 (SVGUtils::HasStroke(mFrame, /*aContextPaint*/ nullptr)) { + GeneralPattern strokePattern; + SVGUtils::MakeStrokePatternFor(mFrame, &mContext, &strokePattern, + mImgParams, /*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; + } + SVGElement* svgOwner = + static_cast<SVGElement*>(mFrame->GetParent()->GetContent()); + + // Apply any stroke-specific transform + gfxMatrix outerSVGToUser; + if (SVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) && + outerSVGToUser.Invert()) { + mContext.Multiply(outerSVGToUser); + } + + RefPtr<Path> path = mContext.GetPath(); + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner, + mFrame->Style(), + /*aContextPaint*/ nullptr); + DrawOptions drawOptions; + drawOptions.mAntialiasMode = + SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); + mContext.GetDrawTarget()->Stroke(path, strokePattern, strokeOptions); + } + } + } +} + +// ============================================================================ +// SVGTextFrame + +// ---------------------------------------------------------------------------- +// Display list item + +class DisplaySVGText final : public nsPaintedDisplayItem { + public: + DisplaySVGText(nsDisplayListBuilder* aBuilder, SVGTextFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(DisplaySVGText); + MOZ_ASSERT(aFrame, "Must have a frame!"); + } +#ifdef NS_BUILD_REFCNT_LOGGING + MOZ_COUNTED_DTOR_OVERRIDE(DisplaySVGText) +#endif + + NS_DISPLAY_DECL_NAME("DisplaySVGText", TYPE_SVG_TEXT) + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) override; + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override { + return new nsDisplayItemGenericGeometry(this, aBuilder); + } + + virtual nsRect GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const override { + bool snap; + return GetBounds(aBuilder, &snap); + } +}; + +void DisplaySVGText::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) / + AppUnitsPerCSSPixel(); + + nsIFrame* target = frame->GetFrameForPoint(userSpacePt); + if (target) { + aOutFrames->AppendElement(target); + } +} + +void DisplaySVGText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* 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 = SVGUtils::GetCSSPxToDevPxMatrix(mFrame) * + gfxMatrix::Translation(devPixelOffset); + + gfxContext* ctx = aCtx; + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + static_cast<SVGTextFrame*>(mFrame)->PaintSVG(*ctx, tm, imgParams); +} + +// --------------------------------------------------------------------- +// nsQueryFrame methods + +NS_QUERYFRAME_HEAD(SVGTextFrame) + NS_QUERYFRAME_ENTRY(SVGTextFrame) +NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame) + +} // namespace mozilla + +// --------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGTextFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +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"); + + SVGDisplayContainerFrame::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); + + if (mState & NS_FRAME_IS_NONDISPLAY) { + // We're inserting a new <text> element into a non-display context. + // Ensure that we get reflowed. + ScheduleReflowSVGNonDisplayText( + IntrinsicDirty::FrameAncestorsAndDescendants); + } +} + +void SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + if (IsSubtreeDirty()) { + // 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->IsForPainting()) { + return; + } + DisplayOutline(aBuilder, aLists); + aLists.Content()->AppendNewToTop<DisplaySVGText>(aBuilder, this); +} + +nsresult SVGTextFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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; +} + +void SVGTextFrame::ReflowSVGNonDisplayText() { + MOZ_ASSERT(SVGUtils::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. + this->MarkSubtreeDirty(); + + // 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(IntrinsicDirty aReason) { + MOZ_ASSERT(!SVGUtils::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 other + // than SVGOuterSVGFrame, since calling FrameNeedsReflow on those other + // SVG frames would do a bunch of unnecessary work on the SVG frames up to + // the SVGOuterSVGFrame.) + + nsIFrame* f = this; + while (f) { + if (!f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + if (f->IsSubtreeDirty()) { + // 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->IsSVGOuterSVGFrame()) { + break; + } + f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + } + f = f->GetParent(); + } + + MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); + + PresShell()->FrameNeedsReflow(f, aReason, NS_FRAME_IS_DIRTY); +} + +NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver) + +void SVGTextFrame::MutationObserver::ContentAppended( + nsIContent* aFirstNewContent) { + mFrame->NotifyGlyphMetricsChange(); +} + +void SVGTextFrame::MutationObserver::ContentInserted(nsIContent* aChild) { + mFrame->NotifyGlyphMetricsChange(); +} + +void SVGTextFrame::MutationObserver::ContentRemoved( + nsIContent* aChild, nsIContent* aPreviousSibling) { + mFrame->NotifyGlyphMetricsChange(); +} + +void SVGTextFrame::MutationObserver::CharacterDataChanged( + nsIContent* aContent, const CharacterDataChangeInfo&) { + mFrame->NotifyGlyphMetricsChange(); +} + +void SVGTextFrame::MutationObserver::AttributeChanged( + Element* aElement, int32_t aNameSpaceID, nsAtom* 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, + nsAtom* aAttribute) { + if (aElement->IsSVGElement(nsGkAtoms::textPath)) { + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::startOffset || + aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::side_)) { + NotifyGlyphMetricsChange(); + } else if ((aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // Blow away our reference, if any + nsIFrame* childElementFrame = aElement->GetPrimaryFrame(); + if (childElementFrame) { + SVGObserverUtils::RemoveTextPathObserver(childElementFrame); + NotifyGlyphMetricsChange(); + } + } + } else { + if (aNameSpaceID == kNameSpaceID_None && + IsGlyphPositioningAttribute(aAttribute)) { + NotifyGlyphMetricsChange(); + } + } +} + +void SVGTextFrame::FindCloserFrameForSelection( + const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) { + if (HasAnyStateBits(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 = + SVGUtils::ToCanvasBounds(userRect.ToThebesRect(), m, presContext); + + if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect, + aCurrentBestFrame->mXDistance, + aCurrentBestFrame->mYDistance)) { + aCurrentBestFrame->mFrame = run.mFrame; + } + } + } +} + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame 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) { + RefPtr<Selection> selection = aCaret->GetSelection(); + if (!selection) { + return -1; + } + + return selection->AnchorOffset(); +} + +/** + * 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; + } + + return uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset && + uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset + + aThisRun.mTextFrameContentLength; +} + +void SVGTextFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect) { + DrawTarget& aDrawTarget = *aContext.GetDrawTarget(); + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + nsPresContext* presContext = PresContext(); + + gfxMatrix initialMatrix = aContext.CurrentMatrixDouble(); + + 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() && + IsSubtreeDirty()) { + return; + } + // Text frames inside <clipPath>, <mask>, etc. will never have had + // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now. + UpdateGlyphPositioning(); + } else if (IsSubtreeDirty()) { + // 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; + } + + const float epsilon = 0.0001; + if (abs(mLengthAdjustScaleFactor) < epsilon) { + // A zero scale factor can be caused by having forced the text length to + // zero. In this situation there is nothing to show. + return; + } + + if (aTransform.IsSingular()) { + NS_WARNING("Can't render text element!"); + return; + } + + 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; + } + } + + // 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 = nsPresContext::AppUnitsToFloatCSSPixels(auPerDevPx); + gfxMatrix canvasTMForChildren = aTransform; + canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx); + initialMatrix.PreScale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx); + + gfxContextMatrixAutoSaveRestore matSR(&aContext); + aContext.NewPath(); + aContext.Multiply(canvasTMForChildren); + gfxMatrix currentMatrix = aContext.CurrentMatrixDouble(); + + RefPtr<nsCaret> caret = presContext->PresShell()->GetCaret(); + nsRect caretRect; + nsIFrame* caretFrame = caret->GetPaintGeometry(&caretRect); + + gfxContextAutoSaveRestore ctxSR; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames); + TextRenderedRun run = it.Current(); + + SVGContextPaint* outerContextPaint = + SVGContextPaint::GetContextPaint(GetContent()); + + while (run.mFrame) { + nsTextFrame* frame = run.mFrame; + + RefPtr<SVGContextPaintImpl> contextPaint = new SVGContextPaintImpl(); + DrawMode drawMode = contextPaint->Init(&aDrawTarget, initialMatrix, frame, + outerContextPaint, aImgParams); + if (drawMode & DrawMode::GLYPH_STROKE) { + ctxSR.EnsureSaved(&aContext); + // This may change the gfxContext's transform (for non-scaling stroke), + // in which case this needs to happen before we call SetMatrix() below. + SVGUtils::SetupStrokeGeometry(frame->GetParent(), &aContext, + outerContextPaint); + } + + nscoord startEdge, endEdge; + run.GetClipEdges(startEdge, endEdge); + + // Set up the transform for painting the text frame for the substring + // indicated by the run. + gfxMatrix runTransform = run.GetTransformFromUserSpaceForPainting( + presContext, startEdge, endEdge) * + currentMatrix; + aContext.SetMatrixDouble(runTransform); + + if (drawMode != DrawMode(0)) { + bool paintSVGGlyphs; + nsTextFrame::PaintTextParams params(&aContext); + params.framePt = Point(); + params.dirtyRect = + LayoutDevicePixel::FromAppUnits(frame->InkOverflowRect(), auPerDevPx); + params.contextPaint = contextPaint; + + const bool isSelected = frame->IsSelected(); + + if (ShouldRenderAsPath(frame, paintSVGGlyphs)) { + SVGTextDrawPathCallbacks callbacks(this, aContext, frame, + matrixForPaintServers, aImgParams, + paintSVGGlyphs); + params.callbacks = &callbacks; + frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected); + } else { + frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected); + } + } + + 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(); + } +} + +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(!IsSubtreeDirty(), "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 (!SVGUtils::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 = SVGUtils::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.TransformPoint(aPoint); + gfxRect frameRect = run.GetRunUserSpaceRect( + presContext, TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke) + .ToThebesRect(); + + if (Inside(frameRect, pointInRunUserSpace)) { + hit = run.mFrame; + } + } + return hit; +} + +void SVGTextFrame::ReflowSVG() { + MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), + "This call is probaby a wasteful mistake"); + + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), + "ReflowSVG mechanism not designed for this"); + + if (!SVGUtils::NeedsReflowSVG(this)) { + MOZ_ASSERT(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | + 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.kind.IsNone()) { + runFlags |= + TextRenderedRun::eIncludeFill | TextRenderedRun::eIncludeTextShadow; + } + if (SVGUtils::HasStroke(run.mFrame)) { + runFlags |= + TextRenderedRun::eIncludeStroke | 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 = SVGUtils::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(), + 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(ceil(presContext->AppUnitsPerDevPixel() / mLastContextScale)); + } + + 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): + SVGObserverUtils::UpdateEffects(this); + } + + // Now unset the various reflow bits. Do this before calling + // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph + // positions (to resolve transform-origin). + RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); + + nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size()); + OverflowAreas overflowAreas(overflow, overflow); + FinishAndStoreOverflow(overflowAreas, mRect.Size()); + + // XXX SVGContainerFrame::ReflowSVG only looks at its ISVGDisplayableFrame + // children, and calls ConsiderChildOverflow on them. Does it matter + // that ConsiderChildOverflow won't be called on our children? + SVGDisplayContainerFrame::ReflowSVG(); +} + +/** + * Converts SVGUtils::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 & SVGUtils::eBBoxIncludeFillGeometry) || + ((aBBoxFlags & SVGUtils::eBBoxIncludeFill) && + !aRun.mFrame->StyleSVG()->mFill.kind.IsNone())) { + flags |= TextRenderedRun::eIncludeFill; + } + if ((aBBoxFlags & SVGUtils::eBBoxIncludeStrokeGeometry) || + ((aBBoxFlags & SVGUtils::eBBoxIncludeStroke) && + SVGUtils::HasStroke(aRun.mFrame))) { + flags |= TextRenderedRun::eIncludeStroke; + } + return flags; +} + +SVGBBox SVGTextFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) { + NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame"); + SVGBBox bbox; + + if (aFlags & SVGUtils::eForGetClientRects) { + Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel()); + if (!rect.IsEmpty()) { + bbox = aToBBoxUserspace.TransformBounds(rect); + } + return bbox; + } + + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid && kid->IsSubtreeDirty()) { + // 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; +} + +//---------------------------------------------------------------------- +// SVGTextFrame SVG DOM methods + +/** + * Returns whether the specified node has any non-empty Text + * beneath it. + */ +static bool HasTextContent(nsIContent* aContent) { + NS_ASSERTION(aContent, "expected non-null aContent"); + + TextNodeIterator it(aContent); + for (Text* 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 (Text* 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) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + return 0; + } + + 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) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + // + // If we ever decide that we need to return accurate values here, + // we could do similar work to GetSubStringLength. + return 0; + } + + UpdateGlyphPositioning(); + + float cssPxPerDevPx = nsPresContext::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. + */ +void SVGTextFrame::SelectSubString(nsIContent* aContent, uint32_t charnum, + uint32_t nchars, ErrorResult& aRv) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + // XXXbz Should this just return without throwing like the no-frame case? + aRv.ThrowInvalidStateError("No layout information available for SVG text"); + return; + } + + 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()) { + aRv.ThrowIndexSizeError("Character index out of range"); + return; + } + charnum = chit.TextElementCharIndex(); + const RefPtr<nsIContent> content = chit.TextFrame()->GetContent(); + chit.NextWithinSubtree(nchars); + nchars = chit.TextElementCharIndex() - charnum; + + RefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); + + frameSelection->HandleClick(content, charnum, charnum + nchars, + nsFrameSelection::FocusMode::kCollapseToNewPoint, + CARET_ASSOCIATE_BEFORE); +} + +/** + * Implements the SVG DOM GetSubStringLength method for the specified + * text content element. + */ +float SVGTextFrame::GetSubStringLength(nsIContent* aContent, uint32_t charnum, + uint32_t nchars, ErrorResult& aRv) { + // For some content we cannot (or currently cannot) compute the length + // without reflowing. In those cases we need to fall back to using + // GetSubStringLengthSlowFallback. + // + // We fall back for textPath since we need glyph positioning in order to + // tell if any characters should be ignored due to having fallen off the + // end of the textPath. + // + // We fall back for bidi because GetTrimmedOffsets does not produce the + // correct results for bidi continuations when passed aPostReflow = false. + // XXX It may be possible to determine which continuations to trim from (and + // which sides), but currently we don't do that. It would require us to + // identify the visual (rather than logical) start and end of the line, to + // avoid trimming at line-internal frame boundaries. Maybe nsBidiPresUtils + // methods like GetFrameToRightOf and GetFrameToLeftOf would help? + // + TextFrameIterator frameIter(this); + for (nsTextFrame* frame = frameIter.Current(); frame; + frame = frameIter.Next()) { + if (frameIter.TextPathFrame() || frame->GetNextContinuation()) { + return GetSubStringLengthSlowFallback(aContent, charnum, nchars, aRv); + } + } + + // We only need our text correspondence to be up to date (no need to call + // UpdateGlyphPositioning). + TextNodeCorrespondenceRecorder::RecordCorrespondence(this); + + // Convert charnum/nchars from addressable characters relative to + // aContent to global character indices. + CharIterator chit(this, CharIterator::eAddressable, aContent, + /* aPostReflow */ false); + if (!chit.AdvanceToSubtree() || !chit.Next(charnum) || + chit.IsAfterSubtree()) { + aRv.ThrowIndexSizeError("Character index out of range"); + return 0; + } + + // We do this after the ThrowIndexSizeError() bit so JS calls correctly throw + // when necessary. + if (nchars == 0) { + return 0.0f; + } + + charnum = chit.TextElementCharIndex(); + chit.NextWithinSubtree(nchars); + nchars = chit.TextElementCharIndex() - charnum; + + // Sum of the substring advances. + nscoord textLength = 0; + + TextFrameIterator frit(this); // aSubtree = nullptr + + // Index of the first non-skipped char in the frame, and of a subsequent char + // that we're interested in. Both are relative to the index of the first + // non-skipped char in the ancestor <text> element. + uint32_t frameStartTextElementCharIndex = 0; + uint32_t textElementCharIndex; + + for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) { + frameStartTextElementCharIndex += frit.UndisplayedCharacters(); + textElementCharIndex = frameStartTextElementCharIndex; + + // Offset into frame's Text: + const uint32_t untrimmedOffset = frame->GetContentOffset(); + const uint32_t untrimmedLength = frame->GetContentEnd() - untrimmedOffset; + + // Trim the offset/length to remove any leading/trailing white space. + uint32_t trimmedOffset = untrimmedOffset; + uint32_t trimmedLength = untrimmedLength; + nsTextFrame::TrimmedOffsets trimmedOffsets = frame->GetTrimmedOffsets( + frame->TextFragment(), nsTextFrame::TrimmedOffsetFlags::NotPostReflow); + TrimOffsets(trimmedOffset, trimmedLength, trimmedOffsets); + + textElementCharIndex += trimmedOffset - untrimmedOffset; + + if (textElementCharIndex >= charnum + nchars) { + break; // we're past the end of the substring + } + + uint32_t offset = textElementCharIndex; + + // Intersect the substring we are interested in with the range covered by + // the nsTextFrame. + IntersectInterval(offset, trimmedLength, charnum, nchars); + + if (trimmedLength != 0) { + // Convert offset into an index into the frame. + offset += trimmedOffset - textElementCharIndex; + + gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); + nsTextFrame::PropertyProvider provider(frame, it); + + Range range = ConvertOriginalToSkipped(it, offset, trimmedLength); + + // Accumulate the advance. + textLength += textRun->GetAdvanceWidth(range, &provider); + } + + // Advance, ready for next call: + frameStartTextElementCharIndex += untrimmedLength; + } + + nsPresContext* presContext = PresContext(); + float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( + presContext->AppUnitsPerDevPixel()); + + return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx / + mFontSizeScaleFactor; +} + +float SVGTextFrame::GetSubStringLengthSlowFallback(nsIContent* aContent, + uint32_t charnum, + uint32_t nchars, + ErrorResult& aRv) { + // We need to make sure that we've been reflowed before updating the glyph + // positioning. + // XXX perf: It may be possible to limit reflow to just calling ReflowSVG, + // but we would still need to resort to full reflow for percentage + // positioning attributes. For now we just do a full reflow regardless since + // the cases that would cause us to be called are relatively uncommon. + RefPtr<mozilla::PresShell> presShell = PresShell(); + presShell->FlushPendingNotifications(FlushType::Layout); + + 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()) { + aRv.ThrowIndexSizeError("Character index out of range"); + return 0; + } + + if (nchars == 0) { + return 0.0f; + } + + 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 it = + run.mFrame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated); + nsTextFrame::PropertyProvider provider(run.mFrame, it); + + Range range = ConvertOriginalToSkipped(it, offset, length); + + // Accumulate the advance. + textLength += textRun->GetAdvanceWidth(range, &provider); + } + + run = runIter.Next(); + } + + nsPresContext* presContext = PresContext(); + float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( + presContext->AppUnitsPerDevPixel()); + + return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx / + mFontSizeScaleFactor; +} + +/** + * Implements the SVG DOM GetCharNumAtPosition method for the specified + * text content element. + */ +int32_t SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent, + const DOMPointInit& aPoint) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + return -1; + } + + UpdateGlyphPositioning(); + + nsPresContext* context = PresContext(); + + gfxPoint p(aPoint.mX, aPoint.mY); + + 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. + */ +already_AddRefed<DOMSVGPoint> SVGTextFrame::GetStartPositionOfChar( + nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + aRv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { + aRv.ThrowIndexSizeError("Character index out of range"); + return nullptr; + } + + // We need to return the start position of the whole glyph. + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + + RefPtr<DOMSVGPoint> point = + new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition)); + return point.forget(); +} + +/** + * Returns the advance of the entire glyph whose starting character is at + * aTextElementCharIndex. + * + * aIterator, if provided, must be a CharIterator that already points to + * aTextElementCharIndex that is restricted to aContent and is using + * filter mode eAddressable. + */ +static gfxFloat GetGlyphAdvance(SVGTextFrame* aFrame, nsIContent* aContent, + uint32_t aTextElementCharIndex, + CharIterator* aIterator) { + MOZ_ASSERT(!aIterator || (aIterator->Filter() == CharIterator::eAddressable && + aIterator->GetSubtree() == aContent && + aIterator->GlyphStartTextElementCharIndex() == + aTextElementCharIndex), + "Invalid aIterator"); + + Maybe<CharIterator> newIterator; + CharIterator* it = aIterator; + if (!it) { + newIterator.emplace(aFrame, CharIterator::eAddressable, aContent); + if (!newIterator->AdvanceToSubtree()) { + MOZ_ASSERT_UNREACHABLE("Invalid aContent"); + return 0.0; + } + it = newIterator.ptr(); + } + + while (it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) { + if (!it->Next()) { + MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex"); + return 0.0; + } + } + + if (it->AtEnd()) { + MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex"); + return 0.0; + } + + nsPresContext* presContext = aFrame->PresContext(); + gfxFloat advance = 0.0; + + for (;;) { + advance += it->GetAdvance(presContext); + if (!it->Next() || + it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) { + break; + } + } + + return advance; +} + +/** + * Implements the SVG DOM GetEndPositionOfChar method for the specified + * text content element. + */ +already_AddRefed<DOMSVGPoint> SVGTextFrame::GetEndPositionOfChar( + nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + aRv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { + aRv.ThrowIndexSizeError("Character index out of range"); + return nullptr; + } + + // We need to return the end position of the whole glyph. + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + + // Get the advance of the glyph. + gfxFloat advance = + GetGlyphAdvance(this, aContent, startIndex, + it.IsClusterAndLigatureGroupStart() ? &it : nullptr); + 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)); + + RefPtr<DOMSVGPoint> point = new DOMSVGPoint(p); + return point.forget(); +} + +/** + * Implements the SVG DOM GetExtentOfChar method for the specified + * text content element. + */ +already_AddRefed<SVGRect> SVGTextFrame::GetExtentOfChar(nsIContent* aContent, + uint32_t aCharNum, + ErrorResult& aRv) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + aRv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + UpdateGlyphPositioning(); + + // Search for the character whose addressable index is aCharNum. + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { + aRv.ThrowIndexSizeError("Character index out of range"); + return nullptr; + } + + nsPresContext* presContext = PresContext(); + float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( + presContext->AppUnitsPerDevPixel()); + + nsTextFrame* textFrame = it.TextFrame(); + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + bool isRTL = it.TextRun()->IsRightToLeft(); + bool isVertical = it.TextRun()->IsVertical(); + + // Get the glyph advance. + gfxFloat advance = + GetGlyphAdvance(this, aContent, startIndex, + it.IsClusterAndLigatureGroupStart() ? &it : nullptr); + gfxFloat x = isRTL ? -advance : 0.0; + + // The ascent and descent gives the height of the glyph. + gfxFloat ascent, descent; + GetAscentAndDescentInAppUnits(textFrame, ascent, descent); + + // The horizontal extent is the origin of the glyph plus the advance + // in the direction of the glyph's rotation. + gfxMatrix m; + m.PreTranslate(mPositions[startIndex].mPosition); + m.PreRotate(mPositions[startIndex].mAngle); + m.PreScale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor); + + gfxRect glyphRect; + if (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); + + RefPtr<SVGRect> rect = new SVGRect(aContent, ToRect(r)); + return rect.forget(); +} + +/** + * Implements the SVG DOM GetRotationOfChar method for the specified + * text content element. + */ +float SVGTextFrame::GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum, + ErrorResult& aRv) { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (kid->IsSubtreeDirty()) { + // We're never reflowed if we're under a non-SVG element that is + // never reflowed (such as the HTML 'caption' element). + aRv.ThrowInvalidStateError("No layout information available for SVG text"); + return 0; + } + + UpdateGlyphPositioning(); + + CharIterator it(this, CharIterator::eAddressable, aContent); + if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { + aRv.ThrowIndexSizeError("Character index out of range"); + return 0; + } + + // we need to account for the glyph's underlying orientation + const gfxTextRun::GlyphRun& glyphRun = it.GlyphRun(); + int32_t glyphOrientation = + 90 * (glyphRun.IsSidewaysRight() - glyphRun.IsSidewaysLeft()); + + return mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI + + glyphOrientation; +} + +//---------------------------------------------------------------------- +// 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->IsText()) { + // We found a text node. + uint32_t length = aContent->AsText()->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)) { + // Any ‘y’ attributes on horizontal <textPath> elements are ignored. + // Similarly, for vertical <texPath>s x attributes are ignored. + // <textPath> elements behave as if they have x="0" y="0" on them, but only + // if there is not a value for the non-ignored coordinate that got inherited + // from a parent. We skip this if there is no text content, so that empty + // <textPath>s don't interrupt the layout of text in the parent element. + 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; + } + bool vertical = GetWritingMode().IsVertical(); + if (vertical || !mPositions[aIndex].IsXSpecified()) { + mPositions[aIndex].mPosition.x = 0.0; + } + if (!vertical || !mPositions[aIndex].IsYSpecified()) { + mPositions[aIndex].mPosition.y = 0.0; + } + mPositions[aIndex].mStartOfChunk = true; + } + } else if (!aContent->IsSVGElement(nsGkAtoms::a)) { + MOZ_ASSERT(aContent->IsSVGElement()); + + // We have a text content element that can have x/y/dx/dy/rotate attributes. + SVGElement* element = static_cast<SVGElement*>(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, /* aSubtree */ nullptr); + 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; + + TextFrameIterator frit(this); + for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) { + gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); + nsTextFrame::PropertyProvider provider(frame, it); + + // 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->TextFragment()); + while (it.GetOriginalOffset() < trimmedOffsets.mStart) { + aPositions.AppendElement(position); + it.AdvanceOriginal(1); + } + + // Visible characters in the text frame. + while (it.GetOriginalOffset() < frame->GetContentEnd()) { + aPositions.AppendElement(position); + if (!it.IsOriginalCharSkipped()) { + // Accumulate partial ligature advance into position. (We must get + // partial advances rather than get the advance of the whole ligature + // group / cluster at once, since the group may span text frames, and + // the PropertyProvider only has spacing information for the current + // text frame.) + uint32_t offset = it.GetSkippedOffset(); + nscoord advance = + textRun->GetAdvanceWidth(Range(offset, offset + 1), &provider); + (textRun->IsVertical() ? position.y : position.x) += + textRun->IsRightToLeft() ? -advance : advance; + } + 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( + StyleTextAnchor aTextAnchor, bool aIsRightToLeft) { + NS_ASSERTION(uint8_t(aTextAnchor) <= 3, "unexpected value for aTextAnchor"); + if (!aIsRightToLeft) { + return TextAnchorSide(uint8_t(aTextAnchor)); + } + return TextAnchorSide(2 - uint8_t(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<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: + MOZ_ASSERT_UNREACHABLE("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 = do_QueryFrame(PrincipalChildList().FirstChild()); + NS_ASSERTION(block, "expected block frame"); + + nsBlockFrame::LineIterator line = block->LinesBegin(); + + CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr); + 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(); + + // Find all of the characters that are in the middle of a cluster or + // ligature group, and adjust their positions and rotations to match + // the first character of the cluster/group. + // + // Also move the boundaries of text rendered runs and anchored chunks to + // not lie in the middle of cluster/group. + + // The partial advance of the current cluster or ligature group that we + // have accumulated. + gfxFloat partialAdvance = 0.0; + + CharIterator it(this, CharIterator::eUnskipped, /* aSubtree */ nullptr); + while (!it.AtEnd()) { + if (it.IsClusterAndLigatureGroupStart()) { + // If we're at the start of a new cluster or ligature group, reset our + // accumulated partial advance. + partialAdvance = 0.0; + } else { + // Otherwise, we're in the middle of a cluster or ligature group, and + // we need to use the currently accumulated partial advance to adjust + // the character's position and rotation. + + // Find the start of the cluster/ligature group. + uint32_t charIndex = it.TextElementCharIndex(); + uint32_t startIndex = it.GlyphStartTextElementCharIndex(); + MOZ_ASSERT(charIndex != startIndex, + "If the current character is in the middle of a cluster or " + "ligature group, then charIndex must be different from " + "startIndex"); + + 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; + } + + // Update the character position. + gfxFloat advance = partialAdvance / mFontSizeScaleFactor; + gfxPoint direction = gfxPoint(cos(angle), sin(angle)) * + (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0); + if (it.TextRun()->IsVertical()) { + std::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; + } + } + } + + // Accumulate the current character's partial advance. + partialAdvance += it.GetAdvance(presContext); + + it.Next(); + } +} + +already_AddRefed<Path> SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) { + nsIContent* content = aTextPathFrame->GetContent(); + SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content); + if (tp->mPath.IsRendered()) { + // This is just an attribute so there's no transform that can apply + // so we can just return the path directly. + return tp->mPath.GetAnimValue().BuildPathForMeasuring(); + } + + SVGGeometryElement* geomElement = + SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame); + if (!geomElement) { + return nullptr; + } + + RefPtr<Path> path = geomElement->GetOrBuildPathForMeasuring(); + if (!path) { + return nullptr; + } + + gfxMatrix matrix = geomElement->PrependLocalTransformsTo(gfxMatrix()); + if (!matrix.IsIdentity()) { + // Apply the geometry element's transform + RefPtr<PathBuilder> builder = + path->TransformedCopyToBuilder(ToMatrix(matrix)); + path = builder->Finish(); + } + + return path.forget(); +} + +gfxFloat SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) { + nsIContent* content = aTextPathFrame->GetContent(); + SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content); + if (tp->mPath.IsRendered()) { + // A path attribute has no pathLength or transform + // so we return a unit scale. + return 1.0; + } + + SVGGeometryElement* geomElement = + SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame); + if (!geomElement) { + return 1.0; + } + return geomElement->GetPathLengthScale(SVGGeometryElement::eForTextPath); +} + +gfxFloat SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) { + SVGTextPathElement* tp = + static_cast<SVGTextPathElement*>(aTextPathFrame->GetContent()); + SVGAnimatedLength* length = + &tp->mLengthAttributes[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::eOriginal, /* aSubtree */ nullptr); + 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) { + uint32_t start = it.TextElementCharIndex(); + it.AdvancePastCurrentTextPathFrame(); + uint32_t end = it.TextElementCharIndex(); + for (uint32_t i = start; i < end; i++) { + mPositions[i].mHidden = true; + } + continue; + } + + SVGTextPathElement* textPath = + static_cast<SVGTextPathElement*>(textPathFrame->GetContent()); + uint16_t side = + textPath->EnumAttributes()[SVGTextPathElement::SIDE].GetAnimValue(); + + gfxFloat offset = GetStartOffset(textPathFrame); + Float pathLength = path->ComputeLength(); + + // If the first character within the text path is in the middle of a + // cluster or ligature group, just skip it and don't apply text path + // positioning. + while (!it.AtEnd()) { + if (it.IsOriginalCharSkipped()) { + it.Next(); + continue; + } + if (it.IsClusterAndLigatureGroupStart()) { + break; + } + it.Next(); + } + + bool skippedEndOfTextPath = false; + + // Loop for each character in the text path. + while (!it.AtEnd() && it.TextPathFrame() && + it.TextPathFrame()->GetContent() == textPath) { + // The index of the cluster or ligature group's first character. + uint32_t i = it.TextElementCharIndex(); + + // The index of the next character of the cluster or ligature. + // We track this as we loop over the characters below so that we + // can detect undisplayed characters and append entries into + // partialAdvances for them. + uint32_t j = i + 1; + + MOZ_ASSERT(!mPositions[i].mClusterOrLigatureGroupMiddle); + + gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0; + bool vertical = it.TextRun()->IsVertical(); + + // Compute cumulative advances for each character of the cluster or + // ligature group. + AutoTArray<gfxFloat, 4> partialAdvances; + gfxFloat partialAdvance = it.GetAdvance(context); + partialAdvances.AppendElement(partialAdvance); + while (it.Next()) { + // Append entries for any undisplayed characters the CharIterator + // skipped over. + MOZ_ASSERT(j <= it.TextElementCharIndex()); + while (j < it.TextElementCharIndex()) { + partialAdvances.AppendElement(partialAdvance); + ++j; + } + // This loop may end up outside of the current text path, but + // that's OK; we'll consider any complete cluster or ligature + // group that begins inside the text path as being affected + // by it. + if (it.IsOriginalCharSkipped()) { + if (!it.TextPathFrame()) { + skippedEndOfTextPath = true; + break; + } + // Leave partialAdvance unchanged. + } else if (it.IsClusterAndLigatureGroupStart()) { + break; + } else { + partialAdvance += it.GetAdvance(context); + } + partialAdvances.AppendElement(partialAdvance); + } + if (skippedEndOfTextPath) { + break; + } + + // Any final undisplayed characters the CharIterator skipped over. + MOZ_ASSERT(j <= it.TextElementCharIndex()); + while (j < it.TextElementCharIndex()) { + partialAdvances.AppendElement(partialAdvance); + ++j; + } + + gfxFloat halfAdvance = + partialAdvances.LastElement() / mFontSizeScaleFactor / 2.0; + 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; + if (side == TEXTPATH_SIDETYPE_RIGHT) { + pt = path->ComputePointAtLength(Float(pathLength - midx), &tangent); + tangent = -tangent; + } else { + 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 k = i + 1; k < j; k++) { + gfxPoint partialAdvance = ThebesPoint(direction) * + partialAdvances[k - i] / mFontSizeScaleFactor; + mPositions[k].mPosition = mPositions[i].mPosition + partialAdvance; + mPositions[k].mAngle = mPositions[i].mAngle; + mPositions[k].mHidden = mPositions[i].mHidden; + } + } + } +} + +void SVGTextFrame::DoAnchoring() { + nsPresContext* presContext = PresContext(); + + CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr); + + // 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 == StyleDirection::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 && kid->IsSubtreeDirty()) { + MOZ_ASSERT(false, "should have already reflowed the kid"); + return; + } + + // Since we can be called directly via GetBBoxContribution, our correspondence + // may not be up to date. + TextNodeCorrespondenceRecorder::RecordCorrespondence(this); + + // 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*>(GetContent()); + SVGAnimatedLength* textLengthAttr = + element->GetAnimatedLength(nsGkAtoms::textLength); + uint16_t lengthAdjust = + element->EnumAttributes()[SVGTextContentElement::LENGTHADJUST] + .GetAnimValue(); + bool adjustingTextLength = textLengthAttr->IsExplicitlySet(); + float expectedTextLength = textLengthAttr->GetAnimValue(element); + + if (adjustingTextLength && + (expectedTextLength < 0.0f || lengthAdjust == LENGTHADJUST_UNKNOWN)) { + // If textLength="" is less than zero or lengthAdjust is unknown, 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 = nsPresContext::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); + + switch (lengthAdjust) { + case LENGTHADJUST_SPACINGANDGLYPHS: + // Scale the glyphs and their positions. + if (actualTextLength > 0) { + mLengthAdjustScaleFactor = expectedTextLength / actualTextLength; + } + break; + + default: + MOZ_ASSERT(lengthAdjust == 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 (HasAnyStateBits(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.kind.IsNone() || + (style->mFill.kind.IsColor() && style->mFillOpacity.IsOpacity() && + style->mFillOpacity.AsOpacity() == 1))) { + return true; + } + + // Text has a stroke. + if (style->HasStroke()) { + if (style->mStrokeWidth.IsContextValue()) { + return true; + } + if (SVGContentUtils::CoordToFloat( + static_cast<SVGElement*>(GetContent()), + style->mStrokeWidth.AsLengthPercentage()) > 0) { + return true; + } + } + + return false; +} + +void SVGTextFrame::ScheduleReflowSVG() { + if (mState & NS_FRAME_IS_NONDISPLAY) { + ScheduleReflowSVGNonDisplayText( + IntrinsicDirty::FrameAncestorsAndDescendants); + } else { + SVGUtils::ScheduleReflowSVG(this); + } +} + +void SVGTextFrame::NotifyGlyphMetricsChange() { + // TODO: perf - adding NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY is overly + // aggressive here. Ideally we would only set that bit when our descendant + // frame tree changes (i.e. after frame construction). + AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | + NS_STATE_SVG_POSITIONING_DIRTY); + nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + ScheduleReflowSVG(); +} + +void SVGTextFrame::UpdateGlyphPositioning() { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + if (mState & NS_STATE_SVG_POSITIONING_DIRTY) { + DoGlyphPositioning(); + } +} + +void SVGTextFrame::MaybeResolveBidiForAnonymousBlockChild() { + nsIFrame* kid = PrincipalChildList().FirstChild(); + + if (kid && kid->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && + PresContext()->BidiEnabled()) { + MOZ_ASSERT(static_cast<nsBlockFrame*>(do_QueryFrame(kid)), + "Expect anonymous child to be an nsBlockFrame"); + nsBidiPresUtils::Resolve(static_cast<nsBlockFrame*>(kid)); + } +} + +void SVGTextFrame::MaybeReflowAnonymousBlockChild() { + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + NS_ASSERTION(!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW), + "should not be in reflow when about to reflow again"); + + if (IsSubtreeDirty()) { + 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 ISVGDisplayableFrame, + // so even when we are called via our ReflowSVG this will not be done for + // us by SVGDisplayContainerFrame::ReflowSVG.) + kid->MarkSubtreeDirty(); + } + + // The RecordCorrespondence and DoReflow calls can result in new text frames + // being created (due to bidi resolution or reflow). We set this bit to + // guard against unnecessarily calling back in to + // ScheduleReflowSVGNonDisplayText from nsIFrame::DidSetComputedStyle on + // those new text frames. + AddStateBits(NS_STATE_SVG_TEXT_IN_REFLOW); + + TextNodeCorrespondenceRecorder::RecordCorrespondence(this); + + MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), + "should be under ReflowSVG"); + nsPresContext::InterruptPreventer noInterrupts(PresContext()); + DoReflow(); + + RemoveStateBits(NS_STATE_SVG_TEXT_IN_REFLOW); + } +} + +void SVGTextFrame::DoReflow() { + MOZ_ASSERT(HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)); + + // Since we are going to reflow the anonymous block frame, we will + // need to update mPositions. + // We also mark our text correspondence as dirty since we can end up needing + // reflow in ways that do not set NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY. + // (We'd then fail the "expected a TextNodeCorrespondenceProperty" assertion + // when UpdateGlyphPositioning() is called after we return.) + AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | + 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. + RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); + } + + nsPresContext* presContext = PresContext(); + nsIFrame* kid = PrincipalChildList().FirstChild(); + if (!kid) { + return; + } + + RefPtr<gfxContext> 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(); + } + + 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); + kid->SetSize(wm, desiredSize.Size(wm)); +} + +// 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; + CSSCoord min = std::numeric_limits<float>::max(); + CSSCoord max = std::numeric_limits<float>::min(); + bool anyText = false; + + // 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 == + StyleTextRendering::Geometricprecision; + } + const auto& fontSize = f->StyleFont()->mFont.size; + if (!fontSize.IsZero()) { + min = std::min(min, fontSize.ToCSSPixels()); + max = std::max(max, fontSize.ToCSSPixels()); + anyText = true; + } + f = it.Next(); + } + + if (!anyText) { + // No text, so no need for scaling. + mFontSizeScaleFactor = 1.0; + return mFontSizeScaleFactor != oldFontSizeScaleFactor; + } + + if (geometricPrecision) { + // We want to ensure minSize is scaled to PRECISE_SIZE. + mFontSizeScaleFactor = PRECISE_SIZE / min; + 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); + if (!std::isfinite(contextScale)) { + contextScale = 1.0f; + } + } + } + mLastContextScale = contextScale; + + // 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 = nsPresContext::AppUnitsToFloatCSSPixels( + presContext->AppUnitsPerDevPixel()); + contextScale *= cssPxPerDevPx; + + double minTextRunSize = min * contextScale; + double maxTextRunSize = max * 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 (max / min > 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. + // Heuristically, if the maxTextRunSize is within the CLAMP_MAX_SIZE + // as a reasonable value, it's likely to be the user's intent to + // get a valid font for the maxTextRunSize one, we should honor it. + // The same for minTextRunSize. + if (maxTextRunSize <= CLAMP_MAX_SIZE) { + mFontSizeScaleFactor = CLAMP_MAX_SIZE / max; + } else if (minTextRunSize >= CLAMP_MIN_SIZE) { + mFontSizeScaleFactor = CLAMP_MIN_SIZE / min; + } else { + // So maxTextRunSize is too big, minTextRunSize is too small, + // we can't really do anything for this case, just leave it as is. + mFontSizeScaleFactor = contextScale; + } + } else if (minTextRunSize < CLAMP_MIN_SIZE) { + mFontSizeScaleFactor = CLAMP_MIN_SIZE / min; + } else { + mFontSizeScaleFactor = CLAMP_MAX_SIZE / max; + } + + 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, const nsIFrame* aChildFrame) { + NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType( + aChildFrame->GetParent(), + LayoutFrameType::SVGText) == 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 = nsPresContext::AppUnitsToFloatCSSPixels( + presContext->AppUnitsPerDevPixel()); + float factor = 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.TransformPoint(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.value, runRect.X(), runRect.XMost()); + pointInRun.y = + clamped(pointInRunUserSpace.y.value, 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.PreScale(mFontSizeScaleFactor, mFontSizeScaleFactor); + return ToPoint(m.TransformPoint(pointInRun) / cssPxPerDevPx); +} + +/** + * 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, const nsIFrame* aChildFrame) { + NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType( + aChildFrame->GetParent(), + LayoutFrameType::SVGText) == 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> + gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); + gfxRect rectInUserSpace = m.TransformRect(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 = AppUnitsPerCSSPixel(); + gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + + return result - framePosition; +} + +Rect SVGTextFrame::TransformFrameRectFromTextChild( + const Rect& aRect, const nsIFrame* aChildFrame) { + nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + nsRect r = LayoutDevicePixel::ToAppUnits( + LayoutDeviceRect::FromUnknownRect(aRect), appUnitsPerDevPixel); + gfxRect resultCssUnits = TransformFrameRectFromTextChild(r, aChildFrame); + float devPixelPerCSSPixel = + float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel; + resultCssUnits.Scale(devPixelPerCSSPixel); + return ToRect(resultCssUnits); +} + +Point SVGTextFrame::TransformFramePointFromTextChild( + const Point& aPoint, const nsIFrame* aChildFrame) { + return TransformFrameRectFromTextChild(Rect(aPoint, Size(1, 1)), aChildFrame) + .TopLeft(); +} + +void SVGTextFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box"); + aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild())); +} + +} // namespace mozilla diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h new file mode 100644 index 0000000000..756088cb96 --- /dev/null +++ b/layout/svg/SVGTextFrame.h @@ -0,0 +1,589 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGTEXTFRAME_H_ +#define LAYOUT_SVG_SVGTEXTFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/PresShellForwards.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/gfx/2D.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "gfxTextRun.h" +#include "nsIContent.h" // for GetContent +#include "nsStubMutationObserver.h" +#include "nsTextFrame.h" + +class gfxContext; + +namespace mozilla { + +class CharIterator; +class DisplaySVGText; +class SVGTextFrame; +class TextFrameIterator; +class TextNodeCorrespondenceRecorder; +struct TextRenderedRun; +class TextRenderedRunIterator; + +namespace dom { +struct DOMPointInit; +class DOMSVGPoint; +class SVGRect; +class SVGGeometryElement; +} // namespace dom +} // namespace mozilla + +nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +/** + * 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) + : Runnable("GlyphMetricsUpdater"), mFrame(aFrame) {} + static void Run(SVGTextFrame* aFrame); + void Revoke() { mFrame = nullptr; } + + private: + SVGTextFrame* mFrame; +}; + +/** + * 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 SVGDisplayContainerFrame { + friend nsIFrame* ::NS_NewSVGTextFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + friend class CharIterator; + friend class DisplaySVGText; + friend class GlyphMetricsUpdater; + friend class MutationObserver; + friend class TextFrameIterator; + friend class TextNodeCorrespondenceRecorder; + friend struct TextRenderedRun; + friend class TextRenderedRunIterator; + + using Range = gfxTextRun::Range; + using DrawTarget = gfx::DrawTarget; + using Path = gfx::Path; + using Point = gfx::Point; + using Rect = gfx::Rect; + + protected: + explicit SVGTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID), + mTrailingUndisplayedCharacters(0), + mFontSizeScaleFactor(1.0f), + mLastContextScale(1.0f), + mLengthAdjustScaleFactor(1.0f) { + AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | + NS_STATE_SVG_POSITIONING_DIRTY); + } + + ~SVGTextFrame() = default; + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(SVGTextFrame) + + // nsIFrame: + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + nsresult AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute, + int32_t aModType) override; + + nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGText"_ns, aResult); + } +#endif + + /** + * Finds the nsTextFrame for the closest rendered run to the specified point. + */ + void FindCloserFrameForSelection( + const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) override; + + // ISVGDisplayableFrame interface: + void NotifySVGChanged(uint32_t aFlags) override; + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + void ReflowSVG() override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + + // SVG DOM text methods: + uint32_t GetNumberOfChars(nsIContent* aContent); + float GetComputedTextLength(nsIContent* aContent); + MOZ_CAN_RUN_SCRIPT_BOUNDARY void SelectSubString(nsIContent* aContent, + uint32_t charnum, + uint32_t nchars, + ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT + float GetSubStringLength(nsIContent* aContent, uint32_t charnum, + uint32_t nchars, ErrorResult& aRv); + int32_t GetCharNumAtPosition(nsIContent* aContent, + const dom::DOMPointInit& aPoint); + + already_AddRefed<dom::DOMSVGPoint> GetStartPositionOfChar( + nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv); + already_AddRefed<dom::DOMSVGPoint> GetEndPositionOfChar(nsIContent* aContent, + uint32_t aCharNum, + ErrorResult& aRv); + already_AddRefed<dom::SVGRect> GetExtentOfChar(nsIContent* aContent, + uint32_t aCharNum, + ErrorResult& aRv); + float GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum, + ErrorResult& aRv); + + // SVGTextFrame methods: + + /** + * Handles a base or animated attribute value change to a descendant + * text content element. + */ + void HandleAttributeChangeInDescendant(dom::Element* aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute); + + /** + * Calls ScheduleReflowSVGNonDisplayText if this is a non-display frame, + * and SVGUtils::ScheduleReflowSVG otherwise. + */ + void ScheduleReflowSVG(); + + /** + * Reflows the anonymous block frame of this non-display SVGTextFrame. + * + * When we are under SVGDisplayContainerFrame::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 SVGUtils::ScheduleReflowSVG, + * but which will skip over any ancestor non-display container frames on the + * way to the SVGOuterSVGFrame. 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 + * SVGDisplayContainerFrame::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 + * IntrinsicDirty::FrameAncestorsAndDescendants (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 None, since layout + * doesn't need to be recomputed. + */ + void ScheduleReflowSVGNonDisplayText(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, + const 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, + const nsIFrame* aChildFrame); + + /** As above, but taking and returning a device px rect. */ + Rect TransformFrameRectFromTextChild(const Rect& aRect, + const nsIFrame* aChildFrame); + + /** As above, but with a single point */ + Point TransformFramePointFromTextChild(const Point& aPoint, + const nsIFrame* aChildFrame); + + // Return our ::-moz-svg-text anonymous box. + void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override; + + 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; + }; + + /** + * Resolves Bidi for the anonymous block child if it needs it. + */ + void MaybeResolveBidiForAnonymousBlockChild(); + + /** + * 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(); + + /** + * Schedules mPositions to be recomputed and the covered region to be + * updated. + */ + void NotifyGlyphMetricsChange(); + + /** + * 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(); + + /** + * This fallback version of GetSubStringLength that flushes layout and takes + * into account glyph positioning. As per the SVG 2 spec, typically glyph + * positioning does not affect the results of getSubStringLength, but one + * exception is text in a textPath where we need to ignore characters that + * fall off the end of the textPath path. + */ + MOZ_CAN_RUN_SCRIPT + float GetSubStringLengthSlowFallback(nsIContent* aContent, uint32_t charnum, + uint32_t nchars, ErrorResult& aRv); + + /** + * 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. + 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; + + /** + * 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<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; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGTEXTFRAME_H_ diff --git a/layout/svg/SVGUseFrame.cpp b/layout/svg/SVGUseFrame.cpp new file mode 100644 index 0000000000..6442657f44 --- /dev/null +++ b/layout/svg/SVGUseFrame.cpp @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGUseFrame.h" + +#include "mozilla/PresShell.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/MutationEvent.h" +#include "mozilla/dom/SVGUseElement.h" +#include "nsLayoutUtils.h" + +using namespace mozilla::dom; + +//---------------------------------------------------------------------- +// Implementation + +nsIFrame* NS_NewSVGUseFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGUseFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +NS_IMPL_FRAMEARENA_HELPERS(SVGUseFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +void SVGUseFrame::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(); + + SVGGFrame::Init(aContent, aParent, aPrevInFlow); +} + +nsresult SVGUseFrame::AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute, + int32_t aModType) { + // Currently our SMIL implementation does not modify the DOM attributes. Once + // we implement the SVG 2 SMIL behaviour this can be removed + // SVGUseElement::AfterSetAttr's implementation will be sufficient. + if (aModType == MutationEvent_Binding::SMIL) { + auto* content = SVGUseElement::FromNode(GetContent()); + content->ProcessAttributeChange(aNamespaceID, aAttribute); + } + + return SVGGFrame::AttributeChanged(aNamespaceID, aAttribute, aModType); +} + +void SVGUseFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + SVGGFrame::DidSetComputedStyle(aOldComputedStyle); + + if (!aOldComputedStyle) { + return; + } + const auto* newSVGReset = StyleSVGReset(); + const auto* oldSVGReset = aOldComputedStyle->StyleSVGReset(); + + if (newSVGReset->mX != oldSVGReset->mX || + newSVGReset->mY != oldSVGReset->mY) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + SVGUtils::ScheduleReflowSVG(this); + SVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED); + } +} + +void SVGUseFrame::DimensionAttributeChanged(bool aHadValidDimensions, + bool aAttributeIsUsed) { + bool invalidate = aAttributeIsUsed; + if (mHasValidDimensions != aHadValidDimensions) { + mHasValidDimensions = !mHasValidDimensions; + invalidate = true; + } + + if (invalidate) { + nsLayoutUtils::PostRestyleEvent(GetContent()->AsElement(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(this); + } +} + +void SVGUseFrame::HrefChanged() { + nsLayoutUtils::PostRestyleEvent(GetContent()->AsElement(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(this); +} + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods + +void SVGUseFrame::ReflowSVG() { + // We only handle x/y offset here, since any width/height that is in force is + // handled by the SVGOuterSVGFrame for the anonymous <svg> that will be + // created for that purpose. + auto [x, y] = ResolvePosition(); + mRect.MoveTo(nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, 0, 0), + 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(); + } + + SVGGFrame::ReflowSVG(); +} + +void SVGUseFrame::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' + if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) { + 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. + SVGUtils::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 SVGInnerSVGFrame will do that. + + SVGGFrame::NotifySVGChanged(aFlags); +} + +SVGBBox SVGUseFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) { + SVGBBox bbox = + SVGDisplayContainerFrame::GetBBoxContribution(aToBBoxUserspace, aFlags); + + if (aFlags & SVGUtils::eForGetClientRects) { + auto [x, y] = ResolvePosition(); + bbox.MoveBy(x, y); + } + return bbox; +} + +std::pair<float, float> SVGUseFrame::ResolvePosition() const { + auto* content = SVGUseElement::FromNode(GetContent()); + return std::make_pair(SVGContentUtils::CoordToFloat( + content, StyleSVGReset()->mX, SVGContentUtils::X), + SVGContentUtils::CoordToFloat( + content, StyleSVGReset()->mY, SVGContentUtils::Y)); +} + +} // namespace mozilla diff --git a/layout/svg/SVGUseFrame.h b/layout/svg/SVGUseFrame.h new file mode 100644 index 0000000000..fa2bf231b2 --- /dev/null +++ b/layout/svg/SVGUseFrame.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGUSEFRAME_H_ +#define LAYOUT_SVG_SVGUSEFRAME_H_ + +// Keep in (case-insensitive) order: +#include "SVGGFrame.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +nsIFrame* NS_NewSVGUseFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +class SVGUseFrame final : public SVGGFrame { + friend nsIFrame* ::NS_NewSVGUseFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGUseFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : SVGGFrame(aStyle, aPresContext, kClassID), mHasValidDimensions(true) {} + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGUseFrame) + + // nsIFrame interface: + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + // Called when the href attributes changed. + void HrefChanged(); + + // Called when the width or height attributes changed. + void DimensionAttributeChanged(bool aHadValidDimensions, + bool aAttributeIsUsed); + + nsresult AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute, + int32_t aModType) override; + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGUse"_ns, aResult); + } +#endif + + // ISVGDisplayableFrame interface: + void ReflowSVG() override; + void NotifySVGChanged(uint32_t aFlags) override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + + private: + std::pair<float, float> ResolvePosition() const; + + bool mHasValidDimensions; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGUSEFRAME_H_ diff --git a/layout/svg/SVGUtils.cpp b/layout/svg/SVGUtils.cpp new file mode 100644 index 0000000000..fe8f65800c --- /dev/null +++ b/layout/svg/SVGUtils.cpp @@ -0,0 +1,1706 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGUtils.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 "nsCSSFrameConstructor.h" +#include "nsDisplayList.h" +#include "nsFrameList.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsStyleStruct.h" +#include "nsStyleTransformMatrix.h" +#include "SVGAnimatedLength.h" +#include "SVGPaintServerFrame.h" +#include "nsTextFrame.h" +#include "mozilla/CSSClipPathInstance.h" +#include "mozilla/FilterInstance.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_svg.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGForeignObjectFrame.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGOuterSVGFrame.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/Unused.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGClipPathElement.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGPathElement.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "mozilla/dom/SVGViewportElement.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGUnitTypes_Binding; +using namespace mozilla::gfx; +using namespace mozilla::image; + +bool NS_SVGDisplayListHitTestingEnabled() { + return mozilla::StaticPrefs::svg_display_lists_hit_testing_enabled(); +} + +bool NS_SVGDisplayListPaintingEnabled() { + return mozilla::StaticPrefs::svg_display_lists_painting_enabled(); +} + +bool NS_SVGNewGetBBoxEnabled() { + return mozilla::StaticPrefs::svg_new_getBBox_enabled(); +} + +namespace mozilla { + +// we only take the address of this: +static gfx::UserDataKey sSVGAutoRenderStateKey; + +SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget) + : mDrawTarget(aDrawTarget), + mOriginalRenderState(nullptr), + mPaintingToWindow(false) { + 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; +} + +nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame, + const nsRect& aPreFilterRect) { + MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), + "Called on invalid frame type"); + + // Note: we do not return here for eHasNoRefs since we must still handle any + // CSS filter functions. + // TODO: We currently pass nullptr instead of an nsTArray* here, but we + // actually should get the filter frames and then pass them into + // GetPostFilterBounds below! See bug 1494263. + // TODO: we should really return an empty rect for eHasRefsSomeInvalid since + // in that case we disable painting of the element. + if (!aFrame->StyleEffects()->HasFilters() || + SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return aPreFilterRect; + } + + return FilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect) + .valueOr(aPreFilterRect); +} + +bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) { + return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); +} + +bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) { + SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); + do { + if (outer->IsCallingReflowSVG()) { + return true; + } + outer = GetOuterSVGFrame(outer->GetParent()); + } while (outer); + return false; +} + +void SVGUtils::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. + MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame), + "Do not call under ISVGDisplayableFrame::ReflowSVG!"); + + // We don't call SVGObserverUtils::InvalidateRenderingObservers here because + // we should only be called under InvalidateAndScheduleReflowSVG (which + // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames + // (at which point the frame has no observers). + + if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return; + } + + if (aFrame->HasAnyStateBits(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; + } + + SVGOuterSVGFrame* outerSVGFrame = nullptr; + + // We must not add dirty bits to the SVGOuterSVGFrame or else + // PresShell::FrameNeedsReflow won't work when we pass it in below. + if (aFrame->IsSVGOuterSVGFrame()) { + outerSVGFrame = static_cast<SVGOuterSVGFrame*>(aFrame); + } else { + aFrame->MarkSubtreeDirty(); + + nsIFrame* f = aFrame->GetParent(); + while (f && !f->IsSVGOuterSVGFrame()) { + if (f->HasAnyStateBits(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), + "IsSVGOuterSVGFrame check above not valid!"); + } + + outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f); + + MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(), + "Did not find SVGOuterSVGFrame!"); + } + + if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { + // We're currently under an SVGOuterSVGFrame::Reflow call so there is no + // need to call PresShell::FrameNeedsReflow, since we have an + // SVGOuterSVGFrame::DidReflow call pending. + return; + } + + nsFrameState dirtyBit = + (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY + : NS_FRAME_HAS_DIRTY_CHILDREN); + + aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None, + dirtyBit); +} + +bool SVGUtils::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 aFrame->IsSubtreeDirty(); +} + +Size SVGUtils::GetContextSize(const nsIFrame* aFrame) { + Size size; + + MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); + const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent()); + + SVGViewportElement* ctx = element->GetCtx(); + if (ctx) { + size.width = ctx->GetLength(SVGContentUtils::X); + size.height = ctx->GetLength(SVGContentUtils::Y); + } + return size; +} + +float SVGUtils::ObjectSpace(const gfxRect& aRect, + const SVGAnimatedLength* 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: + MOZ_ASSERT_UNREACHABLE("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<SVGViewportElement*>(nullptr)) * + axis; +} + +float SVGUtils::UserSpace(SVGElement* aSVGElement, + const SVGAnimatedLength* aLength) { + return aLength->GetAnimValue(aSVGElement); +} + +float SVGUtils::UserSpace(nsIFrame* aNonSVGContext, + const SVGAnimatedLength* aLength) { + return aLength->GetAnimValue(aNonSVGContext); +} + +float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, + const SVGAnimatedLength* aLength) { + return aLength->GetAnimValue(aMetrics); +} + +SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) { + while (aFrame) { + if (aFrame->IsSVGOuterSVGFrame()) { + return static_cast<SVGOuterSVGFrame*>(aFrame); + } + aFrame = aFrame->GetParent(); + } + + return nullptr; +} + +nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, + nsRect* aRect) { + ISVGDisplayableFrame* svg = do_QueryFrame(aFrame); + if (!svg) { + return nullptr; + } + SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); + if (outer == svg) { + return nullptr; + } + + if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + *aRect = nsRect(0, 0, 0, 0); + } else { + uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill | + SVGUtils::eBBoxIncludeStroke | + SVGUtils::eBBoxIncludeMarkers; + + auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame}, + RelativeTo{outer}); + + float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x, + AppUnitsPerCSSPixel()), + initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y, + AppUnitsPerCSSPixel()); + + Matrix mm; + ctm.ProjectTo2D(); + ctm.CanDraw2D(&mm); + gfxMatrix m = ThebesMatrix(mm); + + float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + float devPixelPerCSSPixel = + float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel; + + // The matrix that GetBBox accepts should operate on "user space", + // i.e. with CSS pixel unit. + m = m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel); + + // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor + // will count this displacement, we should remove it here to avoid + // double-counting. + m = m.PreTranslate(-initPositionX, -initPositionY); + + gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m); + *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel); + } + + return outer; +} + +gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) { + // XXX yuck, we really need a common interface for GetCanvasTM + + if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { + return GetCSSPxToDevPxMatrix(aFrame); + } + + LayoutFrameType type = aFrame->Type(); + if (type == LayoutFrameType::SVGForeignObject) { + return static_cast<SVGForeignObjectFrame*>(aFrame)->GetCanvasTM(); + } + if (type == LayoutFrameType::SVGOuterSVG) { + return GetCSSPxToDevPxMatrix(aFrame); + } + + SVGContainerFrame* containerFrame = do_QueryFrame(aFrame); + if (containerFrame) { + return containerFrame->GetCanvasTM(); + } + + return static_cast<SVGGeometryFrame*>(aFrame)->GetCanvasTM(); +} + +void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) { + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(aFlags); + } else { + NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || + SVGUtils::IsInSVGTextSubtree(kid), + "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); + } + } + } +} + +// ************************************************************ + +float SVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity) { + float opacity = aFrame->StyleEffects()->mOpacity; + + if (opacity != 1.0f && + (SVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) { + return 1.0f; + } + + return opacity; +} + +void SVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, + MaskUsage& aUsage) { + using ClipPathType = StyleClipPath::Tag; + + aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + + const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); + + nsTArray<SVGMaskFrame*> maskFrames; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); + aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0); + + SVGClipPathFrame* clipPathFrame; + // XXX check return value? + SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); + MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl()); + + switch (svgReset->mClipPath.tag) { + case ClipPathType::Url: + if (clipPathFrame) { + if (clipPathFrame->IsTrivial()) { + aUsage.shouldApplyClipPath = true; + } else { + aUsage.shouldGenerateClipMaskLayer = true; + } + } + break; + case ClipPathType::Shape: + case ClipPathType::Box: + case ClipPathType::Path: + aUsage.shouldApplyBasicShapeOrPath = true; + break; + case ClipPathType::None: + MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer && + !aUsage.shouldApplyClipPath && + !aUsage.shouldApplyBasicShapeOrPath); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); + break; + } +} + +class MixModeBlender { + public: + using Factory = gfx::Factory; + + MixModeBlender(nsIFrame* aFrame, gfxContext* aContext) + : mFrame(aFrame), mSourceCtx(aContext) { + MOZ_ASSERT(mFrame && mSourceCtx); + } + + bool ShouldCreateDrawTargetForBlend() const { + return mFrame->StyleEffects()->mMixBlendMode != StyleBlend::Normal; + } + + gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) { + MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); + + // Create a temporary context to draw to so we can blend it back with + // another operator. + IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform); + if (drawRect.IsEmpty()) { + return nullptr; + } + + RefPtr<DrawTarget> targetDT = + mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget( + drawRect.Size(), SurfaceFormat::B8G8R8A8); + if (!targetDT || !targetDT->IsValid()) { + return nullptr; + } + + MOZ_ASSERT(!mTargetCtx, + "CreateBlendTarget is designed to be used once only."); + + mTargetCtx = gfxContext::CreateOrNull(targetDT); + MOZ_ASSERT(mTargetCtx); // already checked the draw target above + mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() * + Matrix::Translation(-drawRect.TopLeft())); + + mTargetOffset = drawRect.TopLeft(); + + return mTargetCtx; + } + + void BlendToTarget() { + MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); + MOZ_ASSERT(mTargetCtx, + "BlendToTarget should be used after CreateBlendTarget."); + + RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot(); + + gfxContextAutoSaveRestore save(mSourceCtx); + mSourceCtx->SetMatrix(Matrix()); // This will be restored right after. + RefPtr<gfxPattern> pattern = new gfxPattern( + targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y)); + mSourceCtx->SetPattern(pattern); + mSourceCtx->Paint(); + } + + private: + MixModeBlender() = delete; + + IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) { + // 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.) + gfxContextAutoSaveRestore saver; + + if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + saver.SetContext(mSourceCtx); + // aFrame has a valid ink overflow rect, so clip to it before calling + // PushGroup() to minimize the size of the surfaces we'll composite: + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx); + mSourceCtx->Multiply(aTransform); + nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf(); + if (mFrame->IsSVGGeometryFrameOrSubclass() || + SVGUtils::IsInSVGTextSubtree(mFrame)) { + // Unlike containers, leaf frames do not include GetPosition() in + // GetCanvasTM(). + overflowRect = overflowRect + mFrame->GetPosition(); + } + mSourceCtx->Clip(NSRectToSnappedRect( + overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(), + *mSourceCtx->GetDrawTarget())); + } + + // Get the clip extents in device space. + gfxRect clippedFrameSurfaceRect = + mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace); + clippedFrameSurfaceRect.RoundOut(); + + IntRect result; + ToRect(clippedFrameSurfaceRect).ToIntRect(&result); + + return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect(); + } + + nsIFrame* mFrame; + gfxContext* mSourceCtx; + RefPtr<gfxContext> mTargetCtx; + IntPoint mTargetOffset; +}; + +void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect) { + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || + aFrame->PresContext()->Document()->IsSVGGlyphsDocument(), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); + if (!svgFrame) { + return; + } + + MaskUsage maskUsage; + DetermineMaskUsage(aFrame, true, maskUsage); + if (maskUsage.opacity == 0.0f) { + return; + } + + if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) { + if (!svg->HasValidDimensions()) { + return; + } + } + + if (aDirtyRect && !aFrame->HasAnyStateBits(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->InkOverflowRectRelativeToSelf(); + if (aFrame->IsSVGGeometryFrameOrSubclass() || + SVGUtils::IsInSVGTextSubtree(aFrame)) { + // 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<SVGContainerFrame*>(aFrame)->HasChildrenOnlyTransform( + &childrenOnlyTM)) { + // Undo the children-only transform: + if (!childrenOnlyTM.Invert()) { + return; + } + tm = ThebesMatrix(childrenOnlyTM) * tm; + } + } + nsIntRect bounds = + TransformFrameRectToOuterSVG(overflowRect, tm, aFrame->PresContext()) + .ToOutsidePixels(appUnitsPerDevPx); + if (!aDirtyRect->Intersects(bounds)) { + return; + } + } + + /* 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. */ + SVGClipPathFrame* clipPathFrame; + nsTArray<SVGMaskFrame*> maskFrames; + // TODO: We currently pass nullptr instead of an nsTArray* here, but we + // actually should get the filter frames and then pass them into + // PaintFilteredFrame below! See bug 1494263. + const bool hasInvalidFilter = + SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid; + if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) == + SVGObserverUtils::eHasRefsSomeInvalid || + SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames) == + SVGObserverUtils::eHasRefsSomeInvalid) { + // Some resource is invalid. We shouldn't paint anything. + return; + } + + SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0]; + + MixModeBlender blender(aFrame, &aContext); + gfxContext* target = blender.ShouldCreateDrawTargetForBlend() + ? blender.CreateBlendTarget(aTransform) + : &aContext; + + if (!target) { + return; + } + + /* 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); + bool shouldPushMask = false; + + if (shouldGenerateMask) { + RefPtr<SourceSurface> maskSurface; + + // maskFrame can be nullptr even if maskUsage.shouldGenerateMaskLayer is + // true. That happens when a user gives an unresolvable mask-id, such as + // mask:url() + // mask:url(#id-which-does-not-exist) + // Since we only uses SVGUtils with SVG elements, not like mask on an + // HTML element, we should treat an unresolvable mask as no-mask here. + if (maskUsage.shouldGenerateMaskLayer && maskFrame) { + StyleMaskMode maskMode = + aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode; + SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame, + aTransform, maskUsage.opacity, maskMode, + aImgParams); + + maskSurface = maskFrame->GetMaskForMaskedFrame(params); + + if (!maskSurface) { + // Either entire surface is clipped out, or gfx buffer allocation + // failure in SVGMaskFrame::GetMaskForMaskedFrame. + return; + } + shouldPushMask = true; + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + RefPtr<SourceSurface> clipMaskSurface = + clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface); + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + } else { + // Either entire surface is clipped out, or gfx buffer allocation + // failure in SVGClipPathFrame::GetClipMask. + return; + } + shouldPushMask = true; + } + + if (!maskUsage.shouldGenerateClipMaskLayer && + !maskUsage.shouldGenerateMaskLayer) { + shouldPushMask = true; + } + + // SVG mask multiply opacity into maskSurface already, so we do not bother + // to apply opacity again. + if (shouldPushMask) { + // We want the mask to be untransformed so use the inverse of the + // current transform as the maskTransform to compensate. + Matrix maskTransform = aContext.CurrentMatrix(); + maskTransform.Invert(); + target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, + maskFrame ? 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.shouldApplyBasicShapeOrPath) { + if (maskUsage.shouldApplyClipPath) { + clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); + } else { + CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame, + aTransform); + } + } + + /* Paint the child */ + + // Invalid filters should render the unfiltered contents per spec. + if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) { + 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 = aTransform; + if (userToDeviceSpace.IsSingular()) { + return; + } + gfxMatrix deviceToUserSpace = userToDeviceSpace; + deviceToUserSpace.Invert(); + gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(gfxRect( + aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); + tmpDirtyRegion = nsLayoutUtils::RoundGfxRectToAppRect( + dirtyBounds, AppUnitsPerCSSPixel()) - + aFrame->GetPosition(); + dirtyRegion = &tmpDirtyRegion; + } + + gfxContextMatrixAutoSaveRestore autoSR(target); + + // 'target' is currently scaled such that its user space units are CSS + // pixels (SVG user space units). But PaintFilteredFrame expects it to be + // scaled in such a way that its user space units are device pixels. So we + // have to adjust the scale. + gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame); + DebugOnly<bool> invertible = reverseScaleMatrix.Invert(); + target->SetMatrixDouble(reverseScaleMatrix * aTransform * + target->CurrentMatrixDouble()); + + auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams, + const gfxMatrix* aFilterTransform, + const nsIntRect* aDirtyRect) { + nsIntRect* dirtyRect = nullptr; + nsIntRect tmpDirtyRect; + + // aDirtyRect is in user-space pixels, we need to convert to + // outer-SVG-frame-relative device pixels. + if (aDirtyRect) { + MOZ_ASSERT(aFilterTransform); + gfxMatrix userToDeviceSpace = *aFilterTransform; + if (userToDeviceSpace.IsSingular()) { + return; + } + gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( + gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, + aDirtyRect->height)); + dirtyBounds.RoundOut(); + if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { + dirtyRect = &tmpDirtyRect; + } + } + + svgFrame->PaintSVG(aContext, + aFilterTransform + ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame) + : aTransform, + aImgParams, aFilterTransform ? dirtyRect : aDirtyRect); + }; + FilterInstance::PaintFilteredFrame( + aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), target, callback, + dirtyRegion, aImgParams); + } else { + svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect); + } + + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { + aContext.PopClip(); + } + + if (shouldPushMask) { + target->PopGroupAndBlend(); + } + + if (blender.ShouldCreateDrawTargetForBlend()) { + MOZ_ASSERT(target != &aContext); + blender.BlendToTarget(); + } +} + +bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) { + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + if (!svgReset->HasClipPath()) { + return true; + } + if (svgReset->mClipPath.IsUrl()) { + // If the clip-path property references non-existent or invalid clipPath + // element(s) we ignore it. + SVGClipPathFrame* clipPathFrame; + SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame); + return !clipPathFrame || + clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); + } + return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint); +} + +nsIFrame* SVGUtils::HitTestChildren(SVGDisplayContainerFrame* 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 (auto* svg = SVGElement::FromNode(aFrame->GetContent())) { + gfxMatrix m = svg->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.TransformPoint(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()) { + ISVGDisplayableFrame* SVGFrame = do_QueryFrame(current); + if (SVGFrame) { + const nsIContent* content = current->GetContent(); + if (auto* svg = SVGElement::FromNode(content)) { + if (!svg->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 (auto* svg = SVGElement::FromNode(content)) { + gfxMatrix m = + svg->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + continue; + } + p = m.TransformPoint(p); + } + } + result = SVGFrame->GetFrameForPoint(p); + if (result) break; + } + } + + if (result && !HitTestClip(aFrame, aPoint)) result = nullptr; + + return result; +} + +nsRect SVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, + const gfxMatrix& aMatrix, + nsPresContext* aPresContext) { + gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); + r.Scale(1.0 / AppUnitsPerCSSPixel()); + return nsLayoutUtils::RoundGfxRectToAppRect( + aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); +} + +IntSize SVGUtils::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::AllowedSurfaceSize(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 SVGUtils::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 SVGUtils::GetClipRectForFrame(nsIFrame* aFrame, float aX, float aY, + float aWidth, float aHeight) { + const nsStyleDisplay* disp = aFrame->StyleDisplay(); + const nsStyleEffects* effects = aFrame->StyleEffects(); + + bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden || + disp->mOverflowY == StyleOverflow::Hidden; + + if (!clipApplies || effects->mClip.IsAuto()) { + return gfxRect(aX, aY, aWidth, aHeight); + } + + const auto& rect = effects->mClip.AsRect(); + nsRect coordClipRect = rect.ToLayoutRect(); + nsIntRect clipPxRect = coordClipRect.ToOutsidePixels( + aFrame->PresContext()->AppUnitsPerDevPixel()); + gfxRect clipRect = + gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); + if (rect.right.IsAuto()) { + clipRect.width = aWidth - clipRect.X(); + } + if (rect.bottom.IsAuto()) { + clipRect.height = aHeight - clipRect.Y(); + } + if (disp->mOverflowX != StyleOverflow::Hidden) { + clipRect.x = aX; + clipRect.width = aWidth; + } + if (disp->mOverflowY != StyleOverflow::Hidden) { + clipRect.y = aY; + clipRect.height = aHeight; + } + return clipRect; +} + +void SVGUtils::SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM, + const gfxRect& aRect) { + if (aCTM.IsSingular()) { + return; + } + + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); + aContext->Multiply(aCTM); + aContext->Clip(aRect); +} + +gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags, + const gfxMatrix* aToBoundsSpace) { + if (aFrame->IsTextFrame()) { + aFrame = aFrame->GetParent(); + } + + if (SVGUtils::IsInSVGTextSubtree(aFrame)) { + // 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. + nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); + if (ancestor && SVGUtils::IsInSVGTextSubtree(ancestor)) { + while (!ancestor->IsSVGTextFrame()) { + ancestor = ancestor->GetParent(); + } + } + aFrame = ancestor; + } + + ISVGDisplayableFrame* svg = do_QueryFrame(aFrame); + const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout && !svg) { + // An SVG frame, but not one that can be displayed directly (for + // example, nsGradientFrame). These can't contribute to the bbox. + return gfxRect(); + } + + const bool isOuterSVG = svg && !hasSVGLayout; + MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame()); + if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) { + // An HTML element or an SVG outer frame. + MOZ_ASSERT(!hasSVGLayout); + bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement; + return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame( + aFrame, + /* aUnionContinuations = */ !onlyCurrentFrame); + } + + MOZ_ASSERT(svg); + + if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) { + if (!element->HasValidDimensions()) { + return gfxRect(); + } + } + + // Clean out flags which have no effects on returning bbox from now, so that + // we can cache and reuse ObjectBoundingBoxProperty() in the code below. + aFlags &= ~eIncludeOnlyCurrentFrameForNonSVGElement; + aFlags &= ~eUseFrameBoundsForOuterSVG; + if (!aFrame->IsSVGUseFrame()) { + aFlags &= ~eUseUserSpaceOfUseElement; + } + + if (aFlags == eBBoxIncludeFillGeometry && + // We only cache bbox in element's own user space + !aToBoundsSpace) { + gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty()); + if (prop) { + return *prop; + } + } + + gfxMatrix matrix; + if (aToBoundsSpace) { + matrix = *aToBoundsSpace; + } + + if (aFrame->IsSVGForeignObjectFrame() || + aFlags & SVGUtils::eUseUserSpaceOfUseElement) { + // 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 SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. + MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); + SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent()); + matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace); + } + gfxRect bbox = + svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); + // Account for 'clipped'. + if (aFlags & SVGUtils::eBBoxIncludeClipped) { + gfxRect clipRect(0, 0, 0, 0); + float x, y, width, height; + gfxMatrix tm; + gfxRect fillBBox = + svg->GetBBoxContribution(ToMatrix(tm), SVGUtils::eBBoxIncludeFill) + .ToThebesRect(); + x = fillBBox.x; + y = fillBBox.y; + width = fillBBox.width; + height = fillBBox.height; + bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); + if (hasClip) { + clipRect = SVGUtils::GetClipRectForFrame(aFrame, x, y, width, height); + if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) { + clipRect = matrix.TransformBounds(clipRect); + } + } + SVGClipPathFrame* clipPathFrame; + if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) == + SVGObserverUtils::eHasRefsSomeInvalid) { + bbox = gfxRect(0, 0, 0, 0); + } else { + if (clipPathFrame) { + SVGClipPathElement* clipContent = + static_cast<SVGClipPathElement*>(clipPathFrame->GetContent()); + if (clipContent->IsUnitsObjectBoundingBox()) { + matrix.PreTranslate(gfxPoint(x, y)); + matrix.PreScale(width, height); + } else if (aFrame->IsSVGForeignObjectFrame()) { + matrix = gfxMatrix(); + } + + matrix = + SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame) * matrix; + + bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags) + .ToThebesRect(); + } + + if (hasClip) { + bbox = bbox.Intersect(clipRect); + } + + if (bbox.IsEmpty()) { + bbox = gfxRect(0, 0, 0, 0); + } + } + } + + if (aFlags == eBBoxIncludeFillGeometry && + // We only cache bbox in element's own user space + !aToBoundsSpace) { + // Obtaining the bbox for objectBoundingBox calculations is common so we + // cache the result for future calls, since calculation can be expensive: + aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox)); + } + + return bbox; +} + +gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame* aFrame) { + if (!aFrame->HasAnyStateBits(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->IsSVGGeometryFrameOrSubclass() || + SVGUtils::IsInSVGTextSubtree(aFrame)) { + return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), + AppUnitsPerCSSPixel()) + .TopLeft(); + } + + // For foreignObject frames, SVGUtils::GetBBox applies their local + // transform, so we need to do the same here. + if (aFrame->IsSVGForeignObjectFrame()) { + gfxMatrix transform = + static_cast<SVGElement*>(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 SVGAnimatedLength* aXYWH, + const gfxRect& aBBox) { + return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, &aXYWH[0]), + aBBox.y + SVGUtils::ObjectSpace(aBBox, &aXYWH[1]), + SVGUtils::ObjectSpace(aBBox, &aXYWH[2]), + SVGUtils::ObjectSpace(aBBox, &aXYWH[3])); +} + +gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits, + const SVGAnimatedLength* 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 SVGUtils::GetRelativeRect(uint16_t aUnits, + const SVGAnimatedLength* aXYWH, + const gfxRect& aBBox, nsIFrame* aFrame) { + if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + return GetBoundingBoxRelativeRect(aXYWH, aBBox); + } + if (SVGElement* svgElement = SVGElement::FromNode(aFrame->GetContent())) { + return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement)); + } + return GetRelativeRect(aUnits, aXYWH, aBBox, + NonSVGFrameUserSpaceMetrics(aFrame)); +} + +bool SVGUtils::CanOptimizeOpacity(nsIFrame* aFrame) { + if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + return false; + } + LayoutFrameType type = aFrame->Type(); + if (type != LayoutFrameType::SVGImage && + type != LayoutFrameType::SVGGeometry) { + return false; + } + if (aFrame->StyleEffects()->HasFilters()) { + return false; + } + // XXX The SVG WG is intending to allow fill, stroke and markers on <image> + if (type == LayoutFrameType::SVGImage) { + return true; + } + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->HasMarker()) { + return false; + } + + if (nsLayoutUtils::HasAnimationOfPropertySet( + aFrame, nsCSSPropertyIDSet::OpacityProperties())) { + return false; + } + + return !style->HasFill() || !HasStroke(aFrame); +} + +gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix, + SVGAnimatedEnumeration* aUnits, + nsIFrame* aFrame, uint32_t aFlags) { + if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + gfxRect bbox = GetBBox(aFrame, aFlags); + gfxMatrix tm = aMatrix; + tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y())); + tm.PreScale(bbox.Width(), bbox.Height()); + return tm; + } + return aMatrix; +} + +nsIFrame* SVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) { + for (nsIFrame* ancestorFrame = aStartFrame; ancestorFrame; + ancestorFrame = ancestorFrame->GetParent()) { + if (!ancestorFrame->IsSVGAFrame()) { + return ancestorFrame; + } + } + return nullptr; +} + +bool SVGUtils::GetNonScalingStrokeTransform(nsIFrame* aFrame, + gfxMatrix* aUserToOuterSVG) { + if (aFrame->GetContent()->IsText()) { + aFrame = aFrame->GetParent(); + } + + if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) { + return false; + } + + MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element"); + + *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM( + static_cast<SVGElement*>(aFrame->GetContent()), true)); + + return aUserToOuterSVG->HasNonTranslation(); +} + +// 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 * SVGUtils::GetStrokeWidth(aFrame); + + gfxMatrix matrix = aMatrix; + + gfxMatrix outerSVGToUser; + if (SVGUtils::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 SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsTextFrame* aFrame, + const gfxMatrix& aMatrix) { + NS_ASSERTION(SVGUtils::IsInSVGTextSubtree(aFrame), + "expected an nsTextFrame for SVG text"); + return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, + aMatrix); +} + +/*static*/ +gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + SVGGeometryFrame* 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. + // We only need to do this if the limit is greater than 1, but it's probably + // not worth optimizing for that. + bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements( + nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon); + + if (affectedByMiterlimit) { + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter && + styleExpansionFactor < style->mStrokeMiterlimit / 2.0) { + styleExpansionFactor = style->mStrokeMiterlimit / 2.0; + } + } + + return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, + styleExpansionFactor, aMatrix); +} + +// ---------------------------------------------------------------------- + +/* static */ +nscolor SVGUtils::GetFallbackOrPaintColor( + const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + nscolor aDefaultContextFallbackColor) { + const auto& paint = aStyle.StyleSVG()->*aFillOrStroke; + nscolor color; + switch (paint.kind.tag) { + case StyleSVGPaintKind::Tag::PaintServer: + color = paint.fallback.IsColor() + ? paint.fallback.AsColor().CalcColor(aStyle) + : NS_RGBA(0, 0, 0, 0); + break; + case StyleSVGPaintKind::Tag::ContextStroke: + case StyleSVGPaintKind::Tag::ContextFill: + color = paint.fallback.IsColor() + ? paint.fallback.AsColor().CalcColor(aStyle) + : aDefaultContextFallbackColor; + break; + default: + color = paint.kind.AsColor().CalcColor(aStyle); + break; + } + if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) { + const auto& 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.kind.IsColor() && paint.kind.IsColor()) { + nscolor colors[2] = { + color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)}; + return ComputedStyle::CombineVisitedColors(colors, + aStyle.RelevantLinkVisited()); + } + } + return color; +} + +/* static */ +void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext, + GeneralPattern* aOutPattern, + imgDrawingParams& aImgParams, + SVGContextPaint* aContextPaint) { + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mFill.kind.IsNone()) { + return; + } + + const float opacity = aFrame->StyleEffects()->mOpacity; + + float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint); + if (opacity < 1.0f && SVGUtils::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(); + + SVGPaintServerFrame* ps = + SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill); + + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(), + &nsStyleSVG::mFill, fillOpacity, aImgParams); + if (pattern) { + pattern->CacheColorStops(dt); + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (aContextPaint) { + RefPtr<gfxPattern> pattern; + switch (style->mFill.kind.tag) { + case StyleSVGPaintKind::Tag::ContextFill: + pattern = aContextPaint->GetFillPattern( + dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams); + break; + case StyleSVGPaintKind::Tag::ContextStroke: + pattern = aContextPaint->GetStrokePattern( + dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams); + break; + default:; + } + if (pattern) { + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (style->mFill.fallback.IsNone()) { + 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 + sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor( + *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0)))); + color.a *= fillOpacity; + aOutPattern->InitColorPattern(ToDeviceColor(color)); +} + +/* static */ +void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext, + GeneralPattern* aOutPattern, + imgDrawingParams& aImgParams, + SVGContextPaint* aContextPaint) { + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mStroke.kind.IsNone()) { + return; + } + + const float opacity = aFrame->StyleEffects()->mOpacity; + + float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint); + if (opacity < 1.0f && SVGUtils::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(); + + SVGPaintServerFrame* ps = + SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke); + + if (ps) { + RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern( + aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke, + strokeOpacity, aImgParams); + if (pattern) { + pattern->CacheColorStops(dt); + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (aContextPaint) { + RefPtr<gfxPattern> pattern; + switch (style->mStroke.kind.tag) { + case StyleSVGPaintKind::Tag::ContextFill: + pattern = aContextPaint->GetFillPattern( + dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams); + break; + case StyleSVGPaintKind::Tag::ContextStroke: + pattern = aContextPaint->GetStrokePattern( + dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams); + break; + default:; + } + if (pattern) { + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (style->mStroke.fallback.IsNone()) { + 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 + sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor( + *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0)))); + color.a *= strokeOpacity; + aOutPattern->InitColorPattern(ToDeviceColor(color)); +} + +/* static */ +float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity, + SVGContextPaint* aContextPaint) { + float opacity = 1.0f; + switch (aOpacity.tag) { + case StyleSVGOpacity::Tag::Opacity: + return aOpacity.AsOpacity(); + case StyleSVGOpacity::Tag::ContextFillOpacity: + if (aContextPaint) { + opacity = aContextPaint->GetFillOpacity(); + } + break; + case StyleSVGOpacity::Tag::ContextStrokeOpacity: + if (aContextPaint) { + opacity = aContextPaint->GetStrokeOpacity(); + } + break; + } + return opacity; +} + +bool SVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint) { + const nsStyleSVG* style = aFrame->StyleSVG(); + return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; +} + +float SVGUtils::GetStrokeWidth(nsIFrame* aFrame, + SVGContextPaint* aContextPaint) { + nsIContent* content = aFrame->GetContent(); + if (content->IsText()) { + content = content->GetParent(); + } + + auto* ctx = SVGElement::FromNode(content); + return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint); +} + +void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext, + SVGContextPaint* aContextPaint) { + MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, + SVGElement::FromNode(aFrame->GetContent()), + aFrame->Style(), aContextPaint); + + if (strokeOptions.mLineWidth <= 0) { + return; + } + + // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px; + // convert to device pixels for gfxContext. + float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale; + + aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx); + aContext->SetLineCap(strokeOptions.mLineCap); + aContext->SetMiterLimit(strokeOptions.mMiterLimit); + aContext->SetLineJoin(strokeOptions.mLineJoin); + aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength, + strokeOptions.mDashOffset, devPxPerCSSPx); +} + +uint16_t SVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) { + uint16_t flags = 0; + + switch (aFrame->Style()->PointerEvents()) { + case StylePointerEvents::None: + break; + case StylePointerEvents::Auto: + case StylePointerEvents::Visiblepainted: + if (aFrame->StyleVisibility()->IsVisible()) { + if (!aFrame->StyleSVG()->mFill.kind.IsNone()) + flags |= SVG_HIT_TEST_FILL; + if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) + flags |= SVG_HIT_TEST_STROKE; + if (!aFrame->StyleSVG()->mStrokeOpacity.IsOpacity() || + aFrame->StyleSVG()->mStrokeOpacity.AsOpacity() > 0) + flags |= SVG_HIT_TEST_CHECK_MRECT; + } + break; + case StylePointerEvents::Visiblefill: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL; + } + break; + case StylePointerEvents::Visiblestroke: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_STROKE; + } + break; + case StylePointerEvents::Visible: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; + } + break; + case StylePointerEvents::Painted: + if (!aFrame->StyleSVG()->mFill.kind.IsNone()) flags |= SVG_HIT_TEST_FILL; + if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) + flags |= SVG_HIT_TEST_STROKE; + if (!aFrame->StyleSVG()->mStrokeOpacity.IsOpacity() || + aFrame->StyleSVG()->mStrokeOpacity.AsOpacity() > 0) { + flags |= SVG_HIT_TEST_CHECK_MRECT; + } + break; + case StylePointerEvents::Fill: + flags |= SVG_HIT_TEST_FILL; + break; + case StylePointerEvents::Stroke: + flags |= SVG_HIT_TEST_STROKE; + break; + case StylePointerEvents::All: + flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; + break; + default: + NS_ERROR("not reached"); + break; + } + + return flags; +} + +void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) { + nsIFrame* frame = aElement->GetPrimaryFrame(); + ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame); + if (!svgFrame) { + return; + } + 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 = SVGUtils::GetTransformMatrixInUserSpace(frame); + } + + // SVG-in-OpenType is not allowed to paint external resources, so we can + // just pass a dummy params into PatintSVG. + imgDrawingParams dummy; + svgFrame->PaintSVG(*aContext, m, dummy); +} + +bool SVGUtils::GetSVGGlyphExtents(Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult) { + nsIFrame* frame = aElement->GetPrimaryFrame(); + ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame); + if (!svgFrame) { + return false; + } + + gfxMatrix transform(aSVGToAppSpace); + if (auto* svg = SVGElement::FromNode(frame->GetContent())) { + transform = svg->PrependLocalTransformsTo(aSVGToAppSpace); + } + + *aResult = + svgFrame + ->GetBBoxContribution(gfx::ToMatrix(transform), + SVGUtils::eBBoxIncludeFill | + SVGUtils::eBBoxIncludeFillGeometry | + SVGUtils::eBBoxIncludeStroke | + SVGUtils::eBBoxIncludeStrokeGeometry | + SVGUtils::eBBoxIncludeMarkers) + .ToThebesRect(); + return true; +} + +nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect, + const gfxMatrix& aToCanvas, + const nsPresContext* presContext) { + return nsLayoutUtils::RoundGfxRectToAppRect( + aToCanvas.TransformBounds(aUserspaceRect), + presContext->AppUnitsPerDevPixel()); +} + +gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) { + float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale; + + return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0); +} + +gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) { + // We check element instead of aFrame directly because SVG element + // may have non-SVG frame, <tspan> for example. + MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(), + "Only use this wrapper for SVG elements"); + + if (!aFrame->IsTransformed()) { + return {}; + } + + nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); + nsDisplayTransform::FrameTransformProperties properties{ + aFrame, refBox, AppUnitsPerCSSPixel()}; + + // SVG elements can have x/y offset, their default transform origin + // is the origin of user space, not the top left point of the frame. + Point3D svgTransformOrigin{ + properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()), + properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()), + properties.mToTransformOrigin.z}; + + Matrix svgTransform; + Matrix4x4 trans; + (void)aFrame->IsSVGTransformed(&svgTransform); + + if (properties.HasTransform()) { + trans = nsStyleTransformMatrix::ReadTransforms( + properties.mTranslate, properties.mRotate, properties.mScale, + properties.mMotion, properties.mTransform, refBox, + AppUnitsPerCSSPixel()); + } else { + trans = Matrix4x4::From2D(svgTransform); + } + + trans.ChangeBasis(svgTransformOrigin); + + Matrix mm; + trans.ProjectTo2D(); + (void)trans.CanDraw2D(&mm); + + return ThebesMatrix(mm); +} + +} // namespace mozilla diff --git a/layout/svg/SVGUtils.h b/layout/svg/SVGUtils.h new file mode 100644 index 0000000000..c9e24bea4e --- /dev/null +++ b/layout/svg/SVGUtils.h @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGUTILS_H_ +#define LAYOUT_SVG_SVGUTILS_H_ + +// include math.h to pick up definition of M_ maths defines e.g. M_PI +#include <math.h> + +#include "DrawMode.h" +#include "ImgDrawResult.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 "nsISupports.h" +#include "nsMathUtils.h" +#include "nsStyleStruct.h" +#include <algorithm> + +class gfxContext; +class nsFrameList; +class nsIContent; + +class nsPresContext; +class nsTextFrame; + +struct nsStyleSVG; +struct nsRect; + +namespace mozilla { +class SVGAnimatedEnumeration; +class SVGAnimatedLength; +class SVGContextPaint; +struct SVGContextPaintImpl; +class SVGDisplayContainerFrame; +class SVGGeometryFrame; +class SVGOuterSVGFrame; +namespace dom { +class Element; +class SVGElement; +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_SVGDisplayListHitTestingEnabled(); +bool NS_SVGDisplayListPaintingEnabled(); +bool NS_SVGNewGetBBoxEnabled(); + +namespace mozilla { + +/** + * 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 final { + using Rect = gfx::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) {} + + operator const Rect&() { return mBBox; } + + 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 MoveBy(float x, float y) { mBBox.MoveBy(x, y); } + + 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 final { + using DrawTarget = gfx::DrawTarget; + + public: + explicit SVGAutoRenderState(DrawTarget* aDrawTarget); + ~SVGAutoRenderState(); + + void SetPaintingToWindow(bool aPaintingToWindow); + + static bool IsPaintingToWindow(DrawTarget* aDrawTarget); + + private: + DrawTarget* mDrawTarget; + void* mOriginalRenderState; + bool mPaintingToWindow; +}; + +/** + * 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 SVGUtils final { + public: + using Element = dom::Element; + using SVGElement = dom::SVGElement; + using AntialiasMode = gfx::AntialiasMode; + using DrawTarget = gfx::DrawTarget; + using FillRule = gfx::FillRule; + using GeneralPattern = gfx::GeneralPattern; + using Size = gfx::Size; + using imgDrawingParams = image::imgDrawingParams; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(ObjectBoundingBoxProperty, gfxRect) + + /** + * Returns the frame's post-filter ink overflow rect when passed the + * frame's pre-filter ink overflow rect. If the frame is not currently + * being filtered, this function simply returns aUnfilteredRect. + */ + static nsRect GetPostFilterInkOverflowRect(nsIFrame* aFrame, + const nsRect& aPreFilterRect); + + /** + * 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 SVGOuterSVGFrame::NotifyViewportChange when + * that function is called by SVGOuterSVGFrame::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 SVGOuterSVGFrame's + * children so that when SVGOuterSVGFrame::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 + * SVGForeignObjectFrame frames to mark their children dirty, but we want to + * handle SVGForeignObjectFrame 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); + + /** + * 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 SVGAnimatedLength* 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(SVGElement* aSVGElement, + const SVGAnimatedLength* aLength); + static float UserSpace(nsIFrame* aNonSVGContext, + const SVGAnimatedLength* aLength); + static float UserSpace(const dom::UserSpaceMetrics& aMetrics, + const SVGAnimatedLength* aLength); + + /* Find the outermost SVG frame of the passed frame */ + static SVGOuterSVGFrame* 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 void PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + 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(SVGDisplayContainerFrame* 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); + + /** + * 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); + + 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 gfx::IntSize ConvertToSurfaceSize(const gfxSize& aSize, + bool* aResultOverflows); + + /* + * Hit test a given rectangle/matrix. + */ + static bool HitTestRect(const 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. + * + * @param aFlags One or more of the BBoxFlags values defined below. + */ + static gfxMatrix AdjustMatrixForUnits(const gfxMatrix& aMatrix, + SVGAnimatedEnumeration* aUnits, + nsIFrame* aFrame, uint32_t aFlags); + + enum BBoxFlags { + eBBoxIncludeFill = 1 << 0, + // Include the geometry of the fill even when the fill does not + // actually render (e.g. when fill="none" or fill-opacity="0") + eBBoxIncludeFillGeometry = 1 << 1, + eBBoxIncludeStroke = 1 << 2, + // Include the geometry of the stroke even when the stroke does not + // actually render (e.g. when stroke="none" or stroke-opacity="0") + eBBoxIncludeStrokeGeometry = 1 << 3, + eBBoxIncludeMarkers = 1 << 4, + eBBoxIncludeClipped = 1 << 5, + // Normally a getBBox call on outer-<svg> should only return the + // bounds of the elements children. This flag will cause the + // element's bounds to be returned instead. + eUseFrameBoundsForOuterSVG = 1 << 6, + // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect + eForGetClientRects = 1 << 7, + // If the given frame is an HTML element, only include the region of the + // given frame, instead of all continuations of it, while computing bbox if + // this flag is set. + eIncludeOnlyCurrentFrameForNonSVGElement = 1 << 8, + // This flag is only has an effect when the target is a <use> element. + // getBBox returns the bounds of the elements children in user space if + // this flag is set; Otherwise, getBBox returns the union bounds in + // the coordinate system formed by the <use> element. + eUseUserSpaceOfUseElement = 1 << 9, + // For a frame with a clip-path, if this flag is set then the result + // will not be clipped to the bbox of the content inside the clip-path. + eDoNotClipToBBoxOfContentInsideClipPath = 1 << 10, + }; + /** + * This function in primarily for implementing the SVG DOM function getBBox() + * and the SVG attribute value 'objectBoundingBox'. However, it has been + * extended with various extra parameters in order to become more of a + * general purpose getter of all sorts of bounds that we might need to obtain + * for SVG elements, or even for other elements that have SVG effects applied + * to them. + * + * @param aFrame The frame of the element for which the bounds are to be + * obtained. + * @param aFlags One or more of the BBoxFlags values defined above. + * @param aToBoundsSpace If not specified the returned rect is in aFrame's + * element's "user space". A matrix can optionally be pass to specify a + * transform from aFrame's user space to the bounds space of interest + * (typically this will be the ancestor SVGOuterSVGFrame, but it could be + * to any other coordinate space). + */ + static gfxRect GetBBox(nsIFrame* aFrame, + // If the default arg changes, update the handling for + // ObjectBoundingBoxProperty() in the implementation. + uint32_t aFlags = eBBoxIncludeFillGeometry, + const gfxMatrix* aToBoundsSpace = nullptr); + + /* + * "User space" is the space that the frame's BBox (as calculated by + * SVGUtils::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 SVGAnimatedLength values into a user unit rectangle in user + * space. + * + * @param aXYWH pointer to 4 consecutive SVGAnimatedLength 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 SVGAnimatedLength* aXYWH, + const gfxRect& aBBox, nsIFrame* aFrame); + + static gfxRect GetRelativeRect(uint16_t aUnits, + const SVGAnimatedLength* aXYWH, + const gfxRect& aBBox, + const 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, + SVGGeometryFrame* 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))); + } + + /** + * Convert a floating-point value to a 64-bit integer value, clamping to + * the lowest and highest integers that can be safely compared to a double. + */ + static int64_t ClampToInt64(double aVal) { + return static_cast<int64_t>( + std::clamp<double>(aVal, INT64_MIN, std::nexttoward(INT64_MAX, 0))); + } + + static nscolor GetFallbackOrPaintColor( + const ComputedStyle&, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + nscolor aDefaultContextFallbackColor); + + static void MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext, + GeneralPattern* aOutPattern, + imgDrawingParams& aImgParams, + SVGContextPaint* aContextPaint = nullptr); + + static void MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext, + GeneralPattern* aOutPattern, + imgDrawingParams& aImgParams, + SVGContextPaint* aContextPaint = nullptr); + + static float GetOpacity(const StyleSVGOpacity&, SVGContextPaint*); + + /* + * @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 context for a stroked path (including any dashing that applies). + */ + static void SetupStrokeGeometry(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(StyleFillRule aFillRule) { + return aFillRule == StyleFillRule::Evenodd ? FillRule::FILL_EVEN_ODD + : FillRule::FILL_WINDING; + } + + static AntialiasMode ToAntialiasMode(StyleTextRendering aTextRendering) { + return aTextRendering == StyleTextRendering::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 void 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 shouldApplyBasicShapeOrPath; + float opacity; + + MaskUsage() + : shouldGenerateMaskLayer(false), + shouldGenerateClipMaskLayer(false), + shouldApplyClipPath(false), + shouldApplyBasicShapeOrPath(false), + opacity(0.0) {} + + bool shouldDoSomething() { + return shouldGenerateMaskLayer || shouldGenerateClipMaskLayer || + shouldApplyClipPath || shouldApplyBasicShapeOrPath || + opacity != 1.0; + } + }; + + static void DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, + MaskUsage& aUsage); + + static float ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity); + + /** + * 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); + + static bool IsInSVGTextSubtree(const nsIFrame* aFrame) { + // Returns true if the frame is an SVGTextFrame or one of its descendants. + return aFrame->HasAnyStateBits(NS_FRAME_IS_SVG_TEXT); + } + + /** + * It is a replacement of + * SVGElement::PrependLocalTransformsTo(eUserSpaceToParent). + * If no CSS transform is involved, they should behave exactly the same; + * if there are CSS transforms, this one will take them into account + * while SVGElement::PrependLocalTransformsTo won't. + */ + static gfxMatrix GetTransformMatrixInUserSpace(const nsIFrame* aFrame); +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGUTILS_H_ diff --git a/layout/svg/SVGViewFrame.cpp b/layout/svg/SVGViewFrame.cpp new file mode 100644 index 0000000000..e7105ceb6c --- /dev/null +++ b/layout/svg/SVGViewFrame.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/PresShell.h" +#include "mozilla/SVGOuterSVGFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGViewElement.h" +#include "nsIFrame.h" +#include "nsGkAtoms.h" + +using namespace mozilla::dom; + +nsIFrame* NS_NewSVGViewFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle); + +namespace mozilla { + +/** + * 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 final : public nsIFrame { + friend nsIFrame* ::NS_NewSVGViewFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + protected: + explicit SVGViewFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) + : nsIFrame(aStyle, aPresContext, kClassID) { + AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY); + } + + public: + NS_DECL_FRAMEARENA_HELPERS(SVGViewFrame) + +#ifdef DEBUG + virtual void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; +#endif + + bool IsFrameOfType(uint32_t aFlags) const override { + if (aFlags & eSupportsContainLayoutAndPaint) { + return false; + } + + return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG)); + } + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(u"SVGView"_ns, aResult); + } +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override { + // We don't maintain a ink overflow rect + return false; + } +}; + +} // namespace mozilla + +nsIFrame* NS_NewSVGViewFrame(mozilla::PresShell* aPresShell, + mozilla::ComputedStyle* aStyle) { + return new (aPresShell) + mozilla::SVGViewFrame(aStyle, aPresShell->GetPresContext()); +} + +namespace mozilla { + +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"); + + nsIFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsresult SVGViewFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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)) { + SVGOuterSVGFrame* outerSVGFrame = SVGUtils::GetOuterSVGFrame(this); + NS_ASSERTION(outerSVGFrame->GetContent()->IsSVGElement(nsGkAtoms::svg), + "Expecting an <svg> element"); + + SVGSVGElement* svgElement = + static_cast<SVGSVGElement*>(outerSVGFrame->GetContent()); + + nsAutoString viewID; + mContent->AsElement()->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 nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); +} + +} // namespace mozilla diff --git a/layout/svg/SVGViewportFrame.cpp b/layout/svg/SVGViewportFrame.cpp new file mode 100644 index 0000000000..0b351dfa1b --- /dev/null +++ b/layout/svg/SVGViewportFrame.cpp @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SVGViewportFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/SVGContainerFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGViewportElement.h" +#include "nsLayoutUtils.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { + +//---------------------------------------------------------------------- +// ISVGDisplayableFrame methods + +void SVGViewportFrame::PaintSVG(gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + 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<SVGViewportElement*>(GetContent()) + ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + + if (width <= 0 || height <= 0) { + return; + } + + autoSR.SetContext(&aContext); + gfxRect clipRect = SVGUtils::GetClipRectForFrame(this, x, y, width, height); + SVGUtils::SetClipRect(&aContext, aTransform, clipRect); + } + + SVGDisplayContainerFrame::PaintSVG(aContext, aTransform, aImgParams, + aDirtyRect); +} + +void SVGViewportFrame::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<SVGViewportElement*>(GetContent()) + ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); + mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, width, height), + 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(); + } + + SVGDisplayContainerFrame::ReflowSVG(); +} + +void SVGViewportFrame::NotifySVGChanged(uint32_t aFlags) { + MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), + "Invalidation logic may need adjusting"); + + if (aFlags & COORD_CONTEXT_CHANGED) { + SVGViewportElement* svg = static_cast<SVGViewportElement*>(GetContent()); + + bool xOrYIsPercentage = + svg->mLengthAttributes[SVGViewportElement::ATTR_X].IsPercentage() || + svg->mLengthAttributes[SVGViewportElement::ATTR_Y].IsPercentage(); + bool widthOrHeightIsPercentage = + svg->mLengthAttributes[SVGViewportElement::ATTR_WIDTH].IsPercentage() || + svg->mLengthAttributes[SVGViewportElement::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. + SVGUtils::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->HasViewBox()))) { + aFlags |= TRANSFORM_CHANGED; + } + + if (svg->HasViewBox() || !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 + } + } + } + + SVGDisplayContainerFrame::NotifySVGChanged(aFlags); +} + +SVGBBox SVGViewportFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) { + // XXXjwatt It seems like authors would want the result to be clipped by the + // viewport we establish if IsScrollableOverflow() is true. We should + // consider doing that. See bug 1350755. + + SVGBBox bbox; + + if (aFlags & SVGUtils::eForGetClientRects) { + // XXXjwatt For consistency with the old code this code includes the + // viewport we establish in the result, but only includes the bounds of our + // descendants if they are not clipped to that viewport. However, this is + // both inconsistent with Chrome and with the specs. See bug 1350755. + // Ideally getClientRects/getBoundingClientRect should be consistent with + // getBBox. + float x, y, w, h; + static_cast<SVGViewportElement*>(GetContent()) + ->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); + if (w < 0.0f) w = 0.0f; + if (h < 0.0f) h = 0.0f; + Rect viewport(x, y, w, h); + bbox = aToBBoxUserspace.TransformBounds(viewport); + if (StyleDisplay()->IsScrollableOverflow()) { + return bbox; + } + // Else we're not clipping to our viewport so we fall through and include + // the bounds of our children. + } + + SVGBBox descendantsBbox = + SVGDisplayContainerFrame::GetBBoxContribution(aToBBoxUserspace, aFlags); + + bbox.UnionEdges(descendantsBbox); + + return bbox; +} + +nsresult SVGViewportFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID == kNameSpaceID_None && + !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + SVGViewportElement* content = + static_cast<SVGViewportElement*>(GetContent()); + + if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) { + nsLayoutUtils::PostRestyleEvent( + mContent->AsElement(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(this); + + if (content->HasViewBoxOrSyntheticViewBox()) { + // make sure our cached transform matrix gets (lazily) updated + mCanvasTM = nullptr; + content->ChildrenOnlyTransformChanged(); + SVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED); + } else { + uint32_t flags = COORD_CONTEXT_CHANGED; + if (mCanvasTM && mCanvasTM->IsSingular()) { + mCanvasTM = nullptr; + flags |= TRANSFORM_CHANGED; + } + SVGUtils::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; + + SVGUtils::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(), RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::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* SVGViewportFrame::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<SVGElement*>(GetContent()) + ->GetAnimatedLengthValues(&clip.x, &clip.y, &clip.width, &clip.height, + nullptr); + if (!clip.Contains(ToPoint(aPoint))) { + return nullptr; + } + } + + return SVGDisplayContainerFrame::GetFrameForPoint(aPoint); +} + +//---------------------------------------------------------------------- +// ISVGSVGFrame methods: + +void SVGViewportFrame::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 SVGViewportFrame"); +} + +//---------------------------------------------------------------------- +// SVGContainerFrame methods: + +bool SVGViewportFrame::HasChildrenOnlyTransform(gfx::Matrix* aTransform) const { + SVGViewportElement* content = static_cast<SVGViewportElement*>(GetContent()); + + if (content->HasViewBoxOrSyntheticViewBox()) { + // XXX Maybe return false if the transform is the identity transform? + if (aTransform) { + *aTransform = content->GetViewBoxTransform(); + } + return true; + } + return false; +} + +} // namespace mozilla diff --git a/layout/svg/SVGViewportFrame.h b/layout/svg/SVGViewportFrame.h new file mode 100644 index 0000000000..9c6b23289f --- /dev/null +++ b/layout/svg/SVGViewportFrame.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 LAYOUT_SVG_SVGVIEWPORTFRAME_H_ +#define LAYOUT_SVG_SVGVIEWPORTFRAME_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/ISVGSVGFrame.h" +#include "mozilla/SVGContainerFrame.h" + +class gfxContext; + +namespace mozilla { + +/** + * Superclass for inner SVG frames and symbol frames. + */ +class SVGViewportFrame : public SVGDisplayContainerFrame, public ISVGSVGFrame { + protected: + SVGViewportFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + nsIFrame::ClassID aID) + : SVGDisplayContainerFrame(aStyle, aPresContext, aID) {} + + public: + NS_DECL_ABSTRACT_FRAME(SVGViewportFrame) + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + // ISVGDisplayableFrame interface: + void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, + imgDrawingParams& aImgParams, + const nsIntRect* aDirtyRect = nullptr) override; + void ReflowSVG() override; + void NotifySVGChanged(uint32_t aFlags) override; + SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, + uint32_t aFlags) override; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override; + + // SVGContainerFrame methods: + bool HasChildrenOnlyTransform(Matrix* aTransform) const override; + + // ISVGSVGFrame interface: + void NotifyViewportOrTransformChanged(uint32_t aFlags) override; +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGVIEWPORTFRAME_H_ 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/1072758.html b/layout/svg/crashtests/1072758.html new file mode 100644 index 0000000000..1d4c85fb87 --- /dev/null +++ b/layout/svg/crashtests/1072758.html @@ -0,0 +1,35 @@ +<style> +#x9 { + display:none; +} +</style> + +<body onload="go()"> +<svg> +<path id="a"></path> + +<mask id="m"> + <text id="y"> + <tspan id="x1"></tspan> + <textPath id="x2"></textPath> + <a id="x3">Hello</a> + <tspan><tspan id="x4"></tspan></tspan> + <tspan id="x5"></tspan> + </text> +</mask> + +<rect width="600" height="400" mask="url(#m)"/> +</svg> +</body> + +<script> + +function go() { + x1.style.display = "none"; + x2.style.display = "none"; + x3.style.display = "none"; + x4.style.display = "none"; + x5.id = "x9"; +}; + +</script> 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/1234726-1.svg b/layout/svg/crashtests/1234726-1.svg new file mode 100644 index 0000000000..ec807e2aba --- /dev/null +++ b/layout/svg/crashtests/1234726-1.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> + +window.addEventListener("load", function() { + document.documentElement.style.fontSize = "70%"; + document.documentElement.setAttribute("transform", "scale(2)"); + var filt = document.createElementNS("http://www.w3.org/2000/svg", "filter"); + filt.style.borderWidth = "2rem"; + document.documentElement.appendChild(filt); +}, false); + +</script> +</svg> diff --git a/layout/svg/crashtests/1322537-1.html b/layout/svg/crashtests/1322537-1.html new file mode 100644 index 0000000000..04207db4ad --- /dev/null +++ b/layout/svg/crashtests/1322537-1.html @@ -0,0 +1,2 @@ +<svg> +<animateMotion path='M8,0l69,97m45,-17592186044414A71,23 46,0,0 16382,98Q50,10 48,72T21,0Z'/>
\ No newline at end of file diff --git a/layout/svg/crashtests/1322537-2.html b/layout/svg/crashtests/1322537-2.html new file mode 100644 index 0000000000..d0495d79af --- /dev/null +++ b/layout/svg/crashtests/1322537-2.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<head> + <script> + function go() { + var path = document.getElementById("myPath"); + var len = path.getTotalLength(); + console.log(len); + } + </script> +</head> +<body onload="go()"> + <svg> + <path id="myPath" d="M45,-17592186044414A71,23 46,0,0 16382,98"/> + </svg> +</body> diff --git a/layout/svg/crashtests/1322852.html b/layout/svg/crashtests/1322852.html new file mode 100644 index 0000000000..728ea5c7a9 --- /dev/null +++ b/layout/svg/crashtests/1322852.html @@ -0,0 +1,2 @@ +<svg> +<ellipse stroke='-moz-mac-menushadow'/>
\ No newline at end of file diff --git a/layout/svg/crashtests/1348564.svg b/layout/svg/crashtests/1348564.svg new file mode 100644 index 0000000000..0cb28948e6 --- /dev/null +++ b/layout/svg/crashtests/1348564.svg @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg"> +<g style="outline-style:auto;"> +<radialGradient style="transform-box:fill-box;transform:rotatex(15deg);"></radialGradient> +<rect x="200"></rect> +</g> +</svg> diff --git a/layout/svg/crashtests/1402109.html b/layout/svg/crashtests/1402109.html new file mode 100644 index 0000000000..81ef78a058 --- /dev/null +++ b/layout/svg/crashtests/1402109.html @@ -0,0 +1,11 @@ +<script> +function jsfuzzer() { +try { svgvar00012.getStartPositionOfChar(0); } catch(e) { } +} +</script> +<body onload=jsfuzzer()> +<table> +<caption>,HM>v((Ndeoi=&</caption> +<caption> +<svg> +<text id="svgvar00012"> diff --git a/layout/svg/crashtests/1402124.html b/layout/svg/crashtests/1402124.html new file mode 100644 index 0000000000..793b46b9a1 --- /dev/null +++ b/layout/svg/crashtests/1402124.html @@ -0,0 +1,10 @@ +<script> +function jsfuzzer() { +try { a.getExtentOfChar(0); } catch(e) { } +} +</script> +<body onload=jsfuzzer()> +<svg> +<switch> +<path/> +<text id="a"> diff --git a/layout/svg/crashtests/1402486.html b/layout/svg/crashtests/1402486.html new file mode 100644 index 0000000000..af0c72e351 --- /dev/null +++ b/layout/svg/crashtests/1402486.html @@ -0,0 +1,12 @@ +<script> +function jsfuzzer() { +try { a.getEndPositionOfChar(0); } catch(e) { } +} +</script> +<body onload=jsfuzzer()> +<svg> +<switch> +<text id="a"> +</text> +<feTurbulence +<!-- a -->
\ No newline at end of file diff --git a/layout/svg/crashtests/1403656-1.html b/layout/svg/crashtests/1403656-1.html new file mode 100644 index 0000000000..996562d150 --- /dev/null +++ b/layout/svg/crashtests/1403656-1.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html class="filtered"> +<style> + .filtered { + filter: url(#notsvg); + } + #notsvg { + float: left; + overflow: auto; + } +</style> +<script> +function go() { + const div = document.createElement('div') + div.setAttribute('id', 'notsvg') + document.documentElement.appendChild(div) +} +</script> +<body onload="go();" class="filtered"> diff --git a/layout/svg/crashtests/1403656-2.html b/layout/svg/crashtests/1403656-2.html new file mode 100644 index 0000000000..e4287e933f --- /dev/null +++ b/layout/svg/crashtests/1403656-2.html @@ -0,0 +1,21 @@ +<!-- MUST BE QUIRKS MODE --> +<style> +span { top: 0%; } +</style> +<script> +function jsfuzzer() { + try { text.setAttribute("font-size", "10px"); } catch(e) { } +} +</script> +<body onload=jsfuzzer()> + <div style="overflow: scroll"> + <span></span> + </div> + <svg> + <tref id="marker"> + <text id="text"/> + </tref> + <polyline marker-end="url(#marker)"> + </polyline> + </svg> +</body> diff --git a/layout/svg/crashtests/1403656-3.html b/layout/svg/crashtests/1403656-3.html new file mode 100644 index 0000000000..a5a7632665 --- /dev/null +++ b/layout/svg/crashtests/1403656-3.html @@ -0,0 +1,8 @@ +<style> +* { columns: 0px } +</style> +A +<svg id="a"> +<line marker-end="url(#a)"> +</svg> +<keygen> diff --git a/layout/svg/crashtests/1403656-4.html b/layout/svg/crashtests/1403656-4.html new file mode 100644 index 0000000000..1da8979aa7 --- /dev/null +++ b/layout/svg/crashtests/1403656-4.html @@ -0,0 +1,9 @@ +<style>
+#a {}
+* {
+ text-align: end;
+ writing-mode: sideways-lr;
+ -webkit-filter: url(#a);
+}
+</style>
+<canvas>AAAAAAAAAAAAAAAA</canvas><ol id="a">@
diff --git a/layout/svg/crashtests/1403656-5.html b/layout/svg/crashtests/1403656-5.html new file mode 100644 index 0000000000..3d64bc5b50 --- /dev/null +++ b/layout/svg/crashtests/1403656-5.html @@ -0,0 +1,11 @@ +<style> +* { + -webkit-filter: url(#a); + padding-right: 1vmin; + overflow-y: scroll; +} +</style> +<details id="a"></details> +<image style="bottom: 1em;display:flex"></image> +<canvas height="1"> +<font> diff --git a/layout/svg/crashtests/1404086.html b/layout/svg/crashtests/1404086.html new file mode 100644 index 0000000000..df2ad6a3b3 --- /dev/null +++ b/layout/svg/crashtests/1404086.html @@ -0,0 +1,2 @@ +<svg> +<text textLength="0" lengthAdjust="spacingAndGlyphs">t diff --git a/layout/svg/crashtests/1421807-1.html b/layout/svg/crashtests/1421807-1.html new file mode 100644 index 0000000000..fe5a3e8d66 --- /dev/null +++ b/layout/svg/crashtests/1421807-1.html @@ -0,0 +1,5 @@ +<body onload=b.appendChild(a)> +<div id="a" style="display: contents"> +</div> +<svg> +<text id="b"> diff --git a/layout/svg/crashtests/1421807-2.html b/layout/svg/crashtests/1421807-2.html new file mode 100644 index 0000000000..30b100642c --- /dev/null +++ b/layout/svg/crashtests/1421807-2.html @@ -0,0 +1,15 @@ +<style> +.c1 { display: contents; } +</style> +<script> +function go() { + a.attachShadow({mode: "open"}).innerHTML = `<slot> </slot> `; + b.appendChild(a); +} +</script> +<body onload=go()> +<div id="a" class="c1"> + <span></span> +</div> +<svg> +<text id="b"> diff --git a/layout/svg/crashtests/1422226.html b/layout/svg/crashtests/1422226.html new file mode 100644 index 0000000000..5826e60b69 --- /dev/null +++ b/layout/svg/crashtests/1422226.html @@ -0,0 +1,39 @@ +<style id="htmlvar00001"> +* { + filter: saturate(1) hue-rotate(0deg); + marker-mid: url() +} +</style> +<script> +function jsfuzzer() { +try { var var00078 = window.find("foo",true,true); } catch(e) { } +try { htmlvar00004.setAttribute("onselect", "eventhandler2()"); } catch(e) { } +try { htmlvar00004.selectionStart = 1; } catch(e) { } +try { svgvar00030.addEventListener("DOMSubtreeModified", eventhandler5); } catch(e) { } +} +function eventhandler1() { +try { var var00098 = document.createElement("select"); } catch(e) { } +try { htmlvar00001.appendChild(var00098); } catch(e) { } +} +function eventhandler2() { +try { htmlvar00015.href = "3" } catch(e) { } +try { svgvar00008.before(svgvar00013); } catch(e) { } +try { var var00014 = window.getSelection(); } catch(e) { } +try { var00014.setBaseAndExtent(htmlvar00011,0,htmlvar00010,0); } catch(e) { } +try { var00014.collapseToStart(); } catch(e) { } +} +function eventhandler5() { +try { svgvar00001.addEventListener("DOMNodeRemoved", eventhandler1); } catch(e) { } +} +</script> +<body onload=jsfuzzer()> +<input id="htmlvar00004"> +<svg id="svgvar00001"> +<marker> +<meshgradient id="svgvar00008"> +<foreignObject id="svgvar00013"> +<discard id="svgvar00030"/> +<a id="htmlvar00010" contenteditable="true"></a> +<a id="htmlvar00011"> +<base id="htmlvar00015"> + diff --git a/layout/svg/crashtests/1425434-1.html b/layout/svg/crashtests/1425434-1.html new file mode 100644 index 0000000000..cec773ca25 --- /dev/null +++ b/layout/svg/crashtests/1425434-1.html @@ -0,0 +1,59 @@ +<html>
+<head>
+<style>
+
+#htmlvar00002,.class4,strong:last-of-type {
+ border-image-repeat: round stretch;
+ counter-increment:c
+}
+.class6,#htmlvar00001,#htmlvar00008 {
+ height:63%;
+ grid-column:span 0 middle / middle
+}
+#htmlvar00002 {
+ overflow-y:hidden;
+ grid-column-end:0 inherit
+}
+
+</style>
+<script>
+
+function eventhandler5() {
+
+ var var00066 = document.createElement("audio");
+ try { var00066.controls = true; } catch(e) { }
+
+ try { document.all['htmlvar00002'].appendChild(htmlvar00001); } catch(e) { }
+ try { document.all['htmlvar00002'].appendChild(var00066); } catch(e) { }
+}
+
+</script>
+</head>
+<body onload=eventhandler5()>
+
+ <shadow id="htmlvar00001" axis="PDcIvMde3=%- S^@dz" ondblclick="eventhandler1()" item="8z6h" checked="checked" type="text/x-javascript">
+ aaa
+ </shadow>
+
+ <ul id="htmlvar00002" type="video/mp4; codecs='avc1.4D400C'" type="turbulence" type="javascript_1.0"
+ role="complementary" dir="ltr" required="required" formnovalidate="formnovalidate" scheme="NIST" expanded="true" slot="slot2">
+ bbb
+ </ul>
+
+ <svg id="svgvar00001" marker-end="url(#svgvar00005)" transform="translate(0, 0) scale(0.113899652038) rotate(1) translate(0, 1)"
+ marker-mid="url(#svgvar00009)" xml:id="test-title" viewBox="0 1 78 1" click="none" glyph-orientation-vertical="-1" dominant-baseline="middle" ry="61%" k="1">
+
+ <path id="svgvar00009" d="M 0 0 L 1 0" stroke-dashoffset="inherit" onclick="eventhandler5()" stroke-linejoin="inherit"
+ fill-rule="evenodd" onmouseover="eventhandler3()" onfocusin="eventhandler4()" pointsAtY="1" target="_self" primitiveUnits="userSpaceOnUse" horiz-adv-x="0" />
+
+ <line id="svgvar00010" x1="0px" y1="55%" x2="76%" y2="62%" transform="translate(0 100) scale(0 -1)"
+ transform="translate(00.32347924876,0)" visibility="hidden" stroke-width="inherit" pointer-events="painted"
+ xlink:type="simple" onrepeat="eventhandler4()" clip-path="url(#svgvar00001)" vector-effect="non-scaling-stroke" azimuth="-1">
+ ccc
+ </line>
+
+ </svg>
+
+<!--endhtml-->
+</body>
+</html>
diff --git a/layout/svg/crashtests/1443092-helper.svg b/layout/svg/crashtests/1443092-helper.svg new file mode 100644 index 0000000000..134cbefb58 --- /dev/null +++ b/layout/svg/crashtests/1443092-helper.svg @@ -0,0 +1,6 @@ +<svg class='class2' xmlns='http://www.w3.org/2000/svg'> +<clipPath clipPathUnits='userSpaceOnUse' id='id9' lighting-color='pink' class='class0 class1' > +</clipPath> +<a id='id0' clip-path='url(#id9)' > +</a> +</svg> diff --git a/layout/svg/crashtests/1443092.html b/layout/svg/crashtests/1443092.html new file mode 100644 index 0000000000..7be4561e61 --- /dev/null +++ b/layout/svg/crashtests/1443092.html @@ -0,0 +1,34 @@ +<script> +function start() { + o7=document.createElement('div'); + o7.innerHTML='<style>@keyframes key7{ from{ transform: rotate(-536870911deg)}}\n*{ animation-name: key7; animation-duration: 0.001s'; + o17=document.createElement('div'); + o17.innerHTML='<svg><style>@font-face{}\n*{ outline-style: dotted</style><style>@font-face{ font-family: font3; src: url('; + o18=o17.firstChild.getElementsByTagName('*'); + o20=o18[0]; + o23=o18[1]; + o114=document.createElement('iframe'); + o114.src='1443092-helper.svg'; + o114.addEventListener('load', fun0,false); + document.body.appendChild(o114); +} +function fun0() { + o117=o114.contentDocument; + document.replaceChild(o117.documentElement,document.documentElement); + o124=document.createElement('iframe'); + document.documentElement.appendChild(o124); + o145=document.createElement('div'); + o145.innerHTML='<svg><set attributeName="text-decoration">'; + document.documentElement.appendChild(o20); + document.documentElement.appendChild(o23); + document.documentElement.appendChild(o7); + o124.src='x'; + document.documentElement.appendChild(o145); + o264=document.createElement('style'); + o265=document.createTextNode('*{ float: left'); + o264.appendChild(o265); + o23.appendChild(o264); + setTimeout("location.reload()",40); +} +</script> +<body onload="start()"></body> diff --git a/layout/svg/crashtests/1454201-1.html b/layout/svg/crashtests/1454201-1.html new file mode 100644 index 0000000000..b92a7aa595 --- /dev/null +++ b/layout/svg/crashtests/1454201-1.html @@ -0,0 +1,52 @@ +<html> +<head> +<script> +function loader() { +var tbody = document.createElement("tbody"); +try { document.all[90%document.all.length].appendChild(tbody); } catch(e) { } +} +</script> +</head> +<body onload=loader()> +<span> +<a></a> +</span> +<a></a> +<svg marker-end="url(#poly)"> +<a></a> +<a></a> +<symbol > +<a></a> +</symbol> +<polyline id="poly"> +<a></a> +<a></a> +<a></a> +<a></a> +</polyline> +<line > +<a></a> +</line> +<a></a> +<span> +<a></a> +</span> +<a></a> +<a></a> +<a></a> +</svg> +<dialog style="-moz-appearance: checkbox; overflow-x: auto; max-width: 0em;" open="true"></dialog> +<a></a> +<a></a> +<span> +<a></a> +<a></a> +<a></a> +<a></a> +</span> +<a></a> +<a></a> +<a></a> +<a></a> +</body> +</html> diff --git a/layout/svg/crashtests/1467552-1.html b/layout/svg/crashtests/1467552-1.html new file mode 100644 index 0000000000..df85665c72 --- /dev/null +++ b/layout/svg/crashtests/1467552-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<html> +<style> +fieldset { + mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=), + padding-box; + margin-right: 75px; +} +span { + vertical-align: 672in; +} +</style> +<fieldset><span><video></video></span></fieldset> +</html> diff --git a/layout/svg/crashtests/1474982.html b/layout/svg/crashtests/1474982.html new file mode 100644 index 0000000000..c33e2be474 --- /dev/null +++ b/layout/svg/crashtests/1474982.html @@ -0,0 +1,2 @@ +<svg> +<text lengthAdjust="" textLength="0">A</text> diff --git a/layout/svg/crashtests/1480224.html b/layout/svg/crashtests/1480224.html new file mode 100644 index 0000000000..0bb0dfa7b5 --- /dev/null +++ b/layout/svg/crashtests/1480224.html @@ -0,0 +1,6 @@ +<svg> +<symbol id="a"> +<foreignObject> +<input type="file"> +</symbol> +<use xlink:href="#a"> diff --git a/layout/svg/crashtests/1480275.html b/layout/svg/crashtests/1480275.html new file mode 100644 index 0000000000..b21ebdd062 --- /dev/null +++ b/layout/svg/crashtests/1480275.html @@ -0,0 +1,15 @@ +<script> +function go() { + b.appendChild(a); + d.outerHTML = f.outerHTML; +} +</script> +<table> +<tr id="a"> +<th> +<svg id="c" onload="go()"> +<use xlink:href="#c"/> +</tr> +<code id="d"></code> +<video id="f"> +<details id="b"> diff --git a/layout/svg/crashtests/1502936.html b/layout/svg/crashtests/1502936.html new file mode 100644 index 0000000000..87892ab461 --- /dev/null +++ b/layout/svg/crashtests/1502936.html @@ -0,0 +1,11 @@ +<script> +function go() { + a.setAttribute("requiredExtensions", "x"); + b.getBoundingClientRect(); + a.setAttribute("y", "-1px"); +} +</script> +<body onload=go()> +<svg> +<use id="a"> +<feGaussianBlur id="b"> diff --git a/layout/svg/crashtests/1504072.html b/layout/svg/crashtests/1504072.html new file mode 100644 index 0000000000..a44e2f0251 --- /dev/null +++ b/layout/svg/crashtests/1504072.html @@ -0,0 +1,4 @@ +<style> +svg { perspective: 0px } +</style> +<svg overflow="scroll" systemLanguage=""> diff --git a/layout/svg/crashtests/1504918.svg b/layout/svg/crashtests/1504918.svg new file mode 100644 index 0000000000..63cd3a08e0 --- /dev/null +++ b/layout/svg/crashtests/1504918.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg">
+ <style>textPath { display: contents; }</style>
+ <text>x<textPath><textPath><tspan>y</tspan></textPath></textPath></text>
+</svg>
\ No newline at end of file diff --git a/layout/svg/crashtests/1535517-1.svg b/layout/svg/crashtests/1535517-1.svg new file mode 100644 index 0000000000..6f3f17eabf --- /dev/null +++ b/layout/svg/crashtests/1535517-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<marker><text><tspan id="x"/></text></marker> + +<script> +window.addEventListener("load", function() { + document.getElementById("x").appendChild(document.createTextNode("\u062Ax")); +}, false); +</script> + +</svg> diff --git a/layout/svg/crashtests/1536892.html b/layout/svg/crashtests/1536892.html new file mode 100644 index 0000000000..8bc237138e --- /dev/null +++ b/layout/svg/crashtests/1536892.html @@ -0,0 +1,13 @@ +<style>
+* { -webkit-filter: blur(5px) }
+</style>
+<script>
+function go() {
+ a.setAttribute("text-decoration", "overline")
+}
+</script>
+<body onload=go()>
+<svg id="a">
+<marker>
+<foreignObject>
+<li style="-webkit-box-shadow:8px 0 1px">
diff --git a/layout/svg/crashtests/1539318-1.svg b/layout/svg/crashtests/1539318-1.svg new file mode 100644 index 0000000000..d832f448a3 --- /dev/null +++ b/layout/svg/crashtests/1539318-1.svg @@ -0,0 +1,10 @@ +<script> +window.onload = function() { + a.getComputedTextLength() +} +</script> +<body> +<svg> +<switch> +<hatch> +<text id="a">A</text> diff --git a/layout/svg/crashtests/1548985-1.html b/layout/svg/crashtests/1548985-1.html new file mode 100644 index 0000000000..7156eaf36b --- /dev/null +++ b/layout/svg/crashtests/1548985-1.html @@ -0,0 +1,16 @@ +<!-- a -->
+<style>
+:root { contain: size }
+</style>
+<script>
+window.requestIdleCallback(window.close)
+function go() {
+ a.appendChild(document.head)
+ let b = d.getRootNode({composed: true})
+ b.replaceChild(c, b.childNodes[1])
+}
+</script>
+<body onload=go()>
+<svg id="c">
+<feTile id="d" />
+<foreignObject id="a">
diff --git a/layout/svg/crashtests/1548985-2.svg b/layout/svg/crashtests/1548985-2.svg new file mode 100644 index 0000000000..700facb411 --- /dev/null +++ b/layout/svg/crashtests/1548985-2.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" + style="contain:size"> + <rect width="100px" height="100px" fill="lime"/> +</svg> diff --git a/layout/svg/crashtests/1555851.html b/layout/svg/crashtests/1555851.html new file mode 100644 index 0000000000..2b6f5a596a --- /dev/null +++ b/layout/svg/crashtests/1555851.html @@ -0,0 +1,8 @@ +<body>
+<script>
+ var o0 = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+ document.body.appendChild(o0)
+ var o3 = document.createElementNS("http://www.w3.org/2000/svg", "animateMotion")
+ o0.appendChild(o3)
+ o3.setAttribute("path", "M9,2l2e37,3A40,2 85,102-1")
+</script>
diff --git a/layout/svg/crashtests/1563779.html b/layout/svg/crashtests/1563779.html new file mode 100644 index 0000000000..4c51d0884d --- /dev/null +++ b/layout/svg/crashtests/1563779.html @@ -0,0 +1,19 @@ +<script id="a"> +let count = 0; +function go() { + if (count++ == 3) + return; + try { a.appendChild(c) } catch(e) { } + try { window.getSelection().getRangeAt(0).insertNode(b) } catch(e) { } + try { d.selectSubString(0,-1) } catch(e) { } + document.documentElement.style.display = "none" + document.documentElement.getBoundingClientRect() + document.documentElement.style.display = "" +} +</script> +<pre id="b" style="display:contents">a</pre> +<span id="c"> +<style onload="go()"></style> +</span> +<svg> +<text id="d"><textPath xml:space="preserve"> diff --git a/layout/svg/crashtests/1600855.html b/layout/svg/crashtests/1600855.html new file mode 100644 index 0000000000..31c4809ecb --- /dev/null +++ b/layout/svg/crashtests/1600855.html @@ -0,0 +1,8 @@ +<script> +window.onload = () => { + a.append(String.fromCodePoint(71341)) +} +</script> +<font style="word-spacing: 31pc"> +<svg> +<text id="a">Text</text> diff --git a/layout/svg/crashtests/1601824.html b/layout/svg/crashtests/1601824.html new file mode 100644 index 0000000000..04d0de825d --- /dev/null +++ b/layout/svg/crashtests/1601824.html @@ -0,0 +1,7 @@ +<svg> +<polygon id="a" points="0 1"> +</polygon> +<text> +<textPath xlink:href="#a"> +<marker /> +<a systemLanguage=""> diff --git a/layout/svg/crashtests/1605223-1.html b/layout/svg/crashtests/1605223-1.html new file mode 100644 index 0000000000..6b476a7fd0 --- /dev/null +++ b/layout/svg/crashtests/1605223-1.html @@ -0,0 +1,4 @@ +<svg filter="url(#a)"> +<filter id="a"> +<feComponentTransfer> +<feFuncG type="" /> diff --git a/layout/svg/crashtests/1609663.html b/layout/svg/crashtests/1609663.html new file mode 100644 index 0000000000..c5a5d938e0 --- /dev/null +++ b/layout/svg/crashtests/1609663.html @@ -0,0 +1,34 @@ +<html>
+<head>
+</head>
+<body>
+<div id="main">
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="mySvg" width="400" height="800">
+ <defs>
+ <pattern id="pattern1" x="30" y="10" width="30" height="30" patternUnits="userSpaceOnUse" >
+ <text id="idText1" x="15" y="15" font-size="20">1</text>
+ </pattern>
+ </defs>
+ <rect id="idRect1" fill="url(#pattern1)" width="300" height="200"/>
+ </svg>
+</div>
+<script>
+document.body.offsetHeight;
+
+var pattern = document.getElementById('pattern1');
+var text = document.getElementById('idText1');
+pattern.removeChild(text);
+
+var svgNS = "http://www.w3.org/2000/svg";
+var newText = document.createElementNS(svgNS,"text");
+newText.setAttributeNS(null,"id",'idText1');
+newText.setAttributeNS(null,"x",15);
+newText.setAttributeNS(null,"y",15);
+newText.setAttributeNS(null,"font-size","20");
+
+var textNode = document.createTextNode('x');
+newText.appendChild(textNode);
+pattern.appendChild(newText);
+</script>
+</body>
+</html>
diff --git a/layout/svg/crashtests/1671950.html b/layout/svg/crashtests/1671950.html new file mode 100644 index 0000000000..173ace4ea9 --- /dev/null +++ b/layout/svg/crashtests/1671950.html @@ -0,0 +1,27 @@ +<html> +<head> + <style> + * { + break-before: always ! important; + } + </style> + <script> + window.addEventListener('load', () => { + const style = document.createElement('style') + document.head.appendChild(style) + const svg_1 = document.getElementById('id_3') + const svg_2 = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + const switch_0 = document.createElementNS('http://www.w3.org/2000/svg', 'switch') + const c_0 = document.createElementNS('http://www.w3.org/2000/svg', 'c') + switch_0.appendChild(c_0) + svg_1.appendChild(switch_0) + svg_2.appendChild(svg_1) + document.documentElement.appendChild(svg_2) + style.sheet.insertRule('@-moz-document url-prefix(){*,a{all:inherit', 0) + SpecialPowers.wrap(window).printPreview()?.close() + }) + </script> + <svg id='id_3'></svg> +</head> +</html> + diff --git a/layout/svg/crashtests/1678947.html b/layout/svg/crashtests/1678947.html new file mode 100644 index 0000000000..96a3a37862 --- /dev/null +++ b/layout/svg/crashtests/1678947.html @@ -0,0 +1,21 @@ +<html> +<head> +<style> +ol { float: right; } +</style> +<script> +function start() { + document.elementFromPoint(0,1); + document.dir = "rtl"; +} +</script> +</head> +<body> +<svg onload="start()" requiredExtensions="x"> + <g id="a"/></g> +<text> +<textPath xlink:href="#a"> +</svg> +<ol></ol> +</body> +</html> diff --git a/layout/svg/crashtests/1693032.html b/layout/svg/crashtests/1693032.html new file mode 100644 index 0000000000..49a2bf3c17 --- /dev/null +++ b/layout/svg/crashtests/1693032.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> + <script> + document.addEventListener('DOMContentLoaded', () => { + const svg_1 = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + const svg_2 = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + const switch_1 = document.createElementNS('http://www.w3.org/2000/svg', 'switch') + const metadata_1 = document.createElementNS('http://www.w3.org/2000/svg', 'metadata') + const foreign_1 = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject') + const text_1 = document.createElementNS('http://www.w3.org/2000/svg', 'text') + switch_1.appendChild(metadata_1) + svg_2.appendChild(text_1) + foreign_1.appendChild(svg_2) + switch_1.appendChild(foreign_1) + svg_1.appendChild(switch_1) + document.documentElement.appendChild(svg_1) + }) + </script> +</head> +</html> diff --git a/layout/svg/crashtests/1696505.html b/layout/svg/crashtests/1696505.html new file mode 100644 index 0000000000..92700496e8 --- /dev/null +++ b/layout/svg/crashtests/1696505.html @@ -0,0 +1,9 @@ +<script> +document.addEventListener("DOMContentLoaded", () => { + document.getElementById('a').style.cssText += "grid-row-end:auto" +}) +</script> +<svg> +<polyline marker-mid='url(#a)'/> +<marker id='a'> +<text/> diff --git a/layout/svg/crashtests/1755770-1.html b/layout/svg/crashtests/1755770-1.html new file mode 100644 index 0000000000..ee9cf6fee7 --- /dev/null +++ b/layout/svg/crashtests/1755770-1.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> + <script> + window.addEventListener("load", () => { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); + text.setAttribute("letter-spacing", "65535pc"); + const node = document.createTextNode("\rï¿»ð¨\nó = ð£%?;ð©á©¿ð�"); + text.appendChild(node); + svg.appendChild(text); + document.documentElement.appendChild(svg); + const rect = new DOMRectReadOnly(-128, 256, 0, 1024); + node.convertRectFromNode(rect, document, {}); + }) + </script> +</head> +</html> diff --git a/layout/svg/crashtests/1755770-2.html b/layout/svg/crashtests/1755770-2.html new file mode 100644 index 0000000000..31b42ec452 --- /dev/null +++ b/layout/svg/crashtests/1755770-2.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> + <script> + window.addEventListener("load", () => { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); + text.setAttribute("letter-spacing", "65535pc"); + text.setAttribute("writing-mode", "vertical-lr"); + const node = document.createTextNode("\rï¿»ð¨\nó = ð£%?;ð©á©¿ð�"); + text.appendChild(node); + svg.appendChild(text); + document.documentElement.appendChild(svg); + const rect = new DOMRectReadOnly(-128, 256, 0, 1024); + node.convertRectFromNode(rect, document, {}); + }) + </script> +</head> +</html> diff --git a/layout/svg/crashtests/1758029-1.html b/layout/svg/crashtests/1758029-1.html new file mode 100644 index 0000000000..2097e875ee --- /dev/null +++ b/layout/svg/crashtests/1758029-1.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<meta charset="utf-8"> +<style> + body { background: gray; } + canvas { border: 2px solid black;} +</style> + +<img id="img" + onload="go()" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg=="> +<canvas id="canvas"></canvas> +<script> + const ctx = canvas.getContext("2d", { desynchronized: true }); + const SVG_FILTER = ` + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <filter id="posterize"> + <feComponentTransfer> + <feFuncR type="discrete" tableValues="0,1" /> + <feFuncG type="discrete" tableValues="0,1" /> + <feFuncB type="discrete" tableValues="0,1" /> + <feFuncA type="discrete" tableValues="0,1" /> + </feComponentTransfer> + </filter> + </svg>`; + + const FILTER1 = `url('data:image/svg+xml;utf8,${SVG_FILTER.replace(/\n/g, "") + .replace(/\s+/g, " ") + .trim()}#posterize') grayscale(50%) brightness(50%)`; + function go() { + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + + ctx.imageSmoothingEnabled = true; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.filter = FILTER1; + ctx.drawImage(img, 0, 0); + setTimeout(() => { document.documentElement.removeAttribute("class")}, 0); + } +</script> diff --git a/layout/svg/crashtests/1764936-1.html b/layout/svg/crashtests/1764936-1.html new file mode 100644 index 0000000000..c03789d547 --- /dev/null +++ b/layout/svg/crashtests/1764936-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <script> + window.addEventListener("load", () => { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") + const image = document.createElementNS("http://www.w3.org/2000/svg", "image") + image.setAttribute("height", "78.04250580135444ch") + image.setAttribute("width", "1024rem") + image.setAttribute("clip-path", "path( evenodd, '\\C' )") + svg.appendChild(image) + document.documentElement.appendChild(svg) + }) + </script> +</head> +</html> diff --git a/layout/svg/crashtests/1804958.html b/layout/svg/crashtests/1804958.html new file mode 100644 index 0000000000..663f621d4c --- /dev/null +++ b/layout/svg/crashtests/1804958.html @@ -0,0 +1,4 @@ +<svg>
+<polygon points="0,2 2,0" marker-end="url(#a)"></polygon>
+<marker id="a" clip-path="">
+<svg style="mix-blend-mode: lighten">
diff --git a/layout/svg/crashtests/1810260.html b/layout/svg/crashtests/1810260.html new file mode 100644 index 0000000000..f555581165 --- /dev/null +++ b/layout/svg/crashtests/1810260.html @@ -0,0 +1,28 @@ +<style> +*:only-child { + display: contents; +} +*:nth-child(1) { + mask: url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=) repeat-x view-box; +} +</style> +<script> +document.addEventListener('DOMContentLoaded', () => { + document.execCommand("selectAll", false) + document.designMode = "on" + document.execCommand("backColor", false, "red") + document.execCommand("justifyFull", false) +}) +</script> +A +<q contenteditable="true"> +<link> +<svg> +<line></line> +<text white-space="pre-line"> +A +</text> +<defs> +</svg> +A + 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.xhtml b/layout/svg/crashtests/314244-1.xhtml new file mode 100644 index 0000000000..14717f146d --- /dev/null +++ b/layout/svg/crashtests/314244-1.xhtml @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" 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..8a10eb34a4 --- /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-box; 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..a5bf25b62a --- /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-box"; + } + + 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/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.xhtml b/layout/svg/crashtests/337408-1.xhtml new file mode 100644 index 0000000000..f56c06ec94 --- /dev/null +++ b/layout/svg/crashtests/337408-1.xhtml @@ -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/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..2c427ccc8b --- /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; column-width: 200px;"> + + <svg xmlns="http://www.w3.org/2000/svg" style="float: left;"></svg></body></html> 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..a34552776a --- /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.columnCount = '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/606914.xhtml b/layout/svg/crashtests/606914.xhtml new file mode 100644 index 0000000000..fc019af573 --- /dev/null +++ b/layout/svg/crashtests/606914.xhtml @@ -0,0 +1 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="display: table; position: absolute; left: 2305843009213694000pc; bottom: 2452284pc; padding: 9931442138140%; border-bottom-right-radius: 1152921504606847000pc;">X</html> 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/743469.svg b/layout/svg/crashtests/743469.svg new file mode 100644 index 0000000000..6affc6fab8 --- /dev/null +++ b/layout/svg/crashtests/743469.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="border-width: 51703084143745256mm; border-left-style: dashed; border-top-left-radius: 3%; border-top-style: dashed; border-right-style: solid; border-image-outset: 10;"> +<script> +document.elementFromPoint(20, 20); +</script> +</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..2a923fcc6d --- /dev/null +++ b/layout/svg/crashtests/767535-1.xhtml @@ -0,0 +1,22 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="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/772313-1.svg b/layout/svg/crashtests/772313-1.svg new file mode 100644 index 0000000000..c77af02b6e --- /dev/null +++ b/layout/svg/crashtests/772313-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" style="background: url(#d); width: 0.1px;"></svg> 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/951904-1.html b/layout/svg/crashtests/951904-1.html new file mode 100644 index 0000000000..7a1d1d4dd4 --- /dev/null +++ b/layout/svg/crashtests/951904-1.html @@ -0,0 +1,43 @@ +<body onload="go()"> +<svg> + <switch> + <text id="a">Bonjour</text> + <text id="b">Hello</text> + <a><text id="c">Hello</text></a> + <g> + <mask> + <text>Lundi</text> + </mask> + </g> + <switch> + <text id="d">Au revoir</text> + <g> + <mask> + <text>Mercredi</text> + </mask> + </g> + <text id="e">Goodbye</text> + <a><text id="f">Goodbye</text></a> + </switch> + </switch> +</svg> +<svg> + <switch> + <mask> + <text id="g">Vendredi</text> + </mask> + <a></a> + </switch> +</svg> +</body> +<script> +function go() { + a.getComputedTextLength(); + b.getComputedTextLength(); + c.getComputedTextLength(); + d.getComputedTextLength(); + e.getComputedTextLength(); + f.getComputedTextLength(); + g.getComputedTextLength(); +} +</script> 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/blob-merging-and-retained-display-list.html b/layout/svg/crashtests/blob-merging-and-retained-display-list.html new file mode 100644 index 0000000000..56ca743dc9 --- /dev/null +++ b/layout/svg/crashtests/blob-merging-and-retained-display-list.html @@ -0,0 +1,62 @@ +<!doctype html> +<html class="reftest-wait"> +<script> + var r = 40; + var xmlns = "http://www.w3.org/2000/svg"; + const raf = f => requestAnimationFrame(f); + function rect(x, y) { + var r = document.createElementNS (xmlns, "rect"); + r.setAttribute("x", x); + r.setAttribute("y", y); + r.setAttribute("width", "100"); + r.setAttribute("height", "100"); + r.setAttribute("fill", "blue"); + return r; + } + function f1() { + svg = document.getElementById("cnvs"); + svg.appendChild(rect(0, 0)); + svg.appendChild(rect(600, 0)); + svg.appendChild(rect(600, 400)); + svg.appendChild(rect(0, 400)); + let a = rect(110, 110); + let b = rect(120, 120); + let c = rect(130, 130); + let d = rect(140, 140); + let a2 = rect(310, 140); + let b2 = rect(320, 130); + let c2 = rect(330, 120); + let d2 = rect(340, 110); + raf(() => { + svg.appendChild(a); + svg.appendChild(b); + svg.appendChild(c); + svg.appendChild(d); + raf(() => { + // the display list partial update will end up with these items before x,y,w,z + svg.appendChild(d2); + svg.appendChild(c2); + svg.appendChild(b2); + svg.appendChild(a2); + raf(() => { + // this forces all the items to be ordered and makes the new display list + // contain reorded items outside of the invalid area + let mix = rect(220, 220); + svg.insertBefore(mix, d2); + raf(() => { document.documentElement.className = "" }); + }) + }) + }) + } + + function f() { + requestAnimationFrame(f1); + } + + onload = f; +</script> + +<body> +<svg width="700" height="600" id=cnvs> + +</svg> diff --git a/layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml b/layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml new file mode 100644 index 0000000000..b23f064a6c --- /dev/null +++ b/layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml @@ -0,0 +1,28 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> +<head> + <title>Test of NS_FRAME_IS_NONDISPLAY / NS_FRAME_IS_DIRTY assertion</title> + <!-- reduced from layout/reftests/svg/svg-integration/conditions-outer-svg-01.xhtml + and modified to occur without bug 1308876 patches. --> + <style> + div { position: relative; height: 100px; width: 100px } + svg { position: absolute; top: 0; left: 0; height: 100%; width: 100% } + </style> + <script> + function run() { + var d = document.getElementById("d"); + d.offsetHeight; + d.style.height = "50px"; + } + </script> +</head> +<body onload="run()"> + <div id="d"> + <svg:svg systemLanguage="x"></svg:svg> + </div> +</body> +</html> diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list new file mode 100644 index 0000000000..4b898383cf --- /dev/null +++ b/layout/svg/crashtests/crashtests.list @@ -0,0 +1,256 @@ +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 chrome://reftest/content/crashtests/layout/svg/crashtests/314244-1.xhtml +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 327711-1.svg +load 328137-1.svg +load 329848-1.svg +load chrome://reftest/content/crashtests/layout/svg/crashtests/337408-1.xhtml +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 386690-1.svg +load 387290-1.svg +load 402408-1.svg +load 404677-1.xhtml +load 409565-1.xhtml +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 +asserts(2) load 606914.xhtml # bug 606914, bug 718883 +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 743469.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 772313-1.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 951904-1.html +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 1234726-1.svg +load 1322537-1.html +load 1322537-2.html +load 1322852.html +load 1348564.svg +load 1402109.html +load 1402124.html +load 1402486.html +load 1403656-1.html +load 1403656-2.html +load 1403656-3.html +load 1403656-4.html +load 1403656-5.html +load 1404086.html +load 1421807-1.html +load 1421807-2.html +load 1422226.html +load 1425434-1.html +load 1443092.html +load 1454201-1.html +load 1467552-1.html +load 1474982.html +load conditional-outer-svg-nondirty-reflow-assert.xhtml +load extref-test-1.xhtml +load blob-merging-and-retained-display-list.html +load empty-blob-merging.html +load grouping-empty-bounds.html +load 1480275.html +load 1480224.html +load 1502936.html +load 1504918.svg +load perspective-invalidation.html +load invalid_url.html +load 1535517-1.svg +load 1504072.html +load 1072758.html +load 1536892.html +load 1539318-1.svg +load 1548985-1.html +load 1548985-2.svg +load 1555851.html +load invalidation-of-opacity-0.html +load 1563779.html +load 1600855.html +load 1601824.html +load 1605223-1.html +load 1609663.html +skip-if(Android) load 1671950.html # No print-preview support on android +load 1678947.html +load 1693032.html +load 1696505.html +load 1758029-1.html +HTTP load 1755770-1.html +HTTP load 1755770-2.html +load 1764936-1.html +load 1804958.html +load 1810260.html diff --git a/layout/svg/crashtests/empty-blob-merging.html b/layout/svg/crashtests/empty-blob-merging.html new file mode 100644 index 0000000000..4b603c19d9 --- /dev/null +++ b/layout/svg/crashtests/empty-blob-merging.html @@ -0,0 +1,48 @@ +<html class="reftest-wait"> +<style> +@keyframes spinnow { + 100% { + transform: rotate(360deg) scale(.2, .2); + } +} + +rect { + transform: rotate(0deg) scale(0.6, 1); + transform-origin: center; + animation: 5s spinnow infinite linear; +} + +</style> +<svg width=400 height=400> + <!-- + onwheel is needed so that we get a hit test info display item + before the transform on the rect + --> + <g onwheel="alert(1)"> + <g id="gr"> + <circle r=30 fill=yellow cx=300 cy=100 /> + <circle r=30 fill=yellow cx=10 cy=100 /> + <circle r=30 fill=yellow cx=300 cy=300 /> + <circle r=30 fill=yellow cx=10 cy=300 /> + </g> + <rect width=100 height=100 fill=blue x=100 y=100 /> + <g opacity=0.5> + <circle r=30 fill=pink cx=300 cy=100 /> + <circle r=30 fill=pink cx=10 cy=100 /> + <circle r=30 fill=pink cx=300 cy=300 /> + <circle r=30 fill=pink cx=10 cy=300 /> + </g> + </g> +</svg> +<script> + function blam() { + let gr = document.getElementById("gr"); + gr.remove(); + document.documentElement.removeAttribute("class"); + } +document.addEventListener("MozReftestInvalidate", function() { + requestAnimationFrame(function() { + blam(); + }); +}); +</script> 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/crashtests/grouping-empty-bounds.html b/layout/svg/crashtests/grouping-empty-bounds.html new file mode 100644 index 0000000000..c9f688d0f0 --- /dev/null +++ b/layout/svg/crashtests/grouping-empty-bounds.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en" class='reftest-wait'> +<meta charset="utf-8"> +<title>This testcase might create a non-empty display list with an empty set of drawing commands / items in the EventRecorder</title> + +<style> + +body { + margin: 0; +} + +.animated-opacity { + animation: opacity-animation 1s linear alternate infinite; +} + +@keyframes opacity-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +</style> + +<svg style="width: 100px; height: 100px;"> + <rect class="animated-opacity" x="0" y="0" width="100" height="100"/> + <rect x="0" y="0" width="10" height="10" id="toremove"/> + <g transform="translate(10 10)"><rect x="120" y="0" width="1" height="1"/></g> +</svg> + +<script> + +window.addEventListener("MozReftestInvalidate", () => { + var elem = document.getElementById("toremove"); + elem.parentNode.removeChild(elem); + document.documentElement.removeAttribute('class'); +}); + +</script> diff --git a/layout/svg/crashtests/invalid_url.html b/layout/svg/crashtests/invalid_url.html new file mode 100644 index 0000000000..ac8671b1ce --- /dev/null +++ b/layout/svg/crashtests/invalid_url.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head></head> +<body> + <!-- + If invalid url isn't correctly handled, this test will crash when + gfx.webrender.enabled=true and gfx.webrender.all=true + --> + <div style="padding: 1px; filter:url('data://not-valid-data');"></div> +</body> +</html> diff --git a/layout/svg/crashtests/invalidation-of-opacity-0.html b/layout/svg/crashtests/invalidation-of-opacity-0.html new file mode 100644 index 0000000000..50700e3908 --- /dev/null +++ b/layout/svg/crashtests/invalidation-of-opacity-0.html @@ -0,0 +1,26 @@ +<html class="reftest-wait"> + <script> + var i = 0; + var opac = [0.3, 0.2, 0, 0.3]; + + function f() { + document.getElementById("rim").setAttribute("opacity", opac[i]); + document.getElementById("circ").setAttribute("r", i + 10); + i++; + if (i > opac.length) { + document.documentElement.className = "" + } else { + requestAnimationFrame(f); + } + } + onload = () => requestAnimationFrame(f); +</script> + +<body> + <svg height="1000" width="1000"> + <circle cx="50" cy="50" r="40" fill="red" /> + <g id=rim clip-path="url(#myClip)" opacity=0> + <circle id="circ" cx="150" cy="150" r="40" fill="red" /> + </g> + <circle cx="250" cy="250" r="40" fill="red" /> + </svg> diff --git a/layout/svg/crashtests/masked-3d-transform.html b/layout/svg/crashtests/masked-3d-transform.html new file mode 100644 index 0000000000..6c70aae7fe --- /dev/null +++ b/layout/svg/crashtests/masked-3d-transform.html @@ -0,0 +1,20 @@ +<style> + svg, +svg * { + perspective: 1000px; +} +</style> +<svg style="max-width: 176px;" viewBox="0 0 279 169"> + <g transform="translate(-2.000000, -2.000000)"> + <g mask="url(#mask-2)"> + <g transform="translate(19.000000, 22.000000)"> + <g fill="#FFFFFF"> + <path d="M43,2.3 C41,2.3 39.9,3.3 39.9,5.6 L39.9,12.4 C39.9,14.7 41,15.7 43.1,15.7 C45.7,15.7 45.9,14.1 46.1,13 C46.1,12.7 46.4,12.4 46.8,12.4 C47.3,12.4 47.5,12.6 47.5,13.3 C47.5,15.3 45.8,17 42.9,17 C40.4,17 38.4,15.7 38.4,12.3 L38.4,5.5 C38.4,2.1 40.5,0.9 43,0.9 C45.9,0.9 47.5,2.6 47.5,4.5 C47.5,5.2 47.3,5.4 46.8,5.4 C46.3,5.4 46.1,5.2 46.1,4.9 C46.1,4 45.6,2.3 43,2.3 L43,2.3 Z"></path> + </g> + </g> + <ellipse fill="#000000" cx="157.9" cy="162.1" rx="157.9" ry="4.3"></ellipse> + </g> + </g> +</svg> + + diff --git a/layout/svg/crashtests/perspective-invalidation.html b/layout/svg/crashtests/perspective-invalidation.html new file mode 100644 index 0000000000..179836d500 --- /dev/null +++ b/layout/svg/crashtests/perspective-invalidation.html @@ -0,0 +1,9 @@ +<span> +<svg id="a" transform="skewX(0)" opacity="0"> +<text> +</span> +<marquee> +A +</marquee> +<svg xml:space="preserve"> +<use style="outline: auto; -webkit-perspective: 1px" xlink:href="#a" mask="url(#x)"> diff --git a/layout/svg/moz.build b/layout/svg/moz.build new file mode 100644 index 0000000000..2543609cfe --- /dev/null +++ b/layout/svg/moz.build @@ -0,0 +1,96 @@ +# -*- 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") + +if CONFIG["ENABLE_TESTS"]: + MOCHITEST_MANIFESTS += [ + "tests/mochitest.ini", + ] + MOCHITEST_CHROME_MANIFESTS += [ + "tests/chrome.ini", + ] + +EXPORTS.mozilla += [ + "CSSClipPathInstance.h", + "FilterInstance.h", + "ISVGDisplayableFrame.h", + "ISVGSVGFrame.h", + "SVGClipPathFrame.h", + "SVGContainerFrame.h", + "SVGContextPaint.h", + "SVGFilterInstance.h", + "SVGForeignObjectFrame.h", + "SVGGeometryFrame.h", + "SVGGradientFrame.h", + "SVGImageContext.h", + "SVGImageFrame.h", + "SVGIntegrationUtils.h", + "SVGMaskFrame.h", + "SVGObserverUtils.h", + "SVGOuterSVGFrame.h", + "SVGPaintServerFrame.h", + "SVGTextFrame.h", + "SVGUseFrame.h", + "SVGUtils.h", +] + +UNIFIED_SOURCES += [ + "CSSClipPathInstance.cpp", + "CSSFilterInstance.cpp", + "FilterInstance.cpp", + "SVGAFrame.cpp", + "SVGClipPathFrame.cpp", + "SVGContainerFrame.cpp", + "SVGContextPaint.cpp", + "SVGFEContainerFrame.cpp", + "SVGFEImageFrame.cpp", + "SVGFELeafFrame.cpp", + "SVGFEUnstyledLeafFrame.cpp", + "SVGFilterFrame.cpp", + "SVGFilterInstance.cpp", + "SVGForeignObjectFrame.cpp", + "SVGGeometryFrame.cpp", + "SVGGFrame.cpp", + "SVGGradientFrame.cpp", + "SVGImageContext.cpp", + "SVGImageFrame.cpp", + "SVGInnerSVGFrame.cpp", + "SVGIntegrationUtils.cpp", + "SVGMarkerFrame.cpp", + "SVGMaskFrame.cpp", + "SVGObserverUtils.cpp", + "SVGOuterSVGFrame.cpp", + "SVGPaintServerFrame.cpp", + "SVGPatternFrame.cpp", + "SVGStopFrame.cpp", + "SVGSwitchFrame.cpp", + "SVGSymbolFrame.cpp", + "SVGTextFrame.cpp", + "SVGUseFrame.cpp", + "SVGUtils.cpp", + "SVGViewFrame.cpp", + "SVGViewportFrame.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "../../widget", + "../base", + "../generic", + "../painting", + "../style", + "../xul", + "/dom/base", + "/dom/svg", +] + +RESOURCE_FILES += [ + "svg.css", +] diff --git a/layout/svg/svg.css b/layout/svg/svg.css new file mode 100644 index 0000000000..af3a55343b --- /dev/null +++ b/layout/svg/svg.css @@ -0,0 +1,104 @@ +/* -*- 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 { + display: none; +} + +/* + * This is only to be overridden by the rule right below. + * + * NOTE(emilio): NodeCouldBeRendered in SVGUseElement.cpp relies on this. + */ +symbol { + display: none !important; +} + +/* + * From https://svgwg.org/svg2-draft/struct.html#SymbolNotes: + * + * > The generated instance of a 'symbol' that is the direct referenced element + * > of a 'use' element must always have a computed value of inline for the + * > display property. In other words, it must be rendered whenever the host + * > 'use' element is rendered. + * + * NOTE(emilio): other browsers instead just replace the `<symbol>` element by + * an `<svg>` element while cloning, but they don't implement the SVG2 + * selector-matching rules that would make that observable via selectors. + */ +symbol:-moz-use-shadow-tree-root { + display: inline !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 { + appearance: none ! important; + margin: 0 ! important; + padding: 0 ! important; + border-width: 0 ! important; + white-space: normal; +} + +@media all and (-moz-is-resource-document) { + foreignObject *|* { + 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; +} + +/* Make SVG shapes unselectable to avoid triggering AccessibleCaret on tap. + <mesh> will be supported in bug 1238882. */ +circle, ellipse, line, mesh, path, polygon, polyline, rect { + user-select: none; +} diff --git a/layout/svg/tests/chrome.ini b/layout/svg/tests/chrome.ini new file mode 100644 index 0000000000..cc1b542a66 --- /dev/null +++ b/layout/svg/tests/chrome.ini @@ -0,0 +1,8 @@ +[test_disabled_chrome.html] +support-files = + svg_example_test.html + svg_example_script.svg + +[test_context_properties_allowed_domains.html] +support-files = + file_context_fill_fallback_red.html diff --git a/layout/svg/tests/file_black_yellow.svg b/layout/svg/tests/file_black_yellow.svg new file mode 100644 index 0000000000..58c5689838 --- /dev/null +++ b/layout/svg/tests/file_black_yellow.svg @@ -0,0 +1,9 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <rect height="100%" width="100%" fill="yellow" /> + <rect height="50px" width="100px" fill="black" /> +</svg> diff --git a/layout/svg/tests/file_context_fill_fallback_red.html b/layout/svg/tests/file_context_fill_fallback_red.html new file mode 100644 index 0000000000..ac8b5b4203 --- /dev/null +++ b/layout/svg/tests/file_context_fill_fallback_red.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<style> + img { + -moz-context-properties: fill; + fill: green; + } +</style> +<img style="width: 100px; height: 100px;"> +<script> + let domain = location.search.substring(1); + let img = document.querySelector("img"); + img.src = img.alt = `http://${domain}/tests/layout/svg/tests/file_context_fill_fallback_red.svg`; +</script> diff --git a/layout/svg/tests/file_context_fill_fallback_red.svg b/layout/svg/tests/file_context_fill_fallback_red.svg new file mode 100644 index 0000000000..9088555b53 --- /dev/null +++ b/layout/svg/tests/file_context_fill_fallback_red.svg @@ -0,0 +1,8 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <rect height="100%" width="100%" fill="context-fill red" /> +</svg> diff --git a/layout/svg/tests/file_disabled_iframe.html b/layout/svg/tests/file_disabled_iframe.html new file mode 100644 index 0000000000..55eda75fde --- /dev/null +++ b/layout/svg/tests/file_disabled_iframe.html @@ -0,0 +1,81 @@ +<!doctype html> +<script> + window.is = window.parent.is; + window.SimpleTest = window.parent.SimpleTest; +</script> +<div id="testnodes"><span>hi</span> there <!-- mon ami --></div> +<script> + let t = document.getElementById('testnodes'); + t.innerHTML = null; + t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg")); + t.firstChild.textContent = "<foo>"; + is(t.innerHTML, "<svg:svg><foo></svg:svg>"); + + // This test crashes if the style tags are not handled correctly + t.innerHTML = `<svg version="1.1"> + <style> + circle { + fill: currentColor; + } + </style> + <g><circle cx="25.8" cy="9.3" r="1.5"/></g> + </svg> + `; + is(t.firstChild.tagName.toLowerCase(), 'svg'); + + // This test crashes if the script tags are not handled correctly + t.innerHTML = `<svg version="1.1"> + <scri` + `pt> + throw "badment, should never fire."; + </scri` + `pt> + <g><circle cx="25.8" cy="9.3" r="1.5"/></g> + </svg>`; + is(t.firstChild.tagName.toLowerCase(), 'svg'); + + t.innerHTML = null; + t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg"); + t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script")); + is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg"); + t.firstChild.firstChild.textContent = "1&2<3>4\xA0"; + is(t.innerHTML, '<svg><script>1&2<3>4 \u003C/script></svg>'); + + t.innerHTML = null; + t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg"); + t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "style")); + is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg"); + t.firstChild.firstChild.textContent = "1&2<3>4\xA0"; + is(t.innerHTML, '<svg><style>1&2<3>4 \u003C/style></svg>'); + + // + // Tests for Bug 1673237 + // + + // This test fails if about:blank renders SVGs + t.innerHTML = null; + var iframe = document.createElement("iframe"); + iframe.setAttribute("src", "about:blank") + t.appendChild(iframe); + iframe.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg")); + iframe.firstChild.textContent = "<foo>"; + is(iframe.innerHTML, "<svg:svg><foo></svg:svg>"); + + // This test fails if about:blank renders SVGs + var win = window.open("about:blank"); + win.document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg")) + win.document.body.firstChild.textContent = "<foo>"; + is(win.document.body.innerHTML, "<svg:svg><foo></svg:svg>"); + win.close(); + + // This test fails if about:srcdoc renders SVGs + t.innerHTML = null; + iframe = document.createElement("iframe"); + iframe.srcdoc = "<svg:svg></svg:svg>"; + iframe.onload = function() { + iframe.contentDocument.body.firstChild.textContent = "<foo>"; + is(iframe.contentDocument.body.innerHTML, "<svg:svg><foo></svg:svg>"); + SimpleTest.finish(); + } + t.appendChild(iframe); +</script> diff --git a/layout/svg/tests/file_embed_sizing_both.svg b/layout/svg/tests/file_embed_sizing_both.svg new file mode 100644 index 0000000000..8bc39b7d02 --- /dev/null +++ b/layout/svg/tests/file_embed_sizing_both.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="200" height="400" viewBox="0 0 1 5"></svg> diff --git a/layout/svg/tests/file_embed_sizing_none.svg b/layout/svg/tests/file_embed_sizing_none.svg new file mode 100644 index 0000000000..714efc7ef0 --- /dev/null +++ b/layout/svg/tests/file_embed_sizing_none.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"></svg> diff --git a/layout/svg/tests/file_embed_sizing_ratio.svg b/layout/svg/tests/file_embed_sizing_ratio.svg new file mode 100644 index 0000000000..b590bb7da6 --- /dev/null +++ b/layout/svg/tests/file_embed_sizing_ratio.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 5"></svg> diff --git a/layout/svg/tests/file_embed_sizing_size.svg b/layout/svg/tests/file_embed_sizing_size.svg new file mode 100644 index 0000000000..76f50b5f08 --- /dev/null +++ b/layout/svg/tests/file_embed_sizing_size.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="200" height="900"></svg> diff --git a/layout/svg/tests/file_filter_crossorigin.svg b/layout/svg/tests/file_filter_crossorigin.svg new file mode 100644 index 0000000000..a1b9ad0cab --- /dev/null +++ b/layout/svg/tests/file_filter_crossorigin.svg @@ -0,0 +1,25 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" + xmlns:xlink="http://www.w3.org/1999/xlink"> + + <!-- We include these <use> elements simply to be sure the SVG resource URLs + get a chance to block 'onload', if they can be loaded. --> + <use xlink:href="http://mochi.test:8888/tests/layout/svg/tests/filters.svg#empty" /> + <use xlink:href="http://example.org/tests/layout/svg/tests/filters.svg#empty" /> + + <!-- giant yellow rect in the background, just so you can visually tell + that this SVG file has loaded/rendered. --> + <rect height="100%" width="100%" fill="yellow" /> + + <!-- For both rects below: if it's black, its filter resolved successfully. + If it's yellow, it means we failed to load the resource + (e.g. because it was blocked as a cross-origin resource). --> + <rect height="50px" width="100px" fill="yellow" + filter="url(http://mochi.test:8888/tests/layout/svg/tests/filters.svg#NonWhiteToBlack)"/> + <rect y="50px" + height="50px" width="100px" fill="yellow" + filter="url(http://example.org/tests/layout/svg/tests/filters.svg#NonWhiteToBlack)"/> +</svg> diff --git a/layout/svg/tests/file_yellow_black.svg b/layout/svg/tests/file_yellow_black.svg new file mode 100644 index 0000000000..77c14c9af8 --- /dev/null +++ b/layout/svg/tests/file_yellow_black.svg @@ -0,0 +1,9 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <rect height="100%" width="100%" fill="yellow" /> + <rect y="50px" height="50px" width="100px" fill="black" /> +</svg> diff --git a/layout/svg/tests/filters.svg b/layout/svg/tests/filters.svg new file mode 100644 index 0000000000..213df7fc93 --- /dev/null +++ b/layout/svg/tests/filters.svg @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1"> + <defs> + + <!-- so that other documents can svg:use this one and force it to + load before onload --> + <g id="empty" /> + + <!-- Keep all white pixels white, and change any others to black. --> + <!-- NOTE: alpha is preserved, so it will not adjust alpha edges --> + <filter id="NonWhiteToBlack" x="0%" y="0%" width="100%" height="100%"> + <feComponentTransfer> + <feFuncR type="linear" slope="-1" intercept="1" /> + <feFuncG type="linear" slope="-1" intercept="1" /> + <feFuncB type="linear" slope="-1" intercept="1" /> + </feComponentTransfer> + <feColorMatrix type="matrix" values="255 255 255 0 0 + 255 255 255 0 0 + 255 255 255 0 0 + 0 0 0 1 0" /> + <feComponentTransfer> + <feFuncR type="linear" slope="-1" intercept="1" /> + <feFuncG type="linear" slope="-1" intercept="1" /> + <feFuncB type="linear" slope="-1" intercept="1" /> + </feComponentTransfer> + </filter> + </defs> +</svg> diff --git a/layout/svg/tests/mochitest.ini b/layout/svg/tests/mochitest.ini new file mode 100644 index 0000000000..8b1cc1eadd --- /dev/null +++ b/layout/svg/tests/mochitest.ini @@ -0,0 +1,28 @@ +[DEFAULT] +support-files = + file_disabled_iframe.html + file_context_fill_fallback_red.svg + +[test_disabled.html] +[test_embed_sizing.html] +support-files = + file_embed_sizing_none.svg + file_embed_sizing_size.svg + file_embed_sizing_ratio.svg + file_embed_sizing_both.svg + +[test_filter_crossorigin.html] +support-files = + filters.svg + file_filter_crossorigin.svg + file_black_yellow.svg + file_yellow_black.svg +# Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default" +skip-if = + xorigin + os == "linux" && bits == 64 #Bug 1642198 + win10_2004 && !debug # Bug 1642198 +[test_hover_near_text.html] +[test_multiple_font_size.html] +[test_use_tree_cycle.html] +[test_bug1544209.html] diff --git a/layout/svg/tests/svg_example_script.svg b/layout/svg/tests/svg_example_script.svg new file mode 100644 index 0000000000..5eab758f92 --- /dev/null +++ b/layout/svg/tests/svg_example_script.svg @@ -0,0 +1,7 @@ +<svg version="1.1"> + <script> + document.documentElement.style.backgroundColor = 'rebeccapurple'; + throw "badment, should never fire."; + </script> + <g><circle cx="25.8" cy="9.3" r="1.5"/></g> +</svg> diff --git a/layout/svg/tests/svg_example_test.html b/layout/svg/tests/svg_example_test.html new file mode 100644 index 0000000000..45c31c98b4 --- /dev/null +++ b/layout/svg/tests/svg_example_test.html @@ -0,0 +1,7 @@ +<svg id="layout" viewBox="0 0 120 120" version="1.1" + xmlns="http://www.w3.org/2000/svg"> + <circle cx="60" cy="60" r="50"/> +</svg> + +<svg id="svgel"> +</svg> diff --git a/layout/svg/tests/test_bug1544209.html b/layout/svg/tests/test_bug1544209.html new file mode 100644 index 0000000000..b2226b3ea9 --- /dev/null +++ b/layout/svg/tests/test_bug1544209.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1544209">Mozilla Bug 1544209</a> + +<svg height="800px" width="800px"> + <text transform="scale(3,3)" id="a" x="20px" y="20px">ABC<tspan id="b">ABC</tspan></text> +</svg> + +<script type="application/javascript"> + let a = document.getElementById("a"), + b = document.getElementById("b"); + + let wtext = a.getBoundingClientRect().width, + wtspan = b.getBoundingClientRect().width; + + ok(wtext >= wtspan, "<tspan> should not be wider than <text>"); + isfuzzy((wtext - wtspan) / wtext, 0.5, 0.1, "<tspan> should be approximately half of the <text> width"); +</script> diff --git a/layout/svg/tests/test_context_properties_allowed_domains.html b/layout/svg/tests/test_context_properties_allowed_domains.html new file mode 100644 index 0000000000..26f38a8770 --- /dev/null +++ b/layout/svg/tests/test_context_properties_allowed_domains.html @@ -0,0 +1,95 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Bug 1699892 - SVG context properties for allowed domains</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> +<script> + /** + * Returns a Promise that resolves when target fires a load event. + */ + function waitForLoad(target) { + return new Promise(resolve => { + target.addEventListener("load", () => { + if (event.target == target) { + resolve(); + }}, { once: true }); + }); + } + + function makeContextFillFrame(source) { + return ` + <style> + img { + -moz-context-properties: fill; + fill: green; + } + </style> + <img src="${source}" style="width: 100px; height: 100px;"> + `; + } + + /** + * Creates an iframe, loads src in it, and waits for the load event + * for the iframe to fire. Then it snapshots the iframe and returns + * the snapshot (and removes the iframe from the document, to clean up). + * + * src can be a URL starting with http, or is otherwise assumed to be + * a srcdoc string. + */ + async function loadSrcImageAndSnapshot({ src, srcdoc }) { + let frame = document.createElement("iframe"); + document.body.appendChild(frame); + + if (src) { + frame.src = src; + } else { + frame.srcdoc = srcdoc; + } + + await waitForLoad(frame); + + let snapshot = await snapshotWindow(frame, false); + document.body.removeChild(frame); + return snapshot; + } + + add_task(async () => { + const ALLOWED_DOMAIN = "example.org"; + const DISALLOWED_DOMAIN = "example.com"; + + await SpecialPowers.pushPrefEnv({ + set: [["svg.context-properties.content.allowed-domains", ALLOWED_DOMAIN]] + }); + + // When the context properties are allowed, we expect a green + // square. When they are not allowed, we expected a red square. + + let redReference = await loadSrcImageAndSnapshot({ + srcdoc: `<div style="width: 100px; height: 100px; background: red"></div>`, + }); + + let greenReference = await loadSrcImageAndSnapshot({ + srcdoc: `<div style="width: 100px; height: 100px; background: green"></div>`, + }); + + let allowedSnapshot = await loadSrcImageAndSnapshot({ + src: `file_context_fill_fallback_red.html?${ALLOWED_DOMAIN}` + }); + + let disallowedSnapshot = await loadSrcImageAndSnapshot({ + src: `file_context_fill_fallback_red.html?${DISALLOWED_DOMAIN}` + }); + + const kNoFuzz = null; + info("Reference snapshots should look different from each other"); + assertSnapshots(redReference, greenReference, false, kNoFuzz, "red", "green"); + + info("Allowed domain should be green"); + assertSnapshots(allowedSnapshot, greenReference, true, kNoFuzz, ALLOWED_DOMAIN, "green"); + + info("Disallowed domain should be red"); + assertSnapshots(disallowedSnapshot, redReference, true, kNoFuzz, DISALLOWED_DOMAIN, "red"); + }); +</script> +<body> +</body> diff --git a/layout/svg/tests/test_disabled.html b/layout/svg/tests/test_disabled.html new file mode 100644 index 0000000000..acf52e73a2 --- /dev/null +++ b/layout/svg/tests/test_disabled.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<!-- +Copied from https://bugzilla.mozilla.org/show_bug.cgi?id=744830 +--> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a> +<iframe></iframe> +<script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [["svg.disabled", true]]}, function() { + document.querySelector('iframe').src = "file_disabled_iframe.html"; + }); +</script> diff --git a/layout/svg/tests/test_disabled_chrome.html b/layout/svg/tests/test_disabled_chrome.html new file mode 100644 index 0000000000..e7564f17f2 --- /dev/null +++ b/layout/svg/tests/test_disabled_chrome.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=744830 +--> +<head> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a> +<div id="testnodes"><span>hi</span> there <!-- mon ami --></div> +<pre id="test"> +<script type="application/javascript"> + add_task(async function() { + const initialPrefValue = SpecialPowers.getBoolPref("svg.disabled"); + SpecialPowers.setBoolPref("svg.disabled", true); + const Cu = SpecialPowers.Components.utils; + const { ContentTaskUtils } = ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm"); + let t = document.getElementById('testnodes'); + + let url = 'chrome://mochitests/content/chrome/layout/svg/tests/svg_example_test.html' + const chromeIframeEl = document.createElement('iframe'); + let chromeLoadPromise = ContentTaskUtils.waitForEvent(chromeIframeEl, 'load', false); + chromeIframeEl.src = url; + t.appendChild(chromeIframeEl); + + await chromeLoadPromise; + const chromeBR = chromeIframeEl.contentDocument.body.getBoundingClientRect(); + + url = "http://mochi.test:8888/chrome/layout/svg/tests/svg_example_test.html"; + const iframeEl = document.createElement('iframe'); + iframeEl.src = url; + let loadPromise = ContentTaskUtils.waitForEvent(iframeEl, 'load', false); + t.appendChild(iframeEl); + await loadPromise; + + const contentBR = iframeEl.contentDocument.body.getBoundingClientRect(); + ok(chromeBR.height > contentBR.height, "Chrome content height should be bigger than content due to layout"); + + url = "http://mochi.test:8888/chrome/layout/svg/tests/svg_example_script.svg"; + const iframeElScript = document.createElement("iframe"); + let loadPromiseScript = ContentTaskUtils.waitForEvent(iframeElScript, "load", false); + iframeElScript.src = url; + t.appendChild(iframeElScript); + await loadPromiseScript; + ok(!iframeElScript.contentDocument.documentElement.style, "Content should not be styled"); + + SpecialPowers.setBoolPref("svg.disabled", initialPrefValue); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/svg/tests/test_embed_sizing.html b/layout/svg/tests/test_embed_sizing.html new file mode 100644 index 0000000000..9afbc7ca7d --- /dev/null +++ b/layout/svg/tests/test_embed_sizing.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> + +<p>Test intrinsic sizing of embed elements referencing SVG documents, both same +origin and cross origin.</p> + +<div id="container" style="width: 500px;"></div> + +<script> +const TESTS = [ + { outer: "none", inner: "none", expected: "300x150" }, + { outer: "none", inner: "size", expected: "200x900" }, + { outer: "none", inner: "ratio", expected: "500x2500" }, + { outer: "none", inner: "both", expected: "200x400" }, + { outer: "size", inner: "none", expected: "100x150" }, + { outer: "size", inner: "size", expected: "100x450" }, + { outer: "size", inner: "ratio", expected: "100x500" }, + { outer: "size", inner: "both", expected: "100x200" }, + { outer: "ratio", inner: "none", expected: "500x1500" }, + { outer: "ratio", inner: "size", expected: "200x900" }, + { outer: "ratio", inner: "ratio", expected: "500x1500" }, + { outer: "ratio", inner: "both", expected: "200x400" }, + { outer: "both", inner: "none", expected: "100x300" }, + { outer: "both", inner: "size", expected: "100x300" }, + { outer: "both", inner: "ratio", expected: "100x300" }, + { outer: "both", inner: "both", expected: "100x300" }, +]; + +add_task(async function() { + for (let test of TESTS) { + for (let crossorigin of [false, true]) { + let host = crossorigin ? "http://example.org" : "http://mochi.test:8888"; + let e = document.createElement("embed"); + + switch (test.outer) { + case "none": + break; + case "size": + e.style.width = "100px"; + break; + case "ratio": + e.style.aspectRatio = "1 / 3"; + break; + case "both": + e.style.width = "100px"; + e.style.aspectRatio = "1 / 3"; + break; + default: + throw new Error("unexpected subtest"); + } + + await new Promise(function(resolve) { + e.src = host + location.pathname.replace("test_embed_sizing.html", `file_embed_sizing_${test.inner}.svg`); + e.onload = resolve; + container.append(e); + }); + + let desc = `Subtest (${test.outer}/${test.inner}/${crossorigin ? 'cross' : 'same'} origin)`; + is(`${e.offsetWidth}x${e.offsetHeight}`, test.expected, desc); + e.remove(); + } + } +}); +</script> diff --git a/layout/svg/tests/test_filter_crossorigin.html b/layout/svg/tests/test_filter_crossorigin.html new file mode 100644 index 0000000000..e31c13bee1 --- /dev/null +++ b/layout/svg/tests/test_filter_crossorigin.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=695385 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 695385</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=695385">Mozilla Bug 695385</a> +<br> +<!-- These iframes' renderings are expected to match: --> +<iframe src="http://mochi.test:8888/tests/layout/svg/tests/file_filter_crossorigin.svg"></iframe> +<iframe src="file_black_yellow.svg"></iframe> +<br> +<!-- These iframes' renderings are expected to match: --> +<iframe src="http://example.org/tests/layout/svg/tests/file_filter_crossorigin.svg"></iframe> +<iframe src="file_yellow_black.svg"></iframe> + +<pre id="test"> +<script type="application/javascript"> + +function promiseExecuteSoon() { + return new Promise(SimpleTest.executeSoon); +} + +// Main Function +async function run() { + SimpleTest.waitForExplicitFinish(); + + // XXXdholbert Wait a few ticks, to give the iframes a little more + // opportunity to load their external resources before we call + // snapshotWindow. This is an attempt at a workaround/diagnostic for + // https://bugzilla.mozilla.org/show_bug.cgi?id=1706542 + for (let i = 0; i < 60; i++) { + await promiseExecuteSoon(); + } + + let snapshots = new Array(4); + for (let i = 0; i < snapshots.length; i++) { + snapshots[i] = await snapshotWindow(frames[i], false); + } + + // Compare mochi.test iframe against its reference: + assertSnapshots(snapshots[0], snapshots[1], true, null, + "Testcase loaded from mochi.test", "Reference: black/yellow"); + + // Compare example.org iframe against its reference: + assertSnapshots(snapshots[2], snapshots[3], true, null, + "Testcase loaded from example.org", "Reference: yellow/black"); + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/layout/svg/tests/test_hover_near_text.html b/layout/svg/tests/test_hover_near_text.html new file mode 100644 index 0000000000..2bf808e61a --- /dev/null +++ b/layout/svg/tests/test_hover_near_text.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8"/> + <title>Test mouse hover near text element inside svg element with viewBox attribute</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + </head> + <body style="margin:0"> + <div> + <svg viewBox="-1 -1 2 2" width="300" height="300"> + <text style="font-size:0.1px" onmouseover="this.setAttribute('fill', 'red')">Hi</text> + </svg> + </div> + <p> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1519144">Mozilla Bug 1519144</a> + </p> + <script type="application/javascript"> + let utils = SpecialPowers.getDOMWindowUtils(window); + utils.sendMouseEvent('mousemove', 155, 125, 0, 0, 0); //hover above the text + utils.sendMouseEvent('mousemove', 125, 155, 0, 0, 0); //hover to the left of the text + requestIdleCallback(() => { + ok(!document.getElementsByTagName('text')[0].hasAttribute('fill'), + 'Text element should not receive an event'); + SimpleTest.finish(); + }); + SimpleTest.waitForExplicitFinish() + </script> + </body> +</html> diff --git a/layout/svg/tests/test_multiple_font_size.html b/layout/svg/tests/test_multiple_font_size.html new file mode 100644 index 0000000000..aca32eac03 --- /dev/null +++ b/layout/svg/tests/test_multiple_font_size.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1370646">Mozilla Bug 1370646</a> + +<svg xmlns="http://www.w3.org/2000/svg" width="440" height="100" viewBox="0 0 440 100"> + <text> + <tspan id="a" style="font-size:100px">3</tspan> + </text> + <text> + <tspan id="b" style="font-size:100px">3</tspan> + <tspan style="font-size:0.1px">0</tspan> + </text> +</svg> + +<script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + let alen = document.getElementById("a").getComputedTextLength(), + blen = document.getElementById("b").getComputedTextLength(); + + SimpleTest.isfuzzy(alen, blen, 5, "lengths should be close"); + + SimpleTest.finish(); +</script> diff --git a/layout/svg/tests/test_use_tree_cycle.html b/layout/svg/tests/test_use_tree_cycle.html new file mode 100644 index 0000000000..e820f0f2a2 --- /dev/null +++ b/layout/svg/tests/test_use_tree_cycle.html @@ -0,0 +1,37 @@ +<!doctype html> +<title>Test for bug 1531333</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<style> + symbol { display: block } +</style> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10"> + <symbol id="svg-sprite" viewBox="0 0 133 230866"> + <title>svg-sprite</title> + <symbol id="svg-sprite" viewBox="0 0 133 230866"> + <title>svg-sprite</title> + <use xlink:href="#svg-sprite" width="500" height="500" /> + </symbol> + <use xlink:href="#svg-sprite" y="1601" width="133" height="228958" /> + </symbol> + <use xlink:href="#svg-sprite" y="1601" width="133" height="230866" /> +</svg> +<script> +function countUseElements(subtreeRoot) { + if (!subtreeRoot) + return 0; + + let i = 0; + for (const use of subtreeRoot.querySelectorAll("use")) + i += 1 + countUseElements(SpecialPowers.wrap(use).openOrClosedShadowRoot); + return i; +} +SimpleTest.waitForExplicitFinish(); +onload = function() { + document.body.offsetTop; + // The three in the document, plus the two created from the element that's + // not in the <symbol> subtree. + is(countUseElements(document), 5, "Shouldn't create more than 5 use elements"); + SimpleTest.finish(); +} +</script> |