Exploitable crash happens when an SVG contains an indirect resource inheritance cycle
[WebKit-https.git] / Source / WebCore / rendering / svg / SVGRenderTreeAsText.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2007, 2009 Apple Inc. All rights reserved.
3  *           (C) 2005 Rob Buis <buis@kde.org>
4  *           (C) 2006 Alexander Kellett <lypanov@kde.org>
5  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "SVGRenderTreeAsText.h"
31
32 #include "GraphicsTypes.h"
33 #include "HTMLNames.h"
34 #include "NodeRenderStyle.h"
35 #include "RenderImage.h"
36 #include "RenderIterator.h"
37 #include "RenderSVGGradientStop.h"
38 #include "RenderSVGImage.h"
39 #include "RenderSVGPath.h"
40 #include "RenderSVGResourceClipper.h"
41 #include "RenderSVGResourceFilter.h"
42 #include "RenderSVGResourceLinearGradient.h"
43 #include "RenderSVGResourceMarker.h"
44 #include "RenderSVGResourceMasker.h"
45 #include "RenderSVGResourcePattern.h"
46 #include "RenderSVGResourceRadialGradient.h"
47 #include "RenderSVGResourceSolidColor.h"
48 #include "RenderSVGRoot.h"
49 #include "RenderSVGText.h"
50 #include "RenderTreeAsText.h"
51 #include "SVGCircleElement.h"
52 #include "SVGEllipseElement.h"
53 #include "SVGInlineTextBox.h"
54 #include "SVGLineElement.h"
55 #include "SVGNames.h"
56 #include "SVGPathElement.h"
57 #include "SVGPathUtilities.h"
58 #include "SVGPointList.h"
59 #include "SVGPolyElement.h"
60 #include "SVGRectElement.h"
61 #include "SVGRootInlineBox.h"
62 #include "SVGStopElement.h"
63
64 #include <math.h>
65
66 namespace WebCore {
67
68 /** class + iomanip to help streaming list separators, i.e. ", " in string "a, b, c, d"
69  * Can be used in cases where you don't know which item in the list is the first
70  * one to be printed, but still want to avoid strings like ", b, c".
71  */
72 class TextStreamSeparator {
73 public:
74     TextStreamSeparator(const String& s)
75         : m_separator(s)
76         , m_needToSeparate(false)
77     {
78     }
79
80 private:
81     friend TextStream& operator<<(TextStream&, TextStreamSeparator&);
82
83     String m_separator;
84     bool m_needToSeparate;
85 };
86
87 TextStream& operator<<(TextStream& ts, TextStreamSeparator& sep)
88 {
89     if (sep.m_needToSeparate)
90         ts << sep.m_separator;
91     else
92         sep.m_needToSeparate = true;
93     return ts;
94 }
95
96 template<typename ValueType>
97 static void writeNameValuePair(TextStream& ts, const char* name, ValueType value)
98 {
99     ts << " [" << name << "=" << value << "]";
100 }
101
102 template<typename ValueType>
103 static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value)
104 {
105     ts << " [" << name << "=\"" << value << "\"]";
106 }
107
108 static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value)
109 {
110     if (!value.isEmpty())
111         writeNameValuePair(ts, name, value);
112 }
113
114 template<typename ValueType>
115 static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue)
116 {
117     if (value != defaultValue)
118         writeNameValuePair(ts, name, value);
119 }
120
121 static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType)
122 {
123     ts << SVGPropertyTraits<SVGUnitTypes::SVGUnitType>::toString(unitType);
124     return ts;
125 }
126
127 static TextStream& operator<<(TextStream& ts, const SVGMarkerUnitsType& markerUnit)
128 {
129     ts << SVGPropertyTraits<SVGMarkerUnitsType>::toString(markerUnit);
130     return ts;
131 }
132
133 // FIXME: Maybe this should be in KCanvasRenderingStyle.cpp
134 static TextStream& operator<<(TextStream& ts, const DashArray& a)
135 {
136     ts << "{";
137     DashArray::const_iterator end = a.end();
138     for (DashArray::const_iterator it = a.begin(); it != end; ++it) {
139         if (it != a.begin())
140             ts << ", ";
141         ts << *it;
142     }
143     ts << "}";
144     return ts;
145 }
146
147 static TextStream& operator<<(TextStream& ts, const SVGSpreadMethodType& type)
148 {
149     ts << SVGPropertyTraits<SVGSpreadMethodType>::toString(type).upper();
150     return ts;
151 }
152
153 static void writeSVGPaintingResource(TextStream& ts, RenderSVGResource* resource)
154 {
155     if (resource->resourceType() == SolidColorResourceType) {
156         ts << "[type=SOLID] [color=" << static_cast<RenderSVGResourceSolidColor*>(resource)->color() << "]";
157         return;
158     }
159
160     // All other resources derive from RenderSVGResourceContainer
161     RenderSVGResourceContainer* container = static_cast<RenderSVGResourceContainer*>(resource);
162     SVGElement& element = container->element();
163
164     if (resource->resourceType() == PatternResourceType)
165         ts << "[type=PATTERN]";
166     else if (resource->resourceType() == LinearGradientResourceType)
167         ts << "[type=LINEAR-GRADIENT]";
168     else if (resource->resourceType() == RadialGradientResourceType)
169         ts << "[type=RADIAL-GRADIENT]";
170
171     ts << " [id=\"" << element.getIdAttribute() << "\"]";
172 }
173
174 static void writeStyle(TextStream& ts, const RenderElement& renderer)
175 {
176     const RenderStyle& style = renderer.style();
177     const SVGRenderStyle& svgStyle = style.svgStyle();
178
179     if (!renderer.localTransform().isIdentity())
180         writeNameValuePair(ts, "transform", renderer.localTransform());
181     writeIfNotDefault(ts, "image rendering", style.imageRendering(), RenderStyle::initialImageRendering());
182     writeIfNotDefault(ts, "opacity", style.opacity(), RenderStyle::initialOpacity());
183     if (is<RenderSVGShape>(renderer)) {
184         const auto& shape = downcast<RenderSVGShape>(renderer);
185
186         Color fallbackColor;
187         if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(const_cast<RenderSVGShape&>(shape), shape.style(), fallbackColor)) {
188             TextStreamSeparator s(" ");
189             ts << " [stroke={" << s;
190             writeSVGPaintingResource(ts, strokePaintingResource);
191
192             SVGLengthContext lengthContext(&shape.graphicsElement());
193             double dashOffset = lengthContext.valueForLength(svgStyle.strokeDashOffset());
194             double strokeWidth = lengthContext.valueForLength(svgStyle.strokeWidth());
195             const Vector<SVGLength>& dashes = svgStyle.strokeDashArray();
196
197             DashArray dashArray;
198             const Vector<SVGLength>::const_iterator end = dashes.end();
199             for (Vector<SVGLength>::const_iterator it = dashes.begin(); it != end; ++it)
200                 dashArray.append((*it).value(lengthContext));
201
202             writeIfNotDefault(ts, "opacity", svgStyle.strokeOpacity(), 1.0f);
203             writeIfNotDefault(ts, "stroke width", strokeWidth, 1.0);
204             writeIfNotDefault(ts, "miter limit", svgStyle.strokeMiterLimit(), 4.0f);
205             writeIfNotDefault(ts, "line cap", svgStyle.capStyle(), ButtCap);
206             writeIfNotDefault(ts, "line join", svgStyle.joinStyle(), MiterJoin);
207             writeIfNotDefault(ts, "dash offset", dashOffset, 0.0);
208             if (!dashArray.isEmpty())
209                 writeNameValuePair(ts, "dash array", dashArray);
210
211             ts << "}]";
212         }
213
214         if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(const_cast<RenderSVGShape&>(shape), shape.style(), fallbackColor)) {
215             TextStreamSeparator s(" ");
216             ts << " [fill={" << s;
217             writeSVGPaintingResource(ts, fillPaintingResource);
218
219             writeIfNotDefault(ts, "opacity", svgStyle.fillOpacity(), 1.0f);
220             writeIfNotDefault(ts, "fill rule", svgStyle.fillRule(), RULE_NONZERO);
221             ts << "}]";
222         }
223         writeIfNotDefault(ts, "clip rule", svgStyle.clipRule(), RULE_NONZERO);
224     }
225
226     writeIfNotEmpty(ts, "start marker", svgStyle.markerStartResource());
227     writeIfNotEmpty(ts, "middle marker", svgStyle.markerMidResource());
228     writeIfNotEmpty(ts, "end marker", svgStyle.markerEndResource());
229 }
230
231 static TextStream& writePositionAndStyle(TextStream& ts, const RenderElement& renderer)
232 {
233     ts << " " << enclosingIntRect(renderer.absoluteClippedOverflowRect());
234     writeStyle(ts, renderer);
235     return ts;
236 }
237
238 static TextStream& operator<<(TextStream& ts, const RenderSVGShape& shape)
239 {
240     writePositionAndStyle(ts, shape);
241
242     SVGGraphicsElement& svgElement = shape.graphicsElement();
243     SVGLengthContext lengthContext(&svgElement);
244
245     if (is<SVGRectElement>(svgElement)) {
246         const SVGRectElement& element = downcast<SVGRectElement>(svgElement);
247         writeNameValuePair(ts, "x", element.x().value(lengthContext));
248         writeNameValuePair(ts, "y", element.y().value(lengthContext));
249         writeNameValuePair(ts, "width", element.width().value(lengthContext));
250         writeNameValuePair(ts, "height", element.height().value(lengthContext));
251     } else if (is<SVGLineElement>(svgElement)) {
252         const SVGLineElement& element = downcast<SVGLineElement>(svgElement);
253         writeNameValuePair(ts, "x1", element.x1().value(lengthContext));
254         writeNameValuePair(ts, "y1", element.y1().value(lengthContext));
255         writeNameValuePair(ts, "x2", element.x2().value(lengthContext));
256         writeNameValuePair(ts, "y2", element.y2().value(lengthContext));
257     } else if (is<SVGEllipseElement>(svgElement)) {
258         const SVGEllipseElement& element = downcast<SVGEllipseElement>(svgElement);
259         writeNameValuePair(ts, "cx", element.cx().value(lengthContext));
260         writeNameValuePair(ts, "cy", element.cy().value(lengthContext));
261         writeNameValuePair(ts, "rx", element.rx().value(lengthContext));
262         writeNameValuePair(ts, "ry", element.ry().value(lengthContext));
263     } else if (is<SVGCircleElement>(svgElement)) {
264         const SVGCircleElement& element = downcast<SVGCircleElement>(svgElement);
265         writeNameValuePair(ts, "cx", element.cx().value(lengthContext));
266         writeNameValuePair(ts, "cy", element.cy().value(lengthContext));
267         writeNameValuePair(ts, "r", element.r().value(lengthContext));
268     } else if (is<SVGPolyElement>(svgElement)) {
269         const SVGPolyElement& element = downcast<SVGPolyElement>(svgElement);
270         writeNameAndQuotedValue(ts, "points", element.pointList().valueAsString());
271     } else if (is<SVGPathElement>(svgElement)) {
272         const SVGPathElement& element = downcast<SVGPathElement>(svgElement);
273         String pathString;
274         // FIXME: We should switch to UnalteredParsing here - this will affect the path dumping output of dozens of tests.
275         buildStringFromByteStream(element.pathByteStream(), pathString, NormalizedParsing);
276         writeNameAndQuotedValue(ts, "data", pathString);
277     } else
278         ASSERT_NOT_REACHED();
279     return ts;
280 }
281
282 static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root)
283 {
284     return writePositionAndStyle(ts, root);
285 }
286
287 static void writeRenderSVGTextBox(TextStream& ts, const RenderSVGText& text)
288 {
289     auto* box = downcast<SVGRootInlineBox>(text.firstRootBox());
290     if (!box)
291         return;
292
293     ts << " " << enclosingIntRect(FloatRect(text.location(), FloatSize(box->logicalWidth(), box->logicalHeight())));
294     
295     // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now.
296     ts << " contains 1 chunk(s)";
297
298     if (text.parent() && (text.parent()->style().visitedDependentColor(CSSPropertyColor) != text.style().visitedDependentColor(CSSPropertyColor)))
299         writeNameValuePair(ts, "color", text.style().visitedDependentColor(CSSPropertyColor).nameForRenderTreeAsText());
300 }
301
302 static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent)
303 {
304     Vector<SVGTextFragment>& fragments = textBox->textFragments();
305     if (fragments.isEmpty())
306         return;
307
308     const SVGRenderStyle& svgStyle = textBox->renderer().style().svgStyle();
309     String text = textBox->renderer().text();
310
311     unsigned fragmentsSize = fragments.size();
312     for (unsigned i = 0; i < fragmentsSize; ++i) {
313         SVGTextFragment& fragment = fragments.at(i);
314         writeIndent(ts, indent + 1);
315
316         unsigned startOffset = fragment.characterOffset;
317         unsigned endOffset = fragment.characterOffset + fragment.length;
318
319         // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now.
320         ts << "chunk 1 ";
321         ETextAnchor anchor = svgStyle.textAnchor();
322         bool isVerticalText = svgStyle.isVerticalWritingMode();
323         if (anchor == TA_MIDDLE) {
324             ts << "(middle anchor";
325             if (isVerticalText)
326                 ts << ", vertical";
327             ts << ") ";
328         } else if (anchor == TA_END) {
329             ts << "(end anchor";
330             if (isVerticalText)
331                 ts << ", vertical";
332             ts << ") ";
333         } else if (isVerticalText)
334             ts << "(vertical) ";
335         startOffset -= textBox->start();
336         endOffset -= textBox->start();
337         // </hack>
338
339         ts << "text run " << i + 1 << " at (" << fragment.x << "," << fragment.y << ")";
340         ts << " startOffset " << startOffset << " endOffset " << endOffset;
341         if (isVerticalText)
342             ts << " height " << fragment.height;
343         else
344             ts << " width " << fragment.width;
345
346         if (!textBox->isLeftToRightDirection() || textBox->dirOverride()) {
347             ts << (textBox->isLeftToRightDirection() ? " LTR" : " RTL");
348             if (textBox->dirOverride())
349                 ts << " override";
350         }
351
352         ts << ": " << quoteAndEscapeNonPrintables(text.substring(fragment.characterOffset, fragment.length)) << "\n";
353     }
354 }
355
356 static inline void writeSVGInlineTextBoxes(TextStream& ts, const RenderText& text, int indent)
357 {
358     for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
359         if (!is<SVGInlineTextBox>(*box))
360             continue;
361
362         writeSVGInlineTextBox(ts, downcast<SVGInlineTextBox>(box), indent);
363     }
364 }
365
366 static void writeStandardPrefix(TextStream& ts, const RenderObject& object, int indent)
367 {
368     writeIndent(ts, indent);
369     ts << object.renderName();
370
371     if (object.node())
372         ts << " {" << object.node()->nodeName() << "}";
373 }
374
375 static void writeChildren(TextStream& ts, const RenderElement& parent, int indent)
376 {
377     for (const auto& child : childrenOfType<RenderObject>(parent))
378         write(ts, child, indent + 1);
379 }
380
381 static inline void writeCommonGradientProperties(TextStream& ts, SVGSpreadMethodType spreadMethod, const AffineTransform& gradientTransform, SVGUnitTypes::SVGUnitType gradientUnits)
382 {
383     writeNameValuePair(ts, "gradientUnits", gradientUnits);
384
385     if (spreadMethod != SVGSpreadMethodPad)
386         ts << " [spreadMethod=" << spreadMethod << "]";
387
388     if (!gradientTransform.isIdentity())
389         ts << " [gradientTransform=" << gradientTransform << "]";
390 }
391
392 void writeSVGResourceContainer(TextStream& ts, const RenderSVGResourceContainer& resource, int indent)
393 {
394     writeStandardPrefix(ts, resource, indent);
395
396     const AtomicString& id = resource.element().getIdAttribute();
397     writeNameAndQuotedValue(ts, "id", id);    
398
399     if (resource.resourceType() == MaskerResourceType) {
400         const auto& masker = static_cast<const RenderSVGResourceMasker&>(resource);
401         writeNameValuePair(ts, "maskUnits", masker.maskUnits());
402         writeNameValuePair(ts, "maskContentUnits", masker.maskContentUnits());
403         ts << "\n";
404     } else if (resource.resourceType() == FilterResourceType) {
405         const auto& filter = static_cast<const RenderSVGResourceFilter&>(resource);
406         writeNameValuePair(ts, "filterUnits", filter.filterUnits());
407         writeNameValuePair(ts, "primitiveUnits", filter.primitiveUnits());
408         ts << "\n";
409         // Creating a placeholder filter which is passed to the builder.
410         FloatRect dummyRect;
411         RefPtr<SVGFilter> dummyFilter = SVGFilter::create(AffineTransform(), dummyRect, dummyRect, dummyRect, true);
412         if (auto builder = filter.buildPrimitives(*dummyFilter)) {
413             if (FilterEffect* lastEffect = builder->lastEffect())
414                 lastEffect->externalRepresentation(ts, indent + 1);
415         }
416     } else if (resource.resourceType() == ClipperResourceType) {
417         const auto& clipper = static_cast<const RenderSVGResourceClipper&>(resource);
418         writeNameValuePair(ts, "clipPathUnits", clipper.clipPathUnits());
419         ts << "\n";
420     } else if (resource.resourceType() == MarkerResourceType) {
421         const auto& marker = static_cast<const RenderSVGResourceMarker&>(resource);
422         writeNameValuePair(ts, "markerUnits", marker.markerUnits());
423         ts << " [ref at " << marker.referencePoint() << "]";
424         ts << " [angle=";
425         if (marker.angle() == -1)
426             ts << "auto" << "]\n";
427         else
428             ts << marker.angle() << "]\n";
429     } else if (resource.resourceType() == PatternResourceType) {
430         const auto& pattern = static_cast<const RenderSVGResourcePattern&>(resource);
431
432         // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may
433         // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties()
434         PatternAttributes attributes;
435         pattern.collectPatternAttributes(attributes);
436
437         writeNameValuePair(ts, "patternUnits", attributes.patternUnits());
438         writeNameValuePair(ts, "patternContentUnits", attributes.patternContentUnits());
439
440         AffineTransform transform = attributes.patternTransform();
441         if (!transform.isIdentity())
442             ts << " [patternTransform=" << transform << "]";
443         ts << "\n";
444     } else if (resource.resourceType() == LinearGradientResourceType) {
445         const auto& gradient = static_cast<const RenderSVGResourceLinearGradient&>(resource);
446
447         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
448         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
449         LinearGradientAttributes attributes;
450         gradient.linearGradientElement().collectGradientAttributes(attributes);
451         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
452
453         ts << " [start=" << gradient.startPoint(attributes) << "] [end=" << gradient.endPoint(attributes) << "]\n";
454     }  else if (resource.resourceType() == RadialGradientResourceType) {
455         const auto& gradient = static_cast<const RenderSVGResourceRadialGradient&>(resource);
456
457         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
458         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
459         RadialGradientAttributes attributes;
460         gradient.radialGradientElement().collectGradientAttributes(attributes);
461         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
462
463         FloatPoint focalPoint = gradient.focalPoint(attributes);
464         FloatPoint centerPoint = gradient.centerPoint(attributes);
465         float radius = gradient.radius(attributes);
466         float focalRadius = gradient.focalRadius(attributes);
467
468         ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "] [focalRadius=" << focalRadius << "]\n";
469     } else
470         ts << "\n";
471     writeChildren(ts, resource, indent);
472 }
473
474 void writeSVGContainer(TextStream& ts, const RenderSVGContainer& container, int indent)
475 {
476     // Currently RenderSVGResourceFilterPrimitive has no meaningful output.
477     if (container.isSVGResourceFilterPrimitive())
478         return;
479     writeStandardPrefix(ts, container, indent);
480     writePositionAndStyle(ts, container);
481     ts << "\n";
482     writeResources(ts, container, indent);
483     writeChildren(ts, container, indent);
484 }
485
486 void write(TextStream& ts, const RenderSVGRoot& root, int indent)
487 {
488     writeStandardPrefix(ts, root, indent);
489     ts << root << "\n";
490     writeChildren(ts, root, indent);
491 }
492
493 void writeSVGText(TextStream& ts, const RenderSVGText& text, int indent)
494 {
495     writeStandardPrefix(ts, text, indent);
496     writeRenderSVGTextBox(ts, text);
497     ts << "\n";
498     writeResources(ts, text, indent);
499     writeChildren(ts, text, indent);
500 }
501
502 void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent)
503 {
504     writeStandardPrefix(ts, text, indent);
505     ts << " " << enclosingIntRect(FloatRect(text.firstRunLocation(), text.floatLinesBoundingBox().size())) << "\n";
506     writeResources(ts, text, indent);
507     writeSVGInlineTextBoxes(ts, text, indent);
508 }
509
510 void writeSVGImage(TextStream& ts, const RenderSVGImage& image, int indent)
511 {
512     writeStandardPrefix(ts, image, indent);
513     writePositionAndStyle(ts, image);
514     ts << "\n";
515     writeResources(ts, image, indent);
516 }
517
518 void write(TextStream& ts, const RenderSVGShape& shape, int indent)
519 {
520     writeStandardPrefix(ts, shape, indent);
521     ts << shape << "\n";
522     writeResources(ts, shape, indent);
523 }
524
525 void writeSVGGradientStop(TextStream& ts, const RenderSVGGradientStop& stop, int indent)
526 {
527     writeStandardPrefix(ts, stop, indent);
528
529     ts << " [offset=" << stop.element().offset() << "] [color=" << stop.element().stopColorIncludingOpacity() << "]\n";
530 }
531
532 void writeResources(TextStream& ts, const RenderObject& renderer, int indent)
533 {
534     const RenderStyle& style = renderer.style();
535     const SVGRenderStyle& svgStyle = style.svgStyle();
536
537     // FIXME: We want to use SVGResourcesCache to determine which resources are present, instead of quering the resource <-> id cache.
538     // For now leave the DRT output as is, but later on we should change this so cycles are properly ignored in the DRT output.
539     if (!svgStyle.maskerResource().isEmpty()) {
540         if (RenderSVGResourceMasker* masker = getRenderSVGResourceById<RenderSVGResourceMasker>(renderer.document(), svgStyle.maskerResource())) {
541             writeIndent(ts, indent);
542             ts << " ";
543             writeNameAndQuotedValue(ts, "masker", svgStyle.maskerResource());
544             ts << " ";
545             writeStandardPrefix(ts, *masker, 0);
546             ts << " " << masker->resourceBoundingBox(renderer) << "\n";
547         }
548     }
549     if (!svgStyle.clipperResource().isEmpty()) {
550         if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(renderer.document(), svgStyle.clipperResource())) {
551             writeIndent(ts, indent);
552             ts << " ";
553             writeNameAndQuotedValue(ts, "clipPath", svgStyle.clipperResource());
554             ts << " ";
555             writeStandardPrefix(ts, *clipper, 0);
556             ts << " " << clipper->resourceBoundingBox(renderer) << "\n";
557         }
558     }
559     if (style.hasFilter()) {
560         const FilterOperations& filterOperations = style.filter();
561         if (filterOperations.size() == 1) {
562             const FilterOperation& filterOperation = *filterOperations.at(0);
563             if (filterOperation.type() == FilterOperation::REFERENCE) {
564                 const auto& referenceFilterOperation = downcast<ReferenceFilterOperation>(filterOperation);
565                 AtomicString id = SVGURIReference::fragmentIdentifierFromIRIString(referenceFilterOperation.url(), renderer.document());
566                 if (RenderSVGResourceFilter* filter = getRenderSVGResourceById<RenderSVGResourceFilter>(renderer.document(), id)) {
567                     writeIndent(ts, indent);
568                     ts << " ";
569                     writeNameAndQuotedValue(ts, "filter", id);
570                     ts << " ";
571                     writeStandardPrefix(ts, *filter, 0);
572                     ts << " " << filter->resourceBoundingBox(renderer) << "\n";
573                 }
574             }
575         }
576     }
577 }
578
579 } // namespace WebCore