SVGForeignObjectFrame.cpp (16974B)
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 "SVGForeignObjectFrame.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "ImgDrawResult.h" 12 #include "SVGGeometryProperty.h" 13 #include "gfxContext.h" 14 #include "mozilla/AutoRestore.h" 15 #include "mozilla/PresShell.h" 16 #include "mozilla/SVGContainerFrame.h" 17 #include "mozilla/SVGObserverUtils.h" 18 #include "mozilla/SVGUtils.h" 19 #include "mozilla/dom/SVGForeignObjectElement.h" 20 #include "nsDisplayList.h" 21 #include "nsGkAtoms.h" 22 #include "nsLayoutUtils.h" 23 #include "nsNameSpaceManager.h" 24 #include "nsRegion.h" 25 26 using namespace mozilla::dom; 27 using namespace mozilla::image; 28 namespace SVGT = SVGGeometryProperty::Tags; 29 30 //---------------------------------------------------------------------- 31 // Implementation 32 33 nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell, 34 mozilla::ComputedStyle* aStyle) { 35 return new (aPresShell) 36 mozilla::SVGForeignObjectFrame(aStyle, aPresShell->GetPresContext()); 37 } 38 39 namespace mozilla { 40 41 NS_IMPL_FRAMEARENA_HELPERS(SVGForeignObjectFrame) 42 43 SVGForeignObjectFrame::SVGForeignObjectFrame(ComputedStyle* aStyle, 44 nsPresContext* aPresContext) 45 : nsContainerFrame(aStyle, aPresContext, kClassID) { 46 AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED | 47 NS_FRAME_SVG_LAYOUT | NS_FRAME_FONT_INFLATION_CONTAINER | 48 NS_FRAME_FONT_INFLATION_FLOW_ROOT); 49 } 50 51 //---------------------------------------------------------------------- 52 // nsIFrame methods 53 54 NS_QUERYFRAME_HEAD(SVGForeignObjectFrame) 55 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame) 56 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 57 58 void SVGForeignObjectFrame::Init(nsIContent* aContent, 59 nsContainerFrame* aParent, 60 nsIFrame* aPrevInFlow) { 61 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::foreignObject), 62 "Content is not an SVG foreignObject!"); 63 64 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 65 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); 66 } 67 68 nsresult SVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID, 69 nsAtom* aAttribute, 70 AttrModType) { 71 if (aNameSpaceID == kNameSpaceID_None) { 72 if (aAttribute == nsGkAtoms::transform) { 73 // We don't invalidate for transform changes (the layers code does that). 74 // Also note that SVGTransformableElement::GetAttributeChangeHint will 75 // return nsChangeHint_UpdateOverflow for "transform" attribute changes 76 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. 77 mCanvasTM = nullptr; 78 } else if (aAttribute == nsGkAtoms::viewBox || 79 aAttribute == nsGkAtoms::preserveAspectRatio) { 80 nsLayoutUtils::PostRestyleEvent( 81 mContent->AsElement(), RestyleHint{0}, 82 nsChangeHint_InvalidateRenderingObservers); 83 } 84 } 85 86 return NS_OK; 87 } 88 89 void SVGForeignObjectFrame::DidSetComputedStyle( 90 ComputedStyle* aOldComputedStyle) { 91 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); 92 93 if (aOldComputedStyle) { 94 if (StyleSVGReset()->mX != aOldComputedStyle->StyleSVGReset()->mX || 95 StyleSVGReset()->mY != aOldComputedStyle->StyleSVGReset()->mY) { 96 // Invalidate cached transform matrix. 97 mCanvasTM = nullptr; 98 SVGUtils::ScheduleReflowSVG(this); 99 } 100 } 101 } 102 103 void SVGForeignObjectFrame::Reflow(nsPresContext* aPresContext, 104 ReflowOutput& aDesiredSize, 105 const ReflowInput& aReflowInput, 106 nsReflowStatus& aStatus) { 107 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 108 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 109 "Should not have been called"); 110 111 // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY, 112 // so if that bit is still set we still have a resize pending. If we hit 113 // this assertion, then we should get the presShell to skip reflow roots 114 // that have a dirty parent since a reflow is going to come via the 115 // reflow root's parent anyway. 116 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY), 117 "Reflowing while a resize is pending is wasteful"); 118 119 // ReflowSVG makes sure mRect is up to date before we're called. 120 121 NS_ASSERTION(!aReflowInput.mParentReflowInput, 122 "should only get reflow from being reflow root"); 123 NS_ASSERTION(aReflowInput.ComputedSize() == GetLogicalSize(), 124 "reflow roots should be reflowed at existing size and " 125 "svg.css should ensure we have no padding/border/margin"); 126 127 DoReflow(); 128 129 WritingMode wm = aReflowInput.GetWritingMode(); 130 LogicalSize finalSize(wm, aReflowInput.ComputedISize(), 131 aReflowInput.ComputedBSize()); 132 aDesiredSize.SetSize(wm, finalSize); 133 aDesiredSize.SetOverflowAreasToDesiredBounds(); 134 } 135 136 void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 137 const nsDisplayListSet& aLists) { 138 if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) { 139 return; 140 } 141 nsDisplayList newList(aBuilder); 142 nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList, 143 &newList); 144 DisplayOutline(aBuilder, set); 145 BuildDisplayListForNonBlockChildren(aBuilder, set); 146 aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this, 147 &newList); 148 } 149 150 bool SVGForeignObjectFrame::DoGetParentSVGTransforms( 151 Matrix* aFromParentTransform) const { 152 return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform); 153 } 154 155 void SVGForeignObjectFrame::PaintSVG(gfxContext& aContext, 156 const gfxMatrix& aTransform, 157 imgDrawingParams& aImgParams) { 158 NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 159 "Only painting of non-display SVG should take this code path"); 160 161 if (IsDisabled()) { 162 return; 163 } 164 165 nsIFrame* kid = PrincipalChildList().FirstChild(); 166 if (!kid) { 167 return; 168 } 169 170 if (aTransform.IsSingular()) { 171 NS_WARNING("Can't render foreignObject element!"); 172 return; 173 } 174 175 gfxClipAutoSaveRestore autoSaveClip(&aContext); 176 177 if (StyleDisplay()->IsScrollableOverflow()) { 178 float x, y, width, height; 179 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, 180 SVGT::Height>( 181 static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height); 182 183 gfxRect clipRect = 184 SVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height); 185 autoSaveClip.TransformedClip(aTransform, clipRect); 186 } 187 188 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we 189 // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children 190 // paint correctly. 191 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 192 PresContext()->AppUnitsPerDevPixel()); 193 gfxMatrix canvasTMForChildren = aTransform; 194 canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx); 195 196 aContext.Multiply(canvasTMForChildren); 197 198 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; 199 PaintFrameFlags flags = PaintFrameFlags::InTransform; 200 if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) { 201 flags |= PaintFrameFlags::ToWindow; 202 } 203 if (aImgParams.imageFlags & imgIContainer::FLAG_SYNC_DECODE) { 204 flags |= PaintFrameFlags::SyncDecodeImages; 205 } 206 if (aImgParams.imageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) { 207 flags |= PaintFrameFlags::UseHighQualityScaling; 208 } 209 nsLayoutUtils::PaintFrame(&aContext, kid, nsRegion(kid->InkOverflowRect()), 210 NS_RGBA(0, 0, 0, 0), 211 nsDisplayListBuilderMode::Painting, flags); 212 } 213 214 nsIFrame* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) { 215 MOZ_ASSERT_UNREACHABLE( 216 "A clipPath cannot contain an SVGForeignObject element"); 217 return nullptr; 218 } 219 220 void SVGForeignObjectFrame::ReflowSVG() { 221 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), 222 "This call is probably a wasteful mistake"); 223 224 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 225 "ReflowSVG mechanism not designed for this"); 226 227 if (!SVGUtils::NeedsReflowSVG(this)) { 228 return; 229 } 230 231 // We update mRect before the DoReflow call so that DoReflow uses the 232 // correct dimensions: 233 234 float x, y, w, h; 235 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( 236 static_cast<SVGElement*>(GetContent()), &x, &y, &w, &h); 237 238 // If mRect's width or height are negative, reflow blows up! We must clamp! 239 if (w < 0.0f) { 240 w = 0.0f; 241 } 242 if (h < 0.0f) { 243 h = 0.0f; 244 } 245 246 mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, w, h), 247 AppUnitsPerCSSPixel()); 248 249 // Fully mark our kid dirty so that it gets resized if necessary 250 // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case): 251 nsIFrame* kid = PrincipalChildList().FirstChild(); 252 kid->MarkSubtreeDirty(); 253 254 // Make sure to not allow interrupts if we're not being reflown as a root: 255 nsPresContext::InterruptPreventer noInterrupts(PresContext()); 256 257 DoReflow(); 258 259 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 260 // Make sure we have our filter property (if any) before calling 261 // FinishAndStoreOverflow (subsequent filter changes are handled off 262 // nsChangeHint_UpdateEffects): 263 SVGObserverUtils::UpdateEffects(this); 264 } 265 266 // If we have a filter, we need to invalidate ourselves because filter 267 // output can change even if none of our descendants need repainting. 268 if (StyleEffects()->HasFilters()) { 269 InvalidateFrame(); 270 } 271 272 auto* anonKid = PrincipalChildList().FirstChild(); 273 nsRect overflow = anonKid->InkOverflowRect(); 274 275 OverflowAreas overflowAreas(overflow, overflow); 276 FinishAndStoreOverflow(overflowAreas, mRect.Size()); 277 278 // Now unset the various reflow bits: 279 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 280 NS_FRAME_HAS_DIRTY_CHILDREN); 281 } 282 283 void SVGForeignObjectFrame::NotifySVGChanged(ChangeFlags aFlags) { 284 MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) || 285 aFlags.contains(ChangeFlag::CoordContextChanged), 286 "Invalidation logic may need adjusting"); 287 288 bool needNewBounds = false; // i.e. mRect or ink overflow rect 289 bool needReflow = false; 290 bool needNewCanvasTM = false; 291 292 if (aFlags.contains(ChangeFlag::CoordContextChanged)) { 293 // Coordinate context changes affect mCanvasTM if we have a 294 // percentage 'x' or 'y' 295 if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) { 296 needNewBounds = true; 297 needNewCanvasTM = true; 298 } 299 300 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 301 // Our coordinate context's width/height has changed. If we have a 302 // percentage width/height our dimensions will change so we must reflow. 303 if (StylePosition()->GetWidth(anchorResolutionParams)->HasPercent() || 304 StylePosition()->GetHeight(anchorResolutionParams)->HasPercent()) { 305 needNewBounds = true; 306 needReflow = true; 307 } 308 } 309 310 if (aFlags.contains(ChangeFlag::TransformChanged)) { 311 if (mCanvasTM && mCanvasTM->IsSingular()) { 312 needNewBounds = true; // old bounds are bogus 313 } 314 needNewCanvasTM = true; 315 // In an ideal world we would reflow when our CTM changes. This is because 316 // glyph metrics do not necessarily scale uniformly with change in scale 317 // and, as a result, CTM changes may require text to break at different 318 // points. The problem would be how to keep performance acceptable when 319 // e.g. the transform of an ancestor is animated. 320 // We also seem to get some sort of infinite loop post bug 421584 if we 321 // reflow. 322 } 323 324 if (needNewBounds) { 325 // Ancestor changes can't affect how we render from the perspective of 326 // any rendering observers that we may have, so we don't need to 327 // invalidate them. We also don't need to invalidate ourself, since our 328 // changed ancestor will have invalidated its entire area, which includes 329 // our area. 330 SVGUtils::ScheduleReflowSVG(this); 331 } 332 333 // If we're called while the PresShell is handling reflow events then we 334 // must have been called as a result of the NotifyViewportChange() call in 335 // our SVGOuterSVGFrame's Reflow() method. We must not call RequestReflow 336 // at this point (i.e. during reflow) because it could confuse the 337 // PresShell and prevent it from reflowing us properly in future. Besides 338 // that, SVGOuterSVGFrame::DidReflow will take care of reflowing us 339 // synchronously, so there's no need. 340 if (needReflow && !PresShell()->IsReflowLocked()) { 341 RequestReflow(IntrinsicDirty::None); 342 } 343 344 if (needNewCanvasTM) { 345 // Do this after calling InvalidateAndScheduleBoundsUpdate in case we 346 // change the code and it needs to use it. 347 mCanvasTM = nullptr; 348 } 349 } 350 351 SVGBBox SVGForeignObjectFrame::GetBBoxContribution( 352 const Matrix& aToBBoxUserspace, uint32_t aFlags) { 353 SVGForeignObjectElement* content = 354 static_cast<SVGForeignObjectElement*>(GetContent()); 355 356 float x, y, w, h; 357 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>( 358 content, &x, &y, &w, &h); 359 360 if (w < 0.0f) { 361 w = 0.0f; 362 } 363 if (h < 0.0f) { 364 h = 0.0f; 365 } 366 367 if (aToBBoxUserspace.IsSingular()) { 368 // XXX ReportToConsole 369 return SVGBBox(); 370 } 371 return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h)); 372 } 373 374 //---------------------------------------------------------------------- 375 376 gfxMatrix SVGForeignObjectFrame::GetCanvasTM() { 377 if (!mCanvasTM) { 378 NS_ASSERTION(GetParent(), "null parent"); 379 auto* parent = static_cast<SVGContainerFrame*>(GetParent()); 380 auto* content = static_cast<SVGForeignObjectElement*>(GetContent()); 381 mCanvasTM = MakeUnique<gfxMatrix>(content->ChildToUserSpaceTransform() * 382 parent->GetCanvasTM()); 383 } 384 return *mCanvasTM; 385 } 386 387 //---------------------------------------------------------------------- 388 // Implementation helpers 389 390 void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType) { 391 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 392 // If we haven't had a ReflowSVG() yet, nothing to do. 393 return; 394 } 395 396 nsIFrame* kid = PrincipalChildList().FirstChild(); 397 if (!kid) { 398 return; 399 } 400 401 PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY); 402 } 403 404 void SVGForeignObjectFrame::DoReflow() { 405 MarkInReflow(); 406 // Skip reflow if we're zero-sized, unless this is our first reflow. 407 if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 408 return; 409 } 410 411 nsPresContext* presContext = PresContext(); 412 nsIFrame* kid = PrincipalChildList().FirstChild(); 413 if (!kid) { 414 return; 415 } 416 417 // initiate a synchronous reflow here and now: 418 UniquePtr<gfxContext> renderingContext = 419 presContext->PresShell()->CreateReferenceRenderingContext(); 420 421 WritingMode wm = kid->GetWritingMode(); 422 ReflowInput reflowInput(presContext, kid, renderingContext.get(), 423 LogicalSize(wm, ISize(wm), NS_UNCONSTRAINEDSIZE)); 424 ReflowOutput desiredSize(reflowInput); 425 nsReflowStatus status; 426 427 // We don't use mRect.height above because that tells the child to do 428 // page/column breaking at that height. 429 NS_ASSERTION( 430 reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && 431 reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), 432 "style system should ensure that :-moz-svg-foreign-content " 433 "does not get styled"); 434 NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm), 435 "reflow input made child wrong size"); 436 reflowInput.SetComputedBSize(BSize(wm)); 437 438 ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0, 439 ReflowChildFlags::NoMoveFrame, status); 440 NS_ASSERTION(mRect.width == desiredSize.Width() && 441 mRect.height == desiredSize.Height(), 442 "unexpected size"); 443 FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0, 444 ReflowChildFlags::NoMoveFrame); 445 } 446 447 void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes( 448 nsTArray<OwnedAnonBox>& aResult) { 449 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box"); 450 aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild())); 451 } 452 453 } // namespace mozilla