Bug 769742 - Account for nsSVGOuterSVGFrames' border/padding offset by giving nsSVGOuterSVGFrame an anonymous child to wrap its real children. r=roc.

This commit is contained in:
Jonathan Watt 2012-07-09 02:04:56 +01:00
parent 09ff7e2e99
commit f410406b60
8 changed files with 325 additions and 58 deletions

View File

@ -1750,6 +1750,7 @@ GK_ATOM(svgLinearGradientFrame, "SVGLinearGradientFrame")
GK_ATOM(svgMarkerFrame, "SVGMarkerFrame")
GK_ATOM(svgMaskFrame, "SVGMaskFrame")
GK_ATOM(svgOuterSVGFrame, "SVGOuterSVGFrame")
GK_ATOM(svgOuterSVGAnonChildFrame, "SVGOuterSVGAnonChildFrame")
GK_ATOM(svgPathGeometryFrame, "SVGPathGeometryFrame")
GK_ATOM(svgPatternFrame, "SVGPatternFrame")
GK_ATOM(svgRadialGradientFrame, "SVGRadialGradientFrame")

View File

@ -139,6 +139,8 @@ NS_NewHTMLVideoFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
nsIFrame*
NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
nsIFrame*
NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
nsIFrame*
NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
nsIFrame*
NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
@ -2407,25 +2409,30 @@ nsCSSFrameConstructor::ConstructDocElementFrame(Element* aDocEle
#endif
if (aDocElement->IsSVG()) {
if (aDocElement->Tag() == nsGkAtoms::svg) {
contentFrame = NS_NewSVGOuterSVGFrame(mPresShell, styleContext);
if (NS_UNLIKELY(!contentFrame)) {
return NS_ERROR_OUT_OF_MEMORY;
}
InitAndRestoreFrame(state, aDocElement,
state.GetGeometricParent(display,
mDocElementContainingBlock),
nsnull, contentFrame);
// We're going to call the right function ourselves, so no need to give a
// function to this FrameConstructionData.
// XXXbz on the other hand, if we converted this whole function to
// FrameConstructionData/Item, then we'd need the right function
// here... but would probably be able to get away with less code in this
// function in general.
// Use a null PendingBinding, since our binding is not in fact pending.
static const FrameConstructionData rootSVGData = FCDATA_DECL(0, nsnull);
nsRefPtr<nsStyleContext> extraRef(styleContext);
FrameConstructionItem item(&rootSVGData, aDocElement,
aDocElement->Tag(), kNameSpaceID_SVG,
nsnull, extraRef.forget(), true);
// AddChild takes care of transforming the frame tree for fixed-pos
// or abs-pos situations
nsFrameItems frameItems;
rv = state.AddChild(contentFrame, frameItems, aDocElement,
styleContext, mDocElementContainingBlock);
if (NS_FAILED(rv) || frameItems.IsEmpty()) {
rv = ConstructOuterSVG(state, item, mDocElementContainingBlock,
styleContext->GetStyleDisplay(),
frameItems, &contentFrame);
if (NS_FAILED(rv))
return rv;
}
if (!contentFrame || frameItems.IsEmpty())
return NS_ERROR_FAILURE;
*aNewFrame = frameItems.FirstChild();
processChildren = true;
NS_ASSERTION(frameItems.OnlyChild(), "multiple root element frames");
} else {
return NS_ERROR_FAILURE;
}
@ -2498,8 +2505,9 @@ nsCSSFrameConstructor::ConstructDocElementFrame(Element* aDocEle
// Still need to process the child content
nsFrameItems childItems;
NS_ASSERTION(!nsLayoutUtils::GetAsBlock(contentFrame),
"Only XUL and SVG frames should reach here");
NS_ASSERTION(!nsLayoutUtils::GetAsBlock(contentFrame) &&
!contentFrame->IsFrameOfType(nsIFrame::eSVG),
"Only XUL frames should reach here");
// Use a null PendingBinding, since our binding is not in fact pending.
ProcessChildren(state, aDocElement, styleContext, contentFrame, true,
childItems, false, nsnull);
@ -4702,6 +4710,82 @@ nsCSSFrameConstructor::FindMathMLData(Element* aElement,
ArrayLength(sMathMLData));
}
// Construct an nsSVGOuterSVGFrame, the anonymous child that wraps its real
// children, and its descendant frames.
nsresult
nsCSSFrameConstructor::ConstructOuterSVG(nsFrameConstructorState& aState,
FrameConstructionItem& aItem,
nsIFrame* aParentFrame,
const nsStyleDisplay* aDisplay,
nsFrameItems& aFrameItems,
nsIFrame** aNewFrame)
{
nsIContent* const content = aItem.mContent;
nsStyleContext* const styleContext = aItem.mStyleContext;
nsresult rv = NS_OK;
// Create the nsSVGOuterSVGFrame:
nsIFrame* newFrame = NS_NewSVGOuterSVGFrame(mPresShell, styleContext);
nsIFrame* geometricParent =
aState.GetGeometricParent(styleContext->GetStyleDisplay(),
aParentFrame);
InitAndRestoreFrame(aState, content, geometricParent, nsnull, newFrame);
// Create the pseudo SC for the anonymous wrapper child as a child of the SC:
nsRefPtr<nsStyleContext> scForAnon;
scForAnon = mPresShell->StyleSet()->
ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozSVGOuterSVGAnonChild,
styleContext);
// Create the anonymous inner wrapper frame
nsIFrame* innerFrame = NS_NewSVGOuterSVGAnonChildFrame(mPresShell, scForAnon);
if (!innerFrame) {
newFrame->Destroy();
return NS_ERROR_OUT_OF_MEMORY;
}
InitAndRestoreFrame(aState, content, newFrame, nsnull, innerFrame);
// Put the newly created frames into the right child list
SetInitialSingleChild(newFrame, innerFrame);
rv = aState.AddChild(newFrame, aFrameItems, content, styleContext,
aParentFrame);
if (NS_FAILED(rv)) {
return rv;
}
if (!mRootElementFrame) {
// The frame we're constructing will be the root element frame.
// Set mRootElementFrame before processing children.
mRootElementFrame = newFrame;
}
nsFrameItems childItems;
// Process children
if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
rv = ConstructFramesFromItemList(aState, aItem.mChildItems,
innerFrame, childItems);
} else {
rv = ProcessChildren(aState, content, styleContext, innerFrame,
true, childItems, false, aItem.mPendingBinding);
}
// XXXbz what about cleaning up?
if (NS_FAILED(rv)) return rv;
// Set the inner wrapper frame's initial primary list
innerFrame->SetInitialChildList(kPrincipalList, childItems);
*aNewFrame = newFrame;
return rv;
}
// Only outer <svg> elements can be floated or positioned. All other SVG
// should be in-flow.
#define SIMPLE_SVG_FCDATA(_func) \
@ -4789,8 +4873,7 @@ nsCSSFrameConstructor::FindSVGData(Element* aElement,
// and do the PassesConditionalProcessingTests call in
// nsSVGOuterSVGFrame::Init.
static const FrameConstructionData sOuterSVGData =
FCDATA_DECL(FCDATA_SKIP_ABSPOS_PUSH | FCDATA_DISALLOW_GENERATED_CONTENT,
NS_NewSVGOuterSVGFrame);
FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructOuterSVG);
return &sOuterSVGData;
}

