forked from mirrors/gecko-dev
		
	 5b9588f1aa
			
		
	
	
		5b9588f1aa
		
	
	
	
	
		
			
			Depends on D170369 Differential Revision: https://phabricator.services.mozilla.com/D170370
		
			
				
	
	
		
			1250 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1250 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| // Main header first:
 | |
| #include "SVGIntegrationUtils.h"
 | |
| 
 | |
| // Keep others in (case-insensitive) order:
 | |
| #include "gfxDrawable.h"
 | |
| 
 | |
| #include "nsCSSAnonBoxes.h"
 | |
| #include "nsCSSRendering.h"
 | |
| #include "nsDisplayList.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "gfxContext.h"
 | |
| #include "SVGPaintServerFrame.h"
 | |
| #include "mozilla/gfx/Point.h"
 | |
| #include "mozilla/CSSClipPathInstance.h"
 | |
| #include "mozilla/FilterInstance.h"
 | |
| #include "mozilla/StaticPrefs_layers.h"
 | |
| #include "mozilla/SVGClipPathFrame.h"
 | |
| #include "mozilla/SVGObserverUtils.h"
 | |
| #include "mozilla/SVGMaskFrame.h"
 | |
| #include "mozilla/SVGUtils.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/dom/SVGElement.h"
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| using namespace mozilla::layers;
 | |
| using namespace mozilla::gfx;
 | |
| using namespace mozilla::image;
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| /**
 | |
|  * This class is used to get the pre-effects ink overflow rect of a frame,
 | |
|  * or, in the case of a frame with continuations, to collect the union of the
 | |
|  * pre-effects ink overflow rects of all the continuations. The result is
 | |
|  * relative to the origin (top left corner of the border box) of the frame, or,
 | |
|  * if the frame has continuations, the origin of the  _first_ continuation.
 | |
|  */
 | |
| class PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback {
 | |
|  public:
 | |
|   /**
 | |
|    * If the pre-effects ink overflow rect of the frame being examined
 | |
|    * happens to be known, it can be passed in as aCurrentFrame and its
 | |
|    * pre-effects ink overflow rect can be passed in as
 | |
|    * aCurrentFrameOverflowArea. This is just an optimization to save a
 | |
|    * frame property lookup - these arguments are optional.
 | |
|    */
 | |
|   PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation,
 | |
|                                  nsIFrame* aCurrentFrame,
 | |
|                                  const nsRect& aCurrentFrameOverflowArea,
 | |
|                                  bool aInReflow)
 | |
|       : mFirstContinuation(aFirstContinuation),
 | |
|         mCurrentFrame(aCurrentFrame),
 | |
|         mCurrentFrameOverflowArea(aCurrentFrameOverflowArea),
 | |
|         mInReflow(aInReflow) {
 | |
|     NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
 | |
|                  "We want the first continuation here");
 | |
|   }
 | |
| 
 | |
|   void AddBox(nsIFrame* aFrame) override {
 | |
|     nsRect overflow = (aFrame == mCurrentFrame)
 | |
|                           ? mCurrentFrameOverflowArea
 | |
|                           : PreEffectsInkOverflowRect(aFrame, mInReflow);
 | |
|     mResult.UnionRect(mResult,
 | |
|                       overflow + aFrame->GetOffsetTo(mFirstContinuation));
 | |
|   }
 | |
| 
 | |
|   nsRect GetResult() const { return mResult; }
 | |
| 
 | |
|  private:
 | |
|   static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) {
 | |
|     nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty());
 | |
|     if (r) {
 | |
|       return *r;
 | |
|     }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|     // Having PreTransformOverflowAreasProperty cached means
 | |
|     // InkOverflowRect() will return post-effect rect, which is not what
 | |
|     // we want. This function intentional reports pre-effect rect. But it does
 | |
|     // not matter if there is no SVG effect on this frame, since no effect
 | |
|     // means post-effect rect matches pre-effect rect.
 | |
|     //
 | |
|     // This function may be called during reflow or painting. We should only
 | |
|     // do this check in painting process since the PreEffectsBBoxProperty of
 | |
|     // continuations are not set correctly while reflowing.
 | |
|     if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) &&
 | |
|         !aInReflow) {
 | |
|       OverflowAreas* preTransformOverflows =
 | |
|           aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty());
 | |
| 
 | |
|       MOZ_ASSERT(!preTransformOverflows,
 | |
|                  "InkOverflowRect() won't return the pre-effects rect!");
 | |
|     }
 | |
| #endif
 | |
|     return aFrame->InkOverflowRectRelativeToSelf();
 | |
|   }
 | |
| 
 | |
|   nsIFrame* mFirstContinuation;
 | |
|   nsIFrame* mCurrentFrame;
 | |
|   const nsRect& mCurrentFrameOverflowArea;
 | |
|   nsRect mResult;
 | |
|   bool mInReflow;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Gets the union of the pre-effects ink overflow rects of all of a frame's
 | |
|  * continuations, in "user space".
 | |
|  */
 | |
| static nsRect GetPreEffectsInkOverflowUnion(
 | |
|     nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
 | |
|     const nsRect& aCurrentFramePreEffectsOverflow,
 | |
|     const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) {
 | |
|   NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
 | |
|                "Need first continuation here");
 | |
|   PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame,
 | |
|                                            aCurrentFramePreEffectsOverflow,
 | |
|                                            aInReflow);
 | |
|   // Compute union of all overflow areas relative to aFirstContinuation:
 | |
|   nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
 | |
|   // Return the result in user space:
 | |
|   return collector.GetResult() + aFirstContinuationToUserSpace;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space".
 | |
|  */
 | |
| static nsRect GetPreEffectsInkOverflow(
 | |
|     nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
 | |
|     const nsPoint& aFirstContinuationToUserSpace) {
 | |
|   NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
 | |
|                "Need first continuation here");
 | |
|   PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr,
 | |
|                                            nsRect(), false);
 | |
|   // Compute overflow areas of current frame relative to aFirstContinuation:
 | |
|   nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector);
 | |
|   // Return the result in user space:
 | |
