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: // Keep in (case-insensitive) order: michael@0: #include "nsIAnonymousContentCreator.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGGFrame.h" michael@0: #include "mozilla/dom/SVGUseElement.h" michael@0: #include "nsContentList.h" michael@0: michael@0: typedef nsSVGGFrame nsSVGUseFrameBase; michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: class nsSVGUseFrame : public nsSVGUseFrameBase, michael@0: public nsIAnonymousContentCreator michael@0: { michael@0: friend nsIFrame* michael@0: NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); michael@0: michael@0: protected: michael@0: nsSVGUseFrame(nsStyleContext* aContext) : michael@0: nsSVGUseFrameBase(aContext), michael@0: mHasValidDimensions(true) michael@0: {} michael@0: michael@0: public: michael@0: NS_DECL_QUERYFRAME michael@0: NS_DECL_FRAMEARENA_HELPERS michael@0: michael@0: michael@0: // nsIFrame interface: michael@0: virtual void Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) MOZ_OVERRIDE; michael@0: michael@0: virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; michael@0: michael@0: /** michael@0: * Get the "type" of the frame michael@0: * michael@0: * @see nsGkAtoms::svgUseFrame michael@0: */ michael@0: virtual nsIAtom* GetType() const MOZ_OVERRIDE; michael@0: michael@0: virtual bool IsLeaf() const MOZ_OVERRIDE; michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("SVGUse"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: // nsISVGChildFrame interface: michael@0: virtual void ReflowSVG() MOZ_OVERRIDE; michael@0: virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE; michael@0: michael@0: // nsIAnonymousContentCreator michael@0: virtual nsresult CreateAnonymousContent(nsTArray& aElements) MOZ_OVERRIDE; michael@0: virtual void AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: bool mHasValidDimensions; michael@0: }; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSVGUseFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGUseFrame) michael@0: michael@0: nsIAtom * michael@0: nsSVGUseFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::svgUseFrame; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsQueryFrame methods michael@0: michael@0: NS_QUERYFRAME_HEAD(nsSVGUseFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsSVGUseFrameBase) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsIFrame methods: michael@0: michael@0: void michael@0: nsSVGUseFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ASSERTION(aContent->IsSVG(nsGkAtoms::use), michael@0: "Content is not an SVG use!"); michael@0: michael@0: mHasValidDimensions = michael@0: static_cast(aContent)->HasValidDimensions(); michael@0: michael@0: nsSVGUseFrameBase::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGUseFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: SVGUseElement *useElement = static_cast(mContent); michael@0: michael@0: if (aNameSpaceID == kNameSpaceID_None) { michael@0: if (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: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED); michael@0: } else if (aAttribute == nsGkAtoms::width || michael@0: aAttribute == nsGkAtoms::height) { michael@0: bool invalidate = false; michael@0: if (mHasValidDimensions != useElement->HasValidDimensions()) { michael@0: mHasValidDimensions = !mHasValidDimensions; michael@0: invalidate = true; michael@0: } michael@0: if (useElement->OurWidthAndHeightAreUsed()) { michael@0: invalidate = true; michael@0: useElement->SyncWidthOrHeight(aAttribute); michael@0: } michael@0: if (invalidate) { michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: } michael@0: } michael@0: } else if (aNameSpaceID == kNameSpaceID_XLink && michael@0: aAttribute == nsGkAtoms::href) { michael@0: // we're changing our nature, clear out the clone information michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: useElement->mOriginal = nullptr; michael@0: useElement->UnlinkSource(); michael@0: useElement->TriggerReclone(); michael@0: } michael@0: michael@0: return nsSVGUseFrameBase::AttributeChanged(aNameSpaceID, michael@0: aAttribute, aModType); michael@0: } michael@0: michael@0: void michael@0: nsSVGUseFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: nsRefPtr use = static_cast(mContent); michael@0: nsSVGUseFrameBase::DestroyFrom(aDestructRoot); michael@0: use->DestroyAnonymousContent(); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUseFrame::IsLeaf() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISVGChildFrame methods michael@0: michael@0: void michael@0: nsSVGUseFrame::ReflowSVG() michael@0: { michael@0: // We only handle x/y offset here, since any width/height that is in force is michael@0: // handled by the nsSVGOuterSVGFrame for the anonymous that will be michael@0: // created for that purpose. michael@0: float x, y; michael@0: static_cast(mContent)-> michael@0: GetAnimatedLengthValues(&x, &y, nullptr); michael@0: mRect.MoveTo(nsLayoutUtils::RoundGfxRectToAppRect( michael@0: gfxRect(x, y, 0.0, 0.0), michael@0: PresContext()->AppUnitsPerCSSPixel()).TopLeft()); 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: nsSVGUseFrameBase::ReflowSVG(); michael@0: } michael@0: michael@0: void michael@0: nsSVGUseFrame::NotifySVGChanged(uint32_t aFlags) michael@0: { michael@0: if (aFlags & COORD_CONTEXT_CHANGED && michael@0: !(aFlags & TRANSFORM_CHANGED)) { michael@0: // Coordinate context changes affect mCanvasTM if we have a michael@0: // percentage 'x' or 'y' michael@0: SVGUseElement *use = static_cast(mContent); michael@0: if (use->mLengthAttributes[SVGUseElement::ATTR_X].IsPercentage() || michael@0: use->mLengthAttributes[SVGUseElement::ATTR_Y].IsPercentage()) { michael@0: aFlags |= TRANSFORM_CHANGED; 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: michael@0: // We don't remove the TRANSFORM_CHANGED flag here if we have a viewBox or michael@0: // non-percentage width/height, since if they're set then they are cloned to michael@0: // an anonymous child , and its nsSVGInnerSVGFrame will do that. michael@0: michael@0: nsSVGUseFrameBase::NotifySVGChanged(aFlags); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsIAnonymousContentCreator methods: michael@0: michael@0: nsresult michael@0: nsSVGUseFrame::CreateAnonymousContent(nsTArray& aElements) michael@0: { michael@0: SVGUseElement *use = static_cast(mContent); michael@0: michael@0: nsIContent* clone = use->CreateAnonymousContent(); michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: if (!clone) michael@0: return NS_ERROR_FAILURE; michael@0: if (!aElements.AppendElement(clone)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSVGUseFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: SVGUseElement *use = static_cast(mContent); michael@0: nsIContent* clone = use->GetAnonymousContent(); michael@0: aElements.MaybeAppendElement(clone); michael@0: }