tor-browser

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

SVGForeignObjectFrame.cpp (16974B)


      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 "SVGForeignObjectFrame.h"
      9 
     10 // Keep others in (case-insensitive) order:
     11 #include "ImgDrawResult.h"
     12 #include "SVGGeometryProperty.h"
     13 #include "gfxContext.h"
     14 #include "mozilla/AutoRestore.h"
     15 #include "mozilla/PresShell.h"
     16 #include "mozilla/SVGContainerFrame.h"
     17 #include "mozilla/SVGObserverUtils.h"
     18 #include "mozilla/SVGUtils.h"
     19 #include "mozilla/dom/SVGForeignObjectElement.h"
     20 #include "nsDisplayList.h"
     21 #include "nsGkAtoms.h"
     22 #include "nsLayoutUtils.h"
     23 #include "nsNameSpaceManager.h"
     24 #include "nsRegion.h"
     25 
     26 using namespace mozilla::dom;
     27 using namespace mozilla::image;
     28 namespace SVGT = SVGGeometryProperty::Tags;
     29 
     30 //----------------------------------------------------------------------
     31 // Implementation
     32 
     33 nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell,
     34                                              mozilla::ComputedStyle* aStyle) {
     35  return new (aPresShell)
     36      mozilla::SVGForeignObjectFrame(aStyle, aPresShell->GetPresContext());
     37 }
     38 
     39 namespace mozilla {
     40 
     41 NS_IMPL_FRAMEARENA_HELPERS(SVGForeignObjectFrame)
     42 
     43 SVGForeignObjectFrame::SVGForeignObjectFrame(ComputedStyle* aStyle,
     44                                             nsPresContext* aPresContext)
     45    : nsContainerFrame(aStyle, aPresContext, kClassID) {
     46  AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED |
     47               NS_FRAME_SVG_LAYOUT | NS_FRAME_FONT_INFLATION_CONTAINER |
     48               NS_FRAME_FONT_INFLATION_FLOW_ROOT);
     49 }
     50 
     51 //----------------------------------------------------------------------
     52 // nsIFrame methods
     53 
     54 NS_QUERYFRAME_HEAD(SVGForeignObjectFrame)
     55  NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
     56 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
     57 
     58 void SVGForeignObjectFrame::Init(nsIContent* aContent,
     59                                 nsContainerFrame* aParent,
     60                                 nsIFrame* aPrevInFlow) {
     61  NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::foreignObject),
     62               "Content is not an SVG foreignObject!");
     63 
     64  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
     65  AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
     66 }
     67 
     68 nsresult SVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID,
     69                                                 nsAtom* aAttribute,
     70                                                 AttrModType) {
     71  if (aNameSpaceID == kNameSpaceID_None) {
     72    if (aAttribute == nsGkAtoms::transform) {
     73      // We don't invalidate for transform changes (the layers code does that).
     74      // Also note that SVGTransformableElement::GetAttributeChangeHint will
     75      // return nsChangeHint_UpdateOverflow for "transform" attribute changes
     76      // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
     77      mCanvasTM = nullptr;
     78    } else if (aAttribute == nsGkAtoms::viewBox ||
     79               aAttribute == nsGkAtoms::preserveAspectRatio) {
     80      nsLayoutUtils::PostRestyleEvent(
     81          mContent->AsElement(), RestyleHint{0},
     82          nsChangeHint_InvalidateRenderingObservers);
     83    }
     84  }
     85 
     86  return NS_OK;
     87 }
     88 
     89 void SVGForeignObjectFrame::DidSetComputedStyle(
     90    ComputedStyle* aOldComputedStyle) {
     91  nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
     92 
     93  if (aOldComputedStyle) {
     94    if (StyleSVGReset()->mX != aOldComputedStyle->StyleSVGReset()->mX ||
     95        StyleSVGReset()->mY != aOldComputedStyle->StyleSVGReset()->mY) {
     96      // Invalidate cached transform matrix.
     97      mCanvasTM = nullptr;
     98      SVGUtils::ScheduleReflowSVG(this);
     99    }
    100  }
    101 }
    102 
    103 void SVGForeignObjectFrame::Reflow(nsPresContext* aPresContext,
    104                                   ReflowOutput& aDesiredSize,
    105                                   const ReflowInput& aReflowInput,
    106                                   nsReflowStatus& aStatus) {
    107  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    108  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
    109             "Should not have been called");
    110 
    111  // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
    112  // so if that bit is still set we still have a resize pending. If we hit
    113  // this assertion, then we should get the presShell to skip reflow roots
    114  // that have a dirty parent since a reflow is going to come via the
    115  // reflow root's parent anyway.
    116  NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY),
    117               "Reflowing while a resize is pending is wasteful");
    118 
    119  // ReflowSVG makes sure mRect is up to date before we're called.
    120 
    121  NS_ASSERTION(!aReflowInput.mParentReflowInput,
    122               "should only get reflow from being reflow root");
    123  NS_ASSERTION(aReflowInput.ComputedSize() == GetLogicalSize(),
    124               "reflow roots should be reflowed at existing size and "
    125               "svg.css should ensure we have no padding/border/margin");
    126 
    127  DoReflow();
    128 
    129  WritingMode wm = aReflowInput.GetWritingMode();
    130  LogicalSize finalSize(wm, aReflowInput.ComputedISize(),
    131                        aReflowInput.ComputedBSize());
    132  aDesiredSize.SetSize(wm, finalSize);
    133  aDesiredSize.SetOverflowAreasToDesiredBounds();
    134 }
    135 
    136 void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    137                                             const nsDisplayListSet& aLists) {
    138  if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
    139    return;
    140  }
    141  nsDisplayList newList(aBuilder);
    142  nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList,
    143                       &newList);
    144  DisplayOutline(aBuilder, set);
    145  BuildDisplayListForNonBlockChildren(aBuilder, set);
    146  aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this,
    147                                                           &newList);
    148 }
    149 
    150 bool SVGForeignObjectFrame::DoGetParentSVGTransforms(
    151    Matrix* aFromParentTransform) const {
    152  return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform);
    153 }
    154 
    155 void SVGForeignObjectFrame::PaintSVG(gfxContext& aContext,
    156                                     const gfxMatrix& aTransform,
    157                                     imgDrawingParams& aImgParams) {
    158  NS_ASSERTION(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
    159               "Only painting of non-display SVG should take this code path");
    160 
    161  if (IsDisabled()) {
    162    return;
    163  }
    164 
    165  nsIFrame* kid = PrincipalChildList().FirstChild();
    166  if (!kid) {
    167    return;
    168  }
    169 
    170  if (aTransform.IsSingular()) {
    171    NS_WARNING("Can't render foreignObject element!");
    172    return;
    173  }
    174 
    175  gfxClipAutoSaveRestore autoSaveClip(&aContext);
    176 
    177  if (StyleDisplay()->IsScrollableOverflow()) {
    178    float x, y, width, height;
    179    SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
    180                                    SVGT::Height>(
    181        static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
    182 
    183    gfxRect clipRect =
    184        SVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height);
    185    autoSaveClip.TransformedClip(aTransform, clipRect);
    186  }
    187 
    188  // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
    189  // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
    190  // paint correctly.
    191  float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
    192      PresContext()->AppUnitsPerDevPixel());
    193  gfxMatrix canvasTMForChildren = aTransform;
    194  canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx);
    195 
    196  aContext.Multiply(canvasTMForChildren);
    197 
    198  using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
    199  PaintFrameFlags flags = PaintFrameFlags::InTransform;
    200  if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) {
    201    flags |= PaintFrameFlags::ToWindow;
    202  }
    203  if (aImgParams.imageFlags & imgIContainer::FLAG_SYNC_DECODE) {
    204    flags |= PaintFrameFlags::SyncDecodeImages;
    205  }
    206  if (aImgParams.imageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) {
    207    flags |= PaintFrameFlags::UseHighQualityScaling;
    208  }
    209  nsLayoutUtils::PaintFrame(&aContext, kid, nsRegion(kid->InkOverflowRect()),
    210                            NS_RGBA(0, 0, 0, 0),
    211                            nsDisplayListBuilderMode::Painting, flags);
    212 }
    213 
    214 nsIFrame* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) {
    215  MOZ_ASSERT_UNREACHABLE(
    216      "A clipPath cannot contain an SVGForeignObject element");
    217  return nullptr;
    218 }
    219 
    220 void SVGForeignObjectFrame::ReflowSVG() {
    221  NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
    222               "This call is probably a wasteful mistake");
    223 
    224  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
    225             "ReflowSVG mechanism not designed for this");
    226 
    227  if (!SVGUtils::NeedsReflowSVG(this)) {
    228    return;
    229  }
    230 
    231  // We update mRect before the DoReflow call so that DoReflow uses the
    232  // correct dimensions:
    233 
    234  float x, y, w, h;
    235  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
    236      static_cast<SVGElement*>(GetContent()), &x, &y, &w, &h);
    237 
    238  // If mRect's width or height are negative, reflow blows up! We must clamp!
    239  if (w < 0.0f) {
    240    w = 0.0f;
    241  }
    242  if (h < 0.0f) {
    243    h = 0.0f;
    244  }
    245 
    246  mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, w, h),
    247                                               AppUnitsPerCSSPixel());
    248 
    249  // Fully mark our kid dirty so that it gets resized if necessary
    250  // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
    251  nsIFrame* kid = PrincipalChildList().FirstChild();
    252  kid->MarkSubtreeDirty();
    253 
    254  // Make sure to not allow interrupts if we're not being reflown as a root:
    255  nsPresContext::InterruptPreventer noInterrupts(PresContext());
    256 
    257  DoReflow();
    258 
    259  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    260    // Make sure we have our filter property (if any) before calling
    261    // FinishAndStoreOverflow (subsequent filter changes are handled off
    262    // nsChangeHint_UpdateEffects):
    263    SVGObserverUtils::UpdateEffects(this);
    264  }
    265 
    266  // If we have a filter, we need to invalidate ourselves because filter
    267  // output can change even if none of our descendants need repainting.
    268  if (StyleEffects()->HasFilters()) {
    269    InvalidateFrame();
    270  }
    271 
    272  auto* anonKid = PrincipalChildList().FirstChild();
    273  nsRect overflow = anonKid->InkOverflowRect();
    274 
    275  OverflowAreas overflowAreas(overflow, overflow);
    276  FinishAndStoreOverflow(overflowAreas, mRect.Size());
    277 
    278  // Now unset the various reflow bits:
    279  RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
    280                  NS_FRAME_HAS_DIRTY_CHILDREN);
    281 }
    282 
    283 void SVGForeignObjectFrame::NotifySVGChanged(ChangeFlags aFlags) {
    284  MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) ||
    285                 aFlags.contains(ChangeFlag::CoordContextChanged),
    286             "Invalidation logic may need adjusting");
    287 
    288  bool needNewBounds = false;  // i.e. mRect or ink overflow rect
    289  bool needReflow = false;
    290  bool needNewCanvasTM = false;
    291 
    292  if (aFlags.contains(ChangeFlag::CoordContextChanged)) {
    293    // Coordinate context changes affect mCanvasTM if we have a
    294    // percentage 'x' or 'y'
    295    if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) {
    296      needNewBounds = true;
    297      needNewCanvasTM = true;
    298    }
    299 
    300    const auto anchorResolutionParams = AnchorPosResolutionParams::From(this);
    301    // Our coordinate context's width/height has changed. If we have a
    302    // percentage width/height our dimensions will change so we must reflow.
    303    if (StylePosition()->GetWidth(anchorResolutionParams)->HasPercent() ||
    304        StylePosition()->GetHeight(anchorResolutionParams)->HasPercent()) {
    305      needNewBounds = true;
    306      needReflow = true;
    307    }
    308  }
    309 
    310  if (aFlags.contains(ChangeFlag::TransformChanged)) {
    311    if (mCanvasTM && mCanvasTM->IsSingular()) {
    312      needNewBounds = true;  // old bounds are bogus
    313    }
    314    needNewCanvasTM = true;
    315    // In an ideal world we would reflow when our CTM changes. This is because
    316    // glyph metrics do not necessarily scale uniformly with change in scale
    317    // and, as a result, CTM changes may require text to break at different
    318    // points. The problem would be how to keep performance acceptable when
    319    // e.g. the transform of an ancestor is animated.
    320    // We also seem to get some sort of infinite loop post bug 421584 if we
    321    // reflow.
    322  }
    323 
    324  if (needNewBounds) {
    325    // Ancestor changes can't affect how we render from the perspective of
    326    // any rendering observers that we may have, so we don't need to
    327    // invalidate them. We also don't need to invalidate ourself, since our
    328    // changed ancestor will have invalidated its entire area, which includes
    329    // our area.
    330    SVGUtils::ScheduleReflowSVG(this);
    331  }
    332 
    333  // If we're called while the PresShell is handling reflow events then we
    334  // must have been called as a result of the NotifyViewportChange() call in
    335  // our SVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
    336  // at this point (i.e. during reflow) because it could confuse the
    337  // PresShell and prevent it from reflowing us properly in future. Besides
    338  // that, SVGOuterSVGFrame::DidReflow will take care of reflowing us
    339  // synchronously, so there's no need.
    340  if (needReflow && !PresShell()->IsReflowLocked()) {
    341    RequestReflow(IntrinsicDirty::None);
    342  }
    343 
    344  if (needNewCanvasTM) {
    345    // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
    346    // change the code and it needs to use it.
    347    mCanvasTM = nullptr;
    348  }
    349 }
    350 
    351 SVGBBox SVGForeignObjectFrame::GetBBoxContribution(
    352    const Matrix& aToBBoxUserspace, uint32_t aFlags) {
    353  SVGForeignObjectElement* content =
    354      static_cast<SVGForeignObjectElement*>(GetContent());
    355 
    356  float x, y, w, h;
    357  SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
    358      content, &x, &y, &w, &h);
    359 
    360  if (w < 0.0f) {
    361    w = 0.0f;
    362  }
    363  if (h < 0.0f) {
    364    h = 0.0f;
    365  }
    366 
    367  if (aToBBoxUserspace.IsSingular()) {
    368    // XXX ReportToConsole
    369    return SVGBBox();
    370  }
    371  return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
    372 }
    373 
    374 //----------------------------------------------------------------------
    375 
    376 gfxMatrix SVGForeignObjectFrame::GetCanvasTM() {
    377  if (!mCanvasTM) {
    378    NS_ASSERTION(GetParent(), "null parent");
    379    auto* parent = static_cast<SVGContainerFrame*>(GetParent());
    380    auto* content = static_cast<SVGForeignObjectElement*>(GetContent());
    381    mCanvasTM = MakeUnique<gfxMatrix>(content->ChildToUserSpaceTransform() *
    382                                      parent->GetCanvasTM());
    383  }
    384  return *mCanvasTM;
    385 }
    386 
    387 //----------------------------------------------------------------------
    388 // Implementation helpers
    389 
    390 void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType) {
    391  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    392    // If we haven't had a ReflowSVG() yet, nothing to do.
    393    return;
    394  }
    395 
    396  nsIFrame* kid = PrincipalChildList().FirstChild();
    397  if (!kid) {
    398    return;
    399  }
    400 
    401  PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY);
    402 }
    403 
    404 void SVGForeignObjectFrame::DoReflow() {
    405  MarkInReflow();
    406  // Skip reflow if we're zero-sized, unless this is our first reflow.
    407  if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    408    return;
    409  }
    410 
    411  nsPresContext* presContext = PresContext();
    412  nsIFrame* kid = PrincipalChildList().FirstChild();
    413  if (!kid) {
    414    return;
    415  }
    416 
    417  // initiate a synchronous reflow here and now:
    418  UniquePtr<gfxContext> renderingContext =
    419      presContext->PresShell()->CreateReferenceRenderingContext();
    420 
    421  WritingMode wm = kid->GetWritingMode();
    422  ReflowInput reflowInput(presContext, kid, renderingContext.get(),
    423                          LogicalSize(wm, ISize(wm), NS_UNCONSTRAINEDSIZE));
    424  ReflowOutput desiredSize(reflowInput);
    425  nsReflowStatus status;
    426 
    427  // We don't use mRect.height above because that tells the child to do
    428  // page/column breaking at that height.
    429  NS_ASSERTION(
    430      reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
    431          reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
    432      "style system should ensure that :-moz-svg-foreign-content "
    433      "does not get styled");
    434  NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm),
    435               "reflow input made child wrong size");
    436  reflowInput.SetComputedBSize(BSize(wm));
    437 
    438  ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0,
    439              ReflowChildFlags::NoMoveFrame, status);
    440  NS_ASSERTION(mRect.width == desiredSize.Width() &&
    441                   mRect.height == desiredSize.Height(),
    442               "unexpected size");
    443  FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0,
    444                    ReflowChildFlags::NoMoveFrame);
    445 }
    446 
    447 void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
    448    nsTArray<OwnedAnonBox>& aResult) {
    449  MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
    450  aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
    451 }
    452 
    453 }  // namespace mozilla