1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is the Mozilla SVG project.
17 * The Initial Developer of the Original Code is
18 * Crocodile Clips Ltd..
19 * Portions created by the Initial Developer are Copyright (C) 2001
20 * the Initial Developer. All Rights Reserved.
23 * Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsSVGForeignObjectFrame.h"
41 #include "nsIDOMSVGForeignObjectElem.h"
42 #include "nsIDOMSVGSVGElement.h"
43 #include "nsSVGOuterSVGFrame.h"
45 #include "nsGkAtoms.h"
46 #include "nsLayoutUtils.h"
47 #include "nsSVGUtils.h"
49 #include "nsSVGRect.h"
50 #include "nsSVGMatrix.h"
51 #include "nsINameSpaceManager.h"
52 #include "nsSVGForeignObjectElement.h"
53 #include "nsSVGContainerFrame.h"
54 #include "gfxContext.h"
55 #include "gfxMatrix.h"
57 //----------------------------------------------------------------------
61 NS_NewSVGForeignObjectFrame(nsIPresShell
*aPresShell
,
62 nsStyleContext
*aContext
)
64 return new (aPresShell
) nsSVGForeignObjectFrame(aContext
);
67 NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame
)
69 nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(nsStyleContext
* aContext
)
70 : nsSVGForeignObjectFrameBase(aContext
),
73 AddStateBits(NS_FRAME_REFLOW_ROOT
|
74 NS_FRAME_MAY_BE_TRANSFORMED_OR_HAVE_RENDERING_OBSERVERS
);
77 //----------------------------------------------------------------------
80 NS_QUERYFRAME_HEAD(nsSVGForeignObjectFrame
)
81 NS_QUERYFRAME_ENTRY(nsISVGChildFrame
)
82 NS_QUERYFRAME_TAIL_INHERITING(nsSVGForeignObjectFrameBase
)
85 nsSVGForeignObjectFrame::Init(nsIContent
* aContent
,
87 nsIFrame
* aPrevInFlow
)
90 nsCOMPtr
<nsIDOMSVGForeignObjectElement
> foreignObject
= do_QueryInterface(aContent
);
91 NS_ASSERTION(foreignObject
, "Content is not an SVG foreignObject!");
94 nsresult rv
= nsSVGForeignObjectFrameBase::Init(aContent
, aParent
, aPrevInFlow
);
95 AddStateBits(NS_STATE_SVG_PROPAGATE_TRANSFORM
|
96 (aParent
->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
));
97 if (NS_SUCCEEDED(rv
)) {
98 nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
103 void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
105 nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
106 nsSVGForeignObjectFrameBase::DestroyFrom(aDestructRoot
);
110 nsSVGForeignObjectFrame::GetType() const
112 return nsGkAtoms::svgForeignObjectFrame
;
116 nsSVGForeignObjectFrame::AttributeChanged(PRInt32 aNameSpaceID
,
120 if (aNameSpaceID
== kNameSpaceID_None
) {
121 if (aAttribute
== nsGkAtoms::width
||
122 aAttribute
== nsGkAtoms::height
) {
123 UpdateGraphic(); // update mRect before requesting reflow
124 // XXXjwatt: why mark intrinsic widths dirty? can't we just use eResize?
125 RequestReflow(nsIPresShell::eStyleChange
);
126 } else if (aAttribute
== nsGkAtoms::x
||
127 aAttribute
== nsGkAtoms::y
||
128 aAttribute
== nsGkAtoms::transform
) {
129 // make sure our cached transform matrix gets (lazily) updated
139 nsSVGForeignObjectFrame::Reflow(nsPresContext
* aPresContext
,
140 nsHTMLReflowMetrics
& aDesiredSize
,
141 const nsHTMLReflowState
& aReflowState
,
142 nsReflowStatus
& aStatus
)
144 // InitialUpdate and AttributeChanged make sure mRect is up to date before
145 // we're called (UpdateCoveredRegion sets mRect).
147 NS_ASSERTION(!aReflowState
.parentReflowState
,
148 "should only get reflow from being reflow root");
149 NS_ASSERTION(aReflowState
.ComputedWidth() == GetSize().width
&&
150 aReflowState
.ComputedHeight() == GetSize().height
,
151 "reflow roots should be reflowed at existing size and "
152 "svg.css should ensure we have no padding/border/margin");
156 aDesiredSize
.width
= aReflowState
.ComputedWidth();
157 aDesiredSize
.height
= aReflowState
.ComputedHeight();
158 aDesiredSize
.mOverflowArea
=
159 nsRect(0, 0, aReflowState
.ComputedWidth(), aReflowState
.ComputedHeight());
160 aStatus
= NS_FRAME_COMPLETE
;
166 nsSVGForeignObjectFrame::InvalidateInternal(const nsRect
& aDamageRect
,
167 nscoord aX
, nscoord aY
,
171 // This is called by our descendants when they change.
173 if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
)
176 nsRegion
* region
= (aFlags
& INVALIDATE_CROSS_DOC
)
177 ? &mSubDocDirtyRegion
: &mSameDocDirtyRegion
;
178 region
->Or(*region
, aDamageRect
+ nsPoint(aX
, aY
));
184 * Returns the app unit canvas bounds of a userspace rect.
186 * @param aToCanvas Transform from userspace to canvas device space.
189 ToCanvasBounds(const gfxRect
&aUserspaceRect
,
190 const gfxMatrix
&aToCanvas
,
191 const nsPresContext
*presContext
)
193 return nsLayoutUtils::RoundGfxRectToAppRect(
194 aToCanvas
.TransformBounds(aUserspaceRect
),
195 presContext
->AppUnitsPerDevPixel());
199 nsSVGForeignObjectFrame::PaintSVG(nsSVGRenderState
*aContext
,
200 const nsIntRect
*aDirtyRect
)
205 nsIFrame
* kid
= GetFirstChild(nsnull
);
209 gfxMatrix matrix
= GetCanvasTMForChildren();
211 nsIRenderingContext
*ctx
= aContext
->GetRenderingContext(this);
213 if (!ctx
|| matrix
.IsSingular()) {
214 NS_WARNING("Can't render foreignObject element!");
215 return NS_ERROR_FAILURE
;
218 /* Check if we need to draw anything. */
220 PRInt32 appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
221 if (!mRect
.ToOutsidePixels(appUnitsPerDevPx
).Intersects(*aDirtyRect
))
225 gfxContext
*gfx
= aContext
->GetGfxContext();
229 if (GetStyleDisplay()->IsScrollableOverflow()) {
230 float x
, y
, width
, height
;
231 static_cast<nsSVGElement
*>(mContent
)->
232 GetAnimatedLengthValues(&x
, &y
, &width
, &height
, nsnull
);
235 nsSVGUtils::GetClipRectForFrame(this, 0.0f
, 0.0f
, width
, height
);
236 nsSVGUtils::SetClipRect(gfx
, GetCanvasTM(), clipRect
);
239 gfx
->Multiply(matrix
);
241 nsresult rv
= nsLayoutUtils::PaintFrame(ctx
, kid
, nsRegion(kid
->GetRect()),
243 nsLayoutUtils::PAINT_IN_TRANSFORM
);
251 nsSVGForeignObjectFrame::GetTransformMatrix(nsIFrame
**aOutAncestor
)
253 NS_PRECONDITION(aOutAncestor
, "We need an ancestor to write to!");
255 /* Set the ancestor to be the outer frame. */
256 *aOutAncestor
= nsSVGUtils::GetOuterSVGFrame(this);
257 NS_ASSERTION(*aOutAncestor
, "How did we end up without an outer frame?");
259 /* Return the matrix back to the root, factoring in the x and y offsets. */
260 return GetCanvasTMForChildren();
263 NS_IMETHODIMP_(nsIFrame
*)
264 nsSVGForeignObjectFrame::GetFrameForPoint(const nsPoint
&aPoint
)
266 if (IsDisabled() || (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
))
269 nsIFrame
* kid
= GetFirstChild(nsnull
);
273 float x
, y
, width
, height
;
274 static_cast<nsSVGElement
*>(mContent
)->
275 GetAnimatedLengthValues(&x
, &y
, &width
, &height
, nsnull
);
277 gfxMatrix tm
= GetCanvasTM().Invert();
281 // Convert aPoint from app units in canvas space to user space:
283 gfxPoint pt
= gfxPoint(aPoint
.x
, aPoint
.y
) / PresContext()->AppUnitsPerDevPixel();
284 pt
= tm
.Transform(pt
);
286 if (!gfxRect(0.0f
, 0.0f
, width
, height
).Contains(pt
))
289 // Convert pt to app units in *local* space:
291 pt
= pt
* nsPresContext::AppUnitsPerCSSPixel();
292 nsPoint point
= nsPoint(NSToIntRound(pt
.x
), NSToIntRound(pt
.y
));
294 return nsLayoutUtils::GetFrameForPoint(kid
, point
);
297 NS_IMETHODIMP_(nsRect
)
298 nsSVGForeignObjectFrame::GetCoveredRegion()
304 nsSVGForeignObjectFrame::UpdateCoveredRegion()
306 if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
)
307 return NS_ERROR_FAILURE
;
310 static_cast<nsSVGForeignObjectElement
*>(mContent
)->
311 GetAnimatedLengthValues(&x
, &y
, &w
, &h
, nsnull
);
313 // If mRect's width or height are negative, reflow blows up! We must clamp!
314 if (w
< 0.0f
) w
= 0.0f
;
315 if (h
< 0.0f
) h
= 0.0f
;
317 // GetCanvasTM includes the x,y translation
318 mRect
= ToCanvasBounds(gfxRect(0.0, 0.0, w
, h
), GetCanvasTM(), PresContext());
324 nsSVGForeignObjectFrame::InitialUpdate()
326 NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW
,
327 "Yikes! We've been called already! Hopefully we weren't called "
328 "before our nsSVGOuterSVGFrame's initial Reflow()!!!");
330 UpdateCoveredRegion();
332 // Make sure to not allow interrupts if we're not being reflown as a root
333 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
336 NS_ASSERTION(!(mState
& NS_FRAME_IN_REFLOW
),
337 "We don't actually participate in reflow");
339 // Do unset the various reflow bits, though.
340 mState
&= ~(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
341 NS_FRAME_HAS_DIRTY_CHILDREN
);
347 nsSVGForeignObjectFrame::NotifySVGChanged(PRUint32 aFlags
)
349 PRBool reflow
= PR_FALSE
;
351 if (aFlags
& TRANSFORM_CHANGED
) {
352 // In an ideal world we would reflow when our CTM changes. This is because
353 // glyph metrics do not necessarily scale uniformly with change in scale
354 // and, as a result, CTM changes may require text to break at different
355 // points. The problem would be how to keep performance acceptable when
356 // e.g. the transform of an ancestor is animated.
357 // We also seem to get some sort of infinite loop post bug 421584 if we
360 if (!(aFlags
& SUPPRESS_INVALIDATION
)) {
364 } else if (aFlags
& COORD_CONTEXT_CHANGED
) {
365 // Our coordinate context's width/height has changed. If we have a
366 // percentage width/height our dimensions will change so we must reflow.
367 nsSVGForeignObjectElement
*fO
=
368 static_cast<nsSVGForeignObjectElement
*>(mContent
);
369 if (fO
->mLengthAttributes
[nsSVGForeignObjectElement::WIDTH
].IsPercentage() ||
370 fO
->mLengthAttributes
[nsSVGForeignObjectElement::HEIGHT
].IsPercentage()) {
376 // If we're called while the PresShell is handling reflow events then we
377 // must have been called as a result of the NotifyViewportChange() call in
378 // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
379 // at this point (i.e. during reflow) because it could confuse the
380 // PresShell and prevent it from reflowing us properly in future. Besides
381 // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
382 // synchronously, so there's no need.
384 PresContext()->PresShell()->IsReflowLocked(&reflowing
);
386 UpdateGraphic(); // update mRect before requesting reflow
387 RequestReflow(nsIPresShell::eResize
);
393 nsSVGForeignObjectFrame::NotifyRedrawSuspended()
399 nsSVGForeignObjectFrame::NotifyRedrawUnsuspended()
401 if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
)) {
402 if (GetStateBits() & NS_STATE_SVG_DIRTY
) {
403 UpdateGraphic(); // invalidate our entire area
405 FlushDirtyRegion(); // only invalidate areas dirtied by our descendants
412 nsSVGForeignObjectFrame::SetMatrixPropagation(PRBool aPropagate
)
415 AddStateBits(NS_STATE_SVG_PROPAGATE_TRANSFORM
);
417 RemoveStateBits(NS_STATE_SVG_PROPAGATE_TRANSFORM
);
423 nsSVGForeignObjectFrame::GetMatrixPropagation()
425 return (GetStateBits() & NS_STATE_SVG_PROPAGATE_TRANSFORM
) != 0;
429 nsSVGForeignObjectFrame::GetBBoxContribution(const gfxMatrix
&aToBBoxUserspace
)
431 NS_ASSERTION(!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
),
432 "Should not be calling this on a non-display child");
434 nsSVGForeignObjectElement
*content
=
435 static_cast<nsSVGForeignObjectElement
*>(mContent
);
438 content
->GetAnimatedLengthValues(&x
, &y
, &w
, &h
, nsnull
);
440 if (w
< 0.0f
) w
= 0.0f
;
441 if (h
< 0.0f
) h
= 0.0f
;
443 if (aToBBoxUserspace
.IsSingular()) {
444 // XXX ReportToConsole
445 return gfxRect(0.0, 0.0, 0.0, 0.0);
447 return aToBBoxUserspace
.TransformBounds(gfxRect(0.0, 0.0, w
, h
));
450 //----------------------------------------------------------------------
453 nsSVGForeignObjectFrame::GetCanvasTM()
456 NS_ASSERTION(mParent
, "null parent");
458 nsSVGContainerFrame
*parent
= static_cast<nsSVGContainerFrame
*>(mParent
);
459 nsSVGForeignObjectElement
*content
=
460 static_cast<nsSVGForeignObjectElement
*>(mContent
);
462 gfxMatrix tm
= content
->PrependLocalTransformTo(parent
->GetCanvasTM());
464 mCanvasTM
= NS_NewSVGMatrix(tm
);
466 return nsSVGUtils::ConvertSVGMatrixToThebes(mCanvasTM
);
469 //----------------------------------------------------------------------
470 // Implementation helpers
473 nsSVGForeignObjectFrame::GetCanvasTMForChildren()
475 float cssPxPerDevPx
= PresContext()->
476 AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel());
478 return GetCanvasTM().Scale(cssPxPerDevPx
, cssPxPerDevPx
);
481 void nsSVGForeignObjectFrame::RequestReflow(nsIPresShell::IntrinsicDirty aType
)
483 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
)
484 // If we haven't had an InitialUpdate yet, nothing to do.
487 nsIFrame
* kid
= GetFirstChild(nsnull
);
491 PresContext()->PresShell()->FrameNeedsReflow(kid
, aType
, NS_FRAME_IS_DIRTY
);
494 void nsSVGForeignObjectFrame::UpdateGraphic()
496 nsSVGUtils::UpdateGraphic(this);
498 // We just invalidated our entire area, so clear the caches of areas dirtied
499 // by our descendants:
500 mSameDocDirtyRegion
.SetEmpty();
501 mSubDocDirtyRegion
.SetEmpty();
505 nsSVGForeignObjectFrame::MaybeReflowFromOuterSVGFrame()
507 nsIFrame
* kid
= GetFirstChild(nsnull
);
509 // If we're already scheduled to reflow (if we or our kid is dirty) we don't
510 // want to reflow now or else our presShell will do extra work trying to
511 // reflow us a second time. (It will also complain if it finds that a reflow
512 // root scheduled for reflow isn't dirty).
514 if (kid
->GetStateBits() & NS_FRAME_IS_DIRTY
) {
517 kid
->AddStateBits(NS_FRAME_IS_DIRTY
); // we must be fully marked dirty
518 if (kid
->GetStateBits() & NS_FRAME_HAS_DIRTY_CHILDREN
) {
522 // Make sure to not allow interrupts if we're not being reflown as a root
523 nsPresContext::InterruptPreventer
noInterrupts(PresContext());
528 nsSVGForeignObjectFrame::DoReflow()
531 printf("**nsSVGForeignObjectFrame::DoReflow()\n");
534 NS_ASSERTION(!(nsSVGUtils::GetOuterSVGFrame(this)->
535 GetStateBits() & NS_FRAME_FIRST_REFLOW
),
536 "Calling InitialUpdate too early - must not call DoReflow!!!");
541 if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD
)
544 nsPresContext
*presContext
= PresContext();
545 nsIFrame
* kid
= GetFirstChild(nsnull
);
549 // initiate a synchronous reflow here and now:
550 nsSize
availableSpace(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
551 nsCOMPtr
<nsIRenderingContext
> renderingContext
;
552 nsIPresShell
* presShell
= presContext
->PresShell();
553 NS_ASSERTION(presShell
, "null presShell");
554 presShell
->CreateRenderingContext(this,getter_AddRefs(renderingContext
));
555 if (!renderingContext
)
558 nsSVGForeignObjectElement
*fO
= static_cast<nsSVGForeignObjectElement
*>
562 fO
->mLengthAttributes
[nsSVGForeignObjectElement::WIDTH
].GetAnimValue(fO
);
564 fO
->mLengthAttributes
[nsSVGForeignObjectElement::HEIGHT
].GetAnimValue(fO
);
566 nsSize
size(nsPresContext::CSSPixelsToAppUnits(width
),
567 nsPresContext::CSSPixelsToAppUnits(height
));
571 nsHTMLReflowState
reflowState(presContext
, kid
,
573 nsSize(size
.width
, NS_UNCONSTRAINEDSIZE
));
574 nsHTMLReflowMetrics desiredSize
;
575 nsReflowStatus status
;
577 // We don't use size.height above because that tells the child to do
578 // page/column breaking at that height.
579 NS_ASSERTION(reflowState
.mComputedBorderPadding
== nsMargin(0, 0, 0, 0) &&
580 reflowState
.mComputedMargin
== nsMargin(0, 0, 0, 0),
581 "style system should ensure that :-moz-svg-foreign content "
582 "does not get styled");
583 NS_ASSERTION(reflowState
.ComputedWidth() == size
.width
,
584 "reflow state made child wrong size");
585 reflowState
.SetComputedHeight(size
.height
);
587 ReflowChild(kid
, presContext
, desiredSize
, reflowState
, 0, 0,
588 NS_FRAME_NO_MOVE_FRAME
, status
);
589 NS_ASSERTION(size
.width
== desiredSize
.width
&&
590 size
.height
== desiredSize
.height
, "unexpected size");
591 FinishReflowChild(kid
, presContext
, &reflowState
, desiredSize
, 0, 0,
592 NS_FRAME_NO_MOVE_FRAME
);
594 mInReflow
= PR_FALSE
;
599 nsSVGForeignObjectFrame::InvalidateDirtyRect(nsSVGOuterSVGFrame
* aOuter
,
600 const nsRect
& aRect
, PRUint32 aFlags
)
605 // The areas dirtied by children are in app units, relative to this frame.
606 // We need to convert the rect to userspace to use IntersectRect.
608 gfxRect
r(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
609 r
.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel());
611 nsRect rect
= ToCanvasBounds(r
, GetCanvasTM(), PresContext());
613 // Don't invalidate areas outside our bounds:
614 rect
.IntersectRect(rect
, mRect
);
618 // XXX invalidate the entire covered region
620 rect
.UnionRect(rect
, mRect
);
622 rect
= nsSVGUtils::FindFilterInvalidation(this, rect
);
623 aOuter
->InvalidateWithFlags(rect
, aFlags
);
627 nsSVGForeignObjectFrame::FlushDirtyRegion()
629 if ((mSameDocDirtyRegion
.IsEmpty() && mSubDocDirtyRegion
.IsEmpty()) ||
633 nsSVGOuterSVGFrame
*outerSVGFrame
= nsSVGUtils::GetOuterSVGFrame(this);
634 if (!outerSVGFrame
) {
635 NS_ERROR("null outerSVGFrame");
639 if (outerSVGFrame
->IsRedrawSuspended())
642 InvalidateDirtyRect(outerSVGFrame
, mSameDocDirtyRegion
.GetBounds(), 0);
643 InvalidateDirtyRect(outerSVGFrame
, mSubDocDirtyRegion
.GetBounds(), INVALIDATE_CROSS_DOC
);
645 mSameDocDirtyRegion
.SetEmpty();
646 mSubDocDirtyRegion
.SetEmpty();