tor-browser

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

SVGClipPathFrame.cpp (16190B)


      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 "SVGClipPathFrame.h"
      9 
     10 // Keep others in (case-insensitive) order:
     11 #include "AutoReferenceChainGuard.h"
     12 #include "ImgDrawResult.h"
     13 #include "gfxContext.h"
     14 #include "mozilla/PresShell.h"
     15 #include "mozilla/SVGGeometryFrame.h"
     16 #include "mozilla/SVGObserverUtils.h"
     17 #include "mozilla/SVGUtils.h"
     18 #include "mozilla/dom/SVGClipPathElement.h"
     19 #include "mozilla/dom/SVGGeometryElement.h"
     20 #include "nsGkAtoms.h"
     21 
     22 using namespace mozilla::dom;
     23 using namespace mozilla::gfx;
     24 using namespace mozilla::image;
     25 
     26 //----------------------------------------------------------------------
     27 // Implementation
     28 
     29 nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell,
     30                                 mozilla::ComputedStyle* aStyle) {
     31  return new (aPresShell)
     32      mozilla::SVGClipPathFrame(aStyle, aPresShell->GetPresContext());
     33 }
     34 
     35 namespace mozilla {
     36 
     37 NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame)
     38 
     39 void SVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
     40                                     nsIFrame* aClippedFrame,
     41                                     const gfxMatrix& aMatrix) {
     42  nsIFrame* singleClipPathChild = nullptr;
     43  DebugOnly<bool> trivial = IsTrivial(&singleClipPathChild);
     44  MOZ_ASSERT(trivial, "Caller needs to use GetClipMask");
     45 
     46  const DrawTarget* drawTarget = aContext.GetDrawTarget();
     47 
     48  // No need for AutoReferenceChainGuard since simple clip paths by definition
     49  // don't reference another clip path.
     50 
     51  // Restore current transform after applying clip path:
     52  gfxContextMatrixAutoSaveRestore autoRestoreTransform(&aContext);
     53 
     54  RefPtr<Path> clipPath;
     55 
     56  if (singleClipPathChild) {
     57    SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
     58    if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
     59      SVGGeometryElement* pathElement =
     60          static_cast<SVGGeometryElement*>(pathFrame->GetContent());
     61 
     62      gfxMatrix toChildsUserSpace =
     63          SVGUtils::GetTransformMatrixInUserSpace(pathFrame) *
     64          (GetClipPathTransform(aClippedFrame) * aMatrix);
     65 
     66      gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
     67                                .PreMultiply(toChildsUserSpace)
     68                                .NudgeToIntegers();
     69      if (!newMatrix.IsSingular()) {
     70        aContext.SetMatrixDouble(newMatrix);
     71        FillRule clipRule =
     72            SVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
     73        clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule);
     74      }
     75    }
     76  }
     77 
     78  if (clipPath) {
     79    aContext.Clip(clipPath);
     80  } else {
     81    // The spec says clip away everything if we have no children or the
     82    // clipping path otherwise can't be resolved:
     83    aContext.Clip(Rect());
     84  }
     85 }
     86 
     87 static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask) {
     88  MOZ_ASSERT(aExtraMask);
     89 
     90  Matrix origin = aTarget->GetTransform();
     91  aTarget->SetTransform(Matrix());
     92  aTarget->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
     93                       aExtraMask, Point(0, 0),
     94                       DrawOptions(1.0, CompositionOp::OP_IN));
     95  aTarget->SetTransform(origin);
     96 }
     97 
     98 void SVGClipPathFrame::PaintChildren(gfxContext& aMaskContext,
     99                                     nsIFrame* aClippedFrame,
    100                                     const gfxMatrix& aMatrix) {
    101  // Check if this clipPath is itself clipped by another clipPath:
    102  SVGClipPathFrame* clipPathThatClipsClipPath;
    103  // XXX check return value?
    104  SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath);
    105  SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, true);
    106 
    107  gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aMaskContext);
    108  if (maskUsage.ShouldApplyClipPath()) {
    109    clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
    110                                             aMatrix);
    111  } else if (maskUsage.ShouldGenerateClipMaskLayer()) {
    112    RefPtr<SourceSurface> maskSurface = clipPathThatClipsClipPath->GetClipMask(
    113        aMaskContext, aClippedFrame, aMatrix);
    114    // We want the mask to be untransformed so use the inverse of the current
    115    // transform as the maskTransform to compensate.
    116    Matrix maskTransform = aMaskContext.CurrentMatrix();
    117    maskTransform.Invert();
    118    autoGroupForBlend.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0f,
    119                                            maskSurface, maskTransform);
    120  }
    121 
    122  // Paint our children into the mask:
    123  for (auto* kid : mFrames) {
    124    PaintFrameIntoMask(kid, aClippedFrame, aMaskContext);
    125  }
    126 
    127  if (maskUsage.ShouldApplyClipPath()) {
    128    aMaskContext.PopClip();
    129  }
    130 }
    131 
    132 void SVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
    133                                     nsIFrame* aClippedFrame,
    134                                     const gfxMatrix& aMatrix,
    135                                     SourceSurface* aExtraMask) {
    136  static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
    137 
    138  // A clipPath can reference another clipPath, creating a chain of clipPaths
    139  // that must all be applied.  We re-enter this method for each clipPath in a
    140  // chain, so we need to protect against reference chain related crashes etc.:
    141  AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
    142                                        &sRefChainLengthCounter);
    143  if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
    144    return;  // Break reference chain
    145  }
    146  if (!IsValid()) {
    147    return;
    148  }
    149 
    150  DrawTarget* maskDT = aMaskContext.GetDrawTarget();
    151  MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
    152 
    153  // Paint this clipPath's contents into aMaskDT:
    154  // We need to set mMatrixForChildren here so that under the PaintSVG calls
    155  // on our children (below) our GetCanvasTM() method will return the correct
    156  // transform.
    157  mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
    158 
    159  PaintChildren(aMaskContext, aClippedFrame, aMatrix);
    160 
    161  if (aExtraMask) {
    162    ComposeExtraMask(maskDT, aExtraMask);
    163  }
    164 }
    165 
    166 void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame,
    167                                          nsIFrame* aClippedFrame,
    168                                          gfxContext& aTarget) {
    169  ISVGDisplayableFrame* frame = do_QueryFrame(aFrame);
    170  if (!frame) {
    171    return;
    172  }
    173 
    174  // The CTM of each frame referencing us can be different.
    175  frame->NotifySVGChanged(ISVGDisplayableFrame::ChangeFlag::TransformChanged);
    176 
    177  // Children of this clipPath may themselves be clipped.
    178  SVGClipPathFrame* clipPathThatClipsChild;
    179  // XXX check return value?
    180  if (SVGObserverUtils::GetAndObserveClipPath(aFrame,
    181                                              &clipPathThatClipsChild) ==
    182      SVGObserverUtils::eHasRefsSomeInvalid) {
    183    return;
    184  }
    185 
    186  SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(aFrame, true);
    187  gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aTarget);
    188  if (maskUsage.ShouldApplyClipPath()) {
    189    clipPathThatClipsChild->ApplyClipPath(
    190        aTarget, aClippedFrame,
    191        SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren);
    192  } else if (maskUsage.ShouldGenerateClipMaskLayer()) {
    193    RefPtr<SourceSurface> maskSurface = clipPathThatClipsChild->GetClipMask(
    194        aTarget, aClippedFrame,
    195        SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren);
    196 
    197    // We want the mask to be untransformed so use the inverse of the current
    198    // transform as the maskTransform to compensate.
    199    Matrix maskTransform = aTarget.CurrentMatrix();
    200    maskTransform.Invert();
    201    autoGroupForBlend.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0f,
    202                                            maskSurface, maskTransform);
    203  }
    204 
    205  gfxMatrix toChildsUserSpace = mMatrixForChildren;
    206  nsIFrame* child = do_QueryFrame(frame);
    207  nsIContent* childContent = child->GetContent();
    208  if (childContent->IsSVGElement()) {
    209    toChildsUserSpace =
    210        SVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren;
    211  }
    212 
    213  // clipPath does not result in any image rendering, so we just use a dummy
    214  // imgDrawingParams instead of requiring our caller to pass one.
    215  image::imgDrawingParams imgParams;
    216 
    217  // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
    218  // SVGGeometryFrame::Render checks for that state bit and paints
    219  // only the geometry (opaque black) if set.
    220  frame->PaintSVG(aTarget, toChildsUserSpace, imgParams);
    221 
    222  if (maskUsage.ShouldApplyClipPath()) {
    223    aTarget.PopClip();
    224  }
    225 }
    226 
    227 already_AddRefed<SourceSurface> SVGClipPathFrame::GetClipMask(
    228    gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
    229    const gfxMatrix& aMatrix, SourceSurface* aExtraMask) {
    230  RefPtr<DrawTarget> maskDT =
    231      aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget(
    232          Rect(), SurfaceFormat::A8);
    233  if (!maskDT) {
    234    return nullptr;
    235  }
    236 
    237  gfxContext maskContext(maskDT, /* aPreserveTransform */ true);
    238  PaintClipMask(maskContext, aClippedFrame, aMatrix, aExtraMask);
    239 
    240  RefPtr<SourceSurface> surface = maskDT->Snapshot();
    241  return surface.forget();
    242 }
    243 
    244 bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
    245                                             const gfxPoint& aPoint) {
    246  static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
    247 
    248  // A clipPath can reference another clipPath, creating a chain of clipPaths
    249  // that must all be applied.  We re-enter this method for each clipPath in a
    250  // chain, so we need to protect against reference chain related crashes etc.:
    251  AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
    252                                        &sRefChainLengthCounter);
    253  if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
    254    return false;  // Break reference chain
    255  }
    256  if (!IsValid()) {
    257    return false;
    258  }
    259 
    260  gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
    261  if (!matrix.Invert()) {
    262    return false;
    263  }
    264  gfxPoint point = matrix.TransformPoint(aPoint);
    265 
    266  // clipPath elements can themselves be clipped by a different clip path. In
    267  // that case the other clip path further clips away the element that is being
    268  // clipped by the original clipPath. If this clipPath is being clipped by a
    269  // different clip path we need to check if it prevents the original element
    270  // from receiving events at aPoint:
    271  SVGClipPathFrame* clipPathFrame;
    272  // XXX check return value?
    273  SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame);
    274  if (clipPathFrame &&
    275      !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
    276    return false;
    277  }
    278 
    279  for (auto* kid : mFrames) {
    280    ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
    281    if (SVGFrame) {
    282      gfxPoint pointForChild = point;
    283 
    284      gfxMatrix m = SVGUtils::GetTransformMatrixInUserSpace(kid);
    285      if (!m.IsIdentity()) {
    286        if (!m.Invert()) {
    287          return false;
    288        }
    289        pointForChild = m.TransformPoint(point);
    290      }
    291      if (SVGFrame->GetFrameForPoint(pointForChild)) {
    292        return true;
    293      }
    294    }
    295  }
    296 
    297  return false;
    298 }
    299 
    300 bool SVGClipPathFrame::IsTrivial(nsIFrame** aSingleChild) {
    301  // If the clip path is clipped then it's non-trivial
    302  if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
    303      SVGObserverUtils::eHasRefsAllValid) {
    304    return false;
    305  }
    306 
    307  if (aSingleChild) {
    308    *aSingleChild = nullptr;
    309  }
    310 
    311  nsIFrame* foundChild = nullptr;
    312  for (auto* kid : mFrames) {
    313    ISVGDisplayableFrame* svgChild = do_QueryFrame(kid);
    314    if (!svgChild) {
    315      continue;
    316    }
    317    // We consider a non-trivial clipPath to be one containing
    318    // either more than one svg child and/or a svg container
    319    if (foundChild || svgChild->IsDisplayContainer()) {
    320      return false;
    321    }
    322 
    323    // or where the child is itself clipped
    324    if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) ==
    325        SVGObserverUtils::eHasRefsAllValid) {
    326      return false;
    327    }
    328 
    329    foundChild = kid;
    330  }
    331  if (aSingleChild) {
    332    *aSingleChild = foundChild;
    333  }
    334  return true;
    335 }
    336 
    337 bool SVGClipPathFrame::IsValid() {
    338  if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
    339      SVGObserverUtils::eHasRefsSomeInvalid) {
    340    return false;
    341  }
    342 
    343  for (auto* kid : mFrames) {
    344    LayoutFrameType kidType = kid->Type();
    345 
    346    if (kidType == LayoutFrameType::SVGUse) {
    347      for (nsIFrame* grandKid : kid->PrincipalChildList()) {
    348        LayoutFrameType grandKidType = grandKid->Type();
    349 
    350        if (grandKidType != LayoutFrameType::SVGGeometry &&
    351            grandKidType != LayoutFrameType::SVGText) {
    352          return false;
    353        }
    354      }
    355      continue;
    356    }
    357 
    358    if (kidType != LayoutFrameType::SVGGeometry &&
    359        kidType != LayoutFrameType::SVGText) {
    360      return false;
    361    }
    362  }
    363 
    364  return true;
    365 }
    366 
    367 nsresult SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
    368                                            nsAtom* aAttribute,
    369                                            AttrModType aModType) {
    370  if (aNameSpaceID == kNameSpaceID_None &&
    371      aAttribute == nsGkAtoms::clipPathUnits) {
    372    SVGObserverUtils::InvalidateRenderingObservers(this);
    373  }
    374  return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
    375                                             aModType);
    376 }
    377 
    378 #ifdef DEBUG
    379 void SVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
    380                            nsIFrame* aPrevInFlow) {
    381  NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
    382               "Content is not an SVG clipPath!");
    383 
    384  SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
    385 }
    386 #endif
    387 
    388 gfxMatrix SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
    389 
    390 gfxMatrix SVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
    391  gfxMatrix tm = SVGUtils::GetTransformMatrixInUserSpace(this);
    392 
    393  auto* content = static_cast<SVGClipPathElement*>(GetContent());
    394  SVGAnimatedEnumeration* clipPathUnits =
    395      &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
    396 
    397  uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
    398                   (aClippedFrame->StyleBorder()->mBoxDecorationBreak ==
    399                            StyleBoxDecorationBreak::Clone
    400                        ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
    401                        : 0);
    402 
    403  return SVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame,
    404                                        flags);
    405 }
    406 
    407 SVGBBox SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox,
    408                                                  const gfxMatrix& aMatrix,
    409                                                  uint32_t aFlags) {
    410  SVGClipPathFrame* clipPathThatClipsClipPath;
    411  if (SVGObserverUtils::GetAndObserveClipPath(this,
    412                                              &clipPathThatClipsClipPath) ==
    413      SVGObserverUtils::eHasRefsSomeInvalid) {
    414    return SVGBBox();
    415  }
    416 
    417  nsIContent* node = GetContent()->GetFirstChild();
    418  SVGBBox unionBBox;
    419  for (; node; node = node->GetNextSibling()) {
    420    if (nsIFrame* frame = node->GetPrimaryFrame()) {
    421      ISVGDisplayableFrame* svg = do_QueryFrame(frame);
    422      if (svg) {
    423        gfxMatrix matrix =
    424            SVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix;
    425        SVGBBox tmpBBox = svg->GetBBoxContribution(
    426            gfx::ToMatrix(matrix), SVGUtils::eBBoxIncludeFillGeometry);
    427        SVGClipPathFrame* clipPathFrame;
    428        if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) !=
    429                SVGObserverUtils::eHasRefsSomeInvalid &&
    430            clipPathFrame) {
    431          tmpBBox =
    432              clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
    433        }
    434        if (!(aFlags & SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) {
    435          tmpBBox.Intersect(aBBox);
    436        }
    437        unionBBox.UnionEdges(tmpBBox);
    438      }
    439    }
    440  }
    441 
    442  if (clipPathThatClipsClipPath) {
    443    unionBBox.Intersect(clipPathThatClipsClipPath->GetBBoxForClipPathFrame(
    444        aBBox, aMatrix, aFlags));
    445  }
    446  return unionBBox;
    447 }
    448 
    449 }  // namespace mozilla