|   return collector.GetResult() + aFirstContinuationToUserSpace;
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::UsingOverflowAffectingEffects(
 | |
|     const nsIFrame* aFrame) {
 | |
|   // Currently overflow don't take account of SVG or other non-absolute
 | |
|   // positioned clipping, or masking.
 | |
|   return aFrame->StyleEffects()->HasFilters();
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) {
 | |
|   // Even when SVG display lists are disabled, returning true for SVG frames
 | |
|   // does not adversely affect any of our callers. Therefore we don't bother
 | |
|   // checking the SDL prefs here, since we don't know if we're being called for
 | |
|   // painting or hit-testing anyway.
 | |
|   const nsStyleSVGReset* style = aFrame->StyleSVGReset();
 | |
|   const nsStyleEffects* effects = aFrame->StyleEffects();
 | |
|   // TODO(cbrewster): remove backdrop-filter from this list once it is supported
 | |
|   // in preserve-3d cases.
 | |
|   return effects->HasFilters() || effects->HasBackdropFilters() ||
 | |
|          style->HasClipPath() || style->HasMask();
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) {
 | |
|   const nsStyleSVGReset* style = aFrame->StyleSVGReset();
 | |
|   return style->HasClipPath() || style->HasMask();
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::UsingSimpleClipPathForFrame(const nsIFrame* aFrame) {
 | |
|   const nsStyleSVGReset* style = aFrame->StyleSVGReset();
 | |
|   if (!style->HasClipPath() || style->HasMask()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const auto& clipPath = style->mClipPath;
 | |
|   if (!clipPath.IsShape()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return !clipPath.AsShape()._0->IsPolygon();
 | |
| }
 | |
| 
 | |
| nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) {
 | |
|   if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | |
|     // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
 | |
|     // covered region relative to the SVGOuterSVGFrame, which is absolutely
 | |
|     // not what we want. SVG frames are always in user space, so they have
 | |
|     // no offset adjustment to make.
 | |
|     return nsPoint();
 | |
|   }
 | |
| 
 | |
|   // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
 | |
|   // rects over all continuations, relative to the origin (top-left of the
 | |
|   // border box) of its second argument (here, aFrame, the first continuation).
 | |
|   return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
 | |
| }
 | |
| 
 | |
| struct EffectOffsets {
 | |
|   // The offset between the reference frame and the bounding box of the
 | |
|   // target frame in app unit.
 | |
|   nsPoint offsetToBoundingBox;
 | |
|   // The offset between the reference frame and the bounding box of the
 | |
|   // target frame in app unit.
 | |
|   nsPoint offsetToUserSpace;
 | |
|   // The offset between the reference frame and the bounding box of the
 | |
|   // target frame in device unit.
 | |
|   gfxPoint offsetToUserSpaceInDevPx;
 | |
| };
 | |
| 
 | |
| static EffectOffsets ComputeEffectOffset(
 | |
|     nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
 | |
|   EffectOffsets result;
 | |
| 
 | |
|   result.offsetToBoundingBox =
 | |
|       aParams.builder->ToReferenceFrame(aFrame) -
 | |
|       SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
 | |
|   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
 | |
|     /* Snap the offset if the reference frame is not a SVG frame,
 | |
|      * since other frames will be snapped to pixel when rendering. */
 | |
|     result.offsetToBoundingBox =
 | |
|         nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
 | |
|                     result.offsetToBoundingBox.x),
 | |
|                 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
 | |
|                     result.offsetToBoundingBox.y));
 | |
|   }
 | |
| 
 | |
|   // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
 | |
|   // origin at the top left corner of frame's bounding box (over all
 | |
|   // continuations).
 | |
|   // However, SVG painting needs the origin to be located at the origin of the
 | |
|   // SVG frame's "user space", i.e. the space in which, for example, the
 | |
|   // frame's BBox lives.
 | |
|   // SVG geometry frames and foreignObject frames apply their own offsets, so
 | |
|   // their position is relative to their user space. So for these frame types,
 | |
|   // if we want aParams.ctx to be in user space, we first need to subtract the
 | |
|   // frame's position so that SVG painting can later add it again and the
 | |
|   // frame is painted in the right place.
 | |
|   gfxPoint toUserSpaceGfx =
 | |
|       SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
 | |
|   nsPoint toUserSpace =
 | |
|       nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
 | |
|               nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
 | |
| 
 | |
|   result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
 | |
|   NS_ASSERTION(
 | |
|       hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace,
 | |
|       "For non-SVG frames there shouldn't be any additional offset");
 | |
| #endif
 | |
| 
 | |
|   result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint(
 | |
|       result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel());
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Setup transform matrix of a gfx context by a specific frame. Move the
 | |
|  * origin of aParams.ctx to the user space of aFrame.
 | |
|  */
 | |
| static EffectOffsets MoveContextOriginToUserSpace(
 | |
|     nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
 | |
|   EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
 | |
| 
 | |
|   aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate(
 | |
|       offset.offsetToUserSpaceInDevPx));
 | |
| 
 | |
|   return offset;
 | |
| }
 | |
| 
 | |
| gfxPoint SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(
 | |
|     nsIFrame* aFrame, const PaintFramesParams& aParams) {
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 | |
|   EffectOffsets offset = ComputeEffectOffset(firstFrame, aParams);
 | |
|   return offset.offsetToUserSpaceInDevPx;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| nsSize SVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) {
 | |
|   NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
 | |
|                "SVG frames should not get here");
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
 | |
|   return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
 | |
| }
 | |
| 
 | |
| /* static */ gfx::Size SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(
 | |
|     nsIFrame* aNonSVGFrame) {
 | |
|   NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
 | |
|                "SVG frames should not get here");
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
 | |
|   nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
 | |
|   return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width),
 | |
|                    nsPresContext::AppUnitsToFloatCSSPixels(r.height));
 | |
| }
 | |
| 
 | |
| gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
 | |
|     nsIFrame* aNonSVGFrame, bool aUnionContinuations) {
 | |
|   // Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG
 | |
|   // frames at all. This function is for elements that are laid out using the
 | |
|   // CSS box model rules.
 | |
|   NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
 | |
|                "Frames with SVG layout should not get here");
 | |
|   MOZ_ASSERT(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG) ||
 | |
|              aNonSVGFrame->IsSVGOuterSVGFrame());
 | |
| 
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
 | |
|   // 'r' is in "user space":
 | |
|   nsRect r = (aUnionContinuations)
 | |
|                  ? GetPreEffectsInkOverflowUnion(
 | |
|                        firstFrame, nullptr, nsRect(),
 | |
|                        GetOffsetToBoundingBox(firstFrame), false)
 | |
