https://bugs.webkit.org/show_bug.cgi?id=69714
Reviewed by Dirk Schulze.
Source/WebCore:
When RenderSVGResourceClipper draws its children to a mask, it requires some special constraints:
- fill-opacity/stroke-opacity/opacity forced to 1
- masker/filter resources shouldn't be applied to the children
- fill must be set to the initial fill paint server for all children (solid black)
- stroke must be set to the initial stroke paint server for all children (none)
This was achieved before by mutating the style of the children, which made them need a relayout.
SVGImageBufferTools:renderSubtreeToImageBuffer thus needed to layout the children, if needed, before painting.
This can be completly avoided, when changing RenderSVGResourceClipper to avoid style mutations.
Introduce a new "PaintBehaviorRenderingSVGMask", and set the current FrameViews paintBehaviour to this
state, before painting the subtree. This way we can detect that we're rendering a clip mask, without
having to mutate the style of the children and without having to relayout.
We can now ASSERT(!item->needsLayout()) in renderSubtreeToImageBuffer.
Tests: svg/clip-path/clip-path-tspan-and-stroke.svg
svg/custom/layout-loop.svg
* rendering/PaintPhase.h:
* rendering/svg/RenderSVGResource.cpp:
(WebCore::requestPaintingResource):
* rendering/svg/RenderSVGResourceClipper.cpp:
(WebCore::RenderSVGResourceClipper::RenderSVGResourceClipper):
(WebCore::RenderSVGResourceClipper::removeAllClientsFromCache):
(WebCore::RenderSVGResourceClipper::removeClientFromCache):
(WebCore::RenderSVGResourceClipper::applyClippingToContext):
(WebCore::RenderSVGResourceClipper::drawContentIntoMaskImage):
* rendering/svg/RenderSVGResourceClipper.h:
* rendering/svg/RenderSVGResourceMasker.cpp:
(WebCore::RenderSVGResourceMasker::applyResource):
(WebCore::RenderSVGResourceMasker::drawContentIntoMaskImage):
* rendering/svg/RenderSVGResourceMasker.h:
* rendering/svg/RenderSVGResourcePattern.cpp:
(WebCore::RenderSVGResourcePattern::createTileImage):
* rendering/svg/RenderSVGResourceSolidColor.cpp:
(WebCore::RenderSVGResourceSolidColor::applyResource):
* rendering/svg/SVGImageBufferTools.cpp:
(WebCore::SVGImageBufferTools::renderSubtreeToImageBuffer):
* rendering/svg/SVGInlineTextBox.cpp:
(WebCore::SVGInlineTextBox::paintSelectionBackground):
(WebCore::SVGInlineTextBox::paint):
* rendering/svg/SVGRenderSupport.cpp:
(WebCore::SVGRenderSupport::prepareToRenderSVGContent):
LayoutTests:
Added a new test covering the clipper specific demands when rendering its children to a mask.
(fill/stroke-opacity=1, opacity=1, don't apply masker/filter to children)
All cases are covered by existing tests, I'm adding a new combination of <tspan> within a <text>,
that tries to apply a stroke paint server, which is supposed to be ignored.
* platform/chromium/test_expectations.txt:
* platform/mac/svg/clip-path/clip-path-tspan-and-stroke-expected.png: Added.
* platform/mac/svg/clip-path/clip-path-tspan-and-stroke-expected.txt: Added.
* svg/clip-path/clip-path-tspan-and-stroke.svg: Added.
* svg/custom/layout-loop-expected.txt: Added.
* svg/custom/layout-loop.svg: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@105978
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
2012-01-26 Nikolas Zimmermann <nzimmermann@rim.com>
+ crash in WebCore::RenderSVGContainer::paint
+ https://bugs.webkit.org/show_bug.cgi?id=69714
+
+ Reviewed by Dirk Schulze.
+
+ Added a new test covering the clipper specific demands when rendering its children to a mask.
+ (fill/stroke-opacity=1, opacity=1, don't apply masker/filter to children)
+
+ All cases are covered by existing tests, I'm adding a new combination of <tspan> within a <text>,
+ that tries to apply a stroke paint server, which is supposed to be ignored.
+
+ * platform/chromium/test_expectations.txt:
+ * platform/mac/svg/clip-path/clip-path-tspan-and-stroke-expected.png: Added.
+ * platform/mac/svg/clip-path/clip-path-tspan-and-stroke-expected.txt: Added.
+ * svg/clip-path/clip-path-tspan-and-stroke.svg: Added.
+ * svg/custom/layout-loop-expected.txt: Added.
+ * svg/custom/layout-loop.svg: Added.
+
+2012-01-26 Nikolas Zimmermann <nzimmermann@rim.com>
+
Not reviewed. Regenerated some new SVG results using a Lion machine, to make the baseline pass with --tolerance 0 -p on a vanilla iMac again.
* platform/mac/svg/W3C-SVG-1.1-SE/painting-control-04-f-expected.png:
BUGWK76800 : svg/filters/feImage-reference-invalidation.svg = IMAGE
BUGWK76800 : svg/filters/feImage-reference-svg-primitive.svg = IMAGE
+// Just needs a rebaseline.
+BUGWK69714 : svg/clip-path/clip-path-tspan-and-stroke.svg = IMAGE+TEXT
+
// Change error (misspelling) underlines from Windows look to Mac look.
BUG_CARYCLARK MAC : editing/deleting/delete-3928305-fix.html = IMAGE
BUG_CARYCLARK MAC : editing/deleting/delete-3959464-fix.html = IMAGE
--- /dev/null
+layer at (0,0) size 800x600
+ RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderSVGRoot {svg} at (40,88) size 191x92
+ RenderSVGHiddenContainer {defs} at (0,0) size 0x0
+ RenderSVGResourceClipper {clipPath} [id="clip"] [clipPathUnits=userSpaceOnUse]
+ RenderSVGText {text} at (40,88) size 191x92 contains 1 chunk(s)
+ RenderSVGTSpan {tspan} at (0,0) size 191x92
+ RenderSVGInlineText {#text} at (0,0) size 191x92
+ chunk 1 text run 1 at (40.00,160.00) startOffset 0 endOffset 4 width 191.00: "CLIP"
+ RenderSVGRect {rect} at (40,88) size 191x92 [fill={[type=SOLID] [color=#008000]}] [x=40.00] [y=40.00] [width=300.00] [height=300.00]
+ [clipPath="clip"] RenderSVGResourceClipper {clipPath} at (40,88) size 191x92
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<defs>
+ <clipPath id="clip">
+ <!-- fill and stroke properties should be ignored -->
+ <text x="40" y="160" font-size="80" font-weight="bold"><tspan stroke="red" fill="none">CLIP</tspan></text>
+ </clipPath>
+</defs>
+<rect x="40" y="40" height="300" width="300" style="fill:green;clip-path:url(#clip);"/>
+</svg>
--- /dev/null
+CONSOLE MESSAGE: line 15: Error: Problem parsing d="M147.231,26.23c70.188,0,127.086,57.77,127.086,129.032c0"
+PASS, if it doesn't crash.
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg">
+<g>
+ <linearGradient>
+ <stop id="stop"/>
+ </linearGradient>
+</g>
+<g>
+ <defs>
+ <filter id="filter"/>
+ </defs>
+
+ <mask id="mask">
+ <g filter="url(#filter)">
+ <linearGradient id="gradient"/>
+ <path fill="url(#gradient)" d="M147.231,26.23c70.188,0,127.086,57.77,127.086,129.032c0"/>
+ </g>
+ </mask>
+
+ <path mask="url(#mask)" d="M147.231,26.23c70.188,0,127.086,57.77,127.086,129.032"/>
+</g>
+
+<text>PASS, if it doesn't crash.</text>
+<script><![CDATA[
+if (window.layoutTestController)
+ layoutTestController.dumpAsText();
+document.execCommand("SelectAll");
+range = document.createRange();
+range.setStartBefore(document.getElementById("stop"));
+range.insertNode(document.getElementById("mask"));
+
+/* What does this do? It moves the whole <mask> element as child of the <linearGradient>.
+The final DOM looks like:
+
+<g>
+ <linearGradient>
+ <mask id="mask">
+ <g filter="url(#filter)">
+ <linearGradient id="gradient"/>
+ <path fill="url(#gradient)" d="..."/>
+ </g>
+ </mask>
+ <stop id="stop"/>
+ </linearGradient>
+</g>
+<g>
+ <defs>
+ <filter id="filter"/>
+ </defs>
+ <path mask="url(#mask)" d="...."/>
+</g>
+*/
+]]></script>
+</svg>
2012-01-26 Nikolas Zimmermann <nzimmermann@rim.com>
+ crash in WebCore::RenderSVGContainer::paint
+ https://bugs.webkit.org/show_bug.cgi?id=69714
+
+ Reviewed by Dirk Schulze.
+
+ When RenderSVGResourceClipper draws its children to a mask, it requires some special constraints:
+ - fill-opacity/stroke-opacity/opacity forced to 1
+ - masker/filter resources shouldn't be applied to the children
+ - fill must be set to the initial fill paint server for all children (solid black)
+ - stroke must be set to the initial stroke paint server for all children (none)
+
+ This was achieved before by mutating the style of the children, which made them need a relayout.
+ SVGImageBufferTools:renderSubtreeToImageBuffer thus needed to layout the children, if needed, before painting.
+
+ This can be completly avoided, when changing RenderSVGResourceClipper to avoid style mutations.
+ Introduce a new "PaintBehaviorRenderingSVGMask", and set the current FrameViews paintBehaviour to this
+ state, before painting the subtree. This way we can detect that we're rendering a clip mask, without
+ having to mutate the style of the children and without having to relayout.
+
+ We can now ASSERT(!item->needsLayout()) in renderSubtreeToImageBuffer.
+
+ Tests: svg/clip-path/clip-path-tspan-and-stroke.svg
+ svg/custom/layout-loop.svg
+
+ * rendering/PaintPhase.h:
+ * rendering/svg/RenderSVGResource.cpp:
+ (WebCore::requestPaintingResource):
+ * rendering/svg/RenderSVGResourceClipper.cpp:
+ (WebCore::RenderSVGResourceClipper::RenderSVGResourceClipper):
+ (WebCore::RenderSVGResourceClipper::removeAllClientsFromCache):
+ (WebCore::RenderSVGResourceClipper::removeClientFromCache):
+ (WebCore::RenderSVGResourceClipper::applyClippingToContext):
+ (WebCore::RenderSVGResourceClipper::drawContentIntoMaskImage):
+ * rendering/svg/RenderSVGResourceClipper.h:
+ * rendering/svg/RenderSVGResourceMasker.cpp:
+ (WebCore::RenderSVGResourceMasker::applyResource):
+ (WebCore::RenderSVGResourceMasker::drawContentIntoMaskImage):
+ * rendering/svg/RenderSVGResourceMasker.h:
+ * rendering/svg/RenderSVGResourcePattern.cpp:
+ (WebCore::RenderSVGResourcePattern::createTileImage):
+ * rendering/svg/RenderSVGResourceSolidColor.cpp:
+ (WebCore::RenderSVGResourceSolidColor::applyResource):
+ * rendering/svg/SVGImageBufferTools.cpp:
+ (WebCore::SVGImageBufferTools::renderSubtreeToImageBuffer):
+ * rendering/svg/SVGInlineTextBox.cpp:
+ (WebCore::SVGInlineTextBox::paintSelectionBackground):
+ (WebCore::SVGInlineTextBox::paint):
+ * rendering/svg/SVGRenderSupport.cpp:
+ (WebCore::SVGRenderSupport::prepareToRenderSVGContent):
+
+2012-01-26 Nikolas Zimmermann <nzimmermann@rim.com>
+
Not reviewed. Try to fix Gtk build - JSShadowRoot.cpp can't be found, fix by looking for it in the derived sources.
* GNUmakefile.list.am:
PaintBehaviorNormal = 0,
PaintBehaviorSelectionOnly = 1 << 0,
PaintBehaviorForceBlackText = 1 << 1,
- PaintBehaviorFlattenCompositingLayers = 1 << 2
+ PaintBehaviorFlattenCompositingLayers = 1 << 2,
+ PaintBehaviorRenderingSVGMask = 1 << 3
};
typedef unsigned PaintBehavior;
if (!svgStyle)
return 0;
+ bool isRenderingMask = false;
+ if (object->frame() && object->frame()->view())
+ isRenderingMask = object->frame()->view()->paintBehavior() & PaintBehaviorRenderingSVGMask;
+
// If we have no fill/stroke, return 0.
if (mode == ApplyToFillMode) {
+ // When rendering the mask for a RenderSVGResourceClipper, always use the initial fill paint server, and ignore stroke.
+ if (isRenderingMask) {
+ RenderSVGResourceSolidColor* colorResource = RenderSVGResource::sharedSolidPaintingResource();
+ colorResource->setColor(SVGRenderStyle::initialFillPaintColor());
+ return colorResource;
+ }
+
if (!svgStyle->hasFill())
return 0;
} else {
- if (!svgStyle->hasStroke())
+ if (!svgStyle->hasStroke() || isRenderingMask)
return 0;
}
RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
: RenderSVGResourceContainer(node)
- , m_invalidationBlocked(false)
{
}
void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
{
- if (m_invalidationBlocked)
- return;
-
m_clipBoundaries = FloatRect();
if (!m_clipper.isEmpty()) {
deleteAllValues(m_clipper);
void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
{
ASSERT(client);
- if (m_invalidationBlocked)
- return;
-
if (m_clipper.contains(client))
delete m_clipper.take(client);
}
}
- drawContentIntoMaskImage(clipperData, objectBoundingBox);
+ if (!drawContentIntoMaskImage(clipperData, objectBoundingBox)) {
+ stateSaver.restore();
+ clipperData->clipMaskImage.clear();
+ }
}
if (!clipperData->clipMaskImage)
bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox)
{
+ ASSERT(frame());
ASSERT(clipperData);
ASSERT(clipperData->clipMaskImage);
maskContext->concatCTM(maskContentTransformation);
}
+ // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
+ // - fill-opacity/stroke-opacity/opacity set to 1
+ // - masker/filter not applied when rendering the children
+ // - fill is set to the initial fill paint server (solid, black)
+ // - stroke is set to the initial stroke paint server (none)
+ 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() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer)
continue;
+ if (renderer->needsLayout()) {
+ frame()->view()->setPaintBehavior(oldBehavior);
+ return false;
+ }
RenderStyle* style = renderer->style();
if (!style || style->display() == NONE || style->visibility() != VISIBLE)
continue;
if (!renderer->isSVGShape() && !renderer->isSVGText())
continue;
- // Save the old RenderStyle of the current object for restoring after drawing
- // it to the MaskImage. The new intermediate RenderStyle needs to inherit from
- // the old one.
- RefPtr<RenderStyle> oldRenderStyle = renderer->style();
- RefPtr<RenderStyle> newRenderStyle = RenderStyle::clone(oldRenderStyle.get());
- SVGRenderStyle* svgStyle = newRenderStyle.get()->accessSVGStyle();
- svgStyle->setFillPaint(SVGRenderStyle::initialFillPaintType(), SVGRenderStyle::initialFillPaintColor(), SVGRenderStyle::initialFillPaintUri());
- svgStyle->setStrokePaint(SVGRenderStyle::initialStrokePaintType(), SVGRenderStyle::initialStrokePaintColor(), SVGRenderStyle::initialStrokePaintUri());
- svgStyle->setFillRule(newClipRule);
- newRenderStyle.get()->setOpacity(1);
- svgStyle->setFillOpacity(1);
- svgStyle->setStrokeOpacity(1);
- svgStyle->setFilterResource(String());
- svgStyle->setMaskerResource(String());
-
- // The setStyle() call results in a styleDidChange() call, which in turn invalidations the resources.
- // As we're mutating the resource on purpose, block updates until we've resetted the style again.
- m_invalidationBlocked = true;
- renderer->setStyle(newRenderStyle.release());
+ maskContext->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.
SVGImageBufferTools::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation);
-
- renderer->setStyle(oldRenderStyle.release());
- m_invalidationBlocked = false;
}
+ frame()->view()->setPaintBehavior(oldBehavior);
return true;
}
bool drawContentIntoMaskImage(ClipperData*, const FloatRect& objectBoundingBox);
void calculateClipContentRepaintRect();
- bool m_invalidationBlocked;
FloatRect m_clipBoundaries;
HashMap<RenderObject*, ClipperData*> m_clipper;
};
maskImageContext->translate(-clampedAbsoluteTargetRect.x(), -clampedAbsoluteTargetRect.y());
maskImageContext->concatCTM(absoluteTransform);
- drawContentIntoMaskImage(maskerData, colorSpace, maskElement, object);
+ if (!drawContentIntoMaskImage(maskerData, colorSpace, maskElement, object)) {
+ maskImageContext->restore();
+ maskerData->maskImage.clear();
+ }
}
if (!maskerData->maskImage)
return true;
}
-void RenderSVGResourceMasker::drawContentIntoMaskImage(MaskerData* maskerData, ColorSpace colorSpace, const SVGMaskElement* maskElement, RenderObject* object)
+bool RenderSVGResourceMasker::drawContentIntoMaskImage(MaskerData* maskerData, ColorSpace colorSpace, const SVGMaskElement* maskElement, RenderObject* object)
{
GraphicsContext* maskImageContext = maskerData->maskImage->context();
ASSERT(maskImageContext);
RenderObject* renderer = node->renderer();
if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !renderer)
continue;
+ if (renderer->needsLayout())
+ return false;
RenderStyle* style = renderer->style();
if (!style || style->display() == NONE || style->visibility() != VISIBLE)
continue;
// Create the luminance mask.
maskerData->maskImage->convertToLuminanceMask();
+ return true;
}
void RenderSVGResourceMasker::calculateMaskContentRepaintRect()
static RenderSVGResourceType s_resourceType;
private:
- void drawContentIntoMaskImage(MaskerData*, ColorSpace, const SVGMaskElement*, RenderObject*);
+ bool drawContentIntoMaskImage(MaskerData*, ColorSpace, const SVGMaskElement*, RenderObject*);
void calculateMaskContentRepaintRect();
FloatRect m_maskContentBoundaries;
for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) {
if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer())
continue;
+ if (node->renderer()->needsLayout())
+ return nullptr;
SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation);
}
const SVGRenderStyle* svgStyle = style ? style->svgStyle() : 0;
ColorSpace colorSpace = style ? style->colorSpace() : ColorSpaceDeviceRGB;
+ bool isRenderingMask = false;
+ if (object->frame() && object->frame()->view())
+ isRenderingMask = object->frame()->view()->paintBehavior() & PaintBehaviorRenderingSVGMask;
+
if (resourceMode & ApplyToFillMode) {
- context->setAlpha(svgStyle ? svgStyle->fillOpacity() : 1.0f);
+ if (!isRenderingMask && svgStyle)
+ context->setAlpha(svgStyle->fillOpacity());
+ else
+ context->setAlpha(1);
context->setFillColor(m_color, colorSpace);
- context->setFillRule(svgStyle ? svgStyle->fillRule() : RULE_NONZERO);
+ if (!isRenderingMask)
+ context->setFillRule(svgStyle ? svgStyle->fillRule() : RULE_NONZERO);
if (resourceMode & ApplyToTextMode)
context->setTextDrawingMode(TextModeFill);
} else if (resourceMode & ApplyToStrokeMode) {
- context->setAlpha(svgStyle ? svgStyle->strokeOpacity() : 1.0f);
+ // When rendering the mask for a RenderSVGResourceClipper, the stroke code path is never hit.
+ ASSERT(!isRenderingMask);
+ context->setAlpha(svgStyle ? svgStyle->strokeOpacity() : 1);
context->setStrokeColor(m_color, colorSpace);
if (style)
AffineTransform savedContentTransformation = contentTransformation;
contentTransformation = subtreeContentTransformation * contentTransformation;
- item->layoutIfNeeded();
+ ASSERT(!item->needsLayout());
item->paint(info, IntPoint());
contentTransformation = savedContentTransformation;
const SVGRenderStyle* svgStyle = style->svgStyle();
ASSERT(svgStyle);
- bool hasFill = svgStyle->hasFill();
- bool hasStroke = svgStyle->hasStroke();
-
RenderStyle* selectionStyle = style;
if (hasSelection) {
selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION);
- if (selectionStyle) {
- const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle();
- ASSERT(svgSelectionStyle);
-
- if (!hasFill)
- hasFill = svgSelectionStyle->hasFill();
- if (!hasStroke)
- hasStroke = svgSelectionStyle->hasStroke();
- } else
+ if (!selectionStyle)
selectionStyle = style;
}
selectionStyle = style;
}
+ if (textRenderer->frame() && textRenderer->frame()->view() && textRenderer->frame()->view()->paintBehavior() & PaintBehaviorRenderingSVGMask) {
+ hasFill = true;
+ hasStroke = false;
+ }
+
AffineTransform fragmentTransform;
unsigned textFragmentsSize = m_textFragments.size();
for (unsigned i = 0; i < textFragmentsSize; ++i) {
const SVGRenderStyle* svgStyle = style->svgStyle();
ASSERT(svgStyle);
+ bool isRenderingMask = false;
+ if (object->frame() && object->frame()->view())
+ isRenderingMask = object->frame()->view()->paintBehavior() & PaintBehaviorRenderingSVGMask;
+
// Setup transparency layers before setting up SVG resources!
- float opacity = style->opacity();
+ float opacity = isRenderingMask ? 1 : style->opacity();
const ShadowData* shadow = svgStyle->shadow();
if (opacity < 1 || shadow) {
FloatRect repaintRect = object->repaintRectInLocalCoordinates();
return true;
}
- if (RenderSVGResourceMasker* masker = resources->masker()) {
- if (!masker->applyResource(object, style, paintInfo.context, ApplyToDefaultMode))
- return false;
+ if (!isRenderingMask) {
+ if (RenderSVGResourceMasker* masker = resources->masker()) {
+ if (!masker->applyResource(object, style, paintInfo.context, ApplyToDefaultMode))
+ return false;
+ }
}
if (RenderSVGResourceClipper* clipper = resources->clipper()) {
}
#if ENABLE(FILTERS)
- if (RenderSVGResourceFilter* filter = resources->filter()) {
- if (!filter->applyResource(object, style, paintInfo.context, ApplyToDefaultMode))
- return false;
+ if (!isRenderingMask) {
+ if (RenderSVGResourceFilter* filter = resources->filter()) {
+ if (!filter->applyResource(object, style, paintInfo.context, ApplyToDefaultMode))
+ return false;
+ }
}
#endif