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 "nsSVGContainerFrame.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGElement.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "nsSVGAnimatedTransformList.h" michael@0: #include "SVGTextFrame.h" michael@0: #include "RestyleManager.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: NS_QUERYFRAME_HEAD(nsSVGContainerFrame) michael@0: NS_QUERYFRAME_ENTRY(nsSVGContainerFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrameBase) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame) michael@0: NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame) michael@0: NS_QUERYFRAME_ENTRY(nsISVGChildFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame) michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGContainerFrame(nsIPresShell* aPresShell, michael@0: nsStyleContext* aContext) michael@0: { michael@0: nsIFrame *frame = new (aPresShell) nsSVGContainerFrame(aContext); michael@0: // If we were called directly, then the frame is for a or michael@0: // an unknown element type. In both cases we prevent the content michael@0: // from displaying directly. michael@0: frame->AddStateBits(NS_FRAME_IS_NONDISPLAY); michael@0: return frame; michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame) michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame) michael@0: michael@0: nsresult michael@0: nsSVGContainerFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: return InsertFrames(aListID, mFrames.LastChild(), aFrameList); michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGContainerFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, michael@0: "inserting after sibling frame with different parent"); michael@0: michael@0: mFrames.InsertFrames(this, aPrevFrame, aFrameList); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGContainerFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: michael@0: mFrames.DestroyFrame(aOldFrame); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsSVGContainerFrame::UpdateOverflow() michael@0: { michael@0: if (mState & NS_FRAME_IS_NONDISPLAY) { michael@0: // We don't maintain overflow rects. michael@0: // XXX It would have be better if the restyle request hadn't even happened. michael@0: return false; michael@0: } michael@0: return nsSVGContainerFrameBase::UpdateOverflow(); michael@0: } michael@0: michael@0: /** michael@0: * Traverses a frame tree, marking any SVGTextFrame frames as dirty michael@0: * and calling InvalidateRenderingObservers() on it. michael@0: * michael@0: * The reason that this helper exists is because SVGTextFrame is special. michael@0: * None of the other SVG frames ever need to be reflowed when they have the michael@0: * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods michael@0: * (and those of any containers that they can validly be contained within) do michael@0: * not make use of mRect or overflow rects. "em" lengths, etc., are resolved michael@0: * as those elements are painted. michael@0: * michael@0: * SVGTextFrame is different because its anonymous block and inline frames michael@0: * need to be reflowed in order to get the correct metrics when things like michael@0: * inherited font-size of an ancestor changes, or a delayed webfont loads and michael@0: * applies. michael@0: * michael@0: * We assume that any change that requires the anonymous kid of an michael@0: * SVGTextFrame to reflow will result in an NS_FRAME_IS_DIRTY reflow. When michael@0: * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally michael@0: * stop, but this helper looks for any SVGTextFrame descendants of such michael@0: * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they are michael@0: * painted their anonymous kid will first get the necessary reflow. michael@0: */ michael@0: /* static */ void michael@0: nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) michael@0: { michael@0: NS_ASSERTION(aContainer->GetStateBits() & NS_FRAME_IS_DIRTY, michael@0: "expected aContainer to be NS_FRAME_IS_DIRTY"); michael@0: NS_ASSERTION((aContainer->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || michael@0: !aContainer->IsFrameOfType(nsIFrame::eSVG), michael@0: "it is wasteful to call ReflowSVGNonDisplayText on a container " michael@0: "frame that is not NS_FRAME_IS_NONDISPLAY"); michael@0: for (nsIFrame* kid = aContainer->GetFirstPrincipalChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsIAtom* type = kid->GetType(); michael@0: if (type == nsGkAtoms::svgTextFrame) { michael@0: static_cast(kid)->ReflowSVGNonDisplayText(); michael@0: } else { michael@0: if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) || michael@0: type == nsGkAtoms::svgForeignObjectFrame || michael@0: !kid->IsFrameOfType(nsIFrame::eSVG)) { michael@0: ReflowSVGNonDisplayText(kid); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSVGDisplayContainerFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG)) { michael@0: AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); michael@0: } michael@0: nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: void michael@0: nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: // mContent could be a XUL element so check for an SVG element before casting michael@0: if (mContent->IsSVG() && michael@0: !static_cast(mContent)->HasValidDimensions()) { michael@0: return; michael@0: } michael@0: return BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists); michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: // memorize first old frame after insertion point michael@0: // XXXbz once again, this would work a lot better if the nsIFrame michael@0: // methods returned framelist iterators.... michael@0: nsIFrame* nextFrame = aPrevFrame ? michael@0: aPrevFrame->GetNextSibling() : GetChildList(aListID).FirstChild(); michael@0: nsIFrame* firstNewFrame = aFrameList.FirstChild(); michael@0: michael@0: // Insert the new frames michael@0: nsSVGContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); michael@0: michael@0: // If we are not a non-display SVG frame and we do not have a bounds update michael@0: // pending, then we need to schedule one for our new children: michael@0: if (!(GetStateBits() & michael@0: (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN | michael@0: NS_FRAME_IS_NONDISPLAY))) { michael@0: for (nsIFrame* kid = firstNewFrame; kid != nextFrame; michael@0: kid = kid->GetNextSibling()) { michael@0: nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); michael@0: if (SVGFrame) { michael@0: NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), michael@0: "Check for this explicitly in the |if|, then"); michael@0: bool isFirstReflow = (kid->GetStateBits() & NS_FRAME_FIRST_REFLOW); michael@0: // Remove bits so that ScheduleBoundsUpdate will work: michael@0: kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: // No need to invalidate the new kid's old bounds, so we just use michael@0: // nsSVGUtils::ScheduleBoundsUpdate. michael@0: nsSVGUtils::ScheduleReflowSVG(kid); michael@0: if (isFirstReflow) { michael@0: // Add back the NS_FRAME_FIRST_REFLOW bit: michael@0: kid->AddStateBits(NS_FRAME_FIRST_REFLOW); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: nsSVGEffects::InvalidateRenderingObservers(aOldFrame); michael@0: michael@0: // nsSVGContainerFrame::RemoveFrame doesn't call down into michael@0: // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We michael@0: // need to schedule a repaint and schedule an update to our overflow rects. michael@0: SchedulePaint(); michael@0: PresContext()->RestyleManager()->PostRestyleEvent( michael@0: mContent->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow); michael@0: michael@0: nsresult rv = nsSVGContainerFrame::RemoveFrame(aListID, aOldFrame); michael@0: michael@0: if (!(GetStateBits() & (NS_FRAME_IS_NONDISPLAY | NS_STATE_IS_OUTER_SVG))) { michael@0: nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, michael@0: gfx::Matrix *aFromParentTransform) const michael@0: { michael@0: bool foundTransform = false; michael@0: michael@0: // Check if our parent has children-only transforms: michael@0: nsIFrame *parent = GetParent(); michael@0: if (parent && michael@0: parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { michael@0: foundTransform = static_cast(parent)-> michael@0: HasChildrenOnlyTransform(aFromParentTransform); michael@0: } michael@0: michael@0: // mContent could be a XUL element so check for an SVG element before casting michael@0: if (mContent->IsSVG()) { michael@0: nsSVGElement *content = static_cast(mContent); michael@0: nsSVGAnimatedTransformList* transformList = michael@0: content->GetAnimatedTransformList(); michael@0: if ((transformList && transformList->HasTransform()) || michael@0: content->GetAnimateMotionTransform()) { michael@0: if (aOwnTransform) { michael@0: *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), michael@0: nsSVGElement::eUserSpaceToParent)); michael@0: } michael@0: foundTransform = true; michael@0: } michael@0: } michael@0: return foundTransform; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISVGChildFrame methods michael@0: michael@0: nsresult michael@0: nsSVGDisplayContainerFrame::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: PresContext()->IsGlyph(), michael@0: "If display lists are enabled, only painting of non-display " michael@0: "SVG should take this code path"); michael@0: michael@0: const nsStyleDisplay *display = StyleDisplay(); michael@0: if (display->mOpacity == 0.0) michael@0: return NS_OK; michael@0: michael@0: for (nsIFrame* kid = mFrames.FirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid, aTransformRoot); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsSVGDisplayContainerFrame::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 a " michael@0: "clipPath's contents should take this code path"); michael@0: return nsSVGUtils::HitTestChildren(this, aPoint); michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGDisplayContainerFrame::GetCoveredRegion() michael@0: { michael@0: return nsSVGUtils::GetCoveredRegion(mFrames); michael@0: } michael@0: michael@0: void michael@0: nsSVGDisplayContainerFrame::ReflowSVG() michael@0: { michael@0: NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), michael@0: "This call is probably a wasteful mistake"); michael@0: michael@0: NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), michael@0: "ReflowSVG mechanism not designed for this"); michael@0: michael@0: NS_ABORT_IF_FALSE(GetType() != nsGkAtoms::svgOuterSVGFrame, michael@0: "Do not call on outer-"); michael@0: michael@0: if (!nsSVGUtils::NeedsReflowSVG(this)) { michael@0: return; michael@0: } michael@0: michael@0: // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, michael@0: // then our outer- has previously had its initial reflow. In that case michael@0: // we need to make sure that that bit has been removed from ourself _before_ michael@0: // recursing over our children to ensure that they know too. Otherwise, we michael@0: // need to remove it _after_ recursing over our children so that they know michael@0: // the initial reflow is currently underway. michael@0: michael@0: bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW); michael@0: michael@0: bool outerSVGHasHadFirstReflow = michael@0: (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0; michael@0: michael@0: if (outerSVGHasHadFirstReflow) { michael@0: mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children michael@0: } michael@0: michael@0: nsOverflowAreas overflowRects; michael@0: michael@0: for (nsIFrame* kid = mFrames.FirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); michael@0: if (SVGFrame) { michael@0: NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), michael@0: "Check for this explicitly in the |if|, then"); michael@0: kid->AddStateBits(mState & NS_FRAME_IS_DIRTY); michael@0: SVGFrame->ReflowSVG(); michael@0: michael@0: // We build up our child frame overflows here instead of using michael@0: // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same michael@0: // frame list, and we're iterating over that list now anyway. michael@0: ConsiderChildOverflow(overflowRects, kid); michael@0: } else { michael@0: // Inside a non-display container frame, we might have some michael@0: // SVGTextFrames. We need to cause those to get reflowed in michael@0: // case they are the target of a rendering observer. michael@0: NS_ASSERTION(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY, michael@0: "expected kid to be a NS_FRAME_IS_NONDISPLAY frame"); michael@0: if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) { michael@0: nsSVGContainerFrame* container = do_QueryFrame(kid); michael@0: if (container && container->GetContent()->IsSVG()) { michael@0: ReflowSVGNonDisplayText(container); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // can create an SVG viewport with an offset due to its michael@0: // x/y/width/height attributes, and can introduce an offset with an michael@0: // empty mRect (any width/height is copied to an anonymous child). michael@0: // Other than that containers should not set mRect since all other offsets michael@0: // come from transforms, which are accounted for by nsDisplayTransform. michael@0: // Note that we rely on |overflow:visible| to allow display list items to be michael@0: // created for our children. michael@0: NS_ABORT_IF_FALSE(mContent->Tag() == nsGkAtoms::svg || michael@0: (mContent->Tag() == nsGkAtoms::use && michael@0: mRect.Size() == nsSize(0,0)) || michael@0: mRect.IsEqualEdges(nsRect()), michael@0: "Only inner-/ is expected to have mRect set"); michael@0: michael@0: if (isFirstReflow) { michael@0: // Make sure we have our filter property (if any) before calling michael@0: // FinishAndStoreOverflow (subsequent filter changes are handled off michael@0: // nsChangeHint_UpdateEffects): michael@0: nsSVGEffects::UpdateEffects(this); michael@0: } michael@0: michael@0: FinishAndStoreOverflow(overflowRects, mRect.Size()); michael@0: michael@0: // Remove state bits after FinishAndStoreOverflow so that it doesn't michael@0: // invalidate on first reflow: michael@0: mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: michael@0: void michael@0: nsSVGDisplayContainerFrame::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: nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags); michael@0: } michael@0: michael@0: SVGBBox michael@0: nsSVGDisplayContainerFrame::GetBBoxContribution( michael@0: const Matrix &aToBBoxUserspace, michael@0: uint32_t aFlags) michael@0: { michael@0: SVGBBox bboxUnion; michael@0: michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: while (kid) { michael@0: nsIContent *content = kid->GetContent(); michael@0: nsISVGChildFrame* svgKid = do_QueryFrame(kid); michael@0: // content could be a XUL element so check for an SVG element before casting michael@0: if (svgKid && (!content->IsSVG() || michael@0: static_cast(content)->HasValidDimensions())) { michael@0: michael@0: gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace); michael@0: if (content->IsSVG()) { michael@0: transform = static_cast(content)-> michael@0: PrependLocalTransformsTo(transform); michael@0: } michael@0: // We need to include zero width/height vertical/horizontal lines, so we have michael@0: // to use UnionEdges. michael@0: bboxUnion.UnionEdges(svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags)); michael@0: } michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: michael@0: return bboxUnion; michael@0: }