|                  : GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame,
 | |
|                                             GetOffsetToBoundingBox(firstFrame));
 | |
| 
 | |
|   return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel());
 | |
| }
 | |
| 
 | |
| // XXX Since we're called during reflow, this method is broken for frames with
 | |
| // continuations. When we're called for a frame with continuations, we're
 | |
| // called for each continuation in turn as it's reflowed. However, it isn't
 | |
| // until the last continuation is reflowed that this method's
 | |
| // GetOffsetToBoundingBox() and GetPreEffectsInkOverflowUnion() calls will
 | |
| // obtain valid border boxes for all the continuations. As a result, we'll
 | |
| // end up returning bogus post-filter ink overflow rects for all the prior
 | |
| // continuations. Unfortunately, by the time the last continuation is
 | |
| // reflowed, it's too late to go back and set and propagate the overflow
 | |
| // rects on the previous continuations.
 | |
| //
 | |
| // The reason that we need to pass an override bbox to
 | |
| // GetPreEffectsInkOverflowUnion rather than just letting it call into our
 | |
| // GetSVGBBoxForNonSVGFrame method is because we get called by
 | |
| // ComputeEffectsRect when it has been called with
 | |
| // aStoreRectProperties set to false. In this case the pre-effects visual
 | |
| // overflow rect that it has been passed may be different to that stored on
 | |
| // aFrame, resulting in a different bbox.
 | |
| //
 | |
| // XXXjwatt The pre-effects ink overflow rect passed to
 | |
| // ComputeEffectsRect won't include continuation overflows, so
 | |
| // for frames with continuation the following filter analysis will likely end
 | |
| // up being carried out with a bbox created as if the frame didn't have
 | |
| // continuations.
 | |
| //
 | |
| // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
 | |
| // for SVG frames, since for SVG frames the SVG spec defines the bbox to be
 | |
| // something quite different to the pre-effects ink overflow rect. However,
 | |
| // we're essentially calculating an invalidation area here, and using the
 | |
| // pre-effects overflow rect will actually overestimate that area which, while
 | |
| // being a bit wasteful, isn't otherwise a problem.
 | |
| //
 | |
| nsRect SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(
 | |
|     nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) {
 | |
|   MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
 | |
|              "Don't call this on SVG child frames");
 | |
| 
 | |
|   MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(),
 | |
|              "We should only be called if the frame is filtered, since filters "
 | |
|              "are the only effect that affects overflow.");
 | |
| 
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 | |
|   // Note: we do not return here for eHasNoRefs since we must still handle any
 | |
|   // CSS filter functions.
 | |
|   // TODO: We currently pass nullptr instead of an nsTArray* here, but we
 | |
|   // actually should get the filter frames and then pass them into
 | |
|   // GetPostFilterBounds below!  See bug 1494263.
 | |
|   // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
 | |
|   // in that case we disable painting of the element.
 | |
|   if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
 | |
|       SVGObserverUtils::eHasRefsSomeInvalid) {
 | |
|     return aPreEffectsOverflowRect;
 | |
|   }
 | |
| 
 | |
|   // Create an override bbox - see comment above:
 | |
|   nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
 | |
|   // overrideBBox is in "user space", in _CSS_ pixels:
 | |
|   // XXX Why are we rounding out to pixel boundaries? We don't do that in
 | |
|   // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
 | |
|   gfxRect overrideBBox = nsLayoutUtils::RectToGfxRect(
 | |
|       GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect,
 | |
|                                     firstFrameToBoundingBox, true),
 | |
|       AppUnitsPerCSSPixel());
 | |
|   overrideBBox.RoundOut();
 | |
| 
 | |
|   Maybe<nsRect> overflowRect =
 | |
|       FilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
 | |
|   if (!overflowRect) {
 | |
|     return aPreEffectsOverflowRect;
 | |
|   }
 | |
| 
 | |
|   // Return overflowRect relative to aFrame, rather than "user space":
 | |
|   return overflowRect.value() -
 | |
|          (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
 | |
| }
 | |
| 
 | |
| nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
 | |
|     nsIFrame* aFrame, const nsRect& aDirtyRect) {
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 | |
| 
 | |
|   // If we have any filters to observe then we should have started doing that
 | |
|   // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
 | |
|   // here to avoid needless work (or masking bugs by setting up observers at
 | |
|   // the wrong time).
 | |
|   if (!aFrame->StyleEffects()->HasFilters() ||
 | |
|       SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) ==
 | |
|           SVGObserverUtils::eHasRefsSomeInvalid) {
 | |
|     return aDirtyRect;
 | |
|   }
 | |
| 
 | |
|   // Convert aDirtyRect into "user space" in app units:
 | |
|   nsPoint toUserSpace =
 | |
|       aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
 | |
|   nsRect postEffectsRect = aDirtyRect + toUserSpace;
 | |
| 
 | |
|   // Return ther result, relative to aFrame, not in user space:
 | |
|   return FilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect)
 | |
|              .GetBounds() -
 | |
|          toUserSpace;
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame,
 | |
|                                                  const nsPoint& aPt) {
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 | |
|   // Convert aPt to user space:
 | |
|   nsPoint toUserSpace;
 | |
|   if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | |
|     // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
 | |
|     toUserSpace = aFrame->GetPosition();
 | |
|   } else {
 | |
|     toUserSpace =
 | |
|         aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
 | |
|   }
 | |
|   nsPoint pt = aPt + toUserSpace;
 | |
|   gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel();
 | |
|   return SVGUtils::HitTestClip(firstFrame, userSpacePt);
 | |
| }
 | |
| 
 | |
| using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
 | |
| 
 | |
| /**
 | |
|  * Paint css-positioned-mask onto a given target(aMaskDT).
 | |
|  * Return value indicates if mask is complete.
 | |
|  */
 | |
| static bool PaintMaskSurface(const PaintFramesParams& aParams,
 | |
|                              DrawTarget* aMaskDT, float aOpacity,
 | |
|                              const ComputedStyle* aSC,
 | |
|                              const nsTArray<SVGMaskFrame*>& aMaskFrames,
 | |
|                              const nsPoint& aOffsetToUserSpace) {
 | |
|   MOZ_ASSERT(aMaskFrames.Length() > 0);
 | |
|   MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
 | |
|   MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
 | |
| 
 | |
|   const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
 | |
|   gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
 | |
| 
 | |
|   nsPresContext* presContext = aParams.frame->PresContext();
 | |
|   gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
 | |
|       aOffsetToUserSpace, presContext->AppUnitsPerDevPixel());
 | |
