layout/svg/nsSVGContainerFrame.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/layout/svg/nsSVGContainerFrame.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,423 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +// Main header first:
    1.10 +#include "nsSVGContainerFrame.h"
    1.11 +
    1.12 +// Keep others in (case-insensitive) order:
    1.13 +#include "nsCSSFrameConstructor.h"
    1.14 +#include "nsSVGEffects.h"
    1.15 +#include "nsSVGElement.h"
    1.16 +#include "nsSVGUtils.h"
    1.17 +#include "nsSVGAnimatedTransformList.h"
    1.18 +#include "SVGTextFrame.h"
    1.19 +#include "RestyleManager.h"
    1.20 +
    1.21 +using namespace mozilla;
    1.22 +
    1.23 +NS_QUERYFRAME_HEAD(nsSVGContainerFrame)
    1.24 +  NS_QUERYFRAME_ENTRY(nsSVGContainerFrame)
    1.25 +NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrameBase)
    1.26 +
    1.27 +NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame)
    1.28 +  NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame)
    1.29 +  NS_QUERYFRAME_ENTRY(nsISVGChildFrame)
    1.30 +NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame)
    1.31 +
    1.32 +nsIFrame*
    1.33 +NS_NewSVGContainerFrame(nsIPresShell* aPresShell,
    1.34 +                        nsStyleContext* aContext)
    1.35 +{
    1.36 +  nsIFrame *frame = new (aPresShell) nsSVGContainerFrame(aContext);
    1.37 +  // If we were called directly, then the frame is for a <defs> or
    1.38 +  // an unknown element type. In both cases we prevent the content
    1.39 +  // from displaying directly.
    1.40 +  frame->AddStateBits(NS_FRAME_IS_NONDISPLAY);
    1.41 +  return frame;
    1.42 +}
    1.43 +
    1.44 +NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame)
    1.45 +NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame)
    1.46 +
    1.47 +nsresult
    1.48 +nsSVGContainerFrame::AppendFrames(ChildListID  aListID,
    1.49 +                                  nsFrameList& aFrameList)
    1.50 +{
    1.51 +  return InsertFrames(aListID, mFrames.LastChild(), aFrameList);  
    1.52 +}
    1.53 +
    1.54 +nsresult
    1.55 +nsSVGContainerFrame::InsertFrames(ChildListID aListID,
    1.56 +                                  nsIFrame* aPrevFrame,
    1.57 +                                  nsFrameList& aFrameList)
    1.58 +{
    1.59 +  NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
    1.60 +  NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
    1.61 +               "inserting after sibling frame with different parent");
    1.62 +
    1.63 +  mFrames.InsertFrames(this, aPrevFrame, aFrameList);
    1.64 +
    1.65 +  return NS_OK;
    1.66 +}
    1.67 +
    1.68 +nsresult
    1.69 +nsSVGContainerFrame::RemoveFrame(ChildListID aListID,
    1.70 +                                 nsIFrame* aOldFrame)
    1.71 +{
    1.72 +  NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
    1.73 +
    1.74 +  mFrames.DestroyFrame(aOldFrame);
    1.75 +  return NS_OK;
    1.76 +}
    1.77 +
    1.78 +bool
    1.79 +nsSVGContainerFrame::UpdateOverflow()
    1.80 +{
    1.81 +  if (mState & NS_FRAME_IS_NONDISPLAY) {
    1.82 +    // We don't maintain overflow rects.
    1.83 +    // XXX It would have be better if the restyle request hadn't even happened.
    1.84 +    return false;
    1.85 +  }
    1.86 +  return nsSVGContainerFrameBase::UpdateOverflow();
    1.87 +}
    1.88 +
    1.89 +/**
    1.90 + * Traverses a frame tree, marking any SVGTextFrame frames as dirty
    1.91 + * and calling InvalidateRenderingObservers() on it.
    1.92 + *
    1.93 + * The reason that this helper exists is because SVGTextFrame is special.
    1.94 + * None of the other SVG frames ever need to be reflowed when they have the
    1.95 + * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods
    1.96 + * (and those of any containers that they can validly be contained within) do
    1.97 + * not make use of mRect or overflow rects. "em" lengths, etc., are resolved
    1.98 + * as those elements are painted.
    1.99 + *
   1.100 + * SVGTextFrame is different because its anonymous block and inline frames
   1.101 + * need to be reflowed in order to get the correct metrics when things like
   1.102 + * inherited font-size of an ancestor changes, or a delayed webfont loads and
   1.103 + * applies.
   1.104 + *
   1.105 + * We assume that any change that requires the anonymous kid of an
   1.106 + * SVGTextFrame to reflow will result in an NS_FRAME_IS_DIRTY reflow. When
   1.107 + * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally
   1.108 + * stop, but this helper looks for any SVGTextFrame descendants of such
   1.109 + * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they are
   1.110 + * painted their anonymous kid will first get the necessary reflow.
   1.111 + */
   1.112 +/* static */ void
   1.113 +nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer)
   1.114 +{
   1.115 +  NS_ASSERTION(aContainer->GetStateBits() & NS_FRAME_IS_DIRTY,
   1.116 +               "expected aContainer to be NS_FRAME_IS_DIRTY");
   1.117 +  NS_ASSERTION((aContainer->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ||
   1.118 +               !aContainer->IsFrameOfType(nsIFrame::eSVG),
   1.119 +               "it is wasteful to call ReflowSVGNonDisplayText on a container "
   1.120 +               "frame that is not NS_FRAME_IS_NONDISPLAY");
   1.121 +  for (nsIFrame* kid = aContainer->GetFirstPrincipalChild(); kid;
   1.122 +       kid = kid->GetNextSibling()) {
   1.123 +    nsIAtom* type = kid->GetType();
   1.124 +    if (type == nsGkAtoms::svgTextFrame) {
   1.125 +      static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText();
   1.126 +    } else {
   1.127 +      if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) ||
   1.128 +          type == nsGkAtoms::svgForeignObjectFrame ||
   1.129 +          !kid->IsFrameOfType(nsIFrame::eSVG)) {
   1.130 +        ReflowSVGNonDisplayText(kid);
   1.131 +      }
   1.132 +    }
   1.133 +  }
   1.134 +}
   1.135 +
   1.136 +void
   1.137 +nsSVGDisplayContainerFrame::Init(nsIContent* aContent,
   1.138 +                                 nsIFrame* aParent,
   1.139 +                                 nsIFrame* aPrevInFlow)
   1.140 +{
   1.141 +  if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
   1.142 +    AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
   1.143 +  }
   1.144 +  nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
   1.145 +}
   1.146 +
   1.147 +void
   1.148 +nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
   1.149 +                                             const nsRect&           aDirtyRect,
   1.150 +                                             const nsDisplayListSet& aLists)
   1.151 +{
   1.152 +  // mContent could be a XUL element so check for an SVG element before casting
   1.153 +  if (mContent->IsSVG() &&
   1.154 +      !static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) {
   1.155 +    return;
   1.156 +  }
   1.157 +  return BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists);
   1.158 +}
   1.159 +
   1.160 +nsresult
   1.161 +nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID,
   1.162 +                                         nsIFrame* aPrevFrame,
   1.163 +                                         nsFrameList& aFrameList)
   1.164 +{
   1.165 +  // memorize first old frame after insertion point
   1.166 +  // XXXbz once again, this would work a lot better if the nsIFrame
   1.167 +  // methods returned framelist iterators....
   1.168 +  nsIFrame* nextFrame = aPrevFrame ?
   1.169 +    aPrevFrame->GetNextSibling() : GetChildList(aListID).FirstChild();
   1.170 +  nsIFrame* firstNewFrame = aFrameList.FirstChild();
   1.171 +  
   1.172 +  // Insert the new frames
   1.173 +  nsSVGContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
   1.174 +
   1.175 +  // If we are not a non-display SVG frame and we do not have a bounds update
   1.176 +  // pending, then we need to schedule one for our new children:
   1.177 +  if (!(GetStateBits() &
   1.178 +        (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN |
   1.179 +         NS_FRAME_IS_NONDISPLAY))) {
   1.180 +    for (nsIFrame* kid = firstNewFrame; kid != nextFrame;
   1.181 +         kid = kid->GetNextSibling()) {
   1.182 +      nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
   1.183 +      if (SVGFrame) {
   1.184 +        NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
   1.185 +                          "Check for this explicitly in the |if|, then");
   1.186 +        bool isFirstReflow = (kid->GetStateBits() & NS_FRAME_FIRST_REFLOW);
   1.187 +        // Remove bits so that ScheduleBoundsUpdate will work:
   1.188 +        kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
   1.189 +                             NS_FRAME_HAS_DIRTY_CHILDREN);
   1.190 +        // No need to invalidate the new kid's old bounds, so we just use
   1.191 +        // nsSVGUtils::ScheduleBoundsUpdate.
   1.192 +        nsSVGUtils::ScheduleReflowSVG(kid);
   1.193 +        if (isFirstReflow) {
   1.194 +          // Add back the NS_FRAME_FIRST_REFLOW bit:
   1.195 +          kid->AddStateBits(NS_FRAME_FIRST_REFLOW);
   1.196 +        }
   1.197 +      }
   1.198 +    }
   1.199 +  }
   1.200 +
   1.201 +  return NS_OK;
   1.202 +}
   1.203 +
   1.204 +nsresult
   1.205 +nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID,
   1.206 +                                        nsIFrame* aOldFrame)
   1.207 +{
   1.208 +  nsSVGEffects::InvalidateRenderingObservers(aOldFrame);
   1.209 +
   1.210 +  // nsSVGContainerFrame::RemoveFrame doesn't call down into
   1.211 +  // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
   1.212 +  // need to schedule a repaint and schedule an update to our overflow rects.
   1.213 +  SchedulePaint();
   1.214 +  PresContext()->RestyleManager()->PostRestyleEvent(
   1.215 +    mContent->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow);
   1.216 +
   1.217 +  nsresult rv = nsSVGContainerFrame::RemoveFrame(aListID, aOldFrame);
   1.218 +
   1.219 +  if (!(GetStateBits() & (NS_FRAME_IS_NONDISPLAY | NS_STATE_IS_OUTER_SVG))) {
   1.220 +    nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this);
   1.221 +  }
   1.222 +
   1.223 +  return rv;
   1.224 +}
   1.225 +
   1.226 +bool
   1.227 +nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform,
   1.228 +                                             gfx::Matrix *aFromParentTransform) const
   1.229 +{
   1.230 +  bool foundTransform = false;
   1.231 +
   1.232 +  // Check if our parent has children-only transforms:
   1.233 +  nsIFrame *parent = GetParent();
   1.234 +  if (parent &&
   1.235 +      parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
   1.236 +    foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
   1.237 +                       HasChildrenOnlyTransform(aFromParentTransform);
   1.238 +  }
   1.239 +
   1.240 +  // mContent could be a XUL element so check for an SVG element before casting
   1.241 +  if (mContent->IsSVG()) {
   1.242 +    nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
   1.243 +    nsSVGAnimatedTransformList* transformList =
   1.244 +      content->GetAnimatedTransformList();
   1.245 +    if ((transformList && transformList->HasTransform()) ||
   1.246 +        content->GetAnimateMotionTransform()) {
   1.247 +      if (aOwnTransform) {
   1.248 +        *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(),
   1.249 +                                    nsSVGElement::eUserSpaceToParent));
   1.250 +      }
   1.251 +      foundTransform = true;
   1.252 +    }
   1.253 +  }
   1.254 +  return foundTransform;
   1.255 +}
   1.256 +
   1.257 +//----------------------------------------------------------------------
   1.258 +// nsISVGChildFrame methods
   1.259 +
   1.260 +nsresult
   1.261 +nsSVGDisplayContainerFrame::PaintSVG(nsRenderingContext* aContext,
   1.262 +                                     const nsIntRect *aDirtyRect,
   1.263 +                                     nsIFrame* aTransformRoot)
   1.264 +{
   1.265 +  NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
   1.266 +               (mState & NS_FRAME_IS_NONDISPLAY) ||
   1.267 +               PresContext()->IsGlyph(),
   1.268 +               "If display lists are enabled, only painting of non-display "
   1.269 +               "SVG should take this code path");
   1.270 +
   1.271 +  const nsStyleDisplay *display = StyleDisplay();
   1.272 +  if (display->mOpacity == 0.0)
   1.273 +    return NS_OK;
   1.274 +
   1.275 +  for (nsIFrame* kid = mFrames.FirstChild(); kid;
   1.276 +       kid = kid->GetNextSibling()) {
   1.277 +    nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid, aTransformRoot);
   1.278 +  }
   1.279 +
   1.280 +  return NS_OK;
   1.281 +}
   1.282 +
   1.283 +nsIFrame*
   1.284 +nsSVGDisplayContainerFrame::GetFrameForPoint(const nsPoint &aPoint)
   1.285 +{
   1.286 +  NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
   1.287 +               (mState & NS_FRAME_IS_NONDISPLAY),
   1.288 +               "If display lists are enabled, only hit-testing of a "
   1.289 +               "clipPath's contents should take this code path");
   1.290 +  return nsSVGUtils::HitTestChildren(this, aPoint);
   1.291 +}
   1.292 +
   1.293 +nsRect
   1.294 +nsSVGDisplayContainerFrame::GetCoveredRegion()
   1.295 +{
   1.296 +  return nsSVGUtils::GetCoveredRegion(mFrames);
   1.297 +}
   1.298 +
   1.299 +void
   1.300 +nsSVGDisplayContainerFrame::ReflowSVG()
   1.301 +{
   1.302 +  NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
   1.303 +               "This call is probably a wasteful mistake");
   1.304 +
   1.305 +  NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
   1.306 +                    "ReflowSVG mechanism not designed for this");
   1.307 +
   1.308 +  NS_ABORT_IF_FALSE(GetType() != nsGkAtoms::svgOuterSVGFrame,
   1.309 +                    "Do not call on outer-<svg>");
   1.310 +
   1.311 +  if (!nsSVGUtils::NeedsReflowSVG(this)) {
   1.312 +    return;
   1.313 +  }
   1.314 +
   1.315 +  // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
   1.316 +  // then our outer-<svg> has previously had its initial reflow. In that case
   1.317 +  // we need to make sure that that bit has been removed from ourself _before_
   1.318 +  // recursing over our children to ensure that they know too. Otherwise, we
   1.319 +  // need to remove it _after_ recursing over our children so that they know
   1.320 +  // the initial reflow is currently underway.
   1.321 +
   1.322 +  bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
   1.323 +
   1.324 +  bool outerSVGHasHadFirstReflow =
   1.325 +    (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0;
   1.326 +
   1.327 +  if (outerSVGHasHadFirstReflow) {
   1.328 +    mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children
   1.329 +  }
   1.330 +
   1.331 +  nsOverflowAreas overflowRects;
   1.332 +
   1.333 +  for (nsIFrame* kid = mFrames.FirstChild(); kid;
   1.334 +       kid = kid->GetNextSibling()) {
   1.335 +    nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
   1.336 +    if (SVGFrame) {
   1.337 +      NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
   1.338 +                        "Check for this explicitly in the |if|, then");
   1.339 +      kid->AddStateBits(mState & NS_FRAME_IS_DIRTY);
   1.340 +      SVGFrame->ReflowSVG();
   1.341 +
   1.342 +      // We build up our child frame overflows here instead of using
   1.343 +      // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
   1.344 +      // frame list, and we're iterating over that list now anyway.
   1.345 +      ConsiderChildOverflow(overflowRects, kid);
   1.346 +    } else {
   1.347 +      // Inside a non-display container frame, we might have some
   1.348 +      // SVGTextFrames.  We need to cause those to get reflowed in
   1.349 +      // case they are the target of a rendering observer.
   1.350 +      NS_ASSERTION(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY,
   1.351 +                   "expected kid to be a NS_FRAME_IS_NONDISPLAY frame");
   1.352 +      if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) {
   1.353 +        nsSVGContainerFrame* container = do_QueryFrame(kid);
   1.354 +        if (container && container->GetContent()->IsSVG()) {
   1.355 +          ReflowSVGNonDisplayText(container);
   1.356 +        }
   1.357 +      }
   1.358 +    }
   1.359 +  }
   1.360 +
   1.361 +  // <svg> can create an SVG viewport with an offset due to its
   1.362 +  // x/y/width/height attributes, and <use> can introduce an offset with an
   1.363 +  // empty mRect (any width/height is copied to an anonymous <svg> child).
   1.364 +  // Other than that containers should not set mRect since all other offsets
   1.365 +  // come from transforms, which are accounted for by nsDisplayTransform.
   1.366 +  // Note that we rely on |overflow:visible| to allow display list items to be
   1.367 +  // created for our children.
   1.368 +  NS_ABORT_IF_FALSE(mContent->Tag() == nsGkAtoms::svg ||
   1.369 +                    (mContent->Tag() == nsGkAtoms::use &&
   1.370 +                     mRect.Size() == nsSize(0,0)) ||
   1.371 +                    mRect.IsEqualEdges(nsRect()),
   1.372 +                    "Only inner-<svg>/<use> is expected to have mRect set");
   1.373 +
   1.374 +  if (isFirstReflow) {
   1.375 +    // Make sure we have our filter property (if any) before calling
   1.376 +    // FinishAndStoreOverflow (subsequent filter changes are handled off
   1.377 +    // nsChangeHint_UpdateEffects):
   1.378 +    nsSVGEffects::UpdateEffects(this);
   1.379 +  }
   1.380 +
   1.381 +  FinishAndStoreOverflow(overflowRects, mRect.Size());
   1.382 +
   1.383 +  // Remove state bits after FinishAndStoreOverflow so that it doesn't
   1.384 +  // invalidate on first reflow:
   1.385 +  mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
   1.386 +              NS_FRAME_HAS_DIRTY_CHILDREN);
   1.387 +}  
   1.388 +
   1.389 +void
   1.390 +nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags)
   1.391 +{
   1.392 +  NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
   1.393 +                    "Invalidation logic may need adjusting");
   1.394 +
   1.395 +  nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
   1.396 +}
   1.397 +
   1.398 +SVGBBox
   1.399 +nsSVGDisplayContainerFrame::GetBBoxContribution(
   1.400 +  const Matrix &aToBBoxUserspace,
   1.401 +  uint32_t aFlags)
   1.402 +{
   1.403 +  SVGBBox bboxUnion;
   1.404 +
   1.405 +  nsIFrame* kid = mFrames.FirstChild();
   1.406 +  while (kid) {
   1.407 +    nsIContent *content = kid->GetContent();
   1.408 +    nsISVGChildFrame* svgKid = do_QueryFrame(kid);
   1.409 +    // content could be a XUL element so check for an SVG element before casting
   1.410 +    if (svgKid && (!content->IsSVG() ||
   1.411 +                   static_cast<const nsSVGElement*>(content)->HasValidDimensions())) {
   1.412 +
   1.413 +      gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace);
   1.414 +      if (content->IsSVG()) {
   1.415 +        transform = static_cast<nsSVGElement*>(content)->
   1.416 +                      PrependLocalTransformsTo(transform);
   1.417 +      }
   1.418 +      // We need to include zero width/height vertical/horizontal lines, so we have
   1.419 +      // to use UnionEdges.
   1.420 +      bboxUnion.UnionEdges(svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags));
   1.421 +    }
   1.422 +    kid = kid->GetNextSibling();
   1.423 +  }
   1.424 +
   1.425 +  return bboxUnion;
   1.426 +}

mercurial