1 | /* |
2 | * Copyright (C) 2006 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> |
4 | * Copyright (C) 2007 Rob Buis <buis@kde.org> |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public License |
17 | * along with this library; see the file COPYING.LIB. If not, write to |
18 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
19 | * Boston, MA 02110-1301, USA. |
20 | */ |
21 | |
22 | #include "config.h" |
23 | #include "SVGDocumentExtensions.h" |
24 | |
25 | #include "DOMWindow.h" |
26 | #include "Document.h" |
27 | #include "EventListener.h" |
28 | #include "Frame.h" |
29 | #include "FrameLoader.h" |
30 | #include "Page.h" |
31 | #include "SMILTimeContainer.h" |
32 | #include "SVGElement.h" |
33 | #include "SVGResourcesCache.h" |
34 | #include "SVGSMILElement.h" |
35 | #include "SVGSVGElement.h" |
36 | #include "ScriptableDocumentParser.h" |
37 | #include "ShadowRoot.h" |
38 | #include <wtf/text/AtomicString.h> |
39 | |
40 | namespace WebCore { |
41 | |
42 | SVGDocumentExtensions::SVGDocumentExtensions(Document& document) |
43 | : m_document(document) |
44 | , m_resourcesCache(std::make_unique<SVGResourcesCache>()) |
45 | , m_areAnimationsPaused(!document.page() || !document.page()->isVisible()) |
46 | { |
47 | } |
48 | |
49 | SVGDocumentExtensions::~SVGDocumentExtensions() = default; |
50 | |
51 | void SVGDocumentExtensions::addTimeContainer(SVGSVGElement& element) |
52 | { |
53 | m_timeContainers.add(&element); |
54 | if (m_areAnimationsPaused) |
55 | element.pauseAnimations(); |
56 | } |
57 | |
58 | void SVGDocumentExtensions::removeTimeContainer(SVGSVGElement& element) |
59 | { |
60 | m_timeContainers.remove(&element); |
61 | } |
62 | |
63 | void SVGDocumentExtensions::addResource(const AtomicString& id, RenderSVGResourceContainer& resource) |
64 | { |
65 | if (id.isEmpty()) |
66 | return; |
67 | |
68 | // Replaces resource if already present, to handle potential id changes |
69 | m_resources.set(id, &resource); |
70 | } |
71 | |
72 | void SVGDocumentExtensions::removeResource(const AtomicString& id) |
73 | { |
74 | if (id.isEmpty()) |
75 | return; |
76 | |
77 | m_resources.remove(id); |
78 | } |
79 | |
80 | RenderSVGResourceContainer* SVGDocumentExtensions::resourceById(const AtomicString& id) const |
81 | { |
82 | if (id.isEmpty()) |
83 | return 0; |
84 | |
85 | return m_resources.get(id); |
86 | } |
87 | |
88 | void SVGDocumentExtensions::startAnimations() |
89 | { |
90 | // FIXME: Eventually every "Time Container" will need a way to latch on to some global timer |
91 | // starting animations for a document will do this "latching" |
92 | // FIXME: We hold a ref pointers to prevent a shadow tree from getting removed out from underneath us. |
93 | // In the future we should refactor the use-element to avoid this. See https://webkit.org/b/53704 |
94 | Vector<RefPtr<SVGSVGElement>> timeContainers; |
95 | timeContainers.appendRange(m_timeContainers.begin(), m_timeContainers.end()); |
96 | for (auto& element : timeContainers) |
97 | element->timeContainer().begin(); |
98 | } |
99 | |
100 | void SVGDocumentExtensions::pauseAnimations() |
101 | { |
102 | for (auto& container : m_timeContainers) |
103 | container->pauseAnimations(); |
104 | m_areAnimationsPaused = true; |
105 | } |
106 | |
107 | void SVGDocumentExtensions::unpauseAnimations() |
108 | { |
109 | for (auto& container : m_timeContainers) |
110 | container->unpauseAnimations(); |
111 | m_areAnimationsPaused = false; |
112 | } |
113 | |
114 | void SVGDocumentExtensions::dispatchSVGLoadEventToOutermostSVGElements() |
115 | { |
116 | Vector<RefPtr<SVGSVGElement>> timeContainers; |
117 | timeContainers.appendRange(m_timeContainers.begin(), m_timeContainers.end()); |
118 | |
119 | for (auto& container : timeContainers) { |
120 | if (!container->isOutermostSVGSVGElement()) |
121 | continue; |
122 | container->sendSVGLoadEventIfPossible(); |
123 | } |
124 | } |
125 | |
126 | static void reportMessage(Document& document, MessageLevel level, const String& message) |
127 | { |
128 | if (document.frame()) |
129 | document.addConsoleMessage(MessageSource::Rendering, level, message); |
130 | } |
131 | |
132 | void SVGDocumentExtensions::reportWarning(const String& message) |
133 | { |
134 | reportMessage(m_document, MessageLevel::Warning, "Warning: " + message); |
135 | } |
136 | |
137 | void SVGDocumentExtensions::reportError(const String& message) |
138 | { |
139 | reportMessage(m_document, MessageLevel::Error, "Error: " + message); |
140 | } |
141 | |
142 | void SVGDocumentExtensions::addPendingResource(const AtomicString& id, Element& element) |
143 | { |
144 | if (id.isEmpty()) |
145 | return; |
146 | |
147 | auto result = m_pendingResources.add(id, nullptr); |
148 | if (result.isNewEntry) |
149 | result.iterator->value = std::make_unique<PendingElements>(); |
150 | result.iterator->value->add(&element); |
151 | |
152 | element.setHasPendingResources(); |
153 | } |
154 | |
155 | bool SVGDocumentExtensions::isIdOfPendingResource(const AtomicString& id) const |
156 | { |
157 | if (id.isEmpty()) |
158 | return false; |
159 | |
160 | return m_pendingResources.contains(id); |
161 | } |
162 | |
163 | bool SVGDocumentExtensions::isElementWithPendingResources(Element& element) const |
164 | { |
165 | // This algorithm takes time proportional to the number of pending resources and need not. |
166 | // If performance becomes an issue we can keep a counted set of elements and answer the question efficiently. |
167 | for (auto& elements : m_pendingResources.values()) { |
168 | ASSERT(elements); |
169 | if (elements->contains(&element)) |
170 | return true; |
171 | } |
172 | return false; |
173 | } |
174 | |
175 | bool SVGDocumentExtensions::isPendingResource(Element& element, const AtomicString& id) const |
176 | { |
177 | if (!isIdOfPendingResource(id)) |
178 | return false; |
179 | |
180 | return m_pendingResources.get(id)->contains(&element); |
181 | } |
182 | |
183 | void SVGDocumentExtensions::clearHasPendingResourcesIfPossible(Element& element) |
184 | { |
185 | if (!isElementWithPendingResources(element)) |
186 | element.clearHasPendingResources(); |
187 | } |
188 | |
189 | void SVGDocumentExtensions::removeElementFromPendingResources(Element& element) |
190 | { |
191 | // Remove the element from pending resources. |
192 | if (!m_pendingResources.isEmpty() && element.hasPendingResources()) { |
193 | Vector<AtomicString> toBeRemoved; |
194 | for (auto& resource : m_pendingResources) { |
195 | PendingElements* elements = resource.value.get(); |
196 | ASSERT(elements); |
197 | ASSERT(!elements->isEmpty()); |
198 | |
199 | elements->remove(&element); |
200 | if (elements->isEmpty()) |
201 | toBeRemoved.append(resource.key); |
202 | } |
203 | |
204 | clearHasPendingResourcesIfPossible(element); |
205 | |
206 | // We use the removePendingResource function here because it deals with set lifetime correctly. |
207 | for (auto& resource : toBeRemoved) |
208 | removePendingResource(resource); |
209 | } |
210 | |
211 | // Remove the element from pending resources that were scheduled for removal. |
212 | if (!m_pendingResourcesForRemoval.isEmpty()) { |
213 | Vector<AtomicString> toBeRemoved; |
214 | for (auto& resource : m_pendingResourcesForRemoval) { |
215 | PendingElements* elements = resource.value.get(); |
216 | ASSERT(elements); |
217 | ASSERT(!elements->isEmpty()); |
218 | |
219 | elements->remove(&element); |
220 | if (elements->isEmpty()) |
221 | toBeRemoved.append(resource.key); |
222 | } |
223 | |
224 | // We use the removePendingResourceForRemoval function here because it deals with set lifetime correctly. |
225 | for (auto& resource : toBeRemoved) |
226 | removePendingResourceForRemoval(resource); |
227 | } |
228 | } |
229 | |
230 | std::unique_ptr<SVGDocumentExtensions::PendingElements> SVGDocumentExtensions::removePendingResource(const AtomicString& id) |
231 | { |
232 | ASSERT(m_pendingResources.contains(id)); |
233 | return m_pendingResources.take(id); |
234 | } |
235 | |
236 | std::unique_ptr<SVGDocumentExtensions::PendingElements> SVGDocumentExtensions::removePendingResourceForRemoval(const AtomicString& id) |
237 | { |
238 | ASSERT(m_pendingResourcesForRemoval.contains(id)); |
239 | return m_pendingResourcesForRemoval.take(id); |
240 | } |
241 | |
242 | void SVGDocumentExtensions::markPendingResourcesForRemoval(const AtomicString& id) |
243 | { |
244 | if (id.isEmpty()) |
245 | return; |
246 | |
247 | ASSERT(!m_pendingResourcesForRemoval.contains(id)); |
248 | |
249 | std::unique_ptr<PendingElements> existing = m_pendingResources.take(id); |
250 | if (existing && !existing->isEmpty()) |
251 | m_pendingResourcesForRemoval.add(id, WTFMove(existing)); |
252 | } |
253 | |
254 | RefPtr<Element> SVGDocumentExtensions::removeElementFromPendingResourcesForRemovalMap(const AtomicString& id) |
255 | { |
256 | if (id.isEmpty()) |
257 | return 0; |
258 | |
259 | PendingElements* resourceSet = m_pendingResourcesForRemoval.get(id); |
260 | if (!resourceSet || resourceSet->isEmpty()) |
261 | return 0; |
262 | |
263 | auto firstElement = resourceSet->begin(); |
264 | RefPtr<Element> element = *firstElement; |
265 | |
266 | resourceSet->remove(firstElement); |
267 | |
268 | if (resourceSet->isEmpty()) |
269 | removePendingResourceForRemoval(id); |
270 | |
271 | return element; |
272 | } |
273 | |
274 | HashSet<SVGElement*>* SVGDocumentExtensions::setOfElementsReferencingTarget(SVGElement& referencedElement) const |
275 | { |
276 | return m_elementDependencies.get(&referencedElement); |
277 | } |
278 | |
279 | void SVGDocumentExtensions::addElementReferencingTarget(SVGElement& referencingElement, SVGElement& referencedElement) |
280 | { |
281 | auto result = m_elementDependencies.ensure(&referencedElement, [&referencingElement] { |
282 | return std::make_unique<HashSet<SVGElement*>>(std::initializer_list<SVGElement*> { &referencingElement }); |
283 | }); |
284 | if (!result.isNewEntry) |
285 | result.iterator->value->add(&referencingElement); |
286 | } |
287 | |
288 | void SVGDocumentExtensions::removeAllTargetReferencesForElement(SVGElement& referencingElement) |
289 | { |
290 | Vector<SVGElement*> toBeRemoved; |
291 | |
292 | for (auto& dependency : m_elementDependencies) { |
293 | auto& referencingElements = *dependency.value; |
294 | referencingElements.remove(&referencingElement); |
295 | if (referencingElements.isEmpty()) |
296 | toBeRemoved.append(dependency.key); |
297 | } |
298 | |
299 | for (auto& element : toBeRemoved) |
300 | m_elementDependencies.remove(element); |
301 | } |
302 | |
303 | void SVGDocumentExtensions::rebuildElements() |
304 | { |
305 | Vector<SVGElement*> shadowRebuildElements = WTFMove(m_rebuildElements); |
306 | for (auto* element : shadowRebuildElements) |
307 | element->svgAttributeChanged(SVGNames::hrefAttr); |
308 | } |
309 | |
310 | void SVGDocumentExtensions::clearTargetDependencies(SVGElement& referencedElement) |
311 | { |
312 | auto* referencingElements = m_elementDependencies.get(&referencedElement); |
313 | if (!referencingElements) |
314 | return; |
315 | for (auto* element : *referencingElements) { |
316 | m_rebuildElements.append(element); |
317 | element->callClearTarget(); |
318 | } |
319 | } |
320 | |
321 | void SVGDocumentExtensions::rebuildAllElementReferencesForTarget(SVGElement& referencedElement) |
322 | { |
323 | auto it = m_elementDependencies.find(&referencedElement); |
324 | if (it == m_elementDependencies.end()) |
325 | return; |
326 | ASSERT(it->key == &referencedElement); |
327 | |
328 | HashSet<SVGElement*>* referencingElements = it->value.get(); |
329 | Vector<SVGElement*> elementsToRebuild; |
330 | elementsToRebuild.reserveInitialCapacity(referencingElements->size()); |
331 | for (auto* element : *referencingElements) |
332 | elementsToRebuild.uncheckedAppend(element); |
333 | |
334 | for (auto* element : elementsToRebuild) |
335 | element->svgAttributeChanged(SVGNames::hrefAttr); |
336 | } |
337 | |
338 | void SVGDocumentExtensions::removeAllElementReferencesForTarget(SVGElement& referencedElement) |
339 | { |
340 | m_elementDependencies.remove(&referencedElement); |
341 | m_rebuildElements.removeFirst(&referencedElement); |
342 | } |
343 | |
344 | #if ENABLE(SVG_FONTS) |
345 | |
346 | void SVGDocumentExtensions::registerSVGFontFaceElement(SVGFontFaceElement& element) |
347 | { |
348 | m_svgFontFaceElements.add(&element); |
349 | } |
350 | |
351 | void SVGDocumentExtensions::unregisterSVGFontFaceElement(SVGFontFaceElement& element) |
352 | { |
353 | ASSERT(m_svgFontFaceElements.contains(&element)); |
354 | m_svgFontFaceElements.remove(&element); |
355 | } |
356 | |
357 | #endif |
358 | |
359 | } |
360 | |