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