1/*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "SVGResourcesCache.h"
22
23#include "HTMLNames.h"
24#include "RenderSVGResourceContainer.h"
25#include "SVGResources.h"
26#include "SVGResourcesCycleSolver.h"
27
28namespace WebCore {
29
30SVGResourcesCache::SVGResourcesCache()
31{
32}
33
34SVGResourcesCache::~SVGResourcesCache()
35{
36}
37
38void SVGResourcesCache::addResourcesFromRenderer(RenderElement& renderer, const RenderStyle& style)
39{
40    ASSERT(!m_cache.contains(&renderer));
41
42    const SVGRenderStyle& svgStyle = style.svgStyle();
43
44    // Build a list of all resources associated with the passed RenderObject
45    auto newResources = std::make_unique<SVGResources>();
46    if (!newResources->buildCachedResources(renderer, svgStyle))
47        return;
48
49    // Put object in cache.
50    SVGResources& resources = *m_cache.add(&renderer, WTF::move(newResources)).iterator->value;
51
52    // Run cycle-detection _afterwards_, so self-references can be caught as well.
53    SVGResourcesCycleSolver solver(renderer, resources);
54    solver.resolveCycles();
55
56    // Walk resources and register the render object at each resources.
57    HashSet<RenderSVGResourceContainer*> resourceSet;
58    resources.buildSetOfResources(resourceSet);
59
60    for (auto* resourceContainer : resourceSet)
61        resourceContainer->addClient(renderer);
62}
63
64void SVGResourcesCache::removeResourcesFromRenderer(RenderElement& renderer)
65{
66    std::unique_ptr<SVGResources> resources = m_cache.take(&renderer);
67    if (!resources)
68        return;
69
70    // Walk resources and register the render object at each resources.
71    HashSet<RenderSVGResourceContainer*> resourceSet;
72    resources->buildSetOfResources(resourceSet);
73
74    for (auto* resourceContainer : resourceSet)
75        resourceContainer->removeClient(renderer);
76}
77
78static inline SVGResourcesCache& resourcesCacheFromRenderer(const RenderObject& renderer)
79{
80    SVGDocumentExtensions* extensions = renderer.document().accessSVGExtensions();
81    ASSERT(extensions);
82    return extensions->resourcesCache();
83}
84
85SVGResources* SVGResourcesCache::cachedResourcesForRenderObject(const RenderObject& renderer)
86{
87    return resourcesCacheFromRenderer(renderer).m_cache.get(&renderer);
88}
89
90void SVGResourcesCache::clientLayoutChanged(RenderElement& renderer)
91{
92    SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(renderer);
93    if (!resources)
94        return;
95
96    // Invalidate the resources if either the RenderElement itself changed,
97    // or we have filter resources, which could depend on the layout of children.
98    if (renderer.selfNeedsLayout())
99        resources->removeClientFromCache(renderer);
100}
101
102static inline bool rendererCanHaveResources(RenderObject& renderer)
103{
104    return renderer.node() && renderer.node()->isSVGElement() && !renderer.isSVGInlineText();
105}
106
107void SVGResourcesCache::clientStyleChanged(RenderElement& renderer, StyleDifference diff, const RenderStyle& newStyle)
108{
109    if (diff == StyleDifferenceEqual || !renderer.parent())
110        return;
111
112    // In this case the proper SVGFE*Element will decide whether the modified CSS properties require a relayout or repaint.
113    if (renderer.isSVGResourceFilterPrimitive() && (diff == StyleDifferenceRepaint || diff == StyleDifferenceRepaintIfTextOrBorderOrOutline))
114        return;
115
116    // Dynamic changes of CSS properties like 'clip-path' may require us to recompute the associated resources for a renderer.
117    // FIXME: Avoid passing in a useless StyleDifference, but instead compare oldStyle/newStyle to see which resources changed
118    // to be able to selectively rebuild individual resources, instead of all of them.
119    if (rendererCanHaveResources(renderer)) {
120        auto& cache = resourcesCacheFromRenderer(renderer);
121        cache.removeResourcesFromRenderer(renderer);
122        cache.addResourcesFromRenderer(renderer, newStyle);
123    }
124
125    RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer, false);
126
127    if (renderer.element() && !renderer.element()->isSVGElement())
128        renderer.element()->setNeedsStyleRecalc(SyntheticStyleChange);
129}
130
131void SVGResourcesCache::clientWasAddedToTree(RenderObject& renderer)
132{
133    if (renderer.isAnonymous())
134        return;
135
136    RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer, false);
137
138    if (!rendererCanHaveResources(renderer))
139        return;
140    RenderElement& elementRenderer = toRenderElement(renderer);
141    resourcesCacheFromRenderer(elementRenderer).addResourcesFromRenderer(elementRenderer, elementRenderer.style());
142}
143
144void SVGResourcesCache::clientWillBeRemovedFromTree(RenderObject& renderer)
145{
146    if (renderer.isAnonymous())
147        return;
148
149    RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer, false);
150
151    if (!rendererCanHaveResources(renderer))
152        return;
153    RenderElement& elementRenderer = toRenderElement(renderer);
154    resourcesCacheFromRenderer(elementRenderer).removeResourcesFromRenderer(elementRenderer);
155}
156
157void SVGResourcesCache::clientDestroyed(RenderElement& renderer)
158{
159    SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(renderer);
160    if (resources)
161        resources->removeClientFromCache(renderer);
162
163    resourcesCacheFromRenderer(renderer).removeResourcesFromRenderer(renderer);
164}
165
166void SVGResourcesCache::resourceDestroyed(RenderSVGResourceContainer& resource)
167{
168    auto& cache = resourcesCacheFromRenderer(resource);
169
170    // The resource itself may have clients, that need to be notified.
171    cache.removeResourcesFromRenderer(resource);
172
173    for (auto& it : cache.m_cache) {
174        it.value->resourceDestroyed(resource);
175
176        // Mark users of destroyed resources as pending resolution based on the id of the old resource.
177        Element& resourceElement = resource.element();
178        Element* clientElement = toElement(it.key->node());
179        SVGDocumentExtensions* extensions = clientElement->document().accessSVGExtensions();
180
181        extensions->addPendingResource(resourceElement.getIdAttribute(), clientElement);
182    }
183}
184
185}
186