SVGContainerFrame.cpp (17155B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // Main header first: 8 #include "SVGContainerFrame.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "ImgDrawResult.h" 12 #include "SVGAnimatedTransformList.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/RestyleManager.h" 15 #include "mozilla/SVGObserverUtils.h" 16 #include "mozilla/SVGTextFrame.h" 17 #include "mozilla/SVGUtils.h" 18 #include "mozilla/dom/SVGElement.h" 19 #include "nsCSSFrameConstructor.h" 20 21 using namespace mozilla::dom; 22 using namespace mozilla::image; 23 24 nsIFrame* NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell, 25 mozilla::ComputedStyle* aStyle) { 26 nsIFrame* frame = new (aPresShell) 27 mozilla::SVGContainerFrame(aStyle, aPresShell->GetPresContext(), 28 mozilla::SVGContainerFrame::kClassID); 29 // If we were called directly, then the frame is for a <defs> or 30 // an unknown element type. In both cases we prevent the content 31 // from displaying directly. 32 frame->AddStateBits(NS_FRAME_IS_NONDISPLAY); 33 return frame; 34 } 35 36 namespace mozilla { 37 38 NS_QUERYFRAME_HEAD(SVGContainerFrame) 39 NS_QUERYFRAME_ENTRY(SVGContainerFrame) 40 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 41 42 NS_QUERYFRAME_HEAD(SVGDisplayContainerFrame) 43 NS_QUERYFRAME_ENTRY(SVGDisplayContainerFrame) 44 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame) 45 NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame) 46 47 NS_IMPL_FRAMEARENA_HELPERS(SVGContainerFrame) 48 49 void SVGContainerFrame::AppendFrames(ChildListID aListID, 50 nsFrameList&& aFrameList) { 51 InsertFrames(aListID, mFrames.LastChild(), nullptr, std::move(aFrameList)); 52 } 53 54 void SVGContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 55 const nsLineList::iterator* aPrevFrameLine, 56 nsFrameList&& aFrameList) { 57 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 58 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 59 "inserting after sibling frame with different parent"); 60 61 mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList)); 62 } 63 64 void SVGContainerFrame::RemoveFrame(DestroyContext& aContext, 65 ChildListID aListID, nsIFrame* aOldFrame) { 66 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 67 mFrames.DestroyFrame(aContext, aOldFrame); 68 } 69 70 bool SVGContainerFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { 71 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 72 // We don't maintain overflow rects. 73 // XXX It would have be better if the restyle request hadn't even happened. 74 return false; 75 } 76 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); 77 } 78 79 /** 80 * Traverses a frame tree, marking any SVGTextFrame frames as dirty 81 * and calling InvalidateRenderingObservers() on it. 82 * 83 * The reason that this helper exists is because SVGTextFrame is special. 84 * None of the other SVG frames ever need to be reflowed when they have the 85 * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods 86 * (and those of any containers that they can validly be contained within) do 87 * not make use of mRect or overflow rects. "em" lengths, etc., are resolved 88 * as those elements are painted. 89 * 90 * SVGTextFrame is different because its anonymous block and inline frames 91 * need to be reflowed in order to get the correct metrics when things like 92 * inherited font-size of an ancestor changes, or a delayed webfont loads and 93 * applies. 94 * 95 * However, we only need to do this work if we were reflowed with 96 * NS_FRAME_IS_DIRTY, which implies that all descendants are dirty. When 97 * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally 98 * stop, but this helper looks for any SVGTextFrame descendants of such 99 * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they 100 * are painted their anonymous kid will first get the necessary reflow. 101 */ 102 /* static */ 103 void SVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) { 104 if (!aContainer->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 105 return; 106 } 107 MOZ_ASSERT(aContainer->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || 108 !aContainer->IsSVGFrame(), 109 "it is wasteful to call ReflowSVGNonDisplayText on a container " 110 "frame that is not NS_FRAME_IS_NONDISPLAY or not SVG"); 111 for (nsIFrame* kid : aContainer->PrincipalChildList()) { 112 LayoutFrameType type = kid->Type(); 113 if (type == LayoutFrameType::SVGText) { 114 static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText(); 115 } else if (kid->IsSVGContainerFrame() || 116 type == LayoutFrameType::SVGForeignObject || 117 !kid->IsSVGFrame()) { 118 ReflowSVGNonDisplayText(kid); 119 } 120 } 121 } 122 123 void SVGDisplayContainerFrame::Init(nsIContent* aContent, 124 nsContainerFrame* aParent, 125 nsIFrame* aPrevInFlow) { 126 if (!IsSVGOuterSVGFrame()) { 127 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); 128 } 129 SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); 130 } 131 132 void SVGDisplayContainerFrame::BuildDisplayList( 133 nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { 134 // content could be a XUL element so check for an SVG element 135 if (auto* svg = SVGElement::FromNode(GetContent())) { 136 if (!svg->HasValidDimensions()) { 137 return; 138 } 139 } 140 DisplayOutline(aBuilder, aLists); 141 return BuildDisplayListForNonBlockChildren(aBuilder, aLists); 142 } 143 144 void SVGDisplayContainerFrame::InsertFrames( 145 ChildListID aListID, nsIFrame* aPrevFrame, 146 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 147 // memorize first old frame after insertion point 148 // XXXbz once again, this would work a lot better if the nsIFrame 149 // methods returned framelist iterators.... 150 nsIFrame* nextFrame = aPrevFrame ? aPrevFrame->GetNextSibling() 151 : GetChildList(aListID).FirstChild(); 152 nsIFrame* firstNewFrame = aFrameList.FirstChild(); 153 154 // Insert the new frames 155 SVGContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 156 std::move(aFrameList)); 157 158 // If we are not a non-display SVG frame and we do not have a bounds update 159 // pending, then we need to schedule one for our new children: 160 if (!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN | 161 NS_FRAME_IS_NONDISPLAY)) { 162 for (nsIFrame* kid = firstNewFrame; kid != nextFrame; 163 kid = kid->GetNextSibling()) { 164 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); 165 if (SVGFrame && !kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 166 bool isFirstReflow = kid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 167 // Remove bits so that ScheduleBoundsUpdate will work: 168 kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 169 NS_FRAME_HAS_DIRTY_CHILDREN); 170 // No need to invalidate the new kid's old bounds, so we just use 171 // SVGUtils::ScheduleBoundsUpdate. 172 SVGUtils::ScheduleReflowSVG(kid); 173 if (isFirstReflow) { 174 // Add back the NS_FRAME_FIRST_REFLOW bit: 175 kid->AddStateBits(NS_FRAME_FIRST_REFLOW); 176 } 177 } 178 } 179 } 180 } 181 182 void SVGDisplayContainerFrame::RemoveFrame(DestroyContext& aContext, 183 ChildListID aListID, 184 nsIFrame* aOldFrame) { 185 SVGObserverUtils::InvalidateRenderingObservers(aOldFrame); 186 187 // SVGContainerFrame::RemoveFrame doesn't call down into 188 // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We 189 // need to schedule a repaint and schedule an update to our overflow rects. 190 // TODO(emilio, bug 2008045): It sure looks like it should just call into 191 // nsContainerFrame. 192 SchedulePaint(); 193 if (!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 194 PresContext()->RestyleManager()->PostRestyleEvent( 195 mContent->AsElement(), RestyleHint{0}, nsChangeHint_UpdateOverflow); 196 PresShell()->SynthesizeMouseMove(false); 197 } 198 199 SVGContainerFrame::RemoveFrame(aContext, aListID, aOldFrame); 200 } 201 202 bool SVGDisplayContainerFrame::DoGetParentSVGTransforms( 203 gfx::Matrix* aFromParentTransform) const { 204 return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform); 205 } 206 207 //---------------------------------------------------------------------- 208 // ISVGDisplayableFrame methods 209 210 void SVGDisplayContainerFrame::PaintSVG(gfxContext& aContext, 211 const gfxMatrix& aTransform, 212 imgDrawingParams& aImgParams) { 213 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || 214 PresContext()->Document()->IsSVGGlyphsDocument(), 215 "Only painting of non-display SVG should take this code path"); 216 217 if (StyleEffects()->IsTransparent()) { 218 return; 219 } 220 221 gfxMatrix matrix = aTransform; 222 if (auto* svg = SVGElement::FromNode(GetContent())) { 223 matrix = svg->ChildToUserSpaceTransform() * matrix; 224 if (matrix.IsSingular()) { 225 return; 226 } 227 } 228 229 for (auto* kid : mFrames) { 230 gfxMatrix m = matrix; 231 // PaintFrameWithEffects() expects the transform that is passed to it to 232 // include the transform to the passed frame's user space, so add it: 233 const nsIContent* content = kid->GetContent(); 234 if (const SVGElement* element = SVGElement::FromNode(content)) { 235 if (!element->HasValidDimensions()) { 236 continue; // nothing to paint for kid 237 } 238 239 m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m; 240 if (m.IsSingular()) { 241 continue; 242 } 243 } 244 SVGUtils::PaintFrameWithEffects(kid, aContext, m, aImgParams); 245 } 246 } 247 248 nsIFrame* SVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint& aPoint) { 249 NS_ASSERTION(HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD), 250 "Only hit-testing of a clipPath's contents should take this " 251 "code path"); 252 // First we transform aPoint into the coordinate space established by aFrame 253 // for its children (e.g. take account of any 'viewBox' attribute): 254 gfxPoint point = aPoint; 255 if (const auto* svg = SVGElement::FromNode(GetContent())) { 256 gfxMatrix m = svg->ChildToUserSpaceTransform(); 257 if (!m.IsIdentity()) { 258 if (!m.Invert()) { 259 return nullptr; 260 } 261 point = m.TransformPoint(point); 262 } 263 } 264 265 // Traverse the list in reverse order, so that if we get a hit we know that's 266 // the topmost frame that intersects the point; then we can just return it. 267 nsIFrame* result = nullptr; 268 for (nsIFrame* current = PrincipalChildList().LastChild(); current; 269 current = current->GetPrevSibling()) { 270 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(current); 271 if (!SVGFrame) { 272 continue; 273 } 274 const nsIContent* content = current->GetContent(); 275 if (const auto* svg = SVGElement::FromNode(content)) { 276 if (!svg->HasValidDimensions()) { 277 continue; 278 } 279 } 280 result = SVGFrame->GetFrameForPoint(point); 281 if (result) { 282 break; 283 } 284 } 285 286 if (result && !SVGUtils::HitTestClip(this, aPoint)) { 287 result = nullptr; 288 } 289 290 return result; 291 } 292 293 void SVGDisplayContainerFrame::ReflowSVG() { 294 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), 295 "This call is probably a wasteful mistake"); 296 297 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 298 "ReflowSVG mechanism not designed for this"); 299 300 MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>"); 301 302 if (!SVGUtils::NeedsReflowSVG(this)) { 303 return; 304 } 305 306 // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame, 307 // then our outer-<svg> has previously had its initial reflow. In that case 308 // we need to make sure that that bit has been removed from ourself _before_ 309 // recursing over our children to ensure that they know too. Otherwise, we 310 // need to remove it _after_ recursing over our children so that they know 311 // the initial reflow is currently underway. 312 313 bool isFirstReflow = HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 314 315 bool outerSVGHasHadFirstReflow = 316 !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 317 318 if (outerSVGHasHadFirstReflow) { 319 RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children 320 } 321 322 OverflowAreas overflowRects; 323 324 for (auto* kid : mFrames) { 325 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); 326 if (SVGFrame && !kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 327 SVGFrame->ReflowSVG(); 328 329 // We build up our child frame overflows here instead of using 330 // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same 331 // frame list, and we're iterating over that list now anyway. 332 ConsiderChildOverflow(overflowRects, kid); 333 } else { 334 // Inside a non-display container frame, we might have some 335 // SVGTextFrames. We need to cause those to get reflowed in 336 // case they are the target of a rendering observer. 337 MOZ_ASSERT( 338 kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || !kid->IsSVGFrame(), 339 "expected kid to be a NS_FRAME_IS_NONDISPLAY frame or not SVG"); 340 if (kid->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 341 SVGContainerFrame* container = do_QueryFrame(kid); 342 if (container && container->GetContent()->IsSVGElement()) { 343 ReflowSVGNonDisplayText(container); 344 } 345 } 346 } 347 } 348 349 // <svg> can create an SVG viewport with an offset due to its 350 // x/y/width/height attributes, and <use> can introduce an offset with an 351 // empty mRect (any width/height is copied to an anonymous <svg> child). 352 // Other than that containers should not set mRect since all other offsets 353 // come from transforms, which are accounted for by nsDisplayTransform. 354 // Note that we rely on |overflow:visible| to allow display list items to be 355 // created for our children. 356 MOZ_ASSERT(mContent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol) || 357 (mContent->IsSVGElement(nsGkAtoms::use) && 358 mRect.Size() == nsSize(0, 0)) || 359 mRect.IsEqualEdges(nsRect()), 360 "Only inner-<svg>/<use> is expected to have mRect set"); 361 362 if (isFirstReflow) { 363 // Make sure we have our filter property (if any) before calling 364 // FinishAndStoreOverflow (subsequent filter changes are handled off 365 // nsChangeHint_UpdateEffects): 366 SVGObserverUtils::UpdateEffects(this); 367 } 368 369 FinishAndStoreOverflow(overflowRects, mRect.Size()); 370 371 // Remove state bits after FinishAndStoreOverflow so that it doesn't 372 // invalidate on first reflow: 373 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 374 NS_FRAME_HAS_DIRTY_CHILDREN); 375 } 376 377 void SVGDisplayContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { 378 nsContainerFrame::DidSetComputedStyle(aOldStyle); 379 if (!aOldStyle) { 380 return; 381 } 382 if (StyleDisplay()->CalcTransformPropertyDifference( 383 *aOldStyle->StyleDisplay())) { 384 NotifySVGChanged(ChangeFlag::TransformChanged); 385 } 386 } 387 388 void SVGDisplayContainerFrame::NotifySVGChanged(ChangeFlags aFlags) { 389 MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) || 390 aFlags.contains(ChangeFlag::CoordContextChanged), 391 "Invalidation logic may need adjusting"); 392 393 if (aFlags.contains(ChangeFlag::TransformChanged)) { 394 // make sure our cached transform matrix gets (lazily) updated 395 mCanvasTM = nullptr; 396 } 397 398 SVGUtils::NotifyChildrenOfSVGChange(this, aFlags); 399 } 400 401 SVGBBox SVGDisplayContainerFrame::GetBBoxContribution( 402 const Matrix& aToBBoxUserspace, uint32_t aFlags) { 403 SVGBBox bboxUnion; 404 405 for (nsIFrame* kid : mFrames) { 406 ISVGDisplayableFrame* svgKid = do_QueryFrame(kid); 407 if (!svgKid) { 408 continue; 409 } 410 // content could be a XUL element 411 auto* svg = SVGElement::FromNode(kid->GetContent()); 412 if (svg && !svg->HasValidDimensions()) { 413 continue; 414 } 415 gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace); 416 if (svg) { 417 transform = svg->ChildToUserSpaceTransform() * 418 SVGUtils::GetTransformMatrixInUserSpace(kid) * transform; 419 } 420 // We need to include zero width/height vertical/horizontal lines, so we 421 // have to use UnionEdges. 422 bboxUnion.UnionEdges( 423 svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags)); 424 } 425 426 return bboxUnion; 427 } 428 429 gfxMatrix SVGDisplayContainerFrame::GetCanvasTM() { 430 if (!mCanvasTM) { 431 NS_ASSERTION(GetParent(), "null parent"); 432 auto* parent = static_cast<SVGContainerFrame*>(GetParent()); 433 auto* content = static_cast<SVGElement*>(GetContent()); 434 mCanvasTM = MakeUnique<gfxMatrix>(content->ChildToUserSpaceTransform() * 435 parent->GetCanvasTM()); 436 } 437 438 return *mCanvasTM; 439 } 440 441 } // namespace mozilla