| 
 | |
|   gfxContext maskContext(aMaskDT, /* aPreserveTransform */ true);
 | |
| 
 | |
|   bool isMaskComplete = true;
 | |
| 
 | |
|   // Multiple SVG masks interleave with image mask. Paint each layer onto
 | |
|   // aMaskDT one at a time.
 | |
|   for (int i = aMaskFrames.Length() - 1; i >= 0; i--) {
 | |
|     SVGMaskFrame* maskFrame = aMaskFrames[i];
 | |
|     CompositionOp compositionOp =
 | |
|         (i == int(aMaskFrames.Length() - 1))
 | |
|             ? CompositionOp::OP_OVER
 | |
|             : nsCSSRendering::GetGFXCompositeMode(
 | |
|                   svgReset->mMask.mLayers[i].mComposite);
 | |
| 
 | |
|     // maskFrame != nullptr means we get a SVG mask.
 | |
|     // maskFrame == nullptr means we get an image mask.
 | |
|     if (maskFrame) {
 | |
|       SVGMaskFrame::MaskParams params(
 | |
|           maskContext.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix,
 | |
|           aOpacity, svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams);
 | |
|       RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
 | |
|       if (svgMask) {
 | |
|         Matrix tmp = aMaskDT->GetTransform();
 | |
|         aMaskDT->SetTransform(Matrix());
 | |
|         aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
 | |
|                              svgMask, Point(0, 0),
 | |
|                              DrawOptions(1.0, compositionOp));
 | |
|         aMaskDT->SetTransform(tmp);
 | |
|       }
 | |
|     } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) {
 | |
|       gfxContextMatrixAutoSaveRestore matRestore(&maskContext);
 | |
| 
 | |
|       maskContext.Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
 | |
|       nsCSSRendering::PaintBGParams params =
 | |
|           nsCSSRendering::PaintBGParams::ForSingleLayer(
 | |
|               *presContext, aParams.dirtyRect, aParams.borderArea,
 | |
|               aParams.frame,
 | |
|               aParams.builder->GetBackgroundPaintFlags() |
 | |
|                   nsCSSRendering::PAINTBG_MASK_IMAGE,
 | |
|               i, compositionOp, aOpacity);
 | |
| 
 | |
|       aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC(
 | |
|           params, maskContext, aSC, *aParams.frame->StyleBorder());
 | |
|     } else {
 | |
|       isMaskComplete = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return isMaskComplete;
 | |
| }
 | |
| 
 | |
| struct MaskPaintResult {
 | |
|   RefPtr<SourceSurface> maskSurface;
 | |
|   Matrix maskTransform;
 | |
|   bool transparentBlackMask;
 | |
|   bool opacityApplied;
 | |
| 
 | |
|   MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {}
 | |
| };
 | |
| 
 | |
| static MaskPaintResult CreateAndPaintMaskSurface(
 | |
|     const PaintFramesParams& aParams, float aOpacity, const ComputedStyle* aSC,
 | |
|     const nsTArray<SVGMaskFrame*>& aMaskFrames,
 | |
|     const nsPoint& aOffsetToUserSpace) {
 | |
|   const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
 | |
|   MOZ_ASSERT(aMaskFrames.Length() > 0);
 | |
|   MaskPaintResult paintResult;
 | |
| 
 | |
|   gfxContext& ctx = aParams.ctx;
 | |
| 
 | |
|   // Optimization for single SVG mask.
 | |
|   if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
 | |
|     gfxMatrix cssPxToDevPxMatrix =
 | |
|         SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
 | |
|     paintResult.opacityApplied = true;
 | |
|     SVGMaskFrame::MaskParams params(
 | |
|         ctx.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, aOpacity,
 | |
|         svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams);
 | |
|     paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params);
 | |
|     paintResult.maskTransform = ctx.CurrentMatrix();
 | |
|     paintResult.maskTransform.Invert();
 | |
|     if (!paintResult.maskSurface) {
 | |
|       paintResult.transparentBlackMask = true;
 | |
|     }
 | |
| 
 | |
|     return paintResult;
 | |
|   }
 | |
| 
 | |
|   const LayoutDeviceRect& maskSurfaceRect =
 | |
|       aParams.maskRect.valueOr(LayoutDeviceRect());
 | |
|   if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) {
 | |
|     // XXX: Is this ever true?
 | |
|     paintResult.transparentBlackMask = true;
 | |
|     return paintResult;
 | |
|   }
 | |
| 
 | |
|   RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget(
 | |
|       maskSurfaceRect.ToUnknownRect(), SurfaceFormat::A8);
 | |
|   if (!maskDT || !maskDT->IsValid()) {
 | |
|     return paintResult;
 | |
|   }
 | |
| 
 | |
|   // We can paint mask along with opacity only if
 | |
|   // 1. There is only one mask, or
 | |
|   // 2. No overlap among masks.
 | |
|   // Collision detect in #2 is not that trivial, we only accept #1 here.
 | |
|   paintResult.opacityApplied = (aMaskFrames.Length() == 1);
 | |
| 
 | |
|   // Set context's matrix on maskContext, offset by the maskSurfaceRect's
 | |
|   // position. This makes sure that we combine the masks in device space.
 | |
|   Matrix maskSurfaceMatrix = ctx.CurrentMatrix();
 | |
| 
 | |
|   bool isMaskComplete = PaintMaskSurface(
 | |
|       aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC,
 | |
|       aMaskFrames, aOffsetToUserSpace);
 | |
| 
 | |
