gecko/layout/svg/nsSVGInnerSVGFrame.cpp
Markus Stange 11d4b8640e Bug 997735 - Invalidate when reflowing SVG containers. r=roc
Without this patch, when changing the x/y attributes of svg:use, innerSVG and foreignObject, we were relying on the transform changes of the children to trigger the right invalidations. However, changes to those attributes can also change the filter region. And there's a difference between moving children in a fixed filter region and moving the filter region along with the children: In the first case, we wouldn't need to invalidate anything outside the old filter region, because those parts of the children would be clipped away anyway. But when the filter region changes, we need to invalidate both the old and the new filter region. Also, when the filter has primitives without inputs, e.g. flood or turbulence, the filtered frame needs to be invalidate even if it has no children.
2014-04-23 11:48:07 +02:00

316 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Main header first:
#include "nsSVGInnerSVGFrame.h"
// Keep others in (case-insensitive) order:
#include "gfxContext.h"
#include "nsIFrame.h"
#include "nsISVGChildFrame.h"
#include "nsRenderingContext.h"
#include "nsSVGContainerFrame.h"
#include "nsSVGEffects.h"
#include "nsSVGIntegrationUtils.h"
#include "mozilla/dom/SVGSVGElement.h"
using namespace mozilla;
using namespace mozilla::dom;
nsIFrame*
NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
return new (aPresShell) nsSVGInnerSVGFrame(aContext);
}
NS_IMPL_FRAMEARENA_HELPERS(nsSVGInnerSVGFrame)
//----------------------------------------------------------------------
// nsIFrame methods
NS_QUERYFRAME_HEAD(nsSVGInnerSVGFrame)
NS_QUERYFRAME_ENTRY(nsSVGInnerSVGFrame)
NS_QUERYFRAME_ENTRY(nsISVGSVGFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsSVGInnerSVGFrameBase)
#ifdef DEBUG
void
nsSVGInnerSVGFrame::Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow)
{
NS_ASSERTION(aContent->IsSVG(nsGkAtoms::svg),
"Content is not an SVG 'svg' element!");
nsSVGInnerSVGFrameBase::Init(aContent, aParent, aPrevInFlow);
}
#endif /* DEBUG */
nsIAtom *
nsSVGInnerSVGFrame::GetType() const
{
return nsGkAtoms::svgInnerSVGFrame;
}
//----------------------------------------------------------------------
// nsISVGChildFrame methods
nsresult
nsSVGInnerSVGFrame::PaintSVG(nsRenderingContext *aContext,
const nsIntRect *aDirtyRect,
nsIFrame* aTransformRoot)
{
NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
(mState & NS_FRAME_IS_NONDISPLAY),
"If display lists are enabled, only painting of non-display "
"SVG should take this code path");
gfxContextAutoSaveRestore autoSR;
if (StyleDisplay()->IsScrollableOverflow()) {
float x, y, width, height;
static_cast<SVGSVGElement*>(mContent)->
GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
if (width <= 0 || height <= 0) {
return NS_OK;
}
nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
gfxMatrix clipTransform = parent->GetCanvasTM(FOR_PAINTING, aTransformRoot);
gfxContext *gfx = aContext->ThebesContext();
autoSR.SetContext(gfx);
gfxRect clipRect =
nsSVGUtils::GetClipRectForFrame(this, x, y, width, height);
nsSVGUtils::SetClipRect(gfx, clipTransform, clipRect);
}
return nsSVGInnerSVGFrameBase::PaintSVG(aContext, aDirtyRect);
}
void
nsSVGInnerSVGFrame::ReflowSVG()
{
// mRect must be set before FinishAndStoreOverflow is called in order
// for our overflow areas to be clipped correctly.
float x, y, width, height;
static_cast<SVGSVGElement*>(mContent)->
GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
mRect = nsLayoutUtils::RoundGfxRectToAppRect(
gfxRect(x, y, width, height),
PresContext()->AppUnitsPerCSSPixel());
// If we have a filter, we need to invalidate ourselves because filter
// output can change even if none of our descendants need repainting.
if (StyleSVGReset()->HasFilters()) {
InvalidateFrame();
}
nsSVGInnerSVGFrameBase::ReflowSVG();
}
void
nsSVGInnerSVGFrame::NotifySVGChanged(uint32_t aFlags)
{
NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
"Invalidation logic may need adjusting");
if (aFlags & COORD_CONTEXT_CHANGED) {
SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent);
bool xOrYIsPercentage =
svg->mLengthAttributes[SVGSVGElement::ATTR_X].IsPercentage() ||
svg->mLengthAttributes[SVGSVGElement::ATTR_Y].IsPercentage();
bool widthOrHeightIsPercentage =
svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH].IsPercentage() ||
svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT].IsPercentage();
if (xOrYIsPercentage || widthOrHeightIsPercentage) {
// Ancestor changes can't affect how we render from the perspective of
// any rendering observers that we may have, so we don't need to
// invalidate them. We also don't need to invalidate ourself, since our
// changed ancestor will have invalidated its entire area, which includes
// our area.
// For perf reasons we call this before calling NotifySVGChanged() below.
nsSVGUtils::ScheduleReflowSVG(this);
}
// Coordinate context changes affect mCanvasTM if we have a
// percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND
// a 'viewBox'.
if (!(aFlags & TRANSFORM_CHANGED) &&
(xOrYIsPercentage ||
(widthOrHeightIsPercentage && svg->HasViewBoxRect()))) {
aFlags |= TRANSFORM_CHANGED;
}
if (svg->HasViewBoxRect() || !widthOrHeightIsPercentage) {
// Remove COORD_CONTEXT_CHANGED, since we establish the coordinate
// context for our descendants and this notification won't change its
// dimensions:
aFlags &= ~COORD_CONTEXT_CHANGED;
if (!aFlags) {
return; // No notification flags left
}
}
}
if (aFlags & TRANSFORM_CHANGED) {
// make sure our cached transform matrix gets (lazily) updated
mCanvasTM = nullptr;
}
nsSVGInnerSVGFrameBase::NotifySVGChanged(aFlags);
}
nsresult
nsSVGInnerSVGFrame::AttributeChanged(int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType)
{
if (aNameSpaceID == kNameSpaceID_None &&
!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent);
if (aAttribute == nsGkAtoms::width ||
aAttribute == nsGkAtoms::height) {
nsSVGEffects::InvalidateRenderingObservers(this);
nsSVGUtils::ScheduleReflowSVG(this);
if (content->HasViewBoxOrSyntheticViewBox()) {
// make sure our cached transform matrix gets (lazily) updated
mCanvasTM = nullptr;
content->ChildrenOnlyTransformChanged();
nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
} else {
uint32_t flags = COORD_CONTEXT_CHANGED;
if (mCanvasTM && mCanvasTM->IsSingular()) {
mCanvasTM = nullptr;
flags |= TRANSFORM_CHANGED;
}
nsSVGUtils::NotifyChildrenOfSVGChange(this, flags);
}
} else if (aAttribute == nsGkAtoms::transform ||
aAttribute == nsGkAtoms::preserveAspectRatio ||
aAttribute == nsGkAtoms::viewBox ||
aAttribute == nsGkAtoms::x ||
aAttribute == nsGkAtoms::y) {
// make sure our cached transform matrix gets (lazily) updated
mCanvasTM = nullptr;
nsSVGUtils::NotifyChildrenOfSVGChange(
this, aAttribute == nsGkAtoms::viewBox ?
TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED);
// We don't invalidate for transform changes (the layers code does that).
// Also note that SVGTransformableElement::GetAttributeChangeHint will
// return nsChangeHint_UpdateOverflow for "transform" attribute changes
// and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) {
nsSVGEffects::InvalidateRenderingObservers(this);
nsSVGUtils::ScheduleReflowSVG(this);
} else if (aAttribute == nsGkAtoms::viewBox ||
(aAttribute == nsGkAtoms::preserveAspectRatio &&
content->HasViewBoxOrSyntheticViewBox())) {
content->ChildrenOnlyTransformChanged();
// SchedulePaint sets a global state flag so we only need to call it once
// (on ourself is fine), not once on each child (despite bug 828240).
SchedulePaint();
}
}
}
return NS_OK;
}
nsIFrame*
nsSVGInnerSVGFrame::GetFrameForPoint(const nsPoint &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()) {
nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
float clipX, clipY, clipWidth, clipHeight;
content->GetAnimatedLengthValues(&clipX, &clipY, &clipWidth, &clipHeight, nullptr);
if (!nsSVGUtils::HitTestRect(gfx::ToMatrix(parent->GetCanvasTM(FOR_HIT_TESTING)),
clipX, clipY, clipWidth, clipHeight,
PresContext()->AppUnitsToDevPixels(aPoint.x),
PresContext()->AppUnitsToDevPixels(aPoint.y))) {
return nullptr;
}
}
return nsSVGInnerSVGFrameBase::GetFrameForPoint(aPoint);
}
//----------------------------------------------------------------------
// nsISVGSVGFrame methods:
void
nsSVGInnerSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags)
{
// The dimensions of inner-<svg> frames are purely defined by their "width"
// and "height" attributes, and transform changes can only occur as a result
// of changes to their "width", "height", "viewBox" or "preserveAspectRatio"
// attributes. Changes to all of these attributes are handled in
// AttributeChanged(), so we should never be called.
NS_ERROR("Not called for nsSVGInnerSVGFrame");
}
//----------------------------------------------------------------------
// nsSVGContainerFrame methods:
gfxMatrix
nsSVGInnerSVGFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
{
if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
(aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
}
}
if (!mCanvasTM) {
NS_ASSERTION(mParent, "null parent");
nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
gfxMatrix tm = content->PrependLocalTransformsTo(
this == aTransformRoot ? gfxMatrix() :
parent->GetCanvasTM(aFor, aTransformRoot));
mCanvasTM = new gfxMatrix(tm);
}
return *mCanvasTM;
}
bool
nsSVGInnerSVGFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const
{
SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
if (content->HasViewBoxOrSyntheticViewBox()) {
// XXX Maybe return false if the transform is the identity transform?
if (aTransform) {
*aTransform = content->GetViewBoxTransform();
}
return true;
}
return false;
}