| Index: Source/core/rendering/svg/RenderSVGResourceClipper.cpp
|
| diff --git a/Source/core/rendering/svg/RenderSVGResourceClipper.cpp b/Source/core/rendering/svg/RenderSVGResourceClipper.cpp
|
| index 9da09f9d9f0c299df32978dd0d74385de5bcab12..7a0528220aa73d1d2f34db1007e1bc71355b0474 100644
|
| --- a/Source/core/rendering/svg/RenderSVGResourceClipper.cpp
|
| +++ b/Source/core/rendering/svg/RenderSVGResourceClipper.cpp
|
| @@ -33,6 +33,7 @@
|
| #include "core/rendering/svg/SVGResources.h"
|
| #include "core/rendering/svg/SVGResourcesCache.h"
|
| #include "core/svg/SVGUseElement.h"
|
| +#include "wtf/TemporaryChange.h"
|
|
|
| namespace WebCore {
|
|
|
| @@ -40,35 +41,25 @@ RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResource
|
|
|
| RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
|
| : RenderSVGResourceContainer(node)
|
| + , m_inClipExpansion(false)
|
| {
|
| }
|
|
|
| RenderSVGResourceClipper::~RenderSVGResourceClipper()
|
| {
|
| - if (m_clipper.isEmpty())
|
| - return;
|
| -
|
| - deleteAllValues(m_clipper);
|
| - m_clipper.clear();
|
| }
|
|
|
| void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
|
| {
|
| m_clipBoundaries = FloatRect();
|
| - if (!m_clipper.isEmpty()) {
|
| - deleteAllValues(m_clipper);
|
| - m_clipper.clear();
|
| - }
|
| -
|
| + m_rendererToClipperMap.clear();
|
| markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
|
| }
|
|
|
| void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
|
| {
|
| ASSERT(client);
|
| - if (m_clipper.contains(client))
|
| - delete m_clipper.take(client);
|
| -
|
| + m_rendererToClipperMap.remove(client);
|
| markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
|
| }
|
|
|
| @@ -81,8 +72,8 @@ bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*,
|
| return applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context);
|
| }
|
|
|
| -bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox)
|
| -{
|
| +bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context,
|
| + const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) {
|
| // If the current clip-path gets clipped itself, we have to fallback to masking.
|
| if (!style()->svgStyle()->clipperResource().isEmpty())
|
| return false;
|
| @@ -144,74 +135,93 @@ bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const
|
| return true;
|
| }
|
|
|
| -bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox,
|
| +bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* target, const FloatRect& targetBoundingBox,
|
| const FloatRect& repaintRect, GraphicsContext* context)
|
| {
|
| - bool missingClipperData = !m_clipper.contains(object);
|
| - if (missingClipperData)
|
| - m_clipper.set(object, new ClipperData);
|
| + ASSERT(target);
|
| + ASSERT(context);
|
| + ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout());
|
|
|
| - bool shouldCreateClipData = false;
|
| - AffineTransform animatedLocalTransform = toSVGClipPathElement(node())->animatedLocalTransform();
|
| - ClipperData* clipperData = m_clipper.get(object);
|
| - if (!clipperData->clipMaskImage) {
|
| - if (pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox))
|
| - return true;
|
| - shouldCreateClipData = true;
|
| - }
|
| + if (repaintRect.isEmpty() || m_inClipExpansion)
|
| + return false;
|
| + TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true);
|
|
|
| - AffineTransform absoluteTransform;
|
| - SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(object, absoluteTransform);
|
| + // add(obj, 0) idiom -> lookup & add if not found.
|
| + OwnPtr<ClipperData>& clipperData = m_rendererToClipperMap.add(target, nullptr).iterator->value;
|
| + if (!clipperData)
|
| + clipperData = adoptPtr(new ClipperData);
|
|
|
| - if (shouldCreateClipData && !repaintRect.isEmpty()) {
|
| - if (!SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, clipperData->clipMaskImage, Unaccelerated))
|
| - return false;
|
| + // First, try to apply the clip as a clipPath.
|
| + AffineTransform animatedLocalTransform = toSVGClipPathElement(node())->animatedLocalTransform();
|
| + if (tryPathOnlyClipping(context, animatedLocalTransform, targetBoundingBox)) {
|
| + clipperData->clipMode = ClipperData::PathOnlyClipMode;
|
| + return true;
|
| + }
|
|
|
| - GraphicsContext* maskContext = clipperData->clipMaskImage->context();
|
| - ASSERT(maskContext);
|
| + // Fall back to masking.
|
| + clipperData->clipMode = ClipperData::MaskClipMode;
|
|
|
| - maskContext->concatCTM(animatedLocalTransform);
|
| + // Mask layer start
|
| + context->beginTransparencyLayer(1, &repaintRect);
|
| + {
|
| + GraphicsContextStateSaver maskContentSaver(*context);
|
| + context->concatCTM(animatedLocalTransform);
|
|
|
| // clipPath can also be clipped by another clipPath.
|
| SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
|
| - RenderSVGResourceClipper* clipper;
|
| - bool succeeded;
|
| - if (resources && (clipper = resources->clipper())) {
|
| - GraphicsContextStateSaver stateSaver(*maskContext);
|
| -
|
| - if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext))
|
| + RenderSVGResourceClipper* clipPathClipper = 0;
|
| + if (resources && (clipPathClipper = resources->clipper())) {
|
| + if (!clipPathClipper->applyClippingToContext(this, targetBoundingBox, repaintRect, context)) {
|
| + // FIXME: Awkward state micro-management. Ideally, GraphicsContextStateSaver should
|
| + // a) pop saveLayers also
|
| + // b) pop multiple states if needed (similarly to SkCanvas::restoreToCount())
|
| + // Then we should be able to replace this mess with a single, top-level GCSS.
|
| + maskContentSaver.restore();
|
| + context->restoreLayer();
|
| return false;
|
| + }
|
| + }
|
|
|
| - succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox);
|
| - // The context restore applies the clipping on non-CG platforms.
|
| - } else
|
| - succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox);
|
| + drawMaskContent(context, targetBoundingBox);
|
|
|
| - if (!succeeded)
|
| - clipperData->clipMaskImage.clear();
|
| + if (clipPathClipper)
|
| + clipPathClipper->postApplyResource(this, context, ApplyToDefaultMode, 0, 0);
|
| }
|
|
|
| - if (!clipperData->clipMaskImage)
|
| - return false;
|
| + // Masked content layer start.
|
| + context->beginMaskedLayer(repaintRect);
|
|
|
| - SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperData->clipMaskImage, missingClipperData);
|
| return true;
|
| }
|
|
|
| -bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox)
|
| -{
|
| - ASSERT(frame());
|
| +void RenderSVGResourceClipper::postApplyResource(RenderObject* object, GraphicsContext*& context,
|
| + unsigned short resourceMode, const Path*, const RenderSVGShape*) {
|
| + ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
|
| +
|
| + ClipperData* clipperData = m_rendererToClipperMap.get(object);
|
| ASSERT(clipperData);
|
| - ASSERT(clipperData->clipMaskImage);
|
|
|
| - GraphicsContext* maskContext = clipperData->clipMaskImage->context();
|
| - ASSERT(maskContext);
|
| + // Path-only clipping, no layers to restore.
|
| + if (clipperData->clipMode == ClipperData::PathOnlyClipMode)
|
| + return;
|
| +
|
| + // Transfer content layer -> mask layer (SrcIn)
|
| + context->endLayer();
|
| + // Transfer mask layer -> bg layer (SrcOver)
|
| + context->endLayer();
|
| +}
|
| +
|
| +void RenderSVGResourceClipper::drawMaskContent(GraphicsContext* context, const FloatRect& targetBoundingBox)
|
| +{
|
| + ASSERT(frame());
|
| + ASSERT(context);
|
|
|
| + // Adjust the mask image context according to the target objectBoundingBox.
|
| AffineTransform maskContentTransformation;
|
| if (toSVGClipPathElement(node())->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
|
| - maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y());
|
| - maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
|
| - maskContext->concatCTM(maskContentTransformation);
|
| + maskContentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
|
| + maskContentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
|
| + context->concatCTM(maskContentTransformation);
|
| }
|
|
|
| // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
|
| @@ -222,15 +232,11 @@ bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData
|
| PaintBehavior oldBehavior = frame()->view()->paintBehavior();
|
| frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
|
|
|
| - // Draw all clipPath children into a global mask.
|
| for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
|
| RenderObject* renderer = childNode->renderer();
|
| if (!childNode->isSVGElement() || !renderer)
|
| continue;
|
| - if (renderer->needsLayout()) {
|
| - frame()->view()->setPaintBehavior(oldBehavior);
|
| - return false;
|
| - }
|
| +
|
| RenderStyle* style = renderer->style();
|
| if (!style || style->display() == NONE || style->visibility() != VISIBLE)
|
| continue;
|
| @@ -250,16 +256,15 @@ bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData
|
| if (!renderer->isSVGShape() && !renderer->isSVGText())
|
| continue;
|
|
|
| - maskContext->setFillRule(newClipRule);
|
| + context->setFillRule(newClipRule);
|
|
|
| - // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule.
|
| - // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering.
|
| - // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above.
|
| - SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation);
|
| + if (isUseElement)
|
| + renderer = childNode->renderer();
|
| +
|
| + SVGRenderingContext::renderSubtree(context, renderer, maskContentTransformation);
|
| }
|
|
|
| frame()->view()->setPaintBehavior(oldBehavior);
|
| - return true;
|
| }
|
|
|
| void RenderSVGResourceClipper::calculateClipContentRepaintRect()
|
|
|