1/*
2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2008 Dirk Schulze <krit@webkit.org>
5 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#include "config.h"
24#include "RenderSVGResource.h"
25
26#include "Frame.h"
27#include "FrameView.h"
28#include "RenderSVGResourceClipper.h"
29#include "RenderSVGResourceFilter.h"
30#include "RenderSVGResourceMasker.h"
31#include "RenderSVGResourceSolidColor.h"
32#include "RenderView.h"
33#include "SVGResources.h"
34#include "SVGResourcesCache.h"
35#include "SVGURIReference.h"
36
37namespace WebCore {
38
39static inline bool inheritColorFromParentStyleIfNeeded(RenderElement& object, bool applyToFill, Color& color)
40{
41 if (color.isValid())
42 return true;
43 if (!object.parent())
44 return false;
45 const SVGRenderStyle& parentSVGStyle = object.parent()->style().svgStyle();
46 color = applyToFill ? parentSVGStyle.fillPaintColor() : parentSVGStyle.strokePaintColor();
47 return true;
48}
49
50static inline RenderSVGResource* requestPaintingResource(RenderSVGResourceMode mode, RenderElement& renderer, const RenderStyle& style, Color& fallbackColor)
51{
52 const SVGRenderStyle& svgStyle = style.svgStyle();
53
54 bool isRenderingMask = renderer.view().frameView().paintBehavior().contains(PaintBehavior::RenderingSVGMask);
55
56 // If we have no fill/stroke, return nullptr.
57 if (mode == RenderSVGResourceMode::ApplyToFill) {
58 // When rendering the mask for a RenderSVGResourceClipper, always use the initial fill paint server, and ignore stroke.
59 if (isRenderingMask) {
60 RenderSVGResourceSolidColor* colorResource = RenderSVGResource::sharedSolidPaintingResource();
61 colorResource->setColor(SVGRenderStyle::initialFillPaintColor());
62 return colorResource;
63 }
64
65 if (!svgStyle.hasFill())
66 return nullptr;
67 } else {
68 if (!svgStyle.hasStroke() || isRenderingMask)
69 return nullptr;
70 }
71
72 bool applyToFill = mode == RenderSVGResourceMode::ApplyToFill;
73 SVGPaintType paintType = applyToFill ? svgStyle.fillPaintType() : svgStyle.strokePaintType();
74 if (paintType == SVGPaintType::None)
75 return nullptr;
76
77 Color color;
78 switch (paintType) {
79 case SVGPaintType::CurrentColor:
80 case SVGPaintType::RGBColor:
81 case SVGPaintType::URICurrentColor:
82 case SVGPaintType::URIRGBColor:
83 color = applyToFill ? svgStyle.fillPaintColor() : svgStyle.strokePaintColor();
84 break;
85 default:
86 break;
87 }
88
89 if (style.insideLink() == InsideLink::InsideVisited) {
90 // FIXME: This code doesn't support the uri component of the visited link paint, https://bugs.webkit.org/show_bug.cgi?id=70006
91 SVGPaintType visitedPaintType = applyToFill ? svgStyle.visitedLinkFillPaintType() : svgStyle.visitedLinkStrokePaintType();
92
93 // For SVGPaintType::CurrentColor, 'color' already contains the 'visitedColor'.
94 if (visitedPaintType < SVGPaintType::URINone && visitedPaintType != SVGPaintType::CurrentColor) {
95 const Color& visitedColor = applyToFill ? svgStyle.visitedLinkFillPaintColor() : svgStyle.visitedLinkStrokePaintColor();
96 if (visitedColor.isValid())
97 color = visitedColor.colorWithAlpha(color.alphaAsFloat());
98 }
99 }
100
101 // If the primary resource is just a color, return immediately.
102 RenderSVGResourceSolidColor* colorResource = RenderSVGResource::sharedSolidPaintingResource();
103 if (paintType < SVGPaintType::URINone) {
104 if (!inheritColorFromParentStyleIfNeeded(renderer, applyToFill, color))
105 return nullptr;
106
107 colorResource->setColor(color);
108 return colorResource;
109 }
110
111 // If no resources are associated with the given renderer, return the color resource.
112 auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
113 if (!resources) {
114 if (paintType == SVGPaintType::URINone || !inheritColorFromParentStyleIfNeeded(renderer, applyToFill, color))
115 return nullptr;
116
117 colorResource->setColor(color);
118 return colorResource;
119 }
120
121 // If the requested resource is not available, return the color resource.
122 RenderSVGResource* uriResource = mode == RenderSVGResourceMode::ApplyToFill ? resources->fill() : resources->stroke();
123 if (!uriResource) {
124 if (!inheritColorFromParentStyleIfNeeded(renderer, applyToFill, color))
125 return nullptr;
126
127 colorResource->setColor(color);
128 return colorResource;
129 }
130
131 // The paint server resource exists, though it may be invalid (pattern with width/height=0). Pass the fallback color to our caller
132 // so it can use the solid color painting resource, if applyResource() on the URI resource failed.
133 fallbackColor = color;
134 return uriResource;
135}
136
137RenderSVGResource* RenderSVGResource::fillPaintingResource(RenderElement& renderer, const RenderStyle& style, Color& fallbackColor)
138{
139 return requestPaintingResource(RenderSVGResourceMode::ApplyToFill, renderer, style, fallbackColor);
140}
141
142RenderSVGResource* RenderSVGResource::strokePaintingResource(RenderElement& renderer, const RenderStyle& style, Color& fallbackColor)
143{
144 return requestPaintingResource(RenderSVGResourceMode::ApplyToStroke, renderer, style, fallbackColor);
145}
146
147RenderSVGResourceSolidColor* RenderSVGResource::sharedSolidPaintingResource()
148{
149 static RenderSVGResourceSolidColor* s_sharedSolidPaintingResource = 0;
150 if (!s_sharedSolidPaintingResource)
151 s_sharedSolidPaintingResource = new RenderSVGResourceSolidColor;
152 return s_sharedSolidPaintingResource;
153}
154
155static inline void removeFromCacheAndInvalidateDependencies(RenderElement& renderer, bool needsLayout)
156{
157 if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer)) {
158 if (RenderSVGResourceFilter* filter = resources->filter())
159 filter->removeClientFromCache(renderer);
160
161 if (RenderSVGResourceMasker* masker = resources->masker())
162 masker->removeClientFromCache(renderer);
163
164 if (RenderSVGResourceClipper* clipper = resources->clipper())
165 clipper->removeClientFromCache(renderer);
166 }
167
168 if (!is<SVGElement>(renderer.element()))
169 return;
170 auto* dependencies = renderer.document().accessSVGExtensions().setOfElementsReferencingTarget(downcast<SVGElement>(*renderer.element()));
171 if (!dependencies)
172 return;
173
174 for (auto* element : *dependencies) {
175 if (auto* renderer = element->renderer()) {
176 // We allow cycles in SVGDocumentExtensions reference sets in order to avoid expensive
177 // reference graph adjustments on changes, so we need to break possible cycles here.
178 static NeverDestroyed<HashSet<SVGElement*>> invalidatingDependencies;
179 if (UNLIKELY(!invalidatingDependencies.get().add(element).isNewEntry)) {
180 // Reference cycle: we are in process of invalidating this dependant.
181 continue;
182 }
183 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer, needsLayout);
184 invalidatingDependencies.get().remove(element);
185 }
186 }
187}
188
189void RenderSVGResource::markForLayoutAndParentResourceInvalidation(RenderObject& object, bool needsLayout)
190{
191 ASSERT(object.node());
192
193 if (needsLayout && !object.renderTreeBeingDestroyed())
194 object.setNeedsLayout();
195
196 if (is<RenderElement>(object))
197 removeFromCacheAndInvalidateDependencies(downcast<RenderElement>(object), needsLayout);
198
199 // Invalidate resources in ancestor chain, if needed.
200 auto current = object.parent();
201 while (current) {
202 removeFromCacheAndInvalidateDependencies(*current, needsLayout);
203
204 if (is<RenderSVGResourceContainer>(*current)) {
205 // This will process the rest of the ancestors.
206 downcast<RenderSVGResourceContainer>(*current).removeAllClientsFromCache();
207 break;
208 }
209
210 current = current->parent();
211 }
212}
213
214}
215