View File

@ -1351,6 +1351,18 @@ private:
nsStyleContext* aStyleContext);
// SVG - rods
/**
* Construct an nsSVGOuterSVGFrame, the anonymous child that wraps its real
* children, and its descendant frames. This is the FrameConstructionData
* callback used for the job.
*/
nsresult ConstructOuterSVG(nsFrameConstructorState& aState,
FrameConstructionItem& aItem,
nsIFrame* aParentFrame,
const nsStyleDisplay* aDisplay,
nsFrameItems& aFrameItems,
nsIFrame** aNewFrame);
static const FrameConstructionData* FindSVGData(Element* aElement,
nsIAtom* aTag,
PRInt32 aNameSpaceID,

View File

@ -147,6 +147,7 @@ FRAME_ID(nsSVGLinearGradientFrame)
FRAME_ID(nsSVGMarkerFrame)
FRAME_ID(nsSVGMaskFrame)
FRAME_ID(nsSVGOuterSVGFrame)
FRAME_ID(nsSVGOuterSVGAnonChildFrame)
FRAME_ID(nsSVGPaintServerFrame)
FRAME_ID(nsSVGPathGeometryFrame)
FRAME_ID(nsSVGPatternFrame)

View File

@ -81,4 +81,5 @@ CSS_ANON_BOX(moztreeprogressmeter, ":-moz-tree-progressmeter")
CSS_ANON_BOX(moztreedropfeedback, ":-moz-tree-drop-feedback")
#endif
CSS_ANON_BOX(mozSVGOuterSVGAnonChild, ":-moz-svg-outer-svg-anon-child")
CSS_ANON_BOX(mozSVGForeignContent, ":-moz-svg-foreign-content")

View File

@ -391,29 +391,32 @@ nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext,
NotifyViewportOrTransformChanged(changeBits);
}
// Now that we've marked the necessary children as dirty, call
// UpdateBounds() on them:
nsSVGOuterSVGAnonChildFrame *anonKid =
static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
mCallingUpdateBounds = true;
if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
// Now that we've marked the necessary children as dirty, call
// UpdateBounds() on them:
if (!(mState & NS_STATE_SVG_NONDISPLAY_CHILD)) {
nsIFrame* kid = mFrames.FirstChild();
while (kid) {
nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
if (SVGFrame && !(kid->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
SVGFrame->UpdateBounds();
}
kid = kid->GetNextSibling();
}
mCallingUpdateBounds = true;
// Update the mRects and visual overflow rects of all our descendants,
// including our anonymous wrapper kid:
anonKid->UpdateBounds();
NS_ABORT_IF_FALSE(!anonKid->GetNextSibling(),
"We should have one anonymous child frame wrapping our real children");
mCallingUpdateBounds = false;
}
mCallingUpdateBounds = false;
// Make sure we scroll if we're too big:
// XXX Use the bounding box of our descendants? (See bug 353460 comment 14.)
aDesiredSize.SetOverflowAreasToDesiredBounds();
FinishAndStoreOverflow(&aDesiredSize);
// Set our anonymous kid's offset from our border box:
anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft());
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
("exit nsSVGOuterSVGFrame::Reflow: size=%d,%d",
aDesiredSize.width, aDesiredSize.height));
@ -471,9 +474,12 @@ nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
nsPoint rectCenter(rectAtOrigin.x + rectAtOrigin.width / 2,
rectAtOrigin.y + rectAtOrigin.height / 2);
nsSVGOuterSVGAnonChildFrame *anonKid =
static_cast<nsSVGOuterSVGAnonChildFrame*>(
outerSVGFrame->GetFirstPrincipalChild());
nsIFrame* frame = nsSVGUtils::HitTestChildren(
outerSVGFrame, rectCenter + outerSVGFrame->GetPosition() -
outerSVGFrame->GetContentRect().TopLeft());
anonKid, rectCenter + outerSVGFrame->GetPosition() -
outerSVGFrame->GetContentRect().TopLeft());
if (frame) {
aOutFrames->AppendElement(frame);
}
@ -546,8 +552,8 @@ nsSVGOuterSVGFrame::AttributeChanged(PRInt32 aNameSpaceID,
// make sure our cached transform matrix gets (lazily) updated
mCanvasTM = nsnull;
nsSVGUtils::NotifyChildrenOfSVGChange(
this, aAttribute == nsGkAtoms::viewBox ?
nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(),
aAttribute == nsGkAtoms::viewBox ?
TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED);
static_cast<nsSVGSVGElement*>(mContent)->ChildrenOnlyTransformChanged();
@ -676,7 +682,38 @@ nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(PRUint32 aFlags)
}
}
nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(), aFlags);
}
//----------------------------------------------------------------------
// nsISVGChildFrame methods:
NS_IMETHODIMP
nsSVGOuterSVGFrame::PaintSVG(nsRenderingContext* aContext,
const nsIntRect *aDirtyRect)
{
NS_ASSERTION(GetFirstPrincipalChild()->GetType() ==
nsGkAtoms::svgOuterSVGAnonChildFrame &&
!GetFirstPrincipalChild()->GetNextSibling(),
"We should have a single, anonymous, child");
nsSVGOuterSVGAnonChildFrame *anonKid =
static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
return anonKid->PaintSVG(aContext, aDirtyRect);
}
SVGBBox
nsSVGOuterSVGFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
PRUint32 aFlags)
{
NS_ASSERTION(GetFirstPrincipalChild()->GetType() ==
nsGkAtoms::svgOuterSVGAnonChildFrame &&
!GetFirstPrincipalChild()->GetNextSibling(),
"We should have a single, anonymous, child");
// We must defer to our child so that we don't include our
// content->PrependLocalTransformsTo() transforms.
nsSVGOuterSVGAnonChildFrame *anonKid =
static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags);
}
//----------------------------------------------------------------------
@ -705,24 +742,6 @@ nsSVGOuterSVGFrame::GetCanvasTM(PRUint32 aFor)
return *mCanvasTM;
}
bool
nsSVGOuterSVGFrame::HasChildrenOnlyTransform(gfxMatrix *aTransform) const
{
nsSVGSVGElement *content = static_cast<nsSVGSVGElement*>(mContent);
bool hasTransform = content->HasChildrenOnlyTransform();
if (hasTransform && aTransform) {
// Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
gfxMatrix identity;
*aTransform =
content->PrependLocalTransformsTo(identity,
nsSVGElement::eChildToUserSpace);
}
return hasTransform;
}
//----------------------------------------------------------------------
// Implementation helpers
@ -777,3 +796,55 @@ nsSVGOuterSVGFrame::VerticalScrollbarNotNeeded() const
mLengthAttributes[nsSVGSVGElement::HEIGHT];
return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100;
}
//----------------------------------------------------------------------
// Implementation of nsSVGOuterSVGAnonChildFrame
nsIFrame*
NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext)
{
return new (aPresShell) nsSVGOuterSVGAnonChildFrame(aContext);
}
NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGAnonChildFrame)
#ifdef DEBUG
NS_IMETHODIMP
nsSVGOuterSVGAnonChildFrame::Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow)
{
NS_ABORT_IF_FALSE(aParent->GetType() == nsGkAtoms::svgOuterSVGFrame,
"Unexpected parent");
return nsSVGOuterSVGAnonChildFrameBase::Init(aContent, aParent, aPrevInFlow);
}
#endif
nsIAtom *
nsSVGOuterSVGAnonChildFrame::GetType() const
{
return nsGkAtoms::svgOuterSVGAnonChildFrame;
}
bool
nsSVGOuterSVGAnonChildFrame::HasChildrenOnlyTransform(gfxMatrix *aTransform) const
{
// We must claim our nsSVGOuterSVGFrame's children-only transforms as our own
// so that the children we are used to wrap are transformed properly.
nsSVGSVGElement *content = static_cast<nsSVGSVGElement*>(mContent);
bool hasTransform = content->HasChildrenOnlyTransform();
if (hasTransform && aTransform) {
// Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
gfxMatrix identity;
*aTransform =
content->PrependLocalTransformsTo(identity,
nsSVGElement::eChildToUserSpace);
}
return hasTransform;
}

