SVGOuterSVGFrame.cpp (33526B)
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 "SVGOuterSVGFrame.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "gfxContext.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/SVGUtils.h" 14 #include "mozilla/dom/BrowserChild.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Element.h" 17 #include "mozilla/dom/SVGSVGElement.h" 18 #include "nsDisplayList.h" 19 #include "nsIInterfaceRequestorUtils.h" 20 #include "nsLayoutUtils.h" 21 #include "nsObjectLoadingContent.h" 22 #include "nsSubDocumentFrame.h" 23 24 using namespace mozilla::dom; 25 using namespace mozilla::gfx; 26 using namespace mozilla::image; 27 28 //---------------------------------------------------------------------- 29 // Implementation 30 31 nsContainerFrame* NS_NewSVGOuterSVGFrame(mozilla::PresShell* aPresShell, 32 mozilla::ComputedStyle* aStyle) { 33 return new (aPresShell) 34 mozilla::SVGOuterSVGFrame(aStyle, aPresShell->GetPresContext()); 35 } 36 37 namespace mozilla { 38 39 NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGFrame) 40 41 SVGOuterSVGFrame::SVGOuterSVGFrame(ComputedStyle* aStyle, 42 nsPresContext* aPresContext) 43 : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) { 44 // Outer-<svg> has CSS layout, so remove this bit: 45 RemoveStateBits(NS_FRAME_SVG_LAYOUT); 46 AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_FONT_INFLATION_CONTAINER | 47 NS_FRAME_FONT_INFLATION_FLOW_ROOT); 48 } 49 50 // The CSS Containment spec says that size-contained replaced elements must be 51 // treated as having an intrinsic width and height of 0. That's applicable to 52 // outer SVG frames, unless they're the outermost element (in which case 53 // they're not really "replaced", and there's no outer context to contain sizes 54 // from leaking into). Hence, we check for a parent element before we bother 55 // testing for 'contain:size'. 56 static inline ContainSizeAxes ContainSizeAxesIfApplicable( 57 const SVGOuterSVGFrame* aFrame) { 58 if (!aFrame->GetContent()->GetParent()) { 59 return ContainSizeAxes(false, false); 60 } 61 return aFrame->GetContainSizeAxes(); 62 } 63 64 // This should match ImageDocument::GetZoomLevel. 65 float SVGOuterSVGFrame::ComputeFullZoom() const { 66 MOZ_ASSERT(mIsRootContent); 67 MOZ_ASSERT(!mIsInIframe); 68 if (BrowsingContext* bc = PresContext()->Document()->GetBrowsingContext()) { 69 return bc->FullZoom(); 70 } 71 return 1.0f; 72 } 73 74 class AsyncSendIntrinsicSizeAndRatioToEmbedder final : public Runnable { 75 public: 76 explicit AsyncSendIntrinsicSizeAndRatioToEmbedder(SVGOuterSVGFrame* aFrame) 77 : Runnable("AsyncSendIntrinsicSizeAndRatioToEmbedder") { 78 mElement = aFrame->GetContent()->AsElement(); 79 } 80 NS_IMETHOD Run() override { 81 AUTO_PROFILER_LABEL("AsyncSendIntrinsicSizeAndRatioToEmbedder::Run", OTHER); 82 // Check we're still an outer svg frame. We could have been 83 // moved inside another svg element and now be an SVGInnerSVGFrame. 84 if (SVGOuterSVGFrame* frame = do_QueryFrame(mElement->GetPrimaryFrame())) { 85 frame->MaybeSendIntrinsicSizeAndRatioToEmbedder(); 86 } 87 return NS_OK; 88 } 89 90 private: 91 RefPtr<Element> mElement; 92 }; 93 94 void SVGOuterSVGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 95 nsIFrame* aPrevInFlow) { 96 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), 97 "Content is not an SVG 'svg' element!"); 98 99 // Check for conditional processing attributes here rather than in 100 // nsCSSFrameConstructor::FindSVGData because we want to avoid 101 // simply giving failing outer <svg> elements an SVGContainerFrame. 102 // We don't create other SVG frames if PassesConditionalProcessingTests 103 // returns false, but since we do create SVGOuterSVGFrame frames we 104 // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The 105 // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if 106 // the value returned by PassesConditionalProcessingTests changes. 107 auto* svg = static_cast<SVGSVGElement*>(aContent); 108 if (!svg->PassesConditionalProcessingTests()) { 109 AddStateBits(NS_FRAME_IS_NONDISPLAY); 110 } 111 112 SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); 113 114 Document* doc = mContent->GetUncomposedDoc(); 115 mIsRootContent = doc && doc->GetRootElement() == mContent; 116 117 if (mIsRootContent) { 118 if (nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell()) { 119 RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext(); 120 if (const Maybe<nsString>& type = bc->GetEmbedderElementType()) { 121 mIsInObjectOrEmbed = 122 nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type); 123 mIsInIframe = nsGkAtoms::iframe->Equals(*type); 124 } 125 } 126 if (!mIsInIframe) { 127 mFullZoom = ComputeFullZoom(); 128 } 129 } 130 131 // We need to do this async in order to get the right ordering with 132 // respect to `Destroy()` when reframed. 133 nsContentUtils::AddScriptRunner( 134 new AsyncSendIntrinsicSizeAndRatioToEmbedder(this)); 135 } 136 137 //---------------------------------------------------------------------- 138 // nsQueryFrame methods 139 140 NS_QUERYFRAME_HEAD(SVGOuterSVGFrame) 141 NS_QUERYFRAME_ENTRY(SVGOuterSVGFrame) 142 NS_QUERYFRAME_ENTRY(ISVGSVGFrame) 143 NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame) 144 145 //---------------------------------------------------------------------- 146 // nsIFrame methods 147 148 nscoord SVGOuterSVGFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 149 IntrinsicISizeType aType) { 150 if (aType == IntrinsicISizeType::MinISize) { 151 return GetIntrinsicSize().ISize(GetWritingMode()).valueOr(0); 152 } 153 154 nscoord result; 155 SVGSVGElement* svg = static_cast<SVGSVGElement*>(GetContent()); 156 WritingMode wm = GetWritingMode(); 157 const SVGAnimatedLength& isize = 158 wm.IsVertical() ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT] 159 : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; 160 161 if (Maybe<nscoord> containISize = 162 ContainSizeAxesIfApplicable(this).ContainIntrinsicISize(*this)) { 163 result = *containISize; 164 } else if (isize.IsPercentage()) { 165 // If we are here, our inline size attribute is a percentage either 166 // explicitly (via an attribute value) or implicitly (by being unset, which 167 // is treated as 100%). The following if-condition, deciding to return 168 // either the fallback intrinsic size or zero, is made to match blink and 169 // webkit's behavior for webcompat. 170 if (isize.IsExplicitlySet() || 171 StylePosition() 172 ->ISize(wm, AnchorPosResolutionParams::From(this)) 173 ->HasPercent() || 174 !GetAspectRatio()) { 175 result = wm.IsVertical() ? kFallbackIntrinsicSize.height 176 : kFallbackIntrinsicSize.width; 177 } else { 178 result = nscoord(0); 179 } 180 } else { 181 result = 182 nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValueWithZoom(svg)); 183 if (result < 0) { 184 result = nscoord(0); 185 } 186 } 187 188 return result; 189 } 190 191 /* virtual */ 192 IntrinsicSize SVGOuterSVGFrame::GetIntrinsicSize() { 193 // XXXjwatt Note that here we want to return the CSS width/height if they're 194 // specified and we're embedded inside an nsIObjectLoadingContent. 195 196 const auto containAxes = ContainSizeAxesIfApplicable(this); 197 if (containAxes.IsBoth()) { 198 // Intrinsic size of 'contain:size' replaced elements is determined by 199 // contain-intrinsic-size, defaulting to 0x0. 200 return FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0)); 201 } 202 203 SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent()); 204 const SVGAnimatedLength& width = 205 content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; 206 const SVGAnimatedLength& height = 207 content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; 208 209 IntrinsicSize intrinsicSize; 210 211 if (!width.IsPercentage()) { 212 nscoord val = 213 nsPresContext::CSSPixelsToAppUnits(width.GetAnimValueWithZoom(content)); 214 intrinsicSize.width.emplace(std::max(val, 0)); 215 } 216 217 if (!height.IsPercentage()) { 218 nscoord val = nsPresContext::CSSPixelsToAppUnits( 219 height.GetAnimValueWithZoom(content)); 220 intrinsicSize.height.emplace(std::max(val, 0)); 221 } 222 223 return FinishIntrinsicSize(containAxes, intrinsicSize); 224 } 225 226 /* virtual */ 227 AspectRatio SVGOuterSVGFrame::GetIntrinsicRatio() const { 228 if (ContainSizeAxesIfApplicable(this).IsAny()) { 229 return AspectRatio(); 230 } 231 232 // We only have an intrinsic size/ratio if our width and height attributes 233 // are both specified and set to non-percentage values, or we have a viewBox 234 // rect: https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS 235 236 auto* content = static_cast<SVGSVGElement*>(GetContent()); 237 const SVGAnimatedLength& width = 238 content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; 239 const SVGAnimatedLength& height = 240 content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; 241 if (!width.IsPercentage() && !height.IsPercentage()) { 242 // Use width/height ratio only if 243 // 1. it's not a degenerate ratio, and 244 // 2. width and height are non-negative numbers. 245 // Otherwise, we use the viewbox rect. 246 // https://github.com/w3c/csswg-drafts/issues/6286 247 // Note width/height may have different units and therefore be 248 // affected by zoom in different ways. 249 const float w = width.GetAnimValueWithZoom(content); 250 const float h = height.GetAnimValueWithZoom(content); 251 if (w > 0.0f && h > 0.0f) { 252 return AspectRatio::FromSize(w, h); 253 } 254 } 255 256 const auto& viewBox = content->GetViewBoxInternal(); 257 if (viewBox.HasRect()) { 258 float zoom = Style()->EffectiveZoom().ToFloat(); 259 const auto& anim = viewBox.GetAnimValue() * zoom; 260 return AspectRatio::FromSize(anim.width, anim.height); 261 } 262 263 return SVGDisplayContainerFrame::GetIntrinsicRatio(); 264 } 265 266 /* virtual */ 267 nsIFrame::SizeComputationResult SVGOuterSVGFrame::ComputeSize( 268 const SizeComputationInput& aSizingInput, WritingMode aWritingMode, 269 const LogicalSize& aCBSize, nscoord aAvailableISize, 270 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 271 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 272 if (IsRootOfImage() || mIsInObjectOrEmbed) { 273 // The embedding element has sized itself using the CSS replaced element 274 // sizing rules, using our intrinsic dimensions as necessary. The SVG spec 275 // says that the width and height of embedded SVG is overridden by the 276 // width and height of the embedding element, so we just need to size to 277 // the viewport that the embedding element has established for us. 278 return {aCBSize, AspectRatioUsage::None}; 279 } 280 281 LogicalSize cbSize = aCBSize; 282 IntrinsicSize intrinsicSize = GetIntrinsicSize(); 283 284 if (mIsRootContent) { 285 // We're the root of the outermost browsing context, so we need to scale 286 // cbSize by the full-zoom so that SVGs with percentage width/height zoom: 287 288 NS_ASSERTION(aCBSize.ISize(aWritingMode) != NS_UNCONSTRAINEDSIZE && 289 aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE, 290 "root should not have auto-width/height containing block"); 291 292 if (!mIsInIframe) { 293 // NOTE: We can't just use mFullZoom because this can run before Reflow() 294 // updates it. 295 const float zoom = ComputeFullZoom(); 296 cbSize.ISize(aWritingMode) *= zoom; 297 cbSize.BSize(aWritingMode) *= zoom; 298 } 299 300 // We also need to honour the width and height attributes' default values 301 // of 100% when we're the root of a browsing context. (GetIntrinsicSize() 302 // doesn't report these since there's no such thing as a percentage 303 // intrinsic size. Also note that explicit percentage values are mapped 304 // into style, so the following isn't for them.) 305 306 auto* content = static_cast<SVGSVGElement*>(GetContent()); 307 308 const SVGAnimatedLength& width = 309 content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; 310 if (width.IsPercentage()) { 311 MOZ_ASSERT(!intrinsicSize.width, 312 "GetIntrinsicSize should have reported no intrinsic width"); 313 float val = width.GetAnimValInSpecifiedUnits() / 100.0f; 314 intrinsicSize.width.emplace(std::max(val, 0.0f) * 315 cbSize.Width(aWritingMode)); 316 } 317 318 const SVGAnimatedLength& height = 319 content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; 320 NS_ASSERTION(aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE, 321 "root should not have auto-height containing block"); 322 if (height.IsPercentage()) { 323 MOZ_ASSERT(!intrinsicSize.height, 324 "GetIntrinsicSize should have reported no intrinsic height"); 325 float val = height.GetAnimValInSpecifiedUnits() / 100.0f; 326 intrinsicSize.height.emplace(std::max(val, 0.0f) * 327 cbSize.Height(aWritingMode)); 328 } 329 MOZ_ASSERT(intrinsicSize.height && intrinsicSize.width, 330 "We should have just handled the only situation where" 331 "we lack an intrinsic height or width."); 332 } 333 334 return {ComputeSizeWithIntrinsicDimensions( 335 aSizingInput.mRenderingContext, aWritingMode, intrinsicSize, 336 GetAspectRatio(), cbSize, aMargin, aBorderPadding, aSizeOverrides, 337 aFlags), 338 AspectRatioUsage::None}; 339 } 340 341 void SVGOuterSVGFrame::Reflow(nsPresContext* aPresContext, 342 ReflowOutput& aDesiredSize, 343 const ReflowInput& aReflowInput, 344 nsReflowStatus& aStatus) { 345 MarkInReflow(); 346 DO_GLOBAL_REFLOW_COUNT("SVGOuterSVGFrame"); 347 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 348 NS_FRAME_TRACE( 349 NS_FRAME_TRACE_CALLS, 350 ("enter SVGOuterSVGFrame::Reflow: availSize=%d,%d", 351 aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); 352 353 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow"); 354 355 const auto wm = GetWritingMode(); 356 aDesiredSize.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm)); 357 358 NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages."); 359 360 SVGSVGElement* svgElem = static_cast<SVGSVGElement*>(GetContent()); 361 362 auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( 363 PrincipalChildList().FirstChild()); 364 365 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 366 // Initialize 367 svgElem->UpdateHasChildrenOnlyTransform(); 368 } 369 370 // If our SVG viewport has changed, update our content and notify. 371 // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace 372 373 gfx::Size newViewportSize( 374 nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedWidth()), 375 nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedHeight())); 376 377 ChangeFlags changeBits; 378 if (newViewportSize != svgElem->GetViewportSize()) { 379 // When our viewport size changes, we may need to update the overflow rects 380 // of our child frames. This is the case if: 381 // 382 // * We have a real/synthetic viewBox (a children-only transform), since 383 // the viewBox transform will change as the viewport dimensions change. 384 // 385 // * We do not have a real/synthetic viewBox, but the last time we 386 // reflowed (or the last time UpdateOverflow() was called) we did. 387 // 388 // We only handle the former case here, in which case we mark all our child 389 // frames as dirty so that we reflow them below and update their overflow 390 // rects. 391 // 392 // In the latter case, updating of overflow rects is handled for removal of 393 // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic 394 // viewBox "removal" (e.g. a document references the same SVG via both an 395 // <svg:image> and then as a CSS background image (a synthetic viewBox is 396 // used when painting the former, but not when painting the latter)) is 397 // handled in SVGSVGElement::FlushImageTransformInvalidation. 398 // 399 if (svgElem->HasViewBoxOrSyntheticViewBox()) { 400 nsIFrame* anonChild = PrincipalChildList().FirstChild(); 401 anonChild->MarkSubtreeDirty(); 402 for (nsIFrame* child : anonChild->PrincipalChildList()) { 403 child->MarkSubtreeDirty(); 404 } 405 } 406 changeBits += ChangeFlag::CoordContextChanged; 407 svgElem->SetViewportSize(newViewportSize); 408 } 409 if (mIsRootContent && !mIsInIframe) { 410 const auto oldZoom = mFullZoom; 411 mFullZoom = ComputeFullZoom(); 412 if (oldZoom != mFullZoom) { 413 changeBits += ChangeFlag::FullZoomChanged; 414 } 415 } 416 if (!changeBits.isEmpty() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 417 NotifyViewportOrTransformChanged(changeBits); 418 } 419 420 // Now that we've marked the necessary children as dirty, call 421 // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending 422 // on whether we are non-display. 423 mCallingReflowSVG = true; 424 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 425 ReflowSVGNonDisplayText(this); 426 } else { 427 // Update the mRects and ink overflow rects of all our descendants, 428 // including our anonymous wrapper kid: 429 anonKid->ReflowSVG(); 430 MOZ_ASSERT(!anonKid->GetNextSibling(), 431 "We should have one anonymous child frame wrapping our real " 432 "children"); 433 } 434 mCallingReflowSVG = false; 435 436 // Set our anonymous kid's offset from our border box: 437 anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft()); 438 439 // Including our size in our overflow rects regardless of the value of 440 // 'background', 'border', etc. makes sure that we usually (when we clip to 441 // our content area) don't have to keep changing our overflow rects as our 442 // descendants move about (see perf comment below). Including our size in our 443 // scrollable overflow rect also makes sure that we scroll if we're too big 444 // for our viewport. 445 // 446 // <svg> never allows scrolling to anything outside its mRect (only panning), 447 // so we must always keep our scrollable overflow set to our size. 448 // 449 // With regards to ink overflow, we always clip root-<svg> (see our 450 // BuildDisplayList method) regardless of the value of the 'overflow' 451 // property since that is per-spec, even for the initial 'visible' value. For 452 // that reason there's no point in adding descendant ink overflow to our 453 // own when this frame is for a root-<svg>. That said, there's also a very 454 // good performance reason for us wanting to avoid doing so. If we did, then 455 // the frame's overflow would often change as descendants that are partially 456 // or fully outside its rect moved (think animation on/off screen), and that 457 // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the 458 // entire document tree each such move (see bug 875175). 459 // 460 // So it's only non-root outer-<svg> that has the ink overflow of its 461 // descendants added to its own. (Note that the default user-agent style 462 // sheet makes 'hidden' the default value for :not(root(svg)), so usually 463 // FinishAndStoreOverflow will still clip this back to the frame's rect.) 464 // 465 // WARNING!! Keep UpdateBounds below in sync with whatever we do for our 466 // overflow rects here! (Again, see bug 875175.) 467 // 468 aDesiredSize.SetOverflowAreasToDesiredBounds(); 469 470 // An outer SVG will be here as a nondisplay if it fails the conditional 471 // processing test. In that case, we don't maintain its overflow. 472 if (!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 473 if (!mIsRootContent) { 474 aDesiredSize.mOverflowAreas.InkOverflow().UnionRect( 475 aDesiredSize.mOverflowAreas.InkOverflow(), 476 anonKid->InkOverflowRect() + anonKid->GetPosition()); 477 } 478 FinishAndStoreOverflow(&aDesiredSize); 479 } 480 481 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, 482 ("exit SVGOuterSVGFrame::Reflow: size=%d,%d", 483 aDesiredSize.Width(), aDesiredSize.Height())); 484 } 485 486 /* virtual */ 487 void SVGOuterSVGFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas, 488 bool aAsIfScrolled) { 489 // See the comments in Reflow above. 490 491 // WARNING!! Keep this in sync with Reflow above! 492 493 if (!mIsRootContent) { 494 nsIFrame* anonKid = PrincipalChildList().FirstChild(); 495 aOverflowAreas.InkOverflow().UnionRect( 496 aOverflowAreas.InkOverflow(), 497 anonKid->InkOverflowRect() + anonKid->GetPosition()); 498 } 499 } 500 501 //---------------------------------------------------------------------- 502 // container methods 503 504 nsresult SVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID, 505 nsAtom* aAttribute, AttrModType) { 506 if (aNameSpaceID == kNameSpaceID_None && 507 !HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY)) { 508 if (aAttribute == nsGkAtoms::viewBox || 509 aAttribute == nsGkAtoms::preserveAspectRatio) { 510 // make sure our cached transform matrix gets (lazily) updated 511 mCanvasTM = nullptr; 512 513 SVGUtils::NotifyChildrenOfSVGChange( 514 PrincipalChildList().FirstChild(), 515 aAttribute == nsGkAtoms::viewBox 516 ? ChangeFlags(ChangeFlag::TransformChanged, 517 ChangeFlag::CoordContextChanged) 518 : ChangeFlag::TransformChanged); 519 520 if (aAttribute != nsGkAtoms::transform) { 521 static_cast<SVGSVGElement*>(GetContent()) 522 ->ChildrenOnlyTransformChanged(); 523 } 524 } 525 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || 526 aAttribute == nsGkAtoms::viewBox) { 527 // Don't call ChildrenOnlyTransformChanged() here, since we call it 528 // under Reflow if the width/height/viewBox actually changed. 529 530 MaybeSendIntrinsicSizeAndRatioToEmbedder(); 531 532 if (!mIsInObjectOrEmbed) { 533 // We are not embedded by reference, so our 'width' and 'height' 534 // attributes are not overridden (and viewBox may influence our 535 // intrinsic aspect ratio). We need to reflow. 536 PresShell()->FrameNeedsReflow( 537 this, IntrinsicDirty::FrameAncestorsAndDescendants, 538 NS_FRAME_IS_DIRTY); 539 } 540 } 541 } 542 543 return NS_OK; 544 } 545 546 //---------------------------------------------------------------------- 547 // painting 548 549 void SVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 550 const nsDisplayListSet& aLists) { 551 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 552 return; 553 } 554 555 DisplayBorderBackgroundOutline(aBuilder, aLists); 556 557 nsRect visibleRect = aBuilder->GetVisibleRect(); 558 nsRect dirtyRect = aBuilder->GetDirtyRect(); 559 560 // Per-spec, we always clip root-<svg> even when 'overflow' has its initial 561 // value of 'visible'. See also the "ink overflow" comments in Reflow. 562 DisplayListClipState::AutoSaveRestore autoSR(aBuilder); 563 if (mIsRootContent || StyleDisplay()->IsScrollableOverflow()) { 564 autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this); 565 visibleRect = visibleRect.Intersect(GetContentRectRelativeToSelf()); 566 dirtyRect = dirtyRect.Intersect(GetContentRectRelativeToSelf()); 567 } 568 569 nsDisplayListBuilder::AutoBuildingDisplayList building( 570 aBuilder, this, visibleRect, dirtyRect); 571 572 nsDisplayList* contentList = aLists.Content(); 573 nsDisplayListSet set(contentList, contentList, contentList, contentList, 574 contentList, contentList); 575 BuildDisplayListForNonBlockChildren(aBuilder, set); 576 } 577 578 //---------------------------------------------------------------------- 579 // ISVGSVGFrame methods: 580 581 void SVGOuterSVGFrame::NotifyViewportOrTransformChanged(ChangeFlags aFlags) { 582 auto* content = static_cast<SVGSVGElement*>(GetContent()); 583 if (aFlags.contains(ChangeFlag::CoordContextChanged)) { 584 if (content->HasViewBox()) { 585 // Percentage lengths on children resolve against the viewBox rect so we 586 // don't need to notify them of the viewport change, but the viewBox 587 // transform will have changed, so we need to notify them of that instead. 588 aFlags = ChangeFlag::TransformChanged; 589 } else if (content->ShouldSynthesizeViewBox()) { 590 // In the case of a synthesized viewBox, the synthetic viewBox's rect 591 // changes as the viewport changes. As a result we need to maintain the 592 // COORD_CONTEXT_CHANGED flag. 593 aFlags += ChangeFlag::TransformChanged; 594 } else if (mCanvasTM && mCanvasTM->IsSingular()) { 595 // A width/height of zero will result in us having a singular mCanvasTM 596 // even when we don't have a viewBox. So we also want to recompute our 597 // mCanvasTM for this width/height change even though we don't have a 598 // viewBox. 599 aFlags += ChangeFlag::TransformChanged; 600 } 601 } 602 603 bool haveNonFullZoomTransformChange = 604 aFlags.contains(ChangeFlag::TransformChanged); 605 606 if (aFlags.contains(ChangeFlag::FullZoomChanged)) { 607 // Convert FullZoomChanged to TransformChanged. 608 aFlags -= ChangeFlag::FullZoomChanged; 609 aFlags += ChangeFlag::TransformChanged; 610 } 611 612 if (aFlags.contains(ChangeFlag::TransformChanged)) { 613 // Make sure our canvas transform matrix gets (lazily) recalculated: 614 mCanvasTM = nullptr; 615 616 if (haveNonFullZoomTransformChange && 617 !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 618 SVGViewportElement::ChildrenOnlyTransformChangedFlags flags; 619 if (HasAnyStateBits(NS_FRAME_IN_REFLOW)) { 620 flags += 621 SVGViewportElement::ChildrenOnlyTransformChangedFlag::DuringReflow; 622 } 623 content->ChildrenOnlyTransformChanged(flags); 624 } 625 } 626 627 SVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), 628 aFlags); 629 } 630 631 //---------------------------------------------------------------------- 632 // ISVGDisplayableFrame methods: 633 634 void SVGOuterSVGFrame::PaintSVG(gfxContext& aContext, 635 const gfxMatrix& aTransform, 636 imgDrawingParams& aImgParams) { 637 NS_ASSERTION( 638 PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && 639 !PrincipalChildList().FirstChild()->GetNextSibling(), 640 "We should have a single, anonymous, child"); 641 auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( 642 PrincipalChildList().FirstChild()); 643 anonKid->PaintSVG(aContext, aTransform, aImgParams); 644 } 645 646 SVGBBox SVGOuterSVGFrame::GetBBoxContribution( 647 const gfx::Matrix& aToBBoxUserspace, uint32_t aFlags) { 648 NS_ASSERTION( 649 PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && 650 !PrincipalChildList().FirstChild()->GetNextSibling(), 651 "We should have a single, anonymous, child"); 652 // We must defer to our child so that we don't include our 653 // content->ChildToUserSpaceTransform() transform. 654 auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>( 655 PrincipalChildList().FirstChild()); 656 return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags); 657 } 658 659 //---------------------------------------------------------------------- 660 // SVGContainerFrame methods: 661 662 gfxMatrix SVGOuterSVGFrame::GetCanvasTM() { 663 if (!mCanvasTM) { 664 auto* content = static_cast<SVGSVGElement*>(GetContent()); 665 float devPxPerCSSPx = 1.0f / nsPresContext::AppUnitsToFloatCSSPixels( 666 PresContext()->AppUnitsPerDevPixel()); 667 668 gfxMatrix tm = content->ChildToUserSpaceTransform().PostScale( 669 devPxPerCSSPx, devPxPerCSSPx); 670 mCanvasTM = MakeUnique<gfxMatrix>(tm); 671 } 672 return *mCanvasTM; 673 } 674 675 //---------------------------------------------------------------------- 676 // Implementation helpers 677 678 bool SVGOuterSVGFrame::IsRootOfImage() { 679 if (!mContent->GetParent()) { 680 // Our content is the document element 681 Document* doc = mContent->GetUncomposedDoc(); 682 if (doc && doc->IsBeingUsedAsImage()) { 683 // Our document is being used as an image 684 return true; 685 } 686 } 687 688 return false; 689 } 690 691 bool SVGOuterSVGFrame::VerticalScrollbarNotNeeded() const { 692 const SVGAnimatedLength& height = 693 static_cast<SVGSVGElement*>(GetContent()) 694 ->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; 695 return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100; 696 } 697 698 void SVGOuterSVGFrame::AppendDirectlyOwnedAnonBoxes( 699 nsTArray<OwnedAnonBox>& aResult) { 700 nsIFrame* anonKid = PrincipalChildList().FirstChild(); 701 MOZ_ASSERT(anonKid->IsSVGOuterSVGAnonChildFrame()); 702 aResult.AppendElement(OwnedAnonBox(anonKid)); 703 } 704 705 void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() { 706 MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()), 707 Some(GetAspectRatio())); 708 } 709 710 void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder( 711 Maybe<IntrinsicSize> aIntrinsicSize, Maybe<AspectRatio> aIntrinsicRatio) { 712 if (!mIsInObjectOrEmbed) { 713 return; 714 } 715 716 nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell(); 717 if (!docShell) { 718 return; 719 } 720 721 BrowsingContext* bc = docShell->GetBrowsingContext(); 722 MOZ_ASSERT(bc->IsContentSubframe()); 723 724 if (bc->GetParent()->IsInProcess()) { 725 if (Element* embedder = bc->GetEmbedderElement()) { 726 if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(embedder)) { 727 static_cast<nsObjectLoadingContent*>(olc.get()) 728 ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize, 729 aIntrinsicRatio); 730 } 731 return; 732 } 733 } 734 735 if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { 736 (void)browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize, 737 aIntrinsicRatio); 738 } 739 } 740 741 void SVGOuterSVGFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { 742 SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle); 743 744 if (!aOldComputedStyle) { 745 return; 746 } 747 748 if (aOldComputedStyle->StylePosition()->mAspectRatio != 749 StylePosition()->mAspectRatio) { 750 // Our aspect-ratio property value changed, and an embedding <object> or 751 // <embed> might care about that. 752 MaybeSendIntrinsicSizeAndRatioToEmbedder(); 753 } 754 } 755 756 void SVGOuterSVGFrame::Destroy(DestroyContext& aContext) { 757 // This handles both the case when the root <svg> element is made display:none 758 // (and thus loses its intrinsic size and aspect ratio), and when the frame 759 // is navigated elsewhere & we need to reset parent <object>/<embed>'s 760 // recorded intrinsic size/ratio values. 761 MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing()); 762 763 SVGDisplayContainerFrame::Destroy(aContext); 764 } 765 766 } // namespace mozilla 767 768 //---------------------------------------------------------------------- 769 // Implementation of SVGOuterSVGAnonChildFrame 770 771 nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame( 772 mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { 773 return new (aPresShell) 774 mozilla::SVGOuterSVGAnonChildFrame(aStyle, aPresShell->GetPresContext()); 775 } 776 777 namespace mozilla { 778 779 NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGAnonChildFrame) 780 781 #ifdef DEBUG 782 void SVGOuterSVGAnonChildFrame::Init(nsIContent* aContent, 783 nsContainerFrame* aParent, 784 nsIFrame* aPrevInFlow) { 785 MOZ_ASSERT(aParent->IsSVGOuterSVGFrame(), "Unexpected parent"); 786 SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); 787 } 788 #endif 789 790 void SVGOuterSVGAnonChildFrame::BuildDisplayList( 791 nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { 792 // Wrap our contents into an nsDisplaySVGWrapper. 793 // We wrap this frame instead of the SVGOuterSVGFrame so that the wrapper 794 // doesn't contain the <svg> element's CSS styles, like backgrounds or 795 // borders. Creating the nsDisplaySVGWrapper here also means that it'll be 796 // inside the nsDisplayTransform for our viewbox transform. The 797 // nsDisplaySVGWrapper's reference frame is this frame, because this frame 798 // always returns true from IsSVGTransformed. 799 nsDisplayList newList(aBuilder); 800 nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList, 801 &newList); 802 BuildDisplayListForNonBlockChildren(aBuilder, set); 803 aLists.Content()->AppendNewToTop<nsDisplaySVGWrapper>(aBuilder, this, 804 &newList); 805 } 806 807 bool SVGOuterSVGFrame::HasChildrenOnlyTransform(Matrix* aTransform) const { 808 auto* content = static_cast<SVGSVGElement*>(GetContent()); 809 if (!content->HasChildrenOnlyTransform()) { 810 return false; 811 } 812 if (aTransform) { 813 // Outer-<svg> doesn't use x/y, so we can use the child-to-user-space 814 // transform here. 815 *aTransform = gfx::ToMatrix(content->ChildToUserSpaceTransform()); 816 } 817 return true; 818 } 819 820 bool SVGOuterSVGAnonChildFrame::DoGetParentSVGTransforms( 821 Matrix* aFromParentTransform) const { 822 // We want this frame to be a reference frame. An easy way to achieve that is 823 // to always return true from this method, even for identity transforms. 824 // This frame being a reference frame ensures that the offset between this 825 // <svg> element and the parent reference frame is completely absorbed by the 826 // nsDisplayTransform that's created for this frame, and that this offset does 827 // not affect our descendants' transforms. Consequently, if the <svg> element 828 // moves, e.g. during scrolling, the transform matrices of our contents are 829 // unaffected. This simplifies invalidation. 830 // TODO(emilio): Is the comment above true for WebRender nowadays? 831 SVGUtils::GetParentSVGTransforms(this, aFromParentTransform); 832 return true; 833 } 834 835 } // namespace mozilla