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 "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
21 
22 #include <memory>
23 #include "third_party/blink/renderer/core/html_names.h"
24 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h"
25 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h"
26 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
27 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cycle_solver.h"
28 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
29 
30 namespace blink {
31 
32 SVGResourcesCache::SVGResourcesCache() = default;
33 
34 SVGResourcesCache::~SVGResourcesCache() = default;
35 
AddResourcesFromLayoutObject(LayoutObject & object,const ComputedStyle & style)36 SVGResources* SVGResourcesCache::AddResourcesFromLayoutObject(
37     LayoutObject& object,
38     const ComputedStyle& style) {
39   DCHECK(!cache_.Contains(&object));
40 
41   // Build a list of all resources associated with the passed LayoutObject.
42   std::unique_ptr<SVGResources> new_resources =
43       SVGResources::BuildResources(object, style);
44   if (!new_resources)
45     return nullptr;
46 
47   // Put object in cache.
48   SVGResources* resources =
49       cache_.Set(&object, std::move(new_resources)).stored_value->value.get();
50 
51   // Run cycle-detection _afterwards_, so self-references can be caught as well.
52   HashSet<LayoutSVGResourceContainer*> resource_set;
53   resources->BuildSetOfResources(resource_set);
54 
55   SVGResourcesCycleSolver solver;
56   for (auto* resource_container : resource_set) {
57     if (resource_container->FindCycle(solver))
58       resources->ClearReferencesTo(resource_container);
59   }
60   return resources;
61 }
62 
RemoveResourcesFromLayoutObject(LayoutObject & object)63 bool SVGResourcesCache::RemoveResourcesFromLayoutObject(LayoutObject& object) {
64   std::unique_ptr<SVGResources> resources = cache_.Take(&object);
65   return !!resources;
66 }
67 
68 SVGResourcesCache::ResourceUpdateInfo
UpdateResourcesFromLayoutObject(LayoutObject & object,const ComputedStyle & new_style)69 SVGResourcesCache::UpdateResourcesFromLayoutObject(
70     LayoutObject& object,
71     const ComputedStyle& new_style) {
72   std::unique_ptr<SVGResources> old_resources = cache_.Take(&object);
73   SVGResources* new_resources = AddResourcesFromLayoutObject(object, new_style);
74   return {
75       old_resources || new_resources,
76       SVGResources::DifferenceNeedsLayout(old_resources.get(), new_resources)};
77 }
78 
ResourcesCache(Document & document)79 static inline SVGResourcesCache& ResourcesCache(Document& document) {
80   return document.AccessSVGExtensions().ResourcesCache();
81 }
82 
CachedResourcesForLayoutObject(const LayoutObject & layout_object)83 SVGResources* SVGResourcesCache::CachedResourcesForLayoutObject(
84     const LayoutObject& layout_object) {
85   return ResourcesCache(layout_object.GetDocument()).cache_.at(&layout_object);
86 }
87 
ClientLayoutChanged(LayoutObject & object)88 void SVGResourcesCache::ClientLayoutChanged(LayoutObject& object) {
89   SVGResources* resources = CachedResourcesForLayoutObject(object);
90   if (!resources)
91     return;
92   // Invalidate the resources if either the LayoutObject itself changed,
93   // or we have filter resources, which could depend on the layout of children.
94   if (!object.SelfNeedsLayout() && !resources->Filter())
95     return;
96   SVGElementResourceClient* client = SVGResources::GetClient(object);
97   InvalidationModeMask invalidation_flags =
98       resources->RemoveClientFromCacheAffectingObjectBounds(*client);
99   if (LayoutSVGResourcePaintServer* fill = resources->Fill()) {
100     fill->RemoveClientFromCache(*client);
101     invalidation_flags |= SVGResourceClient::kPaintInvalidation;
102   }
103   if (LayoutSVGResourcePaintServer* stroke = resources->Stroke()) {
104     stroke->RemoveClientFromCache(*client);
105     invalidation_flags |= SVGResourceClient::kPaintInvalidation;
106   }
107   if (invalidation_flags) {
108     LayoutSVGResourceContainer::MarkClientForInvalidation(object,
109                                                           invalidation_flags);
110   }
111 }
112 
LayoutObjectCanHaveResources(const LayoutObject & layout_object)113 static inline bool LayoutObjectCanHaveResources(
114     const LayoutObject& layout_object) {
115   return layout_object.GetNode() && layout_object.GetNode()->IsSVGElement() &&
116          !layout_object.IsSVGInlineText();
117 }
118 
IsLayoutObjectOfResourceContainer(const LayoutObject & layout_object)119 static inline bool IsLayoutObjectOfResourceContainer(
120     const LayoutObject& layout_object) {
121   const LayoutObject* current = &layout_object;
122   while (current) {
123     if (current->IsSVGResourceContainer())
124       return true;
125     current = current->Parent();
126   }
127   return false;
128 }
129 
ClientStyleChanged(LayoutObject & layout_object,StyleDifference diff,const ComputedStyle & new_style)130 void SVGResourcesCache::ClientStyleChanged(LayoutObject& layout_object,
131                                            StyleDifference diff,
132                                            const ComputedStyle& new_style) {
133   DCHECK(layout_object.GetNode());
134   DCHECK(layout_object.GetNode()->IsSVGElement());
135 
136   if (!diff.HasDifference() || !layout_object.Parent())
137     return;
138 
139   // LayoutObjects for SVGFE*Element should not be calling this function.
140   DCHECK(!layout_object.IsSVGFilterPrimitive());
141 
142   // Dynamic changes of CSS properties like 'clip-path' may require us to
143   // recompute the associated resources for a LayoutObject.
144   // TODO(fs): Avoid passing in a useless StyleDifference, but instead compare
145   // oldStyle/newStyle to see which resources changed to be able to selectively
146   // rebuild individual resources, instead of all of them.
147   bool needs_layout = false;
148   if (LayoutObjectCanHaveResources(layout_object)) {
149     SVGResourcesCache& cache = ResourcesCache(layout_object.GetDocument());
150     auto update_info =
151         cache.UpdateResourcesFromLayoutObject(layout_object, new_style);
152     if (update_info) {
153       layout_object.SetNeedsPaintPropertyUpdate();
154       // Since the visual rect has the bounds of the clip-path, mask and filter
155       // baked in, and the visual rect is updated during layout, we need to
156       // trigger layout if the style change could somehow have affected the
157       // bounds that form the visual rect.
158       needs_layout = update_info.needs_layout;
159     }
160   }
161 
162   // If this layoutObject is the child of ResourceContainer and it require
163   // repainting that changes of CSS properties such as 'visibility',
164   // request repainting.
165   needs_layout |= diff.NeedsPaintInvalidation() &&
166                   IsLayoutObjectOfResourceContainer(layout_object);
167 
168   LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
169       layout_object, needs_layout);
170 }
171 
ResourceReferenceChanged(LayoutObject & layout_object)172 void SVGResourcesCache::ResourceReferenceChanged(LayoutObject& layout_object) {
173   DCHECK(layout_object.IsSVG());
174   DCHECK(layout_object.GetNode());
175   DCHECK(layout_object.GetNode()->IsSVGElement());
176 
177   if (!layout_object.Parent())
178     return;
179 
180   // Only LayoutObjects that can actually have resources should be pending and
181   // hence be able to call this method.
182   DCHECK(LayoutObjectCanHaveResources(layout_object));
183 
184   SVGResourcesCache& cache = ResourcesCache(layout_object.GetDocument());
185   if (cache.UpdateResourcesFromLayoutObject(layout_object,
186                                             layout_object.StyleRef())) {
187     layout_object.SetNeedsPaintPropertyUpdate();
188   }
189 
190   LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
191       layout_object, true);
192 }
193 
ClientWasAddedToTree(LayoutObject & layout_object)194 void SVGResourcesCache::ClientWasAddedToTree(LayoutObject& layout_object) {
195   if (!layout_object.GetNode())
196     return;
197   LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
198       layout_object, false);
199 
200   if (!LayoutObjectCanHaveResources(layout_object))
201     return;
202   SVGResourcesCache& cache = ResourcesCache(layout_object.GetDocument());
203   if (cache.AddResourcesFromLayoutObject(layout_object,
204                                          layout_object.StyleRef()))
205     layout_object.SetNeedsPaintPropertyUpdate();
206 }
207 
ClientWillBeRemovedFromTree(LayoutObject & layout_object)208 void SVGResourcesCache::ClientWillBeRemovedFromTree(
209     LayoutObject& layout_object) {
210   if (!layout_object.GetNode())
211     return;
212   LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
213       layout_object, false);
214 
215   if (!LayoutObjectCanHaveResources(layout_object))
216     return;
217   SVGResourcesCache& cache = ResourcesCache(layout_object.GetDocument());
218   if (cache.RemoveResourcesFromLayoutObject(layout_object))
219     layout_object.SetNeedsPaintPropertyUpdate();
220 }
221 
ClientDestroyed(LayoutObject & layout_object)222 void SVGResourcesCache::ClientDestroyed(LayoutObject& layout_object) {
223   SVGResourcesCache& cache = ResourcesCache(layout_object.GetDocument());
224   cache.RemoveResourcesFromLayoutObject(layout_object);
225 }
226 
TemporaryStyleScope(LayoutObject & layout_object,const ComputedStyle & style,const ComputedStyle & temporary_style)227 SVGResourcesCache::TemporaryStyleScope::TemporaryStyleScope(
228     LayoutObject& layout_object,
229     const ComputedStyle& style,
230     const ComputedStyle& temporary_style)
231     : layout_object_(layout_object),
232       original_style_(style),
233       temporary_style_(temporary_style),
234       styles_are_equal_(style == temporary_style) {
235   if (styles_are_equal_)
236     return;
237   DCHECK(LayoutObjectCanHaveResources(layout_object_));
238   auto& element = To<SVGElement>(*layout_object_.GetNode());
239   SVGResources::UpdatePaints(element, nullptr, temporary_style_);
240   SwitchTo(temporary_style);
241 }
242 
~TemporaryStyleScope()243 SVGResourcesCache::TemporaryStyleScope::~TemporaryStyleScope() {
244   if (styles_are_equal_)
245     return;
246   auto& element = To<SVGElement>(*layout_object_.GetNode());
247   SVGResources::ClearPaints(element, &temporary_style_);
248   SwitchTo(original_style_);
249 }
250 
SwitchTo(const ComputedStyle & style)251 void SVGResourcesCache::TemporaryStyleScope::SwitchTo(
252     const ComputedStyle& style) {
253   DCHECK(!styles_are_equal_);
254   SVGResourcesCache& cache = ResourcesCache(layout_object_.GetDocument());
255   cache.UpdateResourcesFromLayoutObject(layout_object_, style);
256 }
257 
258 }  // namespace blink
259