|   if (!isMaskComplete ||
 | |
|       (aParams.imgParams.result != ImgDrawResult::SUCCESS &&
 | |
|        aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE &&
 | |
|        aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) {
 | |
|     // Now we know the status of mask resource since we used it while painting.
 | |
|     // According to the return value of PaintMaskSurface, we know whether mask
 | |
|     // resource is resolvable or not.
 | |
|     //
 | |
|     // For a HTML doc:
 | |
|     //   According to css-masking spec, always create a mask surface when
 | |
|     //   we have any item in maskFrame even if all of those items are
 | |
|     //   non-resolvable <mask-sources> or <images>.
 | |
|     //   Set paintResult.transparentBlackMask as true,  the caller should stop
 | |
|     //   painting masked content as if this mask is a transparent black one.
 | |
|     // For a SVG doc:
 | |
|     //   SVG 1.1 say that if we fail to resolve a mask, we should draw the
 | |
|     //   object unmasked.
 | |
|     //   Left paintResult.maskSurface empty, the caller should paint all
 | |
|     //   masked content as if this mask is an opaque white one(no mask).
 | |
|     paintResult.transparentBlackMask =
 | |
|         !aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
 | |
| 
 | |
|     MOZ_ASSERT(!paintResult.maskSurface);
 | |
|     return paintResult;
 | |
|   }
 | |
| 
 | |
|   paintResult.maskTransform = maskSurfaceMatrix;
 | |
|   if (!paintResult.maskTransform.Invert()) {
 | |
|     return paintResult;
 | |
|   }
 | |
| 
 | |
|   paintResult.maskSurface = maskDT->Snapshot();
 | |
|   return paintResult;
 | |
| }
 | |
| 
 | |
| static bool ValidateSVGFrame(nsIFrame* aFrame) {
 | |
|   NS_ASSERTION(
 | |
|       !aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY),
 | |
|       "Should not use SVGIntegrationUtils on this SVG frame");
 | |
| 
 | |
|   if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | |
| #ifdef DEBUG
 | |
|     ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
 | |
|     MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(),
 | |
|                "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
 | |
| #endif
 | |
| 
 | |
|     const nsIContent* content = aFrame->GetContent();
 | |