View File

@ -76,6 +76,15 @@ public:
nsIAtom* aAttribute,
PRInt32 aModType);
virtual nsIFrame* GetContentInsertionFrame() {
// Any children must be added to our single anonymous inner frame kid.
NS_ABORT_IF_FALSE(GetFirstPrincipalChild() &&
GetFirstPrincipalChild()->GetType() ==
nsGkAtoms::svgOuterSVGAnonChildFrame,
"Where is our anonymous child?");
return GetFirstPrincipalChild()->GetContentInsertionFrame();
}
virtual bool IsSVGTransformed(gfxMatrix *aOwnTransform,
gfxMatrix *aFromParentTransform) const {
// Outer-<svg> can transform its children with viewBox, currentScale and
@ -86,10 +95,23 @@ public:
// nsISVGSVGFrame interface:
virtual void NotifyViewportOrTransformChanged(PRUint32 aFlags);
// nsISVGChildFrame methods:
NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
const nsIntRect *aDirtyRect);
virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
PRUint32 aFlags);
// nsSVGContainerFrame methods:
virtual gfxMatrix GetCanvasTM(PRUint32 aFor);
virtual bool HasChildrenOnlyTransform(gfxMatrix *aTransform) const;
virtual bool HasChildrenOnlyTransform(gfxMatrix *aTransform) const {
// Our anonymous wrapper child must claim our children-only transforms as
// its own so that our real children (the frames it wraps) are transformed
// by them, and we must pretend we don't have any children-only transforms
// so that our anonymous child is _not_ transformed by them.
return false;
}
/**
* Return true only if the height is unspecified (defaulting to 100%) or else
@ -124,4 +146,81 @@ protected:
bool mIsRootContent;
};
////////////////////////////////////////////////////////////////////////
// nsSVGOuterSVGAnonChildFrame class
typedef nsSVGDisplayContainerFrame nsSVGOuterSVGAnonChildFrameBase;
/**
* nsSVGOuterSVGFrames have a single direct child that is an instance of this
* class, and which is used to wrap their real child frames. Such anonymous
* wrapper frames created from this class exist because SVG frames need their
* GetPosition() offset to be their offset relative to "user space" (in app
* units) so that they can play nicely with nsDisplayTransform. This is fine
* for all SVG frames except for direct children of an nsSVGOuterSVGFrame,
* since an nsSVGOuterSVGFrame can have CSS border and padding (unlike other
* SVG frames). The direct children can't include the offsets due to any such
* border/padding in their mRects since that would break nsDisplayTransform,
* but not including these offsets would break other parts of the Mozilla code
* that assume a frame's mRect contains its border-box-to-parent-border-box
* offset, in particular nsIFrame::GetOffsetTo and the functions that depend on
* it. Wrapping an nsSVGOuterSVGFrame's children in an instance of this class
* with its GetPosition() set to its nsSVGOuterSVGFrame's border/padding offset
* keeps both nsDisplayTransform and nsIFrame::GetOffsetTo happy.
*
* The reason that this class inherit from nsSVGDisplayContainerFrame rather
* than simply from nsContainerFrame is so that we can avoid having special
* handling for these inner wrappers in multiple parts of the SVG code. For
* example, the implementations of IsSVGTransformed and GetCanvasTM assume
* nsSVGContainerFrame instances all the way up to the nsSVGOuterSVGFrame.
*/
class nsSVGOuterSVGAnonChildFrame
: public nsSVGOuterSVGAnonChildFrameBase
{
friend nsIFrame*
NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext);
nsSVGOuterSVGAnonChildFrame(nsStyleContext* aContext)
: nsSVGOuterSVGAnonChildFrameBase(aContext)
{}
public:
NS_DECL_FRAMEARENA_HELPERS
#ifdef DEBUG
NS_IMETHOD Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow);
NS_IMETHOD GetFrameName(nsAString& aResult) const {
return MakeFrameName(NS_LITERAL_STRING("SVGOuterSVGAnonChild"), aResult);
}
#endif
/**
* Get the "type" of the frame
*
* @see nsGkAtoms::svgOuterSVGAnonChildFrame
*/
virtual nsIAtom* GetType() const;
virtual bool IsSVGTransformed(gfxMatrix *aOwnTransform,
gfxMatrix *aFromParentTransform) const {
// Outer-<svg> can transform its children with viewBox, currentScale and
// currentTranslate, but it itself is not transformed by _SVG_ transforms.
return false;
}
// nsSVGContainerFrame methods:
virtual gfxMatrix GetCanvasTM(PRUint32 aFor) {
// GetCanvasTM returns the transform from an SVG frame to the frame's
// nsSVGOuterSVGFrame's content box, so we do not include any x/y offset
// set on us for any CSS border or padding on our nsSVGOuterSVGFrame.
return static_cast<nsSVGOuterSVGFrame*>(mParent)->GetCanvasTM(aFor);
}
virtual bool HasChildrenOnlyTransform(gfxMatrix *aTransform) const;
};
#endif

View File

@ -703,7 +703,6 @@ nsSVGUtils::InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate,
NS_ASSERTION(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG,
"SVG frames must always have an nsSVGOuterSVGFrame ancestor!");
invalidArea.MoveBy(aFrame->GetContentRect().TopLeft() - aFrame->GetPosition());
static_cast<nsSVGOuterSVGFrame*>(aFrame)->InvalidateWithFlags(invalidArea,
aFlags);