1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/svg/nsSVGContainerFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,423 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +// Main header first: 1.10 +#include "nsSVGContainerFrame.h" 1.11 + 1.12 +// Keep others in (case-insensitive) order: 1.13 +#include "nsCSSFrameConstructor.h" 1.14 +#include "nsSVGEffects.h" 1.15 +#include "nsSVGElement.h" 1.16 +#include "nsSVGUtils.h" 1.17 +#include "nsSVGAnimatedTransformList.h" 1.18 +#include "SVGTextFrame.h" 1.19 +#include "RestyleManager.h" 1.20 + 1.21 +using namespace mozilla; 1.22 + 1.23 +NS_QUERYFRAME_HEAD(nsSVGContainerFrame) 1.24 + NS_QUERYFRAME_ENTRY(nsSVGContainerFrame) 1.25 +NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrameBase) 1.26 + 1.27 +NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame) 1.28 + NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame) 1.29 + NS_QUERYFRAME_ENTRY(nsISVGChildFrame) 1.30 +NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame) 1.31 + 1.32 +nsIFrame* 1.33 +NS_NewSVGContainerFrame(nsIPresShell* aPresShell, 1.34 + nsStyleContext* aContext) 1.35 +{ 1.36 + nsIFrame *frame = new (aPresShell) nsSVGContainerFrame(aContext); 1.37 + // If we were called directly, then the frame is for a <defs> or 1.38 + // an unknown element type. In both cases we prevent the content 1.39 + // from displaying directly. 1.40 + frame->AddStateBits(NS_FRAME_IS_NONDISPLAY); 1.41 + return frame; 1.42 +} 1.43 + 1.44 +NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame) 1.45 +NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame) 1.46 + 1.47 +nsresult 1.48 +nsSVGContainerFrame::AppendFrames(ChildListID aListID, 1.49 + nsFrameList& aFrameList) 1.50 +{ 1.51 + return InsertFrames(aListID, mFrames.LastChild(), aFrameList); 1.52 +} 1.53 + 1.54 +nsresult 1.55 +nsSVGContainerFrame::InsertFrames(ChildListID aListID, 1.56 + nsIFrame* aPrevFrame, 1.57 + nsFrameList& aFrameList) 1.58 +{ 1.59 + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); 1.60 + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 1.61 + "inserting after sibling frame with different parent"); 1.62 + 1.63 + mFrames.InsertFrames(this, aPrevFrame, aFrameList); 1.64 + 1.65 + return NS_OK; 1.66 +} 1.67 + 1.68 +nsresult 1.69 +nsSVGContainerFrame::RemoveFrame(ChildListID aListID, 1.70 + nsIFrame* aOldFrame) 1.71 +{ 1.72 + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); 1.73 + 1.74 + mFrames.DestroyFrame(aOldFrame); 1.75 + return NS_OK; 1.76 +} 1.77 + 1.78 +bool 1.79 +nsSVGContainerFrame::UpdateOverflow() 1.80 +{ 1.81 + if (mState & NS_FRAME_IS_NONDISPLAY) { 1.82 + // We don't maintain overflow rects. 1.83 + // XXX It would have be better if the restyle request hadn't even happened. 1.84 + return false; 1.85 + } 1.86 + return nsSVGContainerFrameBase::UpdateOverflow(); 1.87 +} 1.88 + 1.89 +/** 1.90 + * Traverses a frame tree, marking any SVGTextFrame frames as dirty 1.91 + * and calling InvalidateRenderingObservers() on it. 1.92 + * 1.93 + * The reason that this helper exists is because SVGTextFrame is special. 1.94 + * None of the other SVG frames ever need to be reflowed when they have the 1.95 + * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods 1.96 + * (and those of any containers that they can validly be contained within) do 1.97 + * not make use of mRect or overflow rects. "em" lengths, etc., are resolved 1.98 + * as those elements are painted. 1.99 + * 1.100 + * SVGTextFrame is different because its anonymous block and inline frames 1.101 + * need to be reflowed in order to get the correct metrics when things like 1.102 + * inherited font-size of an ancestor changes, or a delayed webfont loads and 1.103 + * applies. 1.104 + * 1.105 + * We assume that any change that requires the anonymous kid of an 1.106 + * SVGTextFrame to reflow will result in an NS_FRAME_IS_DIRTY reflow. When 1.107 + * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally 1.108 + * stop, but this helper looks for any SVGTextFrame descendants of such 1.109 + * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they are 1.110 + * painted their anonymous kid will first get the necessary reflow. 1.111 + */ 1.112 +/* static */ void 1.113 +nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) 1.114 +{ 1.115 + NS_ASSERTION(aContainer->GetStateBits() & NS_FRAME_IS_DIRTY, 1.116 + "expected aContainer to be NS_FRAME_IS_DIRTY"); 1.117 + NS_ASSERTION((aContainer->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || 1.118 + !aContainer->IsFrameOfType(nsIFrame::eSVG), 1.119 + "it is wasteful to call ReflowSVGNonDisplayText on a container " 1.120 + "frame that is not NS_FRAME_IS_NONDISPLAY"); 1.121 + for (nsIFrame* kid = aContainer->GetFirstPrincipalChild(); kid; 1.122 + kid = kid->GetNextSibling()) { 1.123 + nsIAtom* type = kid->GetType(); 1.124 + if (type == nsGkAtoms::svgTextFrame) { 1.125 + static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText(); 1.126 + } else { 1.127 + if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) || 1.128 + type == nsGkAtoms::svgForeignObjectFrame || 1.129 + !kid->IsFrameOfType(nsIFrame::eSVG)) { 1.130 + ReflowSVGNonDisplayText(kid); 1.131 + } 1.132 + } 1.133 + } 1.134 +} 1.135 + 1.136 +void 1.137 +nsSVGDisplayContainerFrame::Init(nsIContent* aContent, 1.138 + nsIFrame* aParent, 1.139 + nsIFrame* aPrevInFlow) 1.140 +{ 1.141 + if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG)) { 1.142 + AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); 1.143 + } 1.144 + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); 1.145 +} 1.146 + 1.147 +void 1.148 +nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1.149 + const nsRect& aDirtyRect, 1.150 + const nsDisplayListSet& aLists) 1.151 +{ 1.152 + // mContent could be a XUL element so check for an SVG element before casting 1.153 + if (mContent->IsSVG() && 1.154 + !static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) { 1.155 + return; 1.156 + } 1.157 + return BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists); 1.158 +} 1.159 + 1.160 +nsresult 1.161 +nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID, 1.162 + nsIFrame* aPrevFrame, 1.163 + nsFrameList& aFrameList) 1.164 +{ 1.165 + // memorize first old frame after insertion point 1.166 + // XXXbz once again, this would work a lot better if the nsIFrame 1.167 + // methods returned framelist iterators.... 1.168 + nsIFrame* nextFrame = aPrevFrame ? 1.169 + aPrevFrame->GetNextSibling() : GetChildList(aListID).FirstChild(); 1.170 + nsIFrame* firstNewFrame = aFrameList.FirstChild(); 1.171 + 1.172 + // Insert the new frames 1.173 + nsSVGContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); 1.174 + 1.175 + // If we are not a non-display SVG frame and we do not have a bounds update 1.176 + // pending, then we need to schedule one for our new children: 1.177 + if (!(GetStateBits() & 1.178 + (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN | 1.179 + NS_FRAME_IS_NONDISPLAY))) { 1.180 + for (nsIFrame* kid = firstNewFrame; kid != nextFrame; 1.181 + kid = kid->GetNextSibling()) { 1.182 + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); 1.183 + if (SVGFrame) { 1.184 + NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), 1.185 + "Check for this explicitly in the |if|, then"); 1.186 + bool isFirstReflow = (kid->GetStateBits() & NS_FRAME_FIRST_REFLOW); 1.187 + // Remove bits so that ScheduleBoundsUpdate will work: 1.188 + kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 1.189 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.190 + // No need to invalidate the new kid's old bounds, so we just use 1.191 + // nsSVGUtils::ScheduleBoundsUpdate. 1.192 + nsSVGUtils::ScheduleReflowSVG(kid); 1.193 + if (isFirstReflow) { 1.194 + // Add back the NS_FRAME_FIRST_REFLOW bit: 1.195 + kid->AddStateBits(NS_FRAME_FIRST_REFLOW); 1.196 + } 1.197 + } 1.198 + } 1.199 + } 1.200 + 1.201 + return NS_OK; 1.202 +} 1.203 + 1.204 +nsresult 1.205 +nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID, 1.206 + nsIFrame* aOldFrame) 1.207 +{ 1.208 + nsSVGEffects::InvalidateRenderingObservers(aOldFrame); 1.209 + 1.210 + // nsSVGContainerFrame::RemoveFrame doesn't call down into 1.211 + // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We 1.212 + // need to schedule a repaint and schedule an update to our overflow rects. 1.213 + SchedulePaint(); 1.214 + PresContext()->RestyleManager()->PostRestyleEvent( 1.215 + mContent->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow); 1.216 + 1.217 + nsresult rv = nsSVGContainerFrame::RemoveFrame(aListID, aOldFrame); 1.218 + 1.219 + if (!(GetStateBits() & (NS_FRAME_IS_NONDISPLAY | NS_STATE_IS_OUTER_SVG))) { 1.220 + nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this); 1.221 + } 1.222 + 1.223 + return rv; 1.224 +} 1.225 + 1.226 +bool 1.227 +nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, 1.228 + gfx::Matrix *aFromParentTransform) const 1.229 +{ 1.230 + bool foundTransform = false; 1.231 + 1.232 + // Check if our parent has children-only transforms: 1.233 + nsIFrame *parent = GetParent(); 1.234 + if (parent && 1.235 + parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { 1.236 + foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> 1.237 + HasChildrenOnlyTransform(aFromParentTransform); 1.238 + } 1.239 + 1.240 + // mContent could be a XUL element so check for an SVG element before casting 1.241 + if (mContent->IsSVG()) { 1.242 + nsSVGElement *content = static_cast<nsSVGElement*>(mContent); 1.243 + nsSVGAnimatedTransformList* transformList = 1.244 + content->GetAnimatedTransformList(); 1.245 + if ((transformList && transformList->HasTransform()) || 1.246 + content->GetAnimateMotionTransform()) { 1.247 + if (aOwnTransform) { 1.248 + *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), 1.249 + nsSVGElement::eUserSpaceToParent)); 1.250 + } 1.251 + foundTransform = true; 1.252 + } 1.253 + } 1.254 + return foundTransform; 1.255 +} 1.256 + 1.257 +//---------------------------------------------------------------------- 1.258 +// nsISVGChildFrame methods 1.259 + 1.260 +nsresult 1.261 +nsSVGDisplayContainerFrame::PaintSVG(nsRenderingContext* aContext, 1.262 + const nsIntRect *aDirtyRect, 1.263 + nsIFrame* aTransformRoot) 1.264 +{ 1.265 + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || 1.266 + (mState & NS_FRAME_IS_NONDISPLAY) || 1.267 + PresContext()->IsGlyph(), 1.268 + "If display lists are enabled, only painting of non-display " 1.269 + "SVG should take this code path"); 1.270 + 1.271 + const nsStyleDisplay *display = StyleDisplay(); 1.272 + if (display->mOpacity == 0.0) 1.273 + return NS_OK; 1.274 + 1.275 + for (nsIFrame* kid = mFrames.FirstChild(); kid; 1.276 + kid = kid->GetNextSibling()) { 1.277 + nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid, aTransformRoot); 1.278 + } 1.279 + 1.280 + return NS_OK; 1.281 +} 1.282 + 1.283 +nsIFrame* 1.284 +nsSVGDisplayContainerFrame::GetFrameForPoint(const nsPoint &aPoint) 1.285 +{ 1.286 + NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || 1.287 + (mState & NS_FRAME_IS_NONDISPLAY), 1.288 + "If display lists are enabled, only hit-testing of a " 1.289 + "clipPath's contents should take this code path"); 1.290 + return nsSVGUtils::HitTestChildren(this, aPoint); 1.291 +} 1.292 + 1.293 +nsRect 1.294 +nsSVGDisplayContainerFrame::GetCoveredRegion() 1.295 +{ 1.296 + return nsSVGUtils::GetCoveredRegion(mFrames); 1.297 +} 1.298 + 1.299 +void 1.300 +nsSVGDisplayContainerFrame::ReflowSVG() 1.301 +{ 1.302 + NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), 1.303 + "This call is probably a wasteful mistake"); 1.304 + 1.305 + NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), 1.306 + "ReflowSVG mechanism not designed for this"); 1.307 + 1.308 + NS_ABORT_IF_FALSE(GetType() != nsGkAtoms::svgOuterSVGFrame, 1.309 + "Do not call on outer-<svg>"); 1.310 + 1.311 + if (!nsSVGUtils::NeedsReflowSVG(this)) { 1.312 + return; 1.313 + } 1.314 + 1.315 + // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, 1.316 + // then our outer-<svg> has previously had its initial reflow. In that case 1.317 + // we need to make sure that that bit has been removed from ourself _before_ 1.318 + // recursing over our children to ensure that they know too. Otherwise, we 1.319 + // need to remove it _after_ recursing over our children so that they know 1.320 + // the initial reflow is currently underway. 1.321 + 1.322 + bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW); 1.323 + 1.324 + bool outerSVGHasHadFirstReflow = 1.325 + (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0; 1.326 + 1.327 + if (outerSVGHasHadFirstReflow) { 1.328 + mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children 1.329 + } 1.330 + 1.331 + nsOverflowAreas overflowRects; 1.332 + 1.333 + for (nsIFrame* kid = mFrames.FirstChild(); kid; 1.334 + kid = kid->GetNextSibling()) { 1.335 + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); 1.336 + if (SVGFrame) { 1.337 + NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY), 1.338 + "Check for this explicitly in the |if|, then"); 1.339 + kid->AddStateBits(mState & NS_FRAME_IS_DIRTY); 1.340 + SVGFrame->ReflowSVG(); 1.341 + 1.342 + // We build up our child frame overflows here instead of using 1.343 + // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same 1.344 + // frame list, and we're iterating over that list now anyway. 1.345 + ConsiderChildOverflow(overflowRects, kid); 1.346 + } else { 1.347 + // Inside a non-display container frame, we might have some 1.348 + // SVGTextFrames. We need to cause those to get reflowed in 1.349 + // case they are the target of a rendering observer. 1.350 + NS_ASSERTION(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY, 1.351 + "expected kid to be a NS_FRAME_IS_NONDISPLAY frame"); 1.352 + if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) { 1.353 + nsSVGContainerFrame* container = do_QueryFrame(kid); 1.354 + if (container && container->GetContent()->IsSVG()) { 1.355 + ReflowSVGNonDisplayText(container); 1.356 + } 1.357 + } 1.358 + } 1.359 + } 1.360 + 1.361 + // <svg> can create an SVG viewport with an offset due to its 1.362 + // x/y/width/height attributes, and <use> can introduce an offset with an 1.363 + // empty mRect (any width/height is copied to an anonymous <svg> child). 1.364 + // Other than that containers should not set mRect since all other offsets 1.365 + // come from transforms, which are accounted for by nsDisplayTransform. 1.366 + // Note that we rely on |overflow:visible| to allow display list items to be 1.367 + // created for our children. 1.368 + NS_ABORT_IF_FALSE(mContent->Tag() == nsGkAtoms::svg || 1.369 + (mContent->Tag() == nsGkAtoms::use && 1.370 + mRect.Size() == nsSize(0,0)) || 1.371 + mRect.IsEqualEdges(nsRect()), 1.372 + "Only inner-<svg>/<use> is expected to have mRect set"); 1.373 + 1.374 + if (isFirstReflow) { 1.375 + // Make sure we have our filter property (if any) before calling 1.376 + // FinishAndStoreOverflow (subsequent filter changes are handled off 1.377 + // nsChangeHint_UpdateEffects): 1.378 + nsSVGEffects::UpdateEffects(this); 1.379 + } 1.380 + 1.381 + FinishAndStoreOverflow(overflowRects, mRect.Size()); 1.382 + 1.383 + // Remove state bits after FinishAndStoreOverflow so that it doesn't 1.384 + // invalidate on first reflow: 1.385 + mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 1.386 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.387 +} 1.388 + 1.389 +void 1.390 +nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags) 1.391 +{ 1.392 + NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), 1.393 + "Invalidation logic may need adjusting"); 1.394 + 1.395 + nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags); 1.396 +} 1.397 + 1.398 +SVGBBox 1.399 +nsSVGDisplayContainerFrame::GetBBoxContribution( 1.400 + const Matrix &aToBBoxUserspace, 1.401 + uint32_t aFlags) 1.402 +{ 1.403 + SVGBBox bboxUnion; 1.404 + 1.405 + nsIFrame* kid = mFrames.FirstChild(); 1.406 + while (kid) { 1.407 + nsIContent *content = kid->GetContent(); 1.408 + nsISVGChildFrame* svgKid = do_QueryFrame(kid); 1.409 + // content could be a XUL element so check for an SVG element before casting 1.410 + if (svgKid && (!content->IsSVG() || 1.411 + static_cast<const nsSVGElement*>(content)->HasValidDimensions())) { 1.412 + 1.413 + gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace); 1.414 + if (content->IsSVG()) { 1.415 + transform = static_cast<nsSVGElement*>(content)-> 1.416 + PrependLocalTransformsTo(transform); 1.417 + } 1.418 + // We need to include zero width/height vertical/horizontal lines, so we have 1.419 + // to use UnionEdges. 1.420 + bboxUnion.UnionEdges(svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags)); 1.421 + } 1.422 + kid = kid->GetNextSibling(); 1.423 + } 1.424 + 1.425 + return bboxUnion; 1.426 +}