|     if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) {
 | |
|       // The SVG spec says not to draw _anything_
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| class AutoPopGroup {
 | |
|  public:
 | |
|   AutoPopGroup() : mContext(nullptr) {}
 | |
| 
 | |
|   ~AutoPopGroup() {
 | |
|     if (mContext) {
 | |
|       mContext->PopGroupAndBlend();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void SetContext(gfxContext* aContext) { mContext = aContext; }
 | |
| 
 | |
|  private:
 | |
|   gfxContext* mContext;
 | |
| };
 | |
| 
 | |
| bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
 | |
|                                     bool& aOutIsMaskComplete) {
 | |
|   aOutIsMaskComplete = true;
 | |
| 
 | |
|   SVGUtils::MaskUsage maskUsage;
 | |
|   SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
 | |
|   if (!maskUsage.shouldDoSomething()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = aParams.frame;
 | |
|   if (!ValidateSVGFrame(frame)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   gfxContext& ctx = aParams.ctx;
 | |
|   RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
 | |
| 
 | |
|   if (maskUsage.shouldGenerateMaskLayer &&
 | |
|       (maskUsage.shouldGenerateClipMaskLayer ||
 | |
|        maskUsage.shouldApplyClipPath)) {
 | |
|     // We will paint both mask of positioned mask and clip-path into
 | |
|     // maskTarget.
 | |
|     //
 | |
|     // Create one extra draw target for drawing positioned mask, so that we do
 | |
|     // not have to copy the content of maskTarget before painting
 | |
|     // clip-path into it.
 | |
|     maskTarget = maskTarget->CreateClippedDrawTarget(Rect(), SurfaceFormat::A8);
 | |
|   }
 | |
| 
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
 | |
|   nsTArray<SVGMaskFrame*> maskFrames;
 | |
|   // XXX check return value?
 | |
|   SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
 | |
| 
 | |
|   AutoPopGroup autoPop;
 | |
|   bool shouldPushOpacity =
 | |
|       (maskUsage.opacity != 1.0) && (maskFrames.Length() != 1);
 | |
|   if (shouldPushOpacity) {
 | |
|     ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
 | |
|     autoPop.SetContext(&ctx);
 | |
|   }
 | |
| 
 | |
|   gfxContextMatrixAutoSaveRestore matSR;
 | |
| 
 | |
|   // Paint clip-path-basic-shape onto ctx
 | |
|   gfxContextAutoSaveRestore basicShapeSR;
 | |
|   if (maskUsage.shouldApplyBasicShapeOrPath) {
 | |
|     matSR.SetContext(&ctx);
 | |
| 
 | |
|     MoveContextOriginToUserSpace(firstFrame, aParams);
 | |
| 
 | |
|     basicShapeSR.SetContext(&ctx);
 | |
|     gfxMatrix mat = SVGUtils::GetCSSPxToDevPxMatrix(frame);
 | |
|     if (!maskUsage.shouldGenerateMaskLayer) {
 | |
|       // Only have basic-shape clip-path effect. Fill clipped region by
 | |
|       // opaque white.
 | |
|       ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
 | |
|       RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame(
 | |
|           ctx.GetDrawTarget(), frame, mat);
 | |
|       if (path) {
 | |
|         ctx.SetPath(path);
 | |
|         ctx.Fill();
 | |
|       }
 | |
| 
 | |
|       return true;
 | |
|     }
 | |
|     CSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame, mat);
 | |
|   }
 | |
| 
 | |
|   // Paint mask into maskTarget.
 | |
|   if (maskUsage.shouldGenerateMaskLayer) {
 | |
|     matSR.Restore();
 | |
|     matSR.SetContext(&ctx);
 | |
| 
 | |
|     EffectOffsets offsets = ComputeEffectOffset(frame, aParams);
 | |
|     maskTarget->SetTransform(maskTarget->GetTransform().PreTranslate(
 | |
|         ToPoint(offsets.offsetToUserSpaceInDevPx)));
 | |
|     aOutIsMaskComplete = PaintMaskSurface(
 | |
|         aParams, maskTarget, shouldPushOpacity ? 1.0 : maskUsage.opacity,
 | |
|         firstFrame->Style(), maskFrames, offsets.offsetToUserSpace);
 | |
|   }
 | |
| 
 | |
|   // Paint clip-path onto ctx.
 | |
|   if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
 | |
|     matSR.Restore();
 | |
|     matSR.SetContext(&ctx);
 | |
| 
 | |
|     MoveContextOriginToUserSpace(firstFrame, aParams);
 | |
|     Matrix clipMaskTransform;
 | |
|     gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
 | |
| 
 | |
|     SVGClipPathFrame* clipPathFrame;
 | |
|     // XXX check return value?
 | |
|     SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
 | |
|     RefPtr<SourceSurface> maskSurface =
 | |
|         maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
 | |
|     clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| template <class T>
 | |
| void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams,
 | |
|                                   const T& aPaintChild) {
 | |
|   MOZ_ASSERT(SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aParams.frame),
 | |
|              "Should not use this method when no mask or clipPath effect"
 | |
|              "on this frame");
 | |
| 
 | |
|   /* SVG defines the following rendering model:
 | |
|    *
 | |
|    *  1. Render geometry
 | |
|    *  2. Apply filter
 | |
|    *  3. Apply clipping, masking, group opacity
 | |
|    *
 | |
|    * We handle #3 here and perform a couple of optimizations:
 | |
|    *
 | |
|    * + Use cairo's clipPath when representable natively (single object
 | |
|    *   clip region).
 | |
|    *
 | |
|    * + Merge opacity and masking if both used together.
 | |
|    */
 | |
|   nsIFrame* frame = aParams.frame;
 | |
|   if (!ValidateSVGFrame(frame)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SVGUtils::MaskUsage maskUsage;
 | |
|   SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
 | |
| 
 | |
|   if (maskUsage.opacity == 0.0f) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   gfxContext& context = aParams.ctx;
 | |
|   gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
 | |
| 
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
 | |
| 
 | |
|   SVGClipPathFrame* clipPathFrame;
 | |
|   // XXX check return value?
 | |
|   SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
 | |
| 
 | |
|   nsTArray<SVGMaskFrame*> maskFrames;
 | |
|   // XXX check return value?
 | |
|   SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
 | |
| 
 | |
|   gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
 | |
| 
 | |
|   bool shouldGenerateMask =
 | |
|       (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
 | |
|        maskUsage.shouldGenerateMaskLayer);
 | |
|   bool shouldPushMask = false;
 | |
| 
 | |
|   /* Check if we need to do additional operations on this child's
 | |
|    * rendering, which necessitates rendering into another surface. */
 | |
|   if (shouldGenerateMask) {
 | |
|     gfxContextMatrixAutoSaveRestore matSR;
 | |
| 
 | |
|     RefPtr<SourceSurface> maskSurface;
 | |
|     bool opacityApplied = false;
 | |
| 
 | |
|     if (maskUsage.shouldGenerateMaskLayer) {
 | |
|       matSR.SetContext(&context);
 | |
| 
 | |
|       // For css-mask, we want to generate a mask for each continuation frame,
 | |
|       // so we setup context matrix by the position of the current frame,
 | |
|       // instead of the first continuation frame.
 | |
|       EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
 | |
|       MaskPaintResult paintResult = CreateAndPaintMaskSurface(
 | |
|           aParams, maskUsage.opacity, firstFrame->Style(), maskFrames,
 | |
|           offsets.offsetToUserSpace);
 | |
| 
 | |
|       if (paintResult.transparentBlackMask) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       maskSurface = paintResult.maskSurface;
 | |
|       if (maskSurface) {
 | |
|         shouldPushMask = true;
 | |
| 
 | |
|         opacityApplied = paintResult.opacityApplied;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (maskUsage.shouldGenerateClipMaskLayer) {
 | |
|       matSR.Restore();
 | |
|       matSR.SetContext(&context);
 | |
| 
 | |
|       MoveContextOriginToUserSpace(firstFrame, aParams);
 | |
|       RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
 | |
|           context, frame, cssPxToDevPxMatrix, maskSurface);
 | |
| 
 | |
|       if (clipMaskSurface) {
 | |
|         maskSurface = clipMaskSurface;
 | |
|       } else {
 | |
|         // Either entire surface is clipped out, or gfx buffer allocation
 | |
|         // failure in SVGClipPathFrame::GetClipMask.
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       shouldPushMask = true;
 | |
|     }
 | |
| 
 | |
|     // opacity != 1.0f.
 | |
|     if (!maskUsage.shouldGenerateClipMaskLayer &&
 | |
|         !maskUsage.shouldGenerateMaskLayer) {
 | |
|       MOZ_ASSERT(maskUsage.opacity != 1.0f);
 | |
| 
 | |
|       matSR.SetContext(&context);
 | |
|       MoveContextOriginToUserSpace(firstFrame, aParams);
 | |
|       shouldPushMask = true;
 | |
|     }
 | |
| 
 | |
|     if (shouldPushMask) {
 | |
|       // We want the mask to be untransformed so use the inverse of the
 | |
|       // current transform as the maskTransform to compensate.
 | |
|       Matrix maskTransform = context.CurrentMatrix();
 | |
|       maskTransform.Invert();
 | |
| 
 | |
|       context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
 | |
|                                     opacityApplied ? 1.0 : maskUsage.opacity,
 | |
|                                     maskSurface, maskTransform);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
 | |
|    * we can just do normal painting and get it clipped appropriately.
 | |
|    */
 | |
|   if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
 | |
|     gfxContextMatrixAutoSaveRestore matSR(&context);
 | |
| 
 | |
|     MoveContextOriginToUserSpace(firstFrame, aParams);
 | |
| 
 | |
|     MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
 | |
|                !maskUsage.shouldApplyBasicShapeOrPath);
 | |
|     if (maskUsage.shouldApplyClipPath) {
 | |
|       clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
 | |
|     } else {
 | |
|       CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame,
 | |
|                                                      cssPxToDevPxMatrix);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Paint the child */
 | |
|   context.SetMatrix(matrixAutoSaveRestore.Matrix());
 | |
|   aPaintChild();
 | |
| 
 | |
|   if (StaticPrefs::layers_draw_mask_debug()) {
 | |
|     gfxContextAutoSaveRestore saver(&context);
 | |
| 
 | |
|     context.NewPath();
 | |
|     gfxRect drawingRect = nsLayoutUtils::RectToGfxRect(
 | |
|         aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel());
 | |
|     context.SnappedRectangle(drawingRect);
 | |
|     sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
 | |
|     if (maskUsage.shouldGenerateMaskLayer) {
 | |
|       overlayColor.r = 1.0f;  // red represents css positioned mask.
 | |
|     }
 | |
|     if (maskUsage.shouldApplyClipPath ||
 | |
|         maskUsage.shouldGenerateClipMaskLayer) {
 | |
|       overlayColor.g = 1.0f;  // green represents clip-path:<clip-source>.
 | |
|     }
 | |
|     if (maskUsage.shouldApplyBasicShapeOrPath) {
 | |
|       overlayColor.b = 1.0f;  // blue represents
 | |
|                               // clip-path:<basic-shape>||<geometry-box>.
 | |
|     }
 | |
| 
 | |
|     context.SetColor(overlayColor);
 | |
|     context.Fill();
 | |
|   }
 | |
| 
 | |
|   if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
 | |
|     context.PopClip();
 | |
|   }
 | |
| 
 | |
|   if (shouldPushMask) {
 | |
|     context.PopGroupAndBlend();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGIntegrationUtils::PaintMaskAndClipPath(
 | |
|     const PaintFramesParams& aParams,
 | |
|     const std::function<void()>& aPaintChild) {
 | |
|   PaintMaskAndClipPathInternal(aParams, aPaintChild);
 | |
| }
 | |
| 
 | |
| void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams,
 | |
|                                       Span<const StyleFilter> aFilters,
 | |
|                                       const SVGFilterPaintCallback& aCallback) {
 | |
|   MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
 | |
|              "Filter effect is discarded while generating glyph mask.");
 | |
|   MOZ_ASSERT(!aFilters.IsEmpty(),
 | |
|              "Should not use this method when no filter effect on this frame");
 | |
| 
 | |
|   nsIFrame* frame = aParams.frame;
 | |
|   if (!ValidateSVGFrame(frame)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
 | |
|   if (opacity == 0.0f) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Properties are added lazily and may have been removed by a restyle, so make
 | |
|   // sure all applicable ones are set again.
 | |
|   nsIFrame* firstFrame =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
 | |
|   // Note: we do not return here for eHasNoRefs since we must still handle any
 | |
|   // CSS filter functions.
 | |
|   // TODO: We currently pass nullptr instead of an nsTArray* here, but we
 | |
|   // actually should get the filter frames and then pass them into
 | |
|   // PaintFilteredFrame below!  See bug 1494263.
 | |
|   // XXX: Do we need to check for eHasRefsSomeInvalid here given that
 | |
|   // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid?
 | |
|   // Or can we just assert !eHasRefsSomeInvalid?
 | |
|   if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
 | |
|       SVGObserverUtils::eHasRefsSomeInvalid) {
 | |
|     aCallback(aParams.ctx, aParams.imgParams, nullptr, nullptr);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   gfxContext& context = aParams.ctx;
 | |
| 
 | |
|   gfxContextAutoSaveRestore autoSR(&context);
 | |
|   EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
 | |
| 
 | |
|   /* Paint the child and apply filters */
 | |
|   nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
 | |
| 
 | |
|   FilterInstance::PaintFilteredFrame(frame, aFilters, &context, aCallback,
 | |
|                                      &dirtyRegion, aParams.imgParams, opacity);
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::CreateWebRenderCSSFilters(
 | |
|     Span<const StyleFilter> aFilters, nsIFrame* aFrame,
 | |
|     WrFiltersHolder& aWrFilters) {
 | |
|   // All CSS filters are supported by WebRender. SVG filters are not fully
 | |
|   // supported, those use NS_STYLE_FILTER_URL and are handled separately.
 | |
| 
 | |
|   // If there are too many filters to render, then just pretend that we
 | |
|   // succeeded, and don't render any of them.
 | |
|   if (aFilters.Length() >
 | |
|       StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
 | |
|     return true;
 | |
|   }
 | |
|   aWrFilters.filters.SetCapacity(aFilters.Length());
 | |
|   auto& wrFilters = aWrFilters.filters;
 | |
|   for (const StyleFilter& filter : aFilters) {
 | |
|     switch (filter.tag) {
 | |
|       case StyleFilter::Tag::Brightness:
 | |
|         wrFilters.AppendElement(
 | |
|             wr::FilterOp::Brightness(filter.AsBrightness()));
 | |
|         break;
 | |
|       case StyleFilter::Tag::Contrast:
 | |
|         wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
 | |
|         break;
 | |
|       case StyleFilter::Tag::Grayscale:
 | |
|         wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
 | |
|         break;
 | |
|       case StyleFilter::Tag::Invert:
 | |
|         wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
 | |
|         break;
 | |
|       case StyleFilter::Tag::Opacity: {
 | |
|         float opacity = filter.AsOpacity();
 | |
|         wrFilters.AppendElement(wr::FilterOp::Opacity(
 | |
|             wr::PropertyBinding<float>::Value(opacity), opacity));
 | |
|         break;
 | |
|       }
 | |
|       case StyleFilter::Tag::Saturate:
 | |
|         wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
 | |
|         break;
 | |
|       case StyleFilter::Tag::Sepia:
 | |
|         wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
 | |
|         break;
 | |
|       case StyleFilter::Tag::HueRotate: {
 | |
|         wrFilters.AppendElement(
 | |
|             wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
 | |
|         break;
 | |
|       }
 | |
|       case StyleFilter::Tag::Blur: {
 | |
|         // TODO(emilio): we should go directly from css pixels -> device pixels.
 | |
|         float appUnitsPerDevPixel =
 | |
|             aFrame->PresContext()->AppUnitsPerDevPixel();
 | |
|         float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(),
 | |
|                                                appUnitsPerDevPixel);
 | |
|         wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius));
 | |
|         break;
 | |
|       }
 | |
|       case StyleFilter::Tag::DropShadow: {
 | |
|         float appUnitsPerDevPixel =
 | |
|             aFrame->PresContext()->AppUnitsPerDevPixel();
 | |
|         const StyleSimpleShadow& shadow = filter.AsDropShadow();
 | |
|         nscolor color = shadow.color.CalcColor(aFrame);
 | |
| 
 | |
|         wr::Shadow wrShadow;
 | |
|         wrShadow.offset = {
 | |
|             NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
 | |
|                                     appUnitsPerDevPixel),
 | |
|             NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
 | |
|                                     appUnitsPerDevPixel)};
 | |
|         wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
 | |
|                                                        appUnitsPerDevPixel);
 | |
|         wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
 | |
|                           NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
 | |
|         wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
 | |
|         break;
 | |
|       }
 | |
|       default:
 | |
|         return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::BuildWebRenderFilters(
 | |
|     nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
 | |
|     WrFiltersHolder& aWrFilters, bool& aInitialized) {
 | |
|   return FilterInstance::BuildWebRenderFilters(aFilteredFrame, aFilters,
 | |
|                                                aWrFilters, aInitialized);
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) {
 | |
|   WrFiltersHolder wrFilters;
 | |
|   auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan();
 | |
|   bool initialized = true;
 | |
|   return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
 | |
|          BuildWebRenderFilters(aFrame, filterChain, wrFilters, initialized);
 | |
| }
 | |
| 
 | |
| bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
 | |
|     nsIFrame* aFrame) {
 | |
|   // WebRender supports masks / clip-paths and some filters in the compositor.
 | |
|   if (aFrame->StyleEffects()->HasFilters()) {
 | |
|     return !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| class PaintFrameCallback : public gfxDrawingCallback {
 | |
|  public:
 | |
|   PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
 | |
|                      const IntSize aRenderSize, uint32_t aFlags)
 | |
|       : mFrame(aFrame),
 | |
|         mPaintServerSize(aPaintServerSize),
 | |
|         mRenderSize(aRenderSize),
 | |
|         mFlags(aFlags) {}
 | |
|   virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
 | |
|                           const SamplingFilter aSamplingFilter,
 | |
|                           const gfxMatrix& aTransform) override;
 | |
| 
 | |
|  private:
 | |
|   nsIFrame* mFrame;
 | |
|   nsSize mPaintServerSize;
 | |
|   IntSize mRenderSize;
 | |
|   uint32_t mFlags;
 | |
| };
 | |
| 
 | |
| bool PaintFrameCallback::operator()(gfxContext* aContext,
 | |
|                                     const gfxRect& aFillRect,
 | |
|                                     const SamplingFilter aSamplingFilter,
 | |
|                                     const gfxMatrix& aTransform) {
 | |
|   if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   AutoSetRestorePaintServerState paintServer(mFrame);
 | |
| 
 | |
|   aContext->Save();
 | |
| 
 | |
|   // Clip to aFillRect so that we don't paint outside.
 | |
|   aContext->Clip(aFillRect);
 | |
| 
 | |
|   gfxMatrix invmatrix = aTransform;
 | |
|   if (!invmatrix.Invert()) {
 | |
|     return false;
 | |
|   }
 | |
|   aContext->Multiply(invmatrix);
 | |
| 
 | |
|   // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
 | |
|   // to have it anchored at the top left corner of the bounding box of all of
 | |
|   // mFrame's continuations. So we add a translation transform.
 | |
|   int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
 | |
|   nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
 | |
|   gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
 | |
|   aContext->Multiply(gfxMatrix::Translation(devPxOffset));
 | |
| 
 | |
|   gfxSize paintServerSize =
 | |
|       gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
 | |
|       mFrame->PresContext()->AppUnitsPerDevPixel();
 | |
| 
 | |
|   // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
 | |
|   // want it to render with mRenderSize, so we need to set up a scale transform.
 | |
|   gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
 | |
|   gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
 | |
|   aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
 | |
| 
 | |
|   // Draw.
 | |
|   nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width,
 | |
|                mPaintServerSize.height);
 | |
| 
 | |
|   using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
 | |
|   PaintFrameFlags flags = PaintFrameFlags::InTransform;
 | |
|   if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
 | |
|     flags |= PaintFrameFlags::SyncDecodeImages;
 | |
|   }
 | |
|   nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0),
 | |
|                             nsDisplayListBuilderMode::Painting, flags);
 | |
