tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SVGContainerFrame.cpp (17155B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // Main header first:
      8 #include "SVGContainerFrame.h"
      9 
     10 // Keep others in (case-insensitive) order:
     11 #include "ImgDrawResult.h"
     12 #include "SVGAnimatedTransformList.h"
     13 #include "mozilla/PresShell.h"
     14 #include "mozilla/RestyleManager.h"
     15 #include "mozilla/SVGObserverUtils.h"
     16 #include "mozilla/SVGTextFrame.h"
     17 #include "mozilla/SVGUtils.h"
     18 #include "mozilla/dom/SVGElement.h"
     19 #include "nsCSSFrameConstructor.h"
     20 
     21 using namespace mozilla::dom;
     22 using namespace mozilla::image;
     23 
     24 nsIFrame* NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell,
     25                                  mozilla::ComputedStyle* aStyle) {
     26  nsIFrame* frame = new (aPresShell)
     27      mozilla::SVGContainerFrame(aStyle, aPresShell->GetPresContext(),
     28                                 mozilla::SVGContainerFrame::kClassID);
     29  // If we were called directly, then the frame is for a <defs> or
     30  // an unknown element type. In both cases we prevent the content
     31  // from displaying directly.
     32  frame->AddStateBits(NS_FRAME_IS_NONDISPLAY);
     33  return frame;
     34 }
     35 
     36 namespace mozilla {
     37 
     38 NS_QUERYFRAME_HEAD(SVGContainerFrame)
     39  NS_QUERYFRAME_ENTRY(SVGContainerFrame)
     40 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
     41 
     42 NS_QUERYFRAME_HEAD(SVGDisplayContainerFrame)
     43  NS_QUERYFRAME_ENTRY(SVGDisplayContainerFrame)
     44  NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
     45 NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame)
     46 
     47 NS_IMPL_FRAMEARENA_HELPERS(SVGContainerFrame)
     48 
     49 void SVGContainerFrame::AppendFrames(ChildListID aListID,
     50                                     nsFrameList&& aFrameList) {
     51  InsertFrames(aListID, mFrames.LastChild(), nullptr, std::move(aFrameList));
     52 }
     53 
     54 void SVGContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
     55                                     const nsLineList::iterator* aPrevFrameLine,
     56                                     nsFrameList&& aFrameList) {
     57  NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
     58  NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
     59               "inserting after sibling frame with different parent");
     60 
     61  mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList));
     62 }
     63 
     64 void SVGContainerFrame::RemoveFrame(DestroyContext& aContext,
     65                                    ChildListID aListID, nsIFrame* aOldFrame) {
     66  NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
     67  mFrames.DestroyFrame(aContext, aOldFrame);
     68 }
     69 
     70 bool SVGContainerFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
     71  if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
     72    // We don't maintain overflow rects.
     73    // XXX It would have be better if the restyle request hadn't even happened.
     74    return false;
     75  }
     76  return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
     77 }
     78 
     79 /**
     80 * Traverses a frame tree, marking any SVGTextFrame frames as dirty
     81 * and calling InvalidateRenderingObservers() on it.
     82 *
     83 * The reason that this helper exists is because SVGTextFrame is special.
     84 * None of the other SVG frames ever need to be reflowed when they have the
     85 * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods
     86 * (and those of any containers that they can validly be contained within) do
     87 * not make use of mRect or overflow rects. "em" lengths, etc., are resolved
     88 * as those elements are painted.
     89 *
     90 * SVGTextFrame is different because its anonymous block and inline frames
     91 * need to be reflowed in order to get the correct metrics when things like
     92 * inherited font-size of an ancestor changes, or a delayed webfont loads and
     93 * applies.
     94 *
     95 * However, we only need to do this work if we were reflowed with
     96 * NS_FRAME_IS_DIRTY, which implies that all descendants are dirty.  When
     97 * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally
     98 * stop, but this helper looks for any SVGTextFrame descendants of such
     99 * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they
    100 * are painted their anonymous kid will first get the necessary reflow.
    101 */
    102 /* static */
    103 void SVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) {
    104  if (!aContainer->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
    105    return;
    106  }
    107  MOZ_ASSERT(aContainer->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
    108                 !aContainer->IsSVGFrame(),
    109             "it is wasteful to call ReflowSVGNonDisplayText on a container "
    110             "frame that is not NS_FRAME_IS_NONDISPLAY or not SVG");
    111  for (nsIFrame* kid : aContainer->PrincipalChildList()) {
    112    LayoutFrameType type = kid->Type();
    113    if (type == LayoutFrameType::SVGText) {
    114      static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText();
    115    } else if (kid->IsSVGContainerFrame() ||
    116               type == LayoutFrameType::SVGForeignObject ||
    117               !kid->IsSVGFrame()) {
    118      ReflowSVGNonDisplayText(kid);
    119    }
    120  }
    121 }
    122 
    123 void SVGDisplayContainerFrame::Init(nsIContent* aContent,
    124                                    nsContainerFrame* aParent,
    125                                    nsIFrame* aPrevInFlow) {
    126  if (!IsSVGOuterSVGFrame()) {
    127    AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
    128  }
    129  SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
    130 }
    131 
    132 void SVGDisplayContainerFrame::BuildDisplayList(
    133    nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
    134  // content could be a XUL element so check for an SVG element
    135  if (auto* svg = SVGElement::FromNode(GetContent())) {
    136    if (!svg->HasValidDimensions()) {
    137      return;
    138    }
    139  }
    140  DisplayOutline(aBuilder, aLists);
    141  return BuildDisplayListForNonBlockChildren(aBuilder, aLists);
    142 }
    143 
    144 void SVGDisplayContainerFrame::InsertFrames(
    145    ChildListID aListID, nsIFrame* aPrevFrame,
    146    const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
    147  // memorize first old frame after insertion point
    148  // XXXbz once again, this would work a lot better if the nsIFrame
    149  // methods returned framelist iterators....
    150  nsIFrame* nextFrame = aPrevFrame ? aPrevFrame->GetNextSibling()
    151                                   : GetChildList(aListID).FirstChild();
    152  nsIFrame* firstNewFrame = aFrameList.FirstChild();
    153 
    154  // Insert the new frames
    155  SVGContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
    156                                  std::move(aFrameList));
    157 
    158  // If we are not a non-display SVG frame and we do not have a bounds update
    159  // pending, then we need to schedule one for our new children:
    160  if (!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN |
    161                       NS_FRAME_IS_NONDISPLAY)) {
    162    for (nsIFrame* kid = firstNewFrame; kid != nextFrame;
    163         kid = kid->GetNextSibling()) {
    164      ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
    165      if (SVGFrame && !kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    166        bool isFirstReflow = kid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
    167        // Remove bits so that ScheduleBoundsUpdate will work:
    168        kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
    169                             NS_FRAME_HAS_DIRTY_CHILDREN);
    170        // No need to invalidate the new kid's old bounds, so we just use
    171        // SVGUtils::ScheduleBoundsUpdate.
    172        SVGUtils::ScheduleReflowSVG(kid);
    173        if (isFirstReflow) {
    174          // Add back the NS_FRAME_FIRST_REFLOW bit:
    175          kid->AddStateBits(NS_FRAME_FIRST_REFLOW);
    176        }
    177      }
    178    }
    179  }
    180 }
    181 
    182 void SVGDisplayContainerFrame::RemoveFrame(DestroyContext& aContext,
    183                                           ChildListID aListID,
    184                                           nsIFrame* aOldFrame) {
    185  SVGObserverUtils::InvalidateRenderingObservers(aOldFrame);
    186 
    187  // SVGContainerFrame::RemoveFrame doesn't call down into
    188  // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
    189  // need to schedule a repaint and schedule an update to our overflow rects.
    190  // TODO(emilio, bug 2008045): It sure looks like it should just call into
    191  // nsContainerFrame.
    192  SchedulePaint();
    193  if (!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    194    PresContext()->RestyleManager()->PostRestyleEvent(
    195        mContent->AsElement(), RestyleHint{0}, nsChangeHint_UpdateOverflow);
    196    PresShell()->SynthesizeMouseMove(false);
    197  }
    198 
    199  SVGContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
    200 }
    201 
    202 bool SVGDisplayContainerFrame::DoGetParentSVGTransforms(
    203    gfx::Matrix* aFromParentTransform) const {
    204  return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform);
    205 }
    206 
    207 //----------------------------------------------------------------------
    208 // ISVGDisplayableFrame methods
    209 
    210 void SVGDisplayContainerFrame::PaintSVG(gfxContext& aContext,
    211                                        const gfxMatrix& aTransform,
    212                                        imgDrawingParams& aImgParams) {
    213  NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
    214                   PresContext()->Document()->IsSVGGlyphsDocument(),
    215               "Only painting of non-display SVG should take this code path");
    216 
    217  if (StyleEffects()->IsTransparent()) {
    218    return;
    219  }
    220 
    221  gfxMatrix matrix = aTransform;
    222  if (auto* svg = SVGElement::FromNode(GetContent())) {
    223    matrix = svg->ChildToUserSpaceTransform() * matrix;
    224    if (matrix.IsSingular()) {
    225      return;
    226    }
    227  }
    228 
    229  for (auto* kid : mFrames) {
    230    gfxMatrix m = matrix;
    231    // PaintFrameWithEffects() expects the transform that is passed to it to
    232    // include the transform to the passed frame's user space, so add it:
    233    const nsIContent* content = kid->GetContent();
    234    if (const SVGElement* element = SVGElement::FromNode(content)) {
    235      if (!element->HasValidDimensions()) {
    236        continue;  // nothing to paint for kid
    237      }
    238 
    239      m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m;
    240      if (m.IsSingular()) {
    241        continue;
    242      }
    243    }
    244    SVGUtils::PaintFrameWithEffects(kid, aContext, m, aImgParams);
    245  }
    246 }
    247 
    248 nsIFrame* SVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint& aPoint) {
    249  NS_ASSERTION(HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD),
    250               "Only hit-testing of a clipPath's contents should take this "
    251               "code path");
    252  // First we transform aPoint into the coordinate space established by aFrame
    253  // for its children (e.g. take account of any 'viewBox' attribute):
    254  gfxPoint point = aPoint;
    255  if (const auto* svg = SVGElement::FromNode(GetContent())) {
    256    gfxMatrix m = svg->ChildToUserSpaceTransform();
    257    if (!m.IsIdentity()) {
    258      if (!m.Invert()) {
    259        return nullptr;
    260      }
    261      point = m.TransformPoint(point);
    262    }
    263  }
    264 
    265  // Traverse the list in reverse order, so that if we get a hit we know that's
    266  // the topmost frame that intersects the point; then we can just return it.
    267  nsIFrame* result = nullptr;
    268  for (nsIFrame* current = PrincipalChildList().LastChild(); current;
    269       current = current->GetPrevSibling()) {
    270    ISVGDisplayableFrame* SVGFrame = do_QueryFrame(current);
    271    if (!SVGFrame) {
    272      continue;
    273    }
    274    const nsIContent* content = current->GetContent();
    275    if (const auto* svg = SVGElement::FromNode(content)) {
    276      if (!svg->HasValidDimensions()) {
    277        continue;
    278      }
    279    }
    280    result = SVGFrame->GetFrameForPoint(point);
    281    if (result) {
    282      break;
    283    }
    284  }
    285 
    286  if (result && !SVGUtils::HitTestClip(this, aPoint)) {
    287    result = nullptr;
    288  }
    289 
    290  return result;
    291 }
    292 
    293 void SVGDisplayContainerFrame::ReflowSVG() {
    294  MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
    295             "This call is probably a wasteful mistake");
    296 
    297  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
    298             "ReflowSVG mechanism not designed for this");
    299 
    300  MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>");
    301 
    302  if (!SVGUtils::NeedsReflowSVG(this)) {
    303    return;
    304  }
    305 
    306  // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
    307  // then our outer-<svg> has previously had its initial reflow. In that case
    308  // we need to make sure that that bit has been removed from ourself _before_
    309  // recursing over our children to ensure that they know too. Otherwise, we
    310  // need to remove it _after_ recursing over our children so that they know
    311  // the initial reflow is currently underway.
    312 
    313  bool isFirstReflow = HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
    314 
    315  bool outerSVGHasHadFirstReflow =
    316      !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
    317 
    318  if (outerSVGHasHadFirstReflow) {
    319    RemoveStateBits(NS_FRAME_FIRST_REFLOW);  // tell our children
    320  }
    321 
    322  OverflowAreas overflowRects;
    323 
    324  for (auto* kid : mFrames) {
    325    ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
    326    if (SVGFrame && !kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    327      SVGFrame->ReflowSVG();
    328 
    329      // We build up our child frame overflows here instead of using
    330      // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
    331      // frame list, and we're iterating over that list now anyway.
    332      ConsiderChildOverflow(overflowRects, kid);
    333    } else {
    334      // Inside a non-display container frame, we might have some
    335      // SVGTextFrames.  We need to cause those to get reflowed in
    336      // case they are the target of a rendering observer.
    337      MOZ_ASSERT(
    338          kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || !kid->IsSVGFrame(),
    339          "expected kid to be a NS_FRAME_IS_NONDISPLAY frame or not SVG");
    340      if (kid->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
    341        SVGContainerFrame* container = do_QueryFrame(kid);
    342        if (container && container->GetContent()->IsSVGElement()) {
    343          ReflowSVGNonDisplayText(container);
    344        }
    345      }
    346    }
    347  }
    348 
    349  // <svg> can create an SVG viewport with an offset due to its
    350  // x/y/width/height attributes, and <use> can introduce an offset with an
    351  // empty mRect (any width/height is copied to an anonymous <svg> child).
    352  // Other than that containers should not set mRect since all other offsets
    353  // come from transforms, which are accounted for by nsDisplayTransform.
    354  // Note that we rely on |overflow:visible| to allow display list items to be
    355  // created for our children.
    356  MOZ_ASSERT(mContent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol) ||
    357                 (mContent->IsSVGElement(nsGkAtoms::use) &&
    358                  mRect.Size() == nsSize(0, 0)) ||
    359                 mRect.IsEqualEdges(nsRect()),
    360             "Only inner-<svg>/<use> is expected to have mRect set");
    361 
    362  if (isFirstReflow) {
    363    // Make sure we have our filter property (if any) before calling
    364    // FinishAndStoreOverflow (subsequent filter changes are handled off
    365    // nsChangeHint_UpdateEffects):
    366    SVGObserverUtils::UpdateEffects(this);
    367  }
    368 
    369  FinishAndStoreOverflow(overflowRects, mRect.Size());
    370 
    371  // Remove state bits after FinishAndStoreOverflow so that it doesn't
    372  // invalidate on first reflow:
    373  RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
    374                  NS_FRAME_HAS_DIRTY_CHILDREN);
    375 }
    376 
    377 void SVGDisplayContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
    378  nsContainerFrame::DidSetComputedStyle(aOldStyle);
    379  if (!aOldStyle) {
    380    return;
    381  }
    382  if (StyleDisplay()->CalcTransformPropertyDifference(
    383          *aOldStyle->StyleDisplay())) {
    384    NotifySVGChanged(ChangeFlag::TransformChanged);
    385  }
    386 }
    387 
    388 void SVGDisplayContainerFrame::NotifySVGChanged(ChangeFlags aFlags) {
    389  MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) ||
    390                 aFlags.contains(ChangeFlag::CoordContextChanged),
    391             "Invalidation logic may need adjusting");
    392 
    393  if (aFlags.contains(ChangeFlag::TransformChanged)) {
    394    // make sure our cached transform matrix gets (lazily) updated
    395    mCanvasTM = nullptr;
    396  }
    397 
    398  SVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
    399 }
    400 
    401 SVGBBox SVGDisplayContainerFrame::GetBBoxContribution(
    402    const Matrix& aToBBoxUserspace, uint32_t aFlags) {
    403  SVGBBox bboxUnion;
    404 
    405  for (nsIFrame* kid : mFrames) {
    406    ISVGDisplayableFrame* svgKid = do_QueryFrame(kid);
    407    if (!svgKid) {
    408      continue;
    409    }
    410    // content could be a XUL element
    411    auto* svg = SVGElement::FromNode(kid->GetContent());
    412    if (svg && !svg->HasValidDimensions()) {
    413      continue;
    414    }
    415    gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace);
    416    if (svg) {
    417      transform = svg->ChildToUserSpaceTransform() *
    418                  SVGUtils::GetTransformMatrixInUserSpace(kid) * transform;
    419    }
    420    // We need to include zero width/height vertical/horizontal lines, so we
    421    // have to use UnionEdges.
    422    bboxUnion.UnionEdges(
    423        svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags));
    424  }
    425 
    426  return bboxUnion;
    427 }
    428 
    429 gfxMatrix SVGDisplayContainerFrame::GetCanvasTM() {
    430  if (!mCanvasTM) {
    431    NS_ASSERTION(GetParent(), "null parent");
    432    auto* parent = static_cast<SVGContainerFrame*>(GetParent());
    433    auto* content = static_cast<SVGElement*>(GetContent());
    434    mCanvasTM = MakeUnique<gfxMatrix>(content->ChildToUserSpaceTransform() *
    435                                      parent->GetCanvasTM());
    436  }
    437 
    438  return *mCanvasTM;
    439 }
    440 
    441 }  // namespace mozilla