michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: #include "nsSVGOuterSVGFrame.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "nsDisplayList.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIObjectLoadingContent.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "nsSVGForeignObjectFrame.h" michael@0: #include "mozilla/dom/SVGSVGElement.h" michael@0: #include "mozilla/dom/SVGViewElement.h" michael@0: #include "nsSubDocumentFrame.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation helpers michael@0: michael@0: void michael@0: nsSVGOuterSVGFrame::RegisterForeignObject(nsSVGForeignObjectFrame* aFrame) michael@0: { michael@0: NS_ASSERTION(aFrame, "Who on earth is calling us?!"); michael@0: michael@0: if (!mForeignObjectHash) { michael@0: mForeignObjectHash = new nsTHashtable >(); michael@0: } michael@0: michael@0: NS_ASSERTION(!mForeignObjectHash->GetEntry(aFrame), michael@0: "nsSVGForeignObjectFrame already registered!"); michael@0: michael@0: mForeignObjectHash->PutEntry(aFrame); michael@0: michael@0: NS_ASSERTION(mForeignObjectHash->GetEntry(aFrame), michael@0: "Failed to register nsSVGForeignObjectFrame!"); michael@0: } michael@0: michael@0: void michael@0: nsSVGOuterSVGFrame::UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame) michael@0: { michael@0: NS_ASSERTION(aFrame, "Who on earth is calling us?!"); michael@0: NS_ASSERTION(mForeignObjectHash && mForeignObjectHash->GetEntry(aFrame), michael@0: "nsSVGForeignObjectFrame not in registry!"); michael@0: return mForeignObjectHash->RemoveEntry(aFrame); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSVGOuterSVGFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGFrame) michael@0: michael@0: nsSVGOuterSVGFrame::nsSVGOuterSVGFrame(nsStyleContext* aContext) michael@0: : nsSVGOuterSVGFrameBase(aContext) michael@0: , mFullZoom(aContext->PresContext()->GetFullZoom()) michael@0: , mViewportInitialized(false) michael@0: , mIsRootContent(false) michael@0: { michael@0: // Outer- has CSS layout, so remove this bit: michael@0: RemoveStateBits(NS_FRAME_SVG_LAYOUT); michael@0: } michael@0: michael@0: void michael@0: nsSVGOuterSVGFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ASSERTION(aContent->IsSVG(nsGkAtoms::svg), michael@0: "Content is not an SVG 'svg' element!"); michael@0: michael@0: AddStateBits(NS_STATE_IS_OUTER_SVG | michael@0: NS_FRAME_FONT_INFLATION_CONTAINER | michael@0: NS_FRAME_FONT_INFLATION_FLOW_ROOT); michael@0: michael@0: // Check for conditional processing attributes here rather than in michael@0: // nsCSSFrameConstructor::FindSVGData because we want to avoid michael@0: // simply giving failing outer elements an nsSVGContainerFrame. michael@0: // We don't create other SVG frames if PassesConditionalProcessingTests michael@0: // returns false, but since we do create nsSVGOuterSVGFrame frames we michael@0: // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The michael@0: // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if michael@0: // the value returned by PassesConditionalProcessingTests changes. michael@0: SVGSVGElement *svg = static_cast(aContent); michael@0: if (!svg->PassesConditionalProcessingTests()) { michael@0: AddStateBits(NS_FRAME_IS_NONDISPLAY); michael@0: } michael@0: michael@0: nsSVGOuterSVGFrameBase::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: nsIDocument* doc = mContent->GetCurrentDoc(); michael@0: if (doc) { michael@0: // we only care about our content's zoom and pan values if it's the root element michael@0: if (doc->GetRootElement() == mContent) { michael@0: mIsRootContent = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsQueryFrame methods michael@0: michael@0: NS_QUERYFRAME_HEAD(nsSVGOuterSVGFrame) michael@0: NS_QUERYFRAME_ENTRY(nsISVGSVGFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsSVGOuterSVGFrameBase) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsIFrame methods michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // reflowing michael@0: michael@0: /* virtual */ nscoord michael@0: nsSVGOuterSVGFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: michael@0: result = nscoord(0); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsSVGOuterSVGFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: michael@0: SVGSVGElement *svg = static_cast(mContent); michael@0: nsSVGLength2 &width = svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; michael@0: michael@0: if (width.IsPercentage()) { michael@0: // It looks like our containing block's width may depend on our width. In michael@0: // that case our behavior is undefined according to CSS 2.1 section 10.3.2, michael@0: // so return zero. michael@0: result = nscoord(0); michael@0: } else { michael@0: result = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(svg)); michael@0: if (result < 0) { michael@0: result = nscoord(0); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ IntrinsicSize michael@0: nsSVGOuterSVGFrame::GetIntrinsicSize() michael@0: { michael@0: // XXXjwatt Note that here we want to return the CSS width/height if they're michael@0: // specified and we're embedded inside an nsIObjectLoadingContent. michael@0: michael@0: IntrinsicSize intrinsicSize; michael@0: michael@0: SVGSVGElement *content = static_cast(mContent); michael@0: nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; michael@0: nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; michael@0: michael@0: if (!width.IsPercentage()) { michael@0: nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)); michael@0: if (val < 0) val = 0; michael@0: intrinsicSize.width.SetCoordValue(val); michael@0: } michael@0: michael@0: if (!height.IsPercentage()) { michael@0: nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content)); michael@0: if (val < 0) val = 0; michael@0: intrinsicSize.height.SetCoordValue(val); michael@0: } michael@0: michael@0: return intrinsicSize; michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsSVGOuterSVGFrame::GetIntrinsicRatio() michael@0: { michael@0: // We only have an intrinsic size/ratio if our width and height attributes michael@0: // are both specified and set to non-percentage values, or we have a viewBox michael@0: // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing michael@0: michael@0: SVGSVGElement *content = static_cast(mContent); michael@0: nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; michael@0: nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; michael@0: michael@0: if (!width.IsPercentage() && !height.IsPercentage()) { michael@0: nsSize ratio(NSToCoordRoundWithClamp(width.GetAnimValue(content)), michael@0: NSToCoordRoundWithClamp(height.GetAnimValue(content))); michael@0: if (ratio.width < 0) { michael@0: ratio.width = 0; michael@0: } michael@0: if (ratio.height < 0) { michael@0: ratio.height = 0; michael@0: } michael@0: return ratio; michael@0: } michael@0: michael@0: SVGViewElement* viewElement = content->GetCurrentViewElement(); michael@0: const nsSVGViewBoxRect* viewbox = nullptr; michael@0: michael@0: // The logic here should match HasViewBox(). michael@0: if (viewElement && viewElement->mViewBox.HasRect()) { michael@0: viewbox = &viewElement->mViewBox.GetAnimValue(); michael@0: } else if (content->mViewBox.HasRect()) { michael@0: viewbox = &content->mViewBox.GetAnimValue(); michael@0: } michael@0: michael@0: if (viewbox) { michael@0: float viewBoxWidth = viewbox->width; michael@0: float viewBoxHeight = viewbox->height; michael@0: michael@0: if (viewBoxWidth < 0.0f) { michael@0: viewBoxWidth = 0.0f; michael@0: } michael@0: if (viewBoxHeight < 0.0f) { michael@0: viewBoxHeight = 0.0f; michael@0: } michael@0: return nsSize(NSToCoordRoundWithClamp(viewBoxWidth), michael@0: NSToCoordRoundWithClamp(viewBoxHeight)); michael@0: } michael@0: michael@0: return nsSVGOuterSVGFrameBase::GetIntrinsicRatio(); michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsSVGOuterSVGFrame::ComputeSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: uint32_t aFlags) michael@0: { michael@0: if (IsRootOfImage() || IsRootOfReplacedElementSubDoc()) { michael@0: // The embedding element has sized itself using the CSS replaced element michael@0: // sizing rules, using our intrinsic dimensions as necessary. The SVG spec michael@0: // says that the width and height of embedded SVG is overridden by the michael@0: // width and height of the embedding element, so we just need to size to michael@0: // the viewport that the embedding element has established for us. michael@0: return aCBSize; michael@0: } michael@0: michael@0: nsSize cbSize = aCBSize; michael@0: IntrinsicSize intrinsicSize = GetIntrinsicSize(); michael@0: michael@0: if (!mContent->GetParent()) { michael@0: // We're the root of the outermost browsing context, so we need to scale michael@0: // cbSize by the full-zoom so that SVGs with percentage width/height zoom: michael@0: michael@0: NS_ASSERTION(aCBSize.width != NS_AUTOHEIGHT && michael@0: aCBSize.height != NS_AUTOHEIGHT, michael@0: "root should not have auto-width/height containing block"); michael@0: cbSize.width *= PresContext()->GetFullZoom(); michael@0: cbSize.height *= PresContext()->GetFullZoom(); michael@0: michael@0: // We also need to honour the width and height attributes' default values michael@0: // of 100% when we're the root of a browsing context. (GetIntrinsicSize() michael@0: // doesn't report these since there's no such thing as a percentage michael@0: // intrinsic size. Also note that explicit percentage values are mapped michael@0: // into style, so the following isn't for them.) michael@0: michael@0: SVGSVGElement* content = static_cast(mContent); michael@0: michael@0: nsSVGLength2 &width = michael@0: content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; michael@0: if (width.IsPercentage()) { michael@0: NS_ABORT_IF_FALSE(intrinsicSize.width.GetUnit() == eStyleUnit_None, michael@0: "GetIntrinsicSize should have reported no " michael@0: "intrinsic width"); michael@0: float val = width.GetAnimValInSpecifiedUnits() / 100.0f; michael@0: if (val < 0.0f) val = 0.0f; michael@0: intrinsicSize.width.SetCoordValue(val * cbSize.width); michael@0: } michael@0: michael@0: nsSVGLength2 &height = michael@0: content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; michael@0: NS_ASSERTION(aCBSize.height != NS_AUTOHEIGHT, michael@0: "root should not have auto-height containing block"); michael@0: if (height.IsPercentage()) { michael@0: NS_ABORT_IF_FALSE(intrinsicSize.height.GetUnit() == eStyleUnit_None, michael@0: "GetIntrinsicSize should have reported no " michael@0: "intrinsic height"); michael@0: float val = height.GetAnimValInSpecifiedUnits() / 100.0f; michael@0: if (val < 0.0f) val = 0.0f; michael@0: intrinsicSize.height.SetCoordValue(val * cbSize.height); michael@0: } michael@0: NS_ABORT_IF_FALSE(intrinsicSize.height.GetUnit() == eStyleUnit_Coord && michael@0: intrinsicSize.width.GetUnit() == eStyleUnit_Coord, michael@0: "We should have just handled the only situation where" michael@0: "we lack an intrinsic height or width."); michael@0: } michael@0: michael@0: return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions( michael@0: aRenderingContext, this, michael@0: intrinsicSize, GetIntrinsicRatio(), cbSize, michael@0: aMargin, aBorder, aPadding); michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsSVGOuterSVGFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, michael@0: ("enter nsSVGOuterSVGFrame::Reflow: availSize=%d,%d", michael@0: aReflowState.AvailableWidth(), aReflowState.AvailableHeight())); michael@0: michael@0: NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: aDesiredSize.Width() = aReflowState.ComputedWidth() + michael@0: aReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: aDesiredSize.Height() = aReflowState.ComputedHeight() + michael@0: aReflowState.ComputedPhysicalBorderPadding().TopBottom(); michael@0: michael@0: NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages."); michael@0: michael@0: SVGSVGElement *svgElem = static_cast(mContent); michael@0: michael@0: nsSVGOuterSVGAnonChildFrame *anonKid = michael@0: static_cast(GetFirstPrincipalChild()); michael@0: michael@0: if (mState & NS_FRAME_FIRST_REFLOW) { michael@0: // Initialize michael@0: svgElem->UpdateHasChildrenOnlyTransform(); michael@0: } michael@0: michael@0: // If our SVG viewport has changed, update our content and notify. michael@0: // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace michael@0: michael@0: svgFloatSize newViewportSize( michael@0: nsPresContext::AppUnitsToFloatCSSPixels(aReflowState.ComputedWidth()), michael@0: nsPresContext::AppUnitsToFloatCSSPixels(aReflowState.ComputedHeight())); michael@0: michael@0: svgFloatSize oldViewportSize = svgElem->GetViewportSize(); michael@0: michael@0: uint32_t changeBits = 0; michael@0: if (newViewportSize != oldViewportSize) { michael@0: // When our viewport size changes, we may need to update the overflow rects michael@0: // of our child frames. This is the case if: michael@0: // michael@0: // * We have a real/synthetic viewBox (a children-only transform), since michael@0: // the viewBox transform will change as the viewport dimensions change. michael@0: // michael@0: // * We do not have a real/synthetic viewBox, but the last time we michael@0: // reflowed (or the last time UpdateOverflow() was called) we did. michael@0: // michael@0: // We only handle the former case here, in which case we mark all our child michael@0: // frames as dirty so that we reflow them below and update their overflow michael@0: // rects. michael@0: // michael@0: // In the latter case, updating of overflow rects is handled for removal of michael@0: // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic michael@0: // viewBox "removal" (e.g. a document references the same SVG via both an michael@0: // and then as a CSS background image (a synthetic viewBox is michael@0: // used when painting the former, but not when painting the latter)) is michael@0: // handled in SVGSVGElement::FlushImageTransformInvalidation. michael@0: // michael@0: if (svgElem->HasViewBoxOrSyntheticViewBox()) { michael@0: nsIFrame* anonChild = GetFirstPrincipalChild(); michael@0: anonChild->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: for (nsIFrame* child = anonChild->GetFirstPrincipalChild(); child; michael@0: child = child->GetNextSibling()) { michael@0: child->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: changeBits |= COORD_CONTEXT_CHANGED; michael@0: svgElem->SetViewportSize(newViewportSize); michael@0: } michael@0: if (mFullZoom != PresContext()->GetFullZoom()) { michael@0: changeBits |= FULL_ZOOM_CHANGED; michael@0: mFullZoom = PresContext()->GetFullZoom(); michael@0: } michael@0: if (changeBits) { michael@0: NotifyViewportOrTransformChanged(changeBits); michael@0: } michael@0: mViewportInitialized = true; michael@0: michael@0: // Now that we've marked the necessary children as dirty, call michael@0: // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending michael@0: // on whether we are non-display. michael@0: mCallingReflowSVG = true; michael@0: if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { michael@0: ReflowSVGNonDisplayText(this); michael@0: } else { michael@0: // Update the mRects and visual overflow rects of all our descendants, michael@0: // including our anonymous wrapper kid: michael@0: anonKid->AddStateBits(mState & NS_FRAME_IS_DIRTY); michael@0: anonKid->ReflowSVG(); michael@0: NS_ABORT_IF_FALSE(!anonKid->GetNextSibling(), michael@0: "We should have one anonymous child frame wrapping our real children"); michael@0: } michael@0: mCallingReflowSVG = false; michael@0: michael@0: // Set our anonymous kid's offset from our border box: michael@0: anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft()); michael@0: michael@0: // Including our size in our overflow rects regardless of the value of michael@0: // 'background', 'border', etc. makes sure that we usually (when we clip to michael@0: // our content area) don't have to keep changing our overflow rects as our michael@0: // descendants move about (see perf comment below). Including our size in our michael@0: // scrollable overflow rect also makes sure that we scroll if we're too big michael@0: // for our viewport. michael@0: // michael@0: // never allows scrolling to anything outside its mRect (only panning), michael@0: // so we must always keep our scrollable overflow set to our size. michael@0: // michael@0: // With regards to visual overflow, we always clip root- (see our michael@0: // BuildDisplayList method) regardless of the value of the 'overflow' michael@0: // property since that is per-spec, even for the initial 'visible' value. For michael@0: // that reason there's no point in adding descendant visual overflow to our michael@0: // own when this frame is for a root-. That said, there's also a very michael@0: // good performance reason for us wanting to avoid doing so. If we did, then michael@0: // the frame's overflow would often change as descendants that are partially michael@0: // or fully outside its rect moved (think animation on/off screen), and that michael@0: // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the michael@0: // entire document tree each such move (see bug 875175). michael@0: // michael@0: // So it's only non-root outer- that has the visual overflow of its michael@0: // descendants added to its own. (Note that the default user-agent style michael@0: // sheet makes 'hidden' the default value for :not(root(svg)), so usually michael@0: // FinishAndStoreOverflow will still clip this back to the frame's rect.) michael@0: // michael@0: // WARNING!! Keep UpdateBounds below in sync with whatever we do for our michael@0: // overflow rects here! (Again, see bug 875175.) michael@0: // michael@0: aDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: if (!mIsRootContent) { michael@0: aDesiredSize.mOverflowAreas.VisualOverflow().UnionRect( michael@0: aDesiredSize.mOverflowAreas.VisualOverflow(), michael@0: anonKid->GetVisualOverflowRect() + anonKid->GetPosition()); michael@0: } michael@0: FinishAndStoreOverflow(&aDesiredSize); michael@0: michael@0: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, michael@0: ("exit nsSVGOuterSVGFrame::Reflow: size=%d,%d", michael@0: aDesiredSize.Width(), aDesiredSize.Height())); michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext, michael@0: const nsHTMLReflowState* aReflowState, michael@0: nsDidReflowStatus aStatus) michael@0: { michael@0: nsresult rv = nsSVGOuterSVGFrameBase::DidReflow(aPresContext,aReflowState,aStatus); michael@0: michael@0: // Make sure elements styled by :hover get updated if script/animation moves michael@0: // them under or out from under the pointer: michael@0: PresContext()->PresShell()->SynthesizeMouseMove(false); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsSVGOuterSVGFrame::UpdateOverflow() michael@0: { michael@0: // See the comments in Reflow above. michael@0: michael@0: // WARNING!! Keep this in sync with Reflow above! michael@0: michael@0: nsRect rect(nsPoint(0, 0), GetSize()); michael@0: nsOverflowAreas overflowAreas(rect, rect); michael@0: michael@0: if (!mIsRootContent) { michael@0: nsIFrame *anonKid = GetFirstPrincipalChild(); michael@0: overflowAreas.VisualOverflow().UnionRect( michael@0: overflowAreas.VisualOverflow(), michael@0: anonKid->GetVisualOverflowRect() + anonKid->GetPosition()); michael@0: } michael@0: michael@0: return FinishAndStoreOverflow(overflowAreas, GetSize()); michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // container methods michael@0: michael@0: /** michael@0: * Used to paint/hit-test SVG when SVG display lists are disabled. michael@0: */ michael@0: class nsDisplayOuterSVG : public nsDisplayItem { michael@0: public: michael@0: nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder, michael@0: nsSVGOuterSVGFrame* aFrame) : michael@0: nsDisplayItem(aBuilder, aFrame) { michael@0: MOZ_COUNT_CTOR(nsDisplayOuterSVG); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayOuterSVG() { michael@0: MOZ_COUNT_DTOR(nsDisplayOuterSVG); michael@0: } michael@0: #endif michael@0: michael@0: virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, michael@0: nsTArray *aOutFrames) MOZ_OVERRIDE; michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: michael@0: virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion* aInvalidRegion) MOZ_OVERRIDE; michael@0: michael@0: NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG) michael@0: }; michael@0: michael@0: void michael@0: nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, nsTArray *aOutFrames) michael@0: { michael@0: nsSVGOuterSVGFrame *outerSVGFrame = static_cast(mFrame); michael@0: nsRect rectAtOrigin = aRect - ToReferenceFrame(); michael@0: nsRect thisRect(nsPoint(0,0), outerSVGFrame->GetSize()); michael@0: if (!thisRect.Intersects(rectAtOrigin)) michael@0: return; michael@0: michael@0: nsPoint rectCenter(rectAtOrigin.x + rectAtOrigin.width / 2, michael@0: rectAtOrigin.y + rectAtOrigin.height / 2); michael@0: michael@0: nsSVGOuterSVGAnonChildFrame *anonKid = michael@0: static_cast( michael@0: outerSVGFrame->GetFirstPrincipalChild()); michael@0: nsIFrame* frame = nsSVGUtils::HitTestChildren( michael@0: anonKid, rectCenter + outerSVGFrame->GetPosition() - michael@0: outerSVGFrame->GetContentRect().TopLeft()); michael@0: if (frame) { michael@0: aOutFrames->AppendElement(frame); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aContext) michael@0: { michael@0: #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING) michael@0: PRTime start = PR_Now(); michael@0: #endif michael@0: michael@0: // Create an SVGAutoRenderState so we can call SetPaintingToWindow on michael@0: // it, but do so without changing the render mode: michael@0: SVGAutoRenderState state(aContext, SVGAutoRenderState::GetRenderMode(aContext)); michael@0: michael@0: if (aBuilder->IsPaintingToWindow()) { michael@0: state.SetPaintingToWindow(true); michael@0: } michael@0: michael@0: nsRect viewportRect = michael@0: mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame(); michael@0: michael@0: nsRect clipRect = mVisibleRect.Intersect(viewportRect); michael@0: michael@0: nsIntRect contentAreaDirtyRect = michael@0: (clipRect - viewportRect.TopLeft()). michael@0: ToOutsidePixels(mFrame->PresContext()->AppUnitsPerDevPixel()); michael@0: michael@0: aContext->PushState(); michael@0: aContext->Translate(viewportRect.TopLeft()); michael@0: nsSVGUtils::PaintFrameWithEffects(aContext, &contentAreaDirtyRect, mFrame); michael@0: aContext->PopState(); michael@0: michael@0: NS_ASSERTION(!aContext->ThebesContext()->HasError(), "Cairo in error state"); michael@0: michael@0: #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING) michael@0: PRTime end = PR_Now(); michael@0: printf("SVG Paint Timing: %f ms\n", (end-start)/1000.0); michael@0: #endif michael@0: } michael@0: michael@0: static PLDHashOperator CheckForeignObjectInvalidatedArea(nsPtrHashKey* aEntry, void* aData) michael@0: { michael@0: nsRegion* region = static_cast(aData); michael@0: region->Or(*region, aEntry->GetKey()->GetInvalidRegion()); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsRegion michael@0: nsSVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame) michael@0: { michael@0: nsRegion result; michael@0: if (mForeignObjectHash && mForeignObjectHash->Count()) { michael@0: mForeignObjectHash->EnumerateEntries(CheckForeignObjectInvalidatedArea, &result); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: nsDisplayOuterSVG::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion* aInvalidRegion) michael@0: { michael@0: nsSVGOuterSVGFrame *frame = static_cast(mFrame); michael@0: frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame)); michael@0: michael@0: nsRegion result = frame->GetInvalidRegion(); michael@0: result.MoveBy(ToReferenceFrame()); michael@0: frame->ClearInvalidRegion(); michael@0: michael@0: nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); michael@0: aInvalidRegion->Or(*aInvalidRegion, result); michael@0: } michael@0: michael@0: // helper michael@0: static inline bool michael@0: DependsOnIntrinsicSize(const nsIFrame* aEmbeddingFrame) michael@0: { michael@0: const nsStylePosition *pos = aEmbeddingFrame->StylePosition(); michael@0: const nsStyleCoord &width = pos->mWidth; michael@0: const nsStyleCoord &height = pos->mHeight; michael@0: michael@0: // XXX it would be nice to know if the size of aEmbeddingFrame's containing michael@0: // block depends on aEmbeddingFrame, then we'd know if we can return false michael@0: // for eStyleUnit_Percent too. michael@0: return !width.ConvertsToLength() || michael@0: !height.ConvertsToLength(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (aNameSpaceID == kNameSpaceID_None && michael@0: !(GetStateBits() & (NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY))) { michael@0: if (aAttribute == nsGkAtoms::viewBox || michael@0: aAttribute == nsGkAtoms::preserveAspectRatio || michael@0: aAttribute == nsGkAtoms::transform) { michael@0: michael@0: // make sure our cached transform matrix gets (lazily) updated michael@0: mCanvasTM = nullptr; michael@0: michael@0: nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(), michael@0: aAttribute == nsGkAtoms::viewBox ? michael@0: TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED); michael@0: michael@0: if (aAttribute != nsGkAtoms::transform) { michael@0: static_cast(mContent)->ChildrenOnlyTransformChanged(); michael@0: } michael@0: michael@0: } else if (aAttribute == nsGkAtoms::width || michael@0: aAttribute == nsGkAtoms::height) { michael@0: michael@0: // Don't call ChildrenOnlyTransformChanged() here, since we call it michael@0: // under Reflow if the width/height actually changed. michael@0: michael@0: nsIFrame* embeddingFrame; michael@0: if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) { michael@0: if (DependsOnIntrinsicSize(embeddingFrame)) { michael@0: // Tell embeddingFrame's presShell it needs to be reflowed (which takes michael@0: // care of reflowing us too). michael@0: embeddingFrame->PresContext()->PresShell()-> michael@0: FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); michael@0: } michael@0: // else our width and height is overridden - don't reflow anything michael@0: } else { michael@0: // We are not embedded by reference, so our 'width' and 'height' michael@0: // attributes are not overridden - we need to reflow. michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // painting michael@0: michael@0: void michael@0: nsSVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { michael@0: return; michael@0: } michael@0: michael@0: DisplayBorderBackgroundOutline(aBuilder, aLists); michael@0: michael@0: // Per-spec, we always clip root- even when 'overflow' has its initial michael@0: // value of 'visible'. See also the "visual overflow" comments in Reflow. michael@0: DisplayListClipState::AutoSaveRestore autoSR(aBuilder); michael@0: if (mIsRootContent || michael@0: StyleDisplay()->IsScrollableOverflow()) { michael@0: autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this); michael@0: } michael@0: michael@0: if ((aBuilder->IsForEventDelivery() && michael@0: NS_SVGDisplayListHitTestingEnabled()) || michael@0: NS_SVGDisplayListPaintingEnabled()) { michael@0: nsDisplayList *contentList = aLists.Content(); michael@0: nsDisplayListSet set(contentList, contentList, contentList, michael@0: contentList, contentList, contentList); michael@0: BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, set); michael@0: } else { michael@0: aLists.Content()->AppendNewToTop( michael@0: new (aBuilder) nsDisplayOuterSVG(aBuilder, this)); michael@0: } michael@0: } michael@0: michael@0: nsSplittableType michael@0: nsSVGOuterSVGFrame::GetSplittableType() const michael@0: { michael@0: return NS_FRAME_NOT_SPLITTABLE; michael@0: } michael@0: michael@0: nsIAtom * michael@0: nsSVGOuterSVGFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::svgOuterSVGFrame; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISVGSVGFrame methods: michael@0: michael@0: void michael@0: nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFlags && michael@0: !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED | michael@0: FULL_ZOOM_CHANGED)), michael@0: "Unexpected aFlags value"); michael@0: michael@0: // No point in doing anything when were not init'ed yet: michael@0: if (!mViewportInitialized) { michael@0: return; michael@0: } michael@0: michael@0: SVGSVGElement *content = static_cast(mContent); michael@0: michael@0: if (aFlags & COORD_CONTEXT_CHANGED) { michael@0: if (content->HasViewBoxRect()) { michael@0: // Percentage lengths on children resolve against the viewBox rect so we michael@0: // don't need to notify them of the viewport change, but the viewBox michael@0: // transform will have changed, so we need to notify them of that instead. michael@0: aFlags = TRANSFORM_CHANGED; michael@0: } michael@0: else if (content->ShouldSynthesizeViewBox()) { michael@0: // In the case of a synthesized viewBox, the synthetic viewBox's rect michael@0: // changes as the viewport changes. As a result we need to maintain the michael@0: // COORD_CONTEXT_CHANGED flag. michael@0: aFlags |= TRANSFORM_CHANGED; michael@0: } michael@0: else if (mCanvasTM && mCanvasTM->IsSingular()) { michael@0: // A width/height of zero will result in us having a singular mCanvasTM michael@0: // even when we don't have a viewBox. So we also want to recompute our michael@0: // mCanvasTM for this width/height change even though we don't have a michael@0: // viewBox. michael@0: aFlags |= TRANSFORM_CHANGED; michael@0: } michael@0: } michael@0: michael@0: bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED); michael@0: michael@0: if (aFlags & FULL_ZOOM_CHANGED) { michael@0: // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED: michael@0: aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED; michael@0: } michael@0: michael@0: if (aFlags & TRANSFORM_CHANGED) { michael@0: // Make sure our canvas transform matrix gets (lazily) recalculated: michael@0: mCanvasTM = nullptr; michael@0: michael@0: if (haveNonFulLZoomTransformChange && michael@0: !(mState & NS_FRAME_IS_NONDISPLAY)) { michael@0: uint32_t flags = (mState & NS_FRAME_IN_REFLOW) ? michael@0: SVGSVGElement::eDuringReflow : 0; michael@0: content->ChildrenOnlyTransformChanged(flags); michael@0: } michael@0: } michael@0: michael@0: nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(), aFlags); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISVGChildFrame methods: michael@0: michael@0: nsresult michael@0: nsSVGOuterSVGFrame::PaintSVG(nsRenderingContext* aContext, michael@0: const nsIntRect *aDirtyRect, michael@0: nsIFrame* aTransformRoot) michael@0: { michael@0: NS_ASSERTION(GetFirstPrincipalChild()->GetType() == michael@0: nsGkAtoms::svgOuterSVGAnonChildFrame && michael@0: !GetFirstPrincipalChild()->GetNextSibling(), michael@0: "We should have a single, anonymous, child"); michael@0: nsSVGOuterSVGAnonChildFrame *anonKid = michael@0: static_cast(GetFirstPrincipalChild()); michael@0: return anonKid->PaintSVG(aContext, aDirtyRect, aTransformRoot); michael@0: } michael@0: michael@0: SVGBBox michael@0: nsSVGOuterSVGFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace, michael@0: uint32_t aFlags) michael@0: { michael@0: NS_ASSERTION(GetFirstPrincipalChild()->GetType() == michael@0: nsGkAtoms::svgOuterSVGAnonChildFrame && michael@0: !GetFirstPrincipalChild()->GetNextSibling(), michael@0: "We should have a single, anonymous, child"); michael@0: // We must defer to our child so that we don't include our michael@0: // content->PrependLocalTransformsTo() transforms. michael@0: nsSVGOuterSVGAnonChildFrame *anonKid = michael@0: static_cast(GetFirstPrincipalChild()); michael@0: return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsSVGContainerFrame methods: michael@0: michael@0: gfxMatrix michael@0: nsSVGOuterSVGFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) michael@0: { michael@0: if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) { michael@0: if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || michael@0: (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { michael@0: return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this); michael@0: } michael@0: } michael@0: if (!mCanvasTM) { michael@0: NS_ASSERTION(!aTransformRoot, "transform root will be ignored here"); michael@0: SVGSVGElement *content = static_cast(mContent); michael@0: michael@0: float devPxPerCSSPx = michael@0: 1.0f / PresContext()->AppUnitsToFloatCSSPixels( michael@0: PresContext()->AppUnitsPerDevPixel()); michael@0: michael@0: gfxMatrix tm = content->PrependLocalTransformsTo( michael@0: gfxMatrix().Scale(devPxPerCSSPx, devPxPerCSSPx)); michael@0: mCanvasTM = new gfxMatrix(tm); michael@0: } michael@0: return *mCanvasTM; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation helpers michael@0: michael@0: bool michael@0: nsSVGOuterSVGFrame::IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame) michael@0: { michael@0: if (!mContent->GetParent()) { michael@0: // Our content is the document element michael@0: nsCOMPtr container = PresContext()->GetContainerWeak(); michael@0: nsCOMPtr window = do_GetInterface(container); michael@0: if (window) { michael@0: nsCOMPtr frameElement; michael@0: window->GetFrameElement(getter_AddRefs(frameElement)); michael@0: nsCOMPtr olc = do_QueryInterface(frameElement); michael@0: if (olc) { michael@0: // Our document is inside an HTML 'object', 'embed' or 'applet' element michael@0: if (aEmbeddingFrame) { michael@0: nsCOMPtr element = do_QueryInterface(frameElement); michael@0: *aEmbeddingFrame = element->GetPrimaryFrame(); michael@0: NS_ASSERTION(*aEmbeddingFrame, "Yikes, no embedding frame!"); michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: if (aEmbeddingFrame) { michael@0: *aEmbeddingFrame = nullptr; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsSVGOuterSVGFrame::IsRootOfImage() michael@0: { michael@0: if (!mContent->GetParent()) { michael@0: // Our content is the document element michael@0: nsIDocument* doc = mContent->GetCurrentDoc(); michael@0: if (doc && doc->IsBeingUsedAsImage()) { michael@0: // Our document is being used as an image michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsSVGOuterSVGFrame::VerticalScrollbarNotNeeded() const michael@0: { michael@0: nsSVGLength2 &height = static_cast(mContent)-> michael@0: mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; michael@0: return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100; michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation of nsSVGOuterSVGAnonChildFrame michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, michael@0: nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSVGOuterSVGAnonChildFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGAnonChildFrame) michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: nsSVGOuterSVGAnonChildFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ABORT_IF_FALSE(aParent->GetType() == nsGkAtoms::svgOuterSVGFrame, michael@0: "Unexpected parent"); michael@0: nsSVGOuterSVGAnonChildFrameBase::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: #endif michael@0: michael@0: nsIAtom * michael@0: nsSVGOuterSVGAnonChildFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::svgOuterSVGAnonChildFrame; michael@0: } michael@0: michael@0: bool michael@0: nsSVGOuterSVGAnonChildFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const michael@0: { michael@0: // We must claim our nsSVGOuterSVGFrame's children-only transforms as our own michael@0: // so that the children we are used to wrap are transformed properly. michael@0: michael@0: SVGSVGElement *content = static_cast(mContent); michael@0: michael@0: bool hasTransform = content->HasChildrenOnlyTransform(); michael@0: michael@0: if (hasTransform && aTransform) { michael@0: // Outer- doesn't use x/y, so we can pass eChildToUserSpace here. michael@0: gfxMatrix identity; michael@0: *aTransform = gfx::ToMatrix( michael@0: content->PrependLocalTransformsTo(identity, michael@0: nsSVGElement::eChildToUserSpace)); michael@0: } michael@0: michael@0: return hasTransform; michael@0: }