| 
 | |
|   nsIFrame* currentFrame = mFrame;
 | |
|   while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
 | |
|     offset = currentFrame->GetOffsetToCrossDoc(mFrame);
 | |
|     devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
 | |
| 
 | |
|     aContext->Save();
 | |
|     aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY));
 | |
|     aContext->Multiply(gfxMatrix::Translation(devPxOffset));
 | |
|     aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
 | |
| 
 | |
|     nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset,
 | |
|                               NS_RGBA(0, 0, 0, 0),
 | |
|                               nsDisplayListBuilderMode::Painting, flags);
 | |
| 
 | |
|     aContext->Restore();
 | |
|   }
 | |
| 
 | |
|   aContext->Restore();
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer(
 | |
|     nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
 | |
|     const IntSize& aRenderSize, const DrawTarget* aDrawTarget,
 | |
|     const gfxMatrix& aContextMatrix, uint32_t aFlags) {
 | |
|   // aPaintServerSize is the size that would be filled when using
 | |
|   // background-repeat:no-repeat and background-size:auto. For normal background
 | |
|   // images, this would be the intrinsic size of the image; for gradients and
 | |
|   // patterns this would be the whole target frame fill area.
 | |
|   // aRenderSize is what we will be actually filling after accounting for
 | |
|   // background-size.
 | |
|   if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) {
 | |
|     // aFrame is either a pattern or a gradient. These fill the whole target
 | |
|     // frame by default, so aPaintServerSize is the whole target background fill
 | |
|     // area.
 | |
|     gfxRect overrideBounds(0, 0, aPaintServerSize.width,
 | |
|                            aPaintServerSize.height);
 | |
|     overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel());
 | |
|     imgDrawingParams imgParams(aFlags);
 | |
|     RefPtr<gfxPattern> pattern = server->GetPaintServerPattern(
 | |
|         aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0,
 | |
|         imgParams, &overrideBounds);
 | |
| 
 | |
|     if (!pattern) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     // pattern is now set up to fill aPaintServerSize. But we want it to
 | |
|     // fill aRenderSize, so we need to add a scaling transform.
 | |
|     // We couldn't just have set overrideBounds to aRenderSize - it would have
 | |
|     // worked for gradients, but for patterns it would result in a different
 | |
|     // pattern size.
 | |
|     gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
 | |
|     gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
 | |
|     gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
 | |
|     pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
 | |
|     RefPtr<gfxDrawable> drawable = new gfxPatternDrawable(pattern, aRenderSize);
 | |
|     return drawable.forget();
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
 | |
|       !static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "We should prevent painting of unpaintable SVG "
 | |
|         "before we get here");
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // We don't want to paint into a surface as long as we don't need to, so we
 | |
|   // set up a drawing callback.
 | |
|   RefPtr<gfxDrawingCallback> cb =
 | |
|       new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
 | |
|   RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
 | |
|   return drawable.forget();
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 |