/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Main header first: #include "SVGOuterSVGFrame.h" // Keep others in (case-insensitive) order: #include "gfxContext.h" #include "nsDisplayList.h" #include "nsIInterfaceRequestorUtils.h" #include "nsLayoutUtils.h" #include "nsObjectLoadingContent.h" #include "nsSubDocumentFrame.h" #include "mozilla/PresShell.h" #include "mozilla/SVGUtils.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/SVGSVGElement.h" using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::image; //---------------------------------------------------------------------- // Implementation nsContainerFrame* NS_NewSVGOuterSVGFrame(mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { return new (aPresShell) mozilla::SVGOuterSVGFrame(aStyle, aPresShell->GetPresContext()); } namespace mozilla { NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGFrame) SVGOuterSVGFrame::SVGOuterSVGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) { // Outer- has CSS layout, so remove this bit: RemoveStateBits(NS_FRAME_SVG_LAYOUT); AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_FONT_INFLATION_CONTAINER | NS_FRAME_FONT_INFLATION_FLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED); } // The CSS Containment spec says that size-contained replaced elements must be // treated as having an intrinsic width and height of 0. That's applicable to // outer SVG frames, unless they're the outermost element (in which case // they're not really "replaced", and there's no outer context to contain sizes // from leaking into). Hence, we check for a parent element before we bother // testing for 'contain:size'. static inline ContainSizeAxes ContainSizeAxesIfApplicable( const SVGOuterSVGFrame* aFrame) { if (!aFrame->GetContent()->GetParent()) { return ContainSizeAxes(false, false); } return aFrame->GetContainSizeAxes(); } // This should match ImageDocument::GetZoomLevel. float SVGOuterSVGFrame::ComputeFullZoom() const { MOZ_ASSERT(mIsRootContent); MOZ_ASSERT(!mIsInIframe); if (BrowsingContext* bc = PresContext()->Document()->GetBrowsingContext()) { return bc->FullZoom(); } return 1.0f; } void SVGOuterSVGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), "Content is not an SVG 'svg' element!"); // Check for conditional processing attributes here rather than in // nsCSSFrameConstructor::FindSVGData because we want to avoid // simply giving failing outer elements an SVGContainerFrame. // We don't create other SVG frames if PassesConditionalProcessingTests // returns false, but since we do create SVGOuterSVGFrame frames we // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if // the value returned by PassesConditionalProcessingTests changes. auto* svg = static_cast(aContent); if (!svg->PassesConditionalProcessingTests()) { AddStateBits(NS_FRAME_IS_NONDISPLAY); } SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); Document* doc = mContent->GetUncomposedDoc(); mIsRootContent = doc && doc->GetRootElement() == mContent; if (mIsRootContent) { if (nsCOMPtr docShell = PresContext()->GetDocShell()) { RefPtr bc = docShell->GetBrowsingContext(); if (const Maybe& type = bc->GetEmbedderElementType()) { mIsInObjectOrEmbed = nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type); mIsInIframe = nsGkAtoms::iframe->Equals(*type); } } if (!mIsInIframe) { mFullZoom = ComputeFullZoom(); } } MaybeSendIntrinsicSizeAndRatioToEmbedder(); } //---------------------------------------------------------------------- // nsQueryFrame methods NS_QUERYFRAME_HEAD(SVGOuterSVGFrame) NS_QUERYFRAME_ENTRY(SVGOuterSVGFrame) NS_QUERYFRAME_ENTRY(ISVGSVGFrame) NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame) //---------------------------------------------------------------------- // nsIFrame methods //---------------------------------------------------------------------- // reflowing /* virtual */ nscoord SVGOuterSVGFrame::GetMinISize(gfxContext* aRenderingContext) { nscoord result; DISPLAY_MIN_INLINE_SIZE(this, result); // If this ever changes to return something other than zero, then // nsSubDocumentFrame::GetMinISize will also need to change. result = nscoord(0); return result; } /* virtual */ nscoord SVGOuterSVGFrame::GetPrefISize(gfxContext* aRenderingContext) { nscoord result; DISPLAY_PREF_INLINE_SIZE(this, result); SVGSVGElement* svg = static_cast(GetContent()); WritingMode wm = GetWritingMode(); const SVGAnimatedLength& isize = wm.IsVertical() ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT] : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; if (Maybe containISize = ContainSizeAxesIfApplicable(this).ContainIntrinsicISize(*this)) { result = *containISize; } else if (isize.IsPercentage()) { // If we are here, our inline size attribute is a percentage either // explicitly (via an attribute value) or implicitly (by being unset, which // is treated as 100%). The following if-condition, deciding to return // either the fallback intrinsic size or zero, is made to match blink and // webkit's behavior for webcompat. if (isize.IsExplicitlySet() || StylePosition()->ISize(wm).HasPercent() || !GetAspectRatio()) { result = wm.IsVertical() ? kFallbackIntrinsicSize.height : kFallbackIntrinsicSize.width; } else { result = nscoord(0); } } else { result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg)); if (result < 0) { result = nscoord(0); } } return result; } /* virtual */ IntrinsicSize SVGOuterSVGFrame::GetIntrinsicSize() { // XXXjwatt Note that here we want to return the CSS width/height if they're // specified and we're embedded inside an nsIObjectLoadingContent. const auto containAxes = ContainSizeAxesIfApplicable(this); if (containAxes.IsBoth()) { // Intrinsic size of 'contain:size' replaced elements is determined by // contain-intrinsic-size, defaulting to 0x0. return FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0)); } SVGSVGElement* content = static_cast(GetContent()); const SVGAnimatedLength& width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; const SVGAnimatedLength& height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; IntrinsicSize intrinsicSize; if (!width.IsPercentage()) { nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)); intrinsicSize.width.emplace(std::max(val, 0)); } if (!height.IsPercentage()) { nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content)); intrinsicSize.height.emplace(std::max(val, 0)); } return FinishIntrinsicSize(containAxes, intrinsicSize); } /* virtual */ AspectRatio SVGOuterSVGFrame::GetIntrinsicRatio() const { if (ContainSizeAxesIfApplicable(this).IsAny()) { return AspectRatio(); } // We only have an intrinsic size/ratio if our width and height attributes // are both specified and set to non-percentage values, or we have a viewBox // rect: https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS auto* content = static_cast(GetContent()); const SVGAnimatedLength& width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; const SVGAnimatedLength& height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; if (!width.IsPercentage() && !height.IsPercentage()) { // Use width/height ratio only if // 1. it's not a degenerate ratio, and // 2. width and height are non-negative numbers. // Otherwise, we use the viewbox rect. // https://github.com/w3c/csswg-drafts/issues/6286 const float w = width.GetAnimValue(content); const float h = height.GetAnimValue(content); if (w > 0.0f && h > 0.0f) { return AspectRatio::FromSize(w, h); } } const auto& viewBox = content->GetViewBoxInternal(); if (viewBox.HasRect()) { const auto& anim = viewBox.GetAnimValue(); return AspectRatio::FromSize(anim.width, anim.height); } return SVGDisplayContainerFrame::GetIntrinsicRatio(); } /* virtual */ nsIFrame::SizeComputationResult SVGOuterSVGFrame::ComputeSize( gfxContext* aRenderingContext, WritingMode aWritingMode, const LogicalSize& aCBSize, nscoord aAvailableISize, const LogicalSize& aMargin, const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { if (IsRootOfImage() || mIsInObjectOrEmbed) { // The embedding element has sized itself using the CSS replaced element // sizing rules, using our intrinsic dimensions as necessary. The SVG spec // says that the width and height of embedded SVG is overridden by the // width and height of the embedding element, so we just need to size to // the viewport that the embedding element has established for us. return {aCBSize, AspectRatioUsage::None}; } LogicalSize cbSize = aCBSize; IntrinsicSize intrinsicSize = GetIntrinsicSize(); if (mIsRootContent) { // We're the root of the outermost browsing context, so we need to scale // cbSize by the full-zoom so that SVGs with percentage width/height zoom: NS_ASSERTION(aCBSize.ISize(aWritingMode) != NS_UNCONSTRAINEDSIZE && aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE, "root should not have auto-width/height containing block"); if (!mIsInIframe) { // NOTE: We can't just use mFullZoom because this can run before Reflow() // updates it. const float zoom = ComputeFullZoom(); cbSize.ISize(aWritingMode) *= zoom; cbSize.BSize(aWritingMode) *= zoom; } // We also need to honour the width and height attributes' default values // of 100% when we're the root of a browsing context. (GetIntrinsicSize() // doesn't report these since there's no such thing as a percentage // intrinsic size. Also note that explicit percentage values are mapped // into style, so the following isn't for them.) auto* content = static_cast(GetContent()); const SVGAnimatedLength& width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; if (width.IsPercentage()) { MOZ_ASSERT(!intrinsicSize.width, "GetIntrinsicSize should have reported no intrinsic width"); float val = width.GetAnimValInSpecifiedUnits() / 100.0f; intrinsicSize.width.emplace(std::max(val, 0.0f) * cbSize.Width(aWritingMode)); } const SVGAnimatedLength& height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; NS_ASSERTION(aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE, "root should not have auto-height containing block"); if (height.IsPercentage()) { MOZ_ASSERT(!intrinsicSize.height, "GetIntrinsicSize should have reported no intrinsic height"); float val = height.GetAnimValInSpecifiedUnits() / 100.0f; intrinsicSize.height.emplace(std::max(val, 0.0f) * cbSize.Height(aWritingMode)); } MOZ_ASSERT(intrinsicSize.height && intrinsicSize.width, "We should have just handled the only situation where" "we lack an intrinsic height or width."); } return {ComputeSizeWithIntrinsicDimensions( aRenderingContext, aWritingMode, intrinsicSize, GetAspectRatio(), cbSize, aMargin, aBorderPadding, aSizeOverrides, aFlags), AspectRatioUsage::None}; } void SVGOuterSVGFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("SVGOuterSVGFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); NS_FRAME_TRACE( NS_FRAME_TRACE_CALLS, ("enter SVGOuterSVGFrame::Reflow: availSize=%d,%d", aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow"); const auto wm = GetWritingMode(); aDesiredSize.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm)); NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages."); SVGSVGElement* svgElem = static_cast(GetContent()); auto* anonKid = static_cast( PrincipalChildList().FirstChild()); if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { // Initialize svgElem->UpdateHasChildrenOnlyTransform(); } // If our SVG viewport has changed, update our content and notify. // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace gfx::Size newViewportSize( nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedWidth()), nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedHeight())); uint32_t changeBits = 0; if (newViewportSize != svgElem->GetViewportSize()) { // When our viewport size changes, we may need to update the overflow rects // of our child frames. This is the case if: // // * We have a real/synthetic viewBox (a children-only transform), since // the viewBox transform will change as the viewport dimensions change. // // * We do not have a real/synthetic viewBox, but the last time we // reflowed (or the last time UpdateOverflow() was called) we did. // // We only handle the former case here, in which case we mark all our child // frames as dirty so that we reflow them below and update their overflow // rects. // // In the latter case, updating of overflow rects is handled for removal of // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic // viewBox "removal" (e.g. a document references the same SVG via both an // and then as a CSS background image (a synthetic viewBox is // used when painting the former, but not when painting the latter)) is // handled in SVGSVGElement::FlushImageTransformInvalidation. // if (svgElem->HasViewBoxOrSyntheticViewBox()) { nsIFrame* anonChild = PrincipalChildList().FirstChild(); anonChild->MarkSubtreeDirty(); for (nsIFrame* child : anonChild->PrincipalChildList()) { child->MarkSubtreeDirty(); } } changeBits |= COORD_CONTEXT_CHANGED; svgElem->SetViewportSize(newViewportSize); } if (mIsRootContent && !mIsInIframe) { const auto oldZoom = mFullZoom; mFullZoom = ComputeFullZoom(); if (oldZoom != mFullZoom) { changeBits |= FULL_ZOOM_CHANGED; } } if (changeBits && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { NotifyViewportOrTransformChanged(changeBits); } // Now that we've marked the necessary children as dirty, call // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending // on whether we are non-display. mCallingReflowSVG = true; if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { ReflowSVGNonDisplayText(this); } else { // Update the mRects and ink overflow rects of all our descendants, // including our anonymous wrapper kid: anonKid->ReflowSVG(); MOZ_ASSERT(!anonKid->GetNextSibling(), "We should have one anonymous child frame wrapping our real " "children"); } mCallingReflowSVG = false; // Set our anonymous kid's offset from our border box: anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft()); // Including our size in our overflow rects regardless of the value of // 'background', 'border', etc. makes sure that we usually (when we clip to // our content area) don't have to keep changing our overflow rects as our // descendants move about (see perf comment below). Including our size in our // scrollable overflow rect also makes sure that we scroll if we're too big // for our viewport. // // never allows scrolling to anything outside its mRect (only panning), // so we must always keep our scrollable overflow set to our size. // // With regards to ink overflow, we always clip root- (see our // BuildDisplayList method) regardless of the value of the 'overflow' // property since that is per-spec, even for the initial 'visible' value. For // that reason there's no point in adding descendant ink overflow to our // own when this frame is for a root-. That said, there's also a very // good performance reason for us wanting to avoid doing so. If we did, then // the frame's overflow would often change as descendants that are partially // or fully outside its rect moved (think animation on/off screen), and that // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the // entire document tree each such move (see bug 875175). // // So it's only non-root outer- that has the ink overflow of its // descendants added to its own. (Note that the default user-agent style // sheet makes 'hidden' the default value for :not(root(svg)), so usually // FinishAndStoreOverflow will still clip this back to the frame's rect.) // // WARNING!! Keep UpdateBounds below in sync with whatever we do for our // overflow rects here! (Again, see bug 875175.) // aDesiredSize.SetOverflowAreasToDesiredBounds(); // An outer SVG will be here as a nondisplay if it fails the conditional // processing test. In that case, we don't maintain its overflow. if (!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { if (!mIsRootContent) { aDesiredSize.mOverflowAreas.InkOverflow().UnionRect( aDesiredSize.mOverflowAreas.InkOverflow(), anonKid->InkOverflowRect() + anonKid->GetPosition()); } FinishAndStoreOverflow(&aDesiredSize); } NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit SVGOuterSVGFrame::Reflow: size=%d,%d", aDesiredSize.Width(), aDesiredSize.Height())); } void SVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext, const ReflowInput* aReflowInput) { SVGDisplayContainerFrame::DidReflow(aPresContext, aReflowInput); // Make sure elements styled by :hover get updated if script/animation moves // them under or out from under the pointer: PresShell()->SynthesizeMouseMove(false); } /* virtual */ void SVGOuterSVGFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) { // See the comments in Reflow above. // WARNING!! Keep this in sync with Reflow above! if (!mIsRootContent) { nsIFrame* anonKid = PrincipalChildList().FirstChild(); aOverflowAreas.InkOverflow().UnionRect( aOverflowAreas.InkOverflow(), anonKid->InkOverflowRect() + anonKid->GetPosition()); } } //---------------------------------------------------------------------- // container methods nsresult SVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { if (aNameSpaceID == kNameSpaceID_None && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY)) { if (aAttribute == nsGkAtoms::viewBox || aAttribute == nsGkAtoms::preserveAspectRatio || aAttribute == nsGkAtoms::transform) { // make sure our cached transform matrix gets (lazily) updated mCanvasTM = nullptr; SVGUtils::NotifyChildrenOfSVGChange( PrincipalChildList().FirstChild(), aAttribute == nsGkAtoms::viewBox ? TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED); if (aAttribute != nsGkAtoms::transform) { static_cast(GetContent()) ->ChildrenOnlyTransformChanged(); } } if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::viewBox) { // Don't call ChildrenOnlyTransformChanged() here, since we call it // under Reflow if the width/height/viewBox actually changed. MaybeSendIntrinsicSizeAndRatioToEmbedder(); if (!mIsInObjectOrEmbed) { // We are not embedded by reference, so our 'width' and 'height' // attributes are not overridden (and viewBox may influence our // intrinsic aspect ratio). We need to reflow. PresShell()->FrameNeedsReflow( this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); } } } return NS_OK; } bool SVGOuterSVGFrame::IsSVGTransformed(Matrix* aOwnTransform, Matrix* aFromParentTransform) const { // Our anonymous child's HasChildrenOnlyTransform() implementation makes sure // our children-only transforms are applied to our children. We only care // about transforms that transform our own frame here. bool foundTransform = false; SVGSVGElement* content = static_cast(GetContent()); SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList(); if ((transformList && transformList->HasTransform()) || content->GetAnimateMotionTransform()) { if (aOwnTransform) { *aOwnTransform = gfx::ToMatrix( content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent)); } foundTransform = true; } return foundTransform; } //---------------------------------------------------------------------- // painting void SVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { return; } DisplayBorderBackgroundOutline(aBuilder, aLists); nsRect visibleRect = aBuilder->GetVisibleRect(); nsRect dirtyRect = aBuilder->GetDirtyRect(); // Per-spec, we always clip root- even when 'overflow' has its initial // value of 'visible'. See also the "ink overflow" comments in Reflow. DisplayListClipState::AutoSaveRestore autoSR(aBuilder); if (mIsRootContent || StyleDisplay()->IsScrollableOverflow()) { autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this); visibleRect = visibleRect.Intersect(GetContentRectRelativeToSelf()); dirtyRect = dirtyRect.Intersect(GetContentRectRelativeToSelf()); } nsDisplayListBuilder::AutoBuildingDisplayList building( aBuilder, this, visibleRect, dirtyRect); nsDisplayList* contentList = aLists.Content(); nsDisplayListSet set(contentList, contentList, contentList, contentList, contentList, contentList); BuildDisplayListForNonBlockChildren(aBuilder, set); } //---------------------------------------------------------------------- // ISVGSVGFrame methods: void SVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) { MOZ_ASSERT(aFlags && !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED | FULL_ZOOM_CHANGED)), "Unexpected aFlags value"); SVGSVGElement* content = static_cast(GetContent()); if (aFlags & COORD_CONTEXT_CHANGED) { if (content->HasViewBox()) { // Percentage lengths on children resolve against the viewBox rect so we // don't need to notify them of the viewport change, but the viewBox // transform will have changed, so we need to notify them of that instead. aFlags = TRANSFORM_CHANGED; } else if (content->ShouldSynthesizeViewBox()) { // In the case of a synthesized viewBox, the synthetic viewBox's rect // changes as the viewport changes. As a result we need to maintain the // COORD_CONTEXT_CHANGED flag. aFlags |= TRANSFORM_CHANGED; } else if (mCanvasTM && mCanvasTM->IsSingular()) { // A width/height of zero will result in us having a singular mCanvasTM // even when we don't have a viewBox. So we also want to recompute our // mCanvasTM for this width/height change even though we don't have a // viewBox. aFlags |= TRANSFORM_CHANGED; } } bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED); if (aFlags & FULL_ZOOM_CHANGED) { // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED: aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED; } if (aFlags & TRANSFORM_CHANGED) { // Make sure our canvas transform matrix gets (lazily) recalculated: mCanvasTM = nullptr; if (haveNonFulLZoomTransformChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { uint32_t flags = HasAnyStateBits(NS_FRAME_IN_REFLOW) ? SVGSVGElement::eDuringReflow : 0; content->ChildrenOnlyTransformChanged(flags); } } SVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), aFlags); } //---------------------------------------------------------------------- // ISVGDisplayableFrame methods: void SVGOuterSVGFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, imgDrawingParams& aImgParams) { NS_ASSERTION( PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && !PrincipalChildList().FirstChild()->GetNextSibling(), "We should have a single, anonymous, child"); auto* anonKid = static_cast( PrincipalChildList().FirstChild()); anonKid->PaintSVG(aContext, aTransform, aImgParams); } SVGBBox SVGOuterSVGFrame::GetBBoxContribution( const gfx::Matrix& aToBBoxUserspace, uint32_t aFlags) { NS_ASSERTION( PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && !PrincipalChildList().FirstChild()->GetNextSibling(), "We should have a single, anonymous, child"); // We must defer to our child so that we don't include our // content->PrependLocalTransformsTo() transforms. auto* anonKid = static_cast( PrincipalChildList().FirstChild()); return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags); } //---------------------------------------------------------------------- // SVGContainerFrame methods: gfxMatrix SVGOuterSVGFrame::GetCanvasTM() { if (!mCanvasTM) { SVGSVGElement* content = static_cast(GetContent()); float devPxPerCSSPx = 1.0f / nsPresContext::AppUnitsToFloatCSSPixels( PresContext()->AppUnitsPerDevPixel()); gfxMatrix tm = content->PrependLocalTransformsTo( gfxMatrix::Scaling(devPxPerCSSPx, devPxPerCSSPx)); mCanvasTM = MakeUnique(tm); } return *mCanvasTM; } //---------------------------------------------------------------------- // Implementation helpers bool SVGOuterSVGFrame::IsRootOfImage() { if (!mContent->GetParent()) { // Our content is the document element Document* doc = mContent->GetUncomposedDoc(); if (doc && doc->IsBeingUsedAsImage()) { // Our document is being used as an image return true; } } return false; } bool SVGOuterSVGFrame::VerticalScrollbarNotNeeded() const { const SVGAnimatedLength& height = static_cast(GetContent()) ->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100; } void SVGOuterSVGFrame::AppendDirectlyOwnedAnonBoxes( nsTArray& aResult) { nsIFrame* anonKid = PrincipalChildList().FirstChild(); MOZ_ASSERT(anonKid->IsSVGOuterSVGAnonChildFrame()); aResult.AppendElement(OwnedAnonBox(anonKid)); } void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() { MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()), Some(GetAspectRatio())); } void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder( Maybe aIntrinsicSize, Maybe aIntrinsicRatio) { if (!mIsInObjectOrEmbed) { return; } nsCOMPtr docShell = PresContext()->GetDocShell(); if (!docShell) { return; } BrowsingContext* bc = docShell->GetBrowsingContext(); MOZ_ASSERT(bc->IsContentSubframe()); if (bc->GetParent()->IsInProcess()) { if (Element* embedder = bc->GetEmbedderElement()) { if (nsCOMPtr olc = do_QueryInterface(embedder)) { static_cast(olc.get()) ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize, aIntrinsicRatio); } return; } } if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { Unused << browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize, aIntrinsicRatio); } } void SVGOuterSVGFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle); if (!aOldComputedStyle) { return; } if (aOldComputedStyle->StylePosition()->mAspectRatio != StylePosition()->mAspectRatio) { // Our aspect-ratio property value changed, and an embedding or // might care about that. MaybeSendIntrinsicSizeAndRatioToEmbedder(); } } void SVGOuterSVGFrame::Destroy(DestroyContext& aContext) { // This handles both the case when the root element is made display:none // (and thus loses its intrinsic size and aspect ratio), and when the frame // is navigated elsewhere & we need to reset parent /'s // recorded intrinsic size/ratio values. MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing()); SVGDisplayContainerFrame::Destroy(aContext); } } // namespace mozilla //---------------------------------------------------------------------- // Implementation of SVGOuterSVGAnonChildFrame nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame( mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { return new (aPresShell) mozilla::SVGOuterSVGAnonChildFrame(aStyle, aPresShell->GetPresContext()); } namespace mozilla { NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGAnonChildFrame) #ifdef DEBUG void SVGOuterSVGAnonChildFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { MOZ_ASSERT(aParent->IsSVGOuterSVGFrame(), "Unexpected parent"); SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); } #endif void SVGOuterSVGAnonChildFrame::BuildDisplayList( nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { // Wrap our contents into an nsDisplaySVGWrapper. // We wrap this frame instead of the SVGOuterSVGFrame so that the wrapper // doesn't contain the element's CSS styles, like backgrounds or // borders. Creating the nsDisplaySVGWrapper here also means that it'll be // inside the nsDisplayTransform for our viewbox transform. The // nsDisplaySVGWrapper's reference frame is this frame, because this frame // always returns true from IsSVGTransformed. nsDisplayList newList(aBuilder); nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList, &newList); BuildDisplayListForNonBlockChildren(aBuilder, set); aLists.Content()->AppendNewToTop(aBuilder, this, &newList); } static Matrix ComputeOuterSVGAnonChildFrameTransform( const SVGOuterSVGAnonChildFrame* aFrame) { // Our elements 'transform' attribute is applied to our SVGOuterSVGFrame // parent, and the element's children-only transforms are applied to us, the // anonymous child frame. Since we are the child frame, we apply the // children-only transforms as if they are our own transform. SVGSVGElement* content = static_cast(aFrame->GetContent()); if (!content->HasChildrenOnlyTransform()) { return Matrix(); } // Outer- doesn't use x/y, so we can pass eChildToUserSpace here. gfxMatrix ownMatrix = content->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); if (ownMatrix.HasNonTranslation()) { // viewBox, currentScale and currentTranslate should only produce a // rectilinear transform. MOZ_ASSERT(ownMatrix.IsRectilinear(), "Non-rectilinear transform will break the following logic"); // The nsDisplayTransform code will apply this transform to our frame, // including to our frame position. We don't want our frame position to // be scaled though, so we need to correct for that in the transform. // XXX Yeah, this is a bit hacky. CSSPoint pos = CSSPixel::FromAppUnits(aFrame->GetPosition()); CSSPoint scaledPos = CSSPoint(ownMatrix._11 * pos.x, ownMatrix._22 * pos.y); CSSPoint deltaPos = scaledPos - pos; ownMatrix *= gfxMatrix::Translation(-deltaPos.x, -deltaPos.y); } return gfx::ToMatrix(ownMatrix); } // We want this frame to be a reference frame. An easy way to achieve that is // to always return true from this method, even for identity transforms. // This frame being a reference frame ensures that the offset between this // element and the parent reference frame is completely absorbed by the // nsDisplayTransform that's created for this frame, and that this offset does // not affect our descendants' transforms. Consequently, if the element // moves, e.g. during scrolling, the transform matrices of our contents are // unaffected. This simplifies invalidation. bool SVGOuterSVGAnonChildFrame::IsSVGTransformed( Matrix* aOwnTransform, Matrix* aFromParentTransform) const { if (aOwnTransform) { *aOwnTransform = ComputeOuterSVGAnonChildFrameTransform(this); } return true; } } // namespace mozilla