1 | /* |
2 | * Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> |
3 | * Copyright (C) 2004, 2005, 2006, 2008 Rob Buis <buis@kde.org> |
4 | * Copyright (C) 2008-2019 Apple Inc. All rights reserved. |
5 | * Copyright (C) 2008 Alp Toker <alp@atoker.com> |
6 | * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au> |
7 | * Copyright (C) 2013 Samsung Electronics. All rights reserved. |
8 | * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved. |
9 | * |
10 | * This library is free software; you can redistribute it and/or |
11 | * modify it under the terms of the GNU Library General Public |
12 | * License as published by the Free Software Foundation; either |
13 | * version 2 of the License, or (at your option) any later version. |
14 | * |
15 | * This library is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | * Library General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU Library General Public License |
21 | * along with this library; see the file COPYING.LIB. If not, write to |
22 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
23 | * Boston, MA 02110-1301, USA. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "SVGElement.h" |
28 | |
29 | #include "CSSPropertyParser.h" |
30 | #include "DeprecatedCSSOMValue.h" |
31 | #include "Document.h" |
32 | #include "ElementIterator.h" |
33 | #include "Event.h" |
34 | #include "EventNames.h" |
35 | #include "HTMLElement.h" |
36 | #include "HTMLNames.h" |
37 | #include "HTMLParserIdioms.h" |
38 | #include "RenderObject.h" |
39 | #include "RenderSVGResource.h" |
40 | #include "RenderSVGResourceFilter.h" |
41 | #include "RenderSVGResourceMasker.h" |
42 | #include "SVGDocumentExtensions.h" |
43 | #include "SVGElementRareData.h" |
44 | #include "SVGGraphicsElement.h" |
45 | #include "SVGImageElement.h" |
46 | #include "SVGNames.h" |
47 | #include "SVGPropertyAnimatorFactory.h" |
48 | #include "SVGRenderStyle.h" |
49 | #include "SVGRenderSupport.h" |
50 | #include "SVGSVGElement.h" |
51 | #include "SVGTitleElement.h" |
52 | #include "SVGUseElement.h" |
53 | #include "ShadowRoot.h" |
54 | #include "XMLNames.h" |
55 | #include <wtf/Assertions.h> |
56 | #include <wtf/HashMap.h> |
57 | #include <wtf/IsoMallocInlines.h> |
58 | #include <wtf/NeverDestroyed.h> |
59 | #include <wtf/StdLibExtras.h> |
60 | #include <wtf/text/WTFString.h> |
61 | |
62 | |
63 | namespace WebCore { |
64 | |
65 | WTF_MAKE_ISO_ALLOCATED_IMPL(SVGElement); |
66 | |
67 | static NEVER_INLINE HashMap<AtomicStringImpl*, CSSPropertyID> createAttributeNameToCSSPropertyIDMap() |
68 | { |
69 | using namespace HTMLNames; |
70 | using namespace SVGNames; |
71 | |
72 | // This list should include all base CSS and SVG CSS properties which are exposed as SVG XML attributes. |
73 | static const QualifiedName* const attributeNames[] = { |
74 | &alignment_baselineAttr.get(), |
75 | &baseline_shiftAttr.get(), |
76 | &buffered_renderingAttr.get(), |
77 | &clipAttr.get(), |
78 | &clip_pathAttr.get(), |
79 | &clip_ruleAttr.get(), |
80 | &SVGNames::colorAttr.get(), |
81 | &color_interpolationAttr.get(), |
82 | &color_interpolation_filtersAttr.get(), |
83 | &color_profileAttr.get(), |
84 | &color_renderingAttr.get(), |
85 | &cursorAttr.get(), |
86 | &cxAttr.get(), |
87 | &cyAttr.get(), |
88 | &SVGNames::directionAttr.get(), |
89 | &displayAttr.get(), |
90 | &dominant_baselineAttr.get(), |
91 | &enable_backgroundAttr.get(), |
92 | &fillAttr.get(), |
93 | &fill_opacityAttr.get(), |
94 | &fill_ruleAttr.get(), |
95 | &filterAttr.get(), |
96 | &flood_colorAttr.get(), |
97 | &flood_opacityAttr.get(), |
98 | &font_familyAttr.get(), |
99 | &font_sizeAttr.get(), |
100 | &font_stretchAttr.get(), |
101 | &font_styleAttr.get(), |
102 | &font_variantAttr.get(), |
103 | &font_weightAttr.get(), |
104 | &glyph_orientation_horizontalAttr.get(), |
105 | &glyph_orientation_verticalAttr.get(), |
106 | &image_renderingAttr.get(), |
107 | &SVGNames::heightAttr.get(), |
108 | &kerningAttr.get(), |
109 | &letter_spacingAttr.get(), |
110 | &lighting_colorAttr.get(), |
111 | &marker_endAttr.get(), |
112 | &marker_midAttr.get(), |
113 | &marker_startAttr.get(), |
114 | &maskAttr.get(), |
115 | &mask_typeAttr.get(), |
116 | &opacityAttr.get(), |
117 | &overflowAttr.get(), |
118 | &paint_orderAttr.get(), |
119 | &pointer_eventsAttr.get(), |
120 | &rAttr.get(), |
121 | &rxAttr.get(), |
122 | &ryAttr.get(), |
123 | &shape_renderingAttr.get(), |
124 | &stop_colorAttr.get(), |
125 | &stop_opacityAttr.get(), |
126 | &strokeAttr.get(), |
127 | &stroke_dasharrayAttr.get(), |
128 | &stroke_dashoffsetAttr.get(), |
129 | &stroke_linecapAttr.get(), |
130 | &stroke_linejoinAttr.get(), |
131 | &stroke_miterlimitAttr.get(), |
132 | &stroke_opacityAttr.get(), |
133 | &stroke_widthAttr.get(), |
134 | &text_anchorAttr.get(), |
135 | &text_decorationAttr.get(), |
136 | &text_renderingAttr.get(), |
137 | &unicode_bidiAttr.get(), |
138 | &vector_effectAttr.get(), |
139 | &visibilityAttr.get(), |
140 | &SVGNames::widthAttr.get(), |
141 | &word_spacingAttr.get(), |
142 | &writing_modeAttr.get(), |
143 | &xAttr.get(), |
144 | &yAttr.get(), |
145 | }; |
146 | |
147 | HashMap<AtomicStringImpl*, CSSPropertyID> map; |
148 | |
149 | for (auto& name : attributeNames) { |
150 | const AtomicString& localName = name->localName(); |
151 | map.add(localName.impl(), cssPropertyID(localName)); |
152 | } |
153 | |
154 | // FIXME: When CSS supports "transform-origin" this special case can be removed, |
155 | // and we can add transform_originAttr to the table above instead. |
156 | map.add(transform_originAttr->localName().impl(), CSSPropertyTransformOrigin); |
157 | |
158 | return map; |
159 | } |
160 | |
161 | SVGElement::SVGElement(const QualifiedName& tagName, Document& document) |
162 | : StyledElement(tagName, document, CreateSVGElement) |
163 | , SVGLangSpace(this) |
164 | , m_propertyAnimatorFactory(std::make_unique<SVGPropertyAnimatorFactory>()) |
165 | { |
166 | static std::once_flag onceFlag; |
167 | std::call_once(onceFlag, [] { |
168 | PropertyRegistry::registerProperty<HTMLNames::classAttr, &SVGElement::m_className>(); |
169 | }); |
170 | } |
171 | |
172 | SVGElement::~SVGElement() |
173 | { |
174 | if (m_svgRareData) { |
175 | for (SVGElement* instance : m_svgRareData->instances()) |
176 | instance->m_svgRareData->setCorrespondingElement(nullptr); |
177 | if (auto correspondingElement = makeRefPtr(m_svgRareData->correspondingElement())) |
178 | correspondingElement->m_svgRareData->instances().remove(this); |
179 | |
180 | m_svgRareData = nullptr; |
181 | } |
182 | document().accessSVGExtensions().rebuildAllElementReferencesForTarget(*this); |
183 | document().accessSVGExtensions().removeAllElementReferencesForTarget(*this); |
184 | } |
185 | |
186 | int SVGElement::tabIndex() const |
187 | { |
188 | if (supportsFocus()) |
189 | return Element::tabIndex(); |
190 | return -1; |
191 | } |
192 | |
193 | void SVGElement::willRecalcStyle(Style::Change change) |
194 | { |
195 | if (!m_svgRareData || styleResolutionShouldRecompositeLayer()) |
196 | return; |
197 | // If the style changes because of a regular property change (not induced by SMIL animations themselves) |
198 | // reset the "computed style without SMIL style properties", so the base value change gets reflected. |
199 | if (change > Style::NoChange || needsStyleRecalc()) |
200 | m_svgRareData->setNeedsOverrideComputedStyleUpdate(); |
201 | } |
202 | |
203 | SVGElementRareData& SVGElement::ensureSVGRareData() |
204 | { |
205 | if (!m_svgRareData) |
206 | m_svgRareData = std::make_unique<SVGElementRareData>(); |
207 | return *m_svgRareData; |
208 | } |
209 | |
210 | bool SVGElement::isOutermostSVGSVGElement() const |
211 | { |
212 | if (!is<SVGSVGElement>(*this)) |
213 | return false; |
214 | |
215 | // If we're living in a shadow tree, we're a <svg> element that got created as replacement |
216 | // for a <symbol> element or a cloned <svg> element in the referenced tree. In that case |
217 | // we're always an inner <svg> element. |
218 | if (isInShadowTree() && parentOrShadowHostElement() && parentOrShadowHostElement()->isSVGElement()) |
219 | return false; |
220 | |
221 | // Element may not be in the document, pretend we're outermost for viewport(), getCTM(), etc. |
222 | if (!parentNode()) |
223 | return true; |
224 | |
225 | // We act like an outermost SVG element, if we're a direct child of a <foreignObject> element. |
226 | if (parentNode()->hasTagName(SVGNames::foreignObjectTag)) |
227 | return true; |
228 | |
229 | // This is true whenever this is the outermost SVG, even if there are HTML elements outside it |
230 | return !parentNode()->isSVGElement(); |
231 | } |
232 | |
233 | void SVGElement::reportAttributeParsingError(SVGParsingError error, const QualifiedName& name, const AtomicString& value) |
234 | { |
235 | if (error == NoError) |
236 | return; |
237 | |
238 | String errorString = "<" + tagName() + "> attribute " + name.toString() + "=\"" + value + "\"" ; |
239 | SVGDocumentExtensions& extensions = document().accessSVGExtensions(); |
240 | |
241 | if (error == NegativeValueForbiddenError) { |
242 | extensions.reportError("Invalid negative value for " + errorString); |
243 | return; |
244 | } |
245 | |
246 | if (error == ParsingAttributeFailedError) { |
247 | extensions.reportError("Invalid value for " + errorString); |
248 | return; |
249 | } |
250 | |
251 | ASSERT_NOT_REACHED(); |
252 | } |
253 | |
254 | void SVGElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
255 | { |
256 | if (removalType.disconnectedFromDocument) |
257 | updateRelativeLengthsInformation(false, this); |
258 | |
259 | StyledElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
260 | |
261 | if (removalType.disconnectedFromDocument) { |
262 | document().accessSVGExtensions().clearTargetDependencies(*this); |
263 | document().accessSVGExtensions().removeAllElementReferencesForTarget(*this); |
264 | } |
265 | invalidateInstances(); |
266 | } |
267 | |
268 | SVGSVGElement* SVGElement::ownerSVGElement() const |
269 | { |
270 | ContainerNode* node = parentOrShadowHostNode(); |
271 | while (node) { |
272 | if (is<SVGSVGElement>(*node)) |
273 | return downcast<SVGSVGElement>(node); |
274 | |
275 | node = node->parentOrShadowHostNode(); |
276 | } |
277 | |
278 | return nullptr; |
279 | } |
280 | |
281 | SVGElement* SVGElement::viewportElement() const |
282 | { |
283 | // This function needs shadow tree support - as RenderSVGContainer uses this function |
284 | // to determine the "overflow" property. <use> on <symbol> wouldn't work otherwhise. |
285 | ContainerNode* node = parentOrShadowHostNode(); |
286 | while (node) { |
287 | if (is<SVGSVGElement>(*node) || is<SVGImageElement>(*node) || node->hasTagName(SVGNames::symbolTag)) |
288 | return downcast<SVGElement>(node); |
289 | |
290 | node = node->parentOrShadowHostNode(); |
291 | } |
292 | |
293 | return nullptr; |
294 | } |
295 | |
296 | const HashSet<SVGElement*>& SVGElement::instances() const |
297 | { |
298 | if (!m_svgRareData) { |
299 | static NeverDestroyed<HashSet<SVGElement*>> emptyInstances; |
300 | return emptyInstances; |
301 | } |
302 | return m_svgRareData->instances(); |
303 | } |
304 | |
305 | bool SVGElement::getBoundingBox(FloatRect& rect, SVGLocatable::StyleUpdateStrategy styleUpdateStrategy) |
306 | { |
307 | if (is<SVGGraphicsElement>(*this)) { |
308 | rect = downcast<SVGGraphicsElement>(*this).getBBox(styleUpdateStrategy); |
309 | return true; |
310 | } |
311 | return false; |
312 | } |
313 | |
314 | SVGElement* SVGElement::correspondingElement() const |
315 | { |
316 | return m_svgRareData ? m_svgRareData->correspondingElement() : nullptr; |
317 | } |
318 | |
319 | RefPtr<SVGUseElement> SVGElement::correspondingUseElement() const |
320 | { |
321 | auto* root = containingShadowRoot(); |
322 | if (!root) |
323 | return nullptr; |
324 | if (root->mode() != ShadowRootMode::UserAgent) |
325 | return nullptr; |
326 | auto* host = root->host(); |
327 | if (!is<SVGUseElement>(host)) |
328 | return nullptr; |
329 | return &downcast<SVGUseElement>(*host); |
330 | } |
331 | |
332 | void SVGElement::setCorrespondingElement(SVGElement* correspondingElement) |
333 | { |
334 | if (m_svgRareData) { |
335 | if (auto oldCorrespondingElement = makeRefPtr(m_svgRareData->correspondingElement())) |
336 | oldCorrespondingElement->m_svgRareData->instances().remove(this); |
337 | } |
338 | if (m_svgRareData || correspondingElement) |
339 | ensureSVGRareData().setCorrespondingElement(correspondingElement); |
340 | if (correspondingElement) |
341 | correspondingElement->ensureSVGRareData().instances().add(this); |
342 | } |
343 | |
344 | void SVGElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
345 | { |
346 | if (name == HTMLNames::classAttr) { |
347 | m_className->setBaseValInternal(value); |
348 | return; |
349 | } |
350 | |
351 | if (name == HTMLNames::tabindexAttr) { |
352 | if (value.isEmpty()) |
353 | clearTabIndexExplicitlyIfNeeded(); |
354 | else if (auto optionalTabIndex = parseHTMLInteger(value)) |
355 | setTabIndexExplicitly(optionalTabIndex.value()); |
356 | return; |
357 | } |
358 | |
359 | auto& eventName = HTMLElement::eventNameForEventHandlerAttribute(name); |
360 | if (!eventName.isNull()) { |
361 | setAttributeEventListener(eventName, name, value); |
362 | return; |
363 | } |
364 | |
365 | SVGLangSpace::parseAttribute(name, value); |
366 | } |
367 | |
368 | bool SVGElement::haveLoadedRequiredResources() |
369 | { |
370 | for (auto& child : childrenOfType<SVGElement>(*this)) { |
371 | if (!child.haveLoadedRequiredResources()) |
372 | return false; |
373 | } |
374 | return true; |
375 | } |
376 | |
377 | bool SVGElement::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options) |
378 | { |
379 | // Add event listener to regular DOM element |
380 | if (!Node::addEventListener(eventType, listener.copyRef(), options)) |
381 | return false; |
382 | |
383 | if (containingShadowRoot()) |
384 | return true; |
385 | |
386 | // Add event listener to all shadow tree DOM element instances |
387 | ASSERT(!instanceUpdatesBlocked()); |
388 | for (auto* instance : instances()) { |
389 | ASSERT(instance->correspondingElement() == this); |
390 | bool result = instance->Node::addEventListener(eventType, listener.copyRef(), options); |
391 | ASSERT_UNUSED(result, result); |
392 | } |
393 | |
394 | return true; |
395 | } |
396 | |
397 | bool SVGElement::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options) |
398 | { |
399 | if (containingShadowRoot()) |
400 | return Node::removeEventListener(eventType, listener, options); |
401 | |
402 | // EventTarget::removeEventListener creates a Ref around the given EventListener |
403 | // object when creating a temporary RegisteredEventListener object used to look up the |
404 | // event listener in a cache. If we want to be able to call removeEventListener() multiple |
405 | // times on different nodes, we have to delay its immediate destruction, which would happen |
406 | // after the first call below. |
407 | Ref<EventListener> protector(listener); |
408 | |
409 | // Remove event listener from regular DOM element |
410 | if (!Node::removeEventListener(eventType, listener, options)) |
411 | return false; |
412 | |
413 | // Remove event listener from all shadow tree DOM element instances |
414 | ASSERT(!instanceUpdatesBlocked()); |
415 | for (auto& instance : instances()) { |
416 | ASSERT(instance->correspondingElement() == this); |
417 | |
418 | if (instance->Node::removeEventListener(eventType, listener, options)) |
419 | continue; |
420 | |
421 | // This case can only be hit for event listeners created from markup |
422 | ASSERT(listener.wasCreatedFromMarkup()); |
423 | |
424 | // If the event listener 'listener' has been created from markup and has been fired before |
425 | // then JSLazyEventListener::parseCode() has been called and m_jsFunction of that listener |
426 | // has been created (read: it's not 0 anymore). During shadow tree creation, the event |
427 | // listener DOM attribute has been cloned, and another event listener has been setup in |
428 | // the shadow tree. If that event listener has not been used yet, m_jsFunction is still 0, |
429 | // and tryRemoveEventListener() above will fail. Work around that very rare problem. |
430 | ASSERT(instance->eventTargetData()); |
431 | instance->eventTargetData()->eventListenerMap.removeFirstEventListenerCreatedFromMarkup(eventType); |
432 | } |
433 | |
434 | return true; |
435 | } |
436 | |
437 | static bool hasLoadListener(Element* element) |
438 | { |
439 | if (element->hasEventListeners(eventNames().loadEvent)) |
440 | return true; |
441 | |
442 | for (element = element->parentOrShadowHostElement(); element; element = element->parentOrShadowHostElement()) { |
443 | if (element->hasCapturingEventListeners(eventNames().loadEvent)) |
444 | return true; |
445 | } |
446 | |
447 | return false; |
448 | } |
449 | |
450 | void SVGElement::sendSVGLoadEventIfPossible(bool sendParentLoadEvents) |
451 | { |
452 | if (!isConnected() || !document().frame()) |
453 | return; |
454 | |
455 | RefPtr<SVGElement> currentTarget = this; |
456 | while (currentTarget && currentTarget->haveLoadedRequiredResources()) { |
457 | RefPtr<Element> parent; |
458 | if (sendParentLoadEvents) |
459 | parent = currentTarget->parentOrShadowHostElement(); // save the next parent to dispatch too incase dispatching the event changes the tree |
460 | if (hasLoadListener(currentTarget.get())) |
461 | currentTarget->dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
462 | currentTarget = (parent && parent->isSVGElement()) ? static_pointer_cast<SVGElement>(parent) : RefPtr<SVGElement>(); |
463 | SVGElement* element = currentTarget.get(); |
464 | if (!element || !element->isOutermostSVGSVGElement()) |
465 | continue; |
466 | |
467 | // Consider <svg onload="foo()"><image xlink:href="foo.png" externalResourcesRequired="true"/></svg>. |
468 | // If foo.png is not yet loaded, the first SVGLoad event will go to the <svg> element, sent through |
469 | // Document::implicitClose(). Then the SVGLoad event will fire for <image>, once its loaded. |
470 | ASSERT(sendParentLoadEvents); |
471 | |
472 | // If the load event was not sent yet by Document::implicitClose(), but the <image> from the example |
473 | // above, just appeared, don't send the SVGLoad event to the outermost <svg>, but wait for the document |
474 | // to be "ready to render", first. |
475 | if (!document().loadEventFinished()) |
476 | break; |
477 | } |
478 | } |
479 | |
480 | void SVGElement::sendSVGLoadEventIfPossibleAsynchronously() |
481 | { |
482 | svgLoadEventTimer()->startOneShot(0_s); |
483 | } |
484 | |
485 | void SVGElement::svgLoadEventTimerFired() |
486 | { |
487 | sendSVGLoadEventIfPossible(); |
488 | } |
489 | |
490 | Timer* SVGElement::svgLoadEventTimer() |
491 | { |
492 | ASSERT_NOT_REACHED(); |
493 | return nullptr; |
494 | } |
495 | |
496 | void SVGElement::finishParsingChildren() |
497 | { |
498 | StyledElement::finishParsingChildren(); |
499 | |
500 | // The outermost SVGSVGElement SVGLoad event is fired through Document::dispatchWindowLoadEvent. |
501 | if (isOutermostSVGSVGElement()) |
502 | return; |
503 | |
504 | // finishParsingChildren() is called when the close tag is reached for an element (e.g. </svg>) |
505 | // we send SVGLoad events here if we can, otherwise they'll be sent when any required loads finish |
506 | sendSVGLoadEventIfPossible(); |
507 | |
508 | // Notify all the elements which have references to this element to rebuild their shadow and render |
509 | // trees, e.g. a <use> element references a target element before this target element is defined. |
510 | invalidateInstances(); |
511 | } |
512 | |
513 | bool SVGElement::childShouldCreateRenderer(const Node& child) const |
514 | { |
515 | if (!child.isSVGElement()) |
516 | return false; |
517 | auto& svgChild = downcast<SVGElement>(child); |
518 | |
519 | static const QualifiedName* const invalidTextContent[] { |
520 | #if ENABLE(SVG_FONTS) |
521 | &SVGNames::altGlyphTag.get(), |
522 | #endif |
523 | &SVGNames::textPathTag.get(), |
524 | &SVGNames::trefTag.get(), |
525 | &SVGNames::tspanTag.get(), |
526 | }; |
527 | auto& name = svgChild.localName(); |
528 | for (auto* tag : invalidTextContent) { |
529 | if (name == tag->localName()) |
530 | return false; |
531 | } |
532 | |
533 | return svgChild.isValid(); |
534 | } |
535 | |
536 | void SVGElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason) |
537 | { |
538 | StyledElement::attributeChanged(name, oldValue, newValue); |
539 | |
540 | if (name == HTMLNames::idAttr) |
541 | document().accessSVGExtensions().rebuildAllElementReferencesForTarget(*this); |
542 | |
543 | // Changes to the style attribute are processed lazily (see Element::getAttribute() and related methods), |
544 | // so we don't want changes to the style attribute to result in extra work here except invalidateInstances(). |
545 | if (name == HTMLNames::styleAttr) |
546 | invalidateInstances(); |
547 | else |
548 | svgAttributeChanged(name); |
549 | } |
550 | |
551 | void SVGElement::synchronizeAttribute(const QualifiedName& name) |
552 | { |
553 | // If the value of the property has changed, serialize the new value to the attribute. |
554 | if (auto value = propertyRegistry().synchronize(name)) |
555 | setSynchronizedLazyAttribute(name, *value); |
556 | } |
557 | |
558 | void SVGElement::synchronizeAllAttributes() |
559 | { |
560 | // SVGPropertyRegistry::synchronizeAllAttributes() returns the new values of |
561 | // the properties which have changed but not committed yet. |
562 | auto map = propertyRegistry().synchronizeAllAttributes(); |
563 | for (const auto& entry : map) |
564 | setSynchronizedLazyAttribute(entry.key, entry.value); |
565 | } |
566 | |
567 | void SVGElement::synchronizeAllAnimatedSVGAttribute(SVGElement& svgElement) |
568 | { |
569 | svgElement.synchronizeAllAttributes(); |
570 | } |
571 | |
572 | void SVGElement::commitPropertyChange(SVGProperty* property) |
573 | { |
574 | // We want to dirty the top-level property when a descendant changes. For example |
575 | // a change in an SVGLength item in SVGLengthList should set the dirty flag on |
576 | // SVGLengthList and not the SVGLength. |
577 | property->setDirty(); |
578 | |
579 | invalidateSVGAttributes(); |
580 | svgAttributeChanged(propertyRegistry().propertyAttributeName(*property)); |
581 | } |
582 | |
583 | void SVGElement::commitPropertyChange(SVGAnimatedProperty& animatedProperty) |
584 | { |
585 | QualifiedName attributeName = propertyRegistry().animatedPropertyAttributeName(animatedProperty); |
586 | ASSERT(attributeName != nullQName()); |
587 | |
588 | // A change in a style property, e.g SVGRectElement::x should be serialized to |
589 | // the attribute immediately. Otherwise it is okay to be lazy in this regard. |
590 | if (!propertyRegistry().isAnimatedStylePropertyAttribute(attributeName)) |
591 | animatedProperty.setDirty(); |
592 | else |
593 | setSynchronizedLazyAttribute(attributeName, animatedProperty.baseValAsString()); |
594 | |
595 | invalidateSVGAttributes(); |
596 | svgAttributeChanged(attributeName); |
597 | } |
598 | |
599 | bool SVGElement::isAnimatedPropertyAttribute(const QualifiedName& attributeName) const |
600 | { |
601 | return propertyRegistry().isAnimatedPropertyAttribute(attributeName); |
602 | } |
603 | |
604 | bool SVGElement::isAnimatedAttribute(const QualifiedName& attributeName) const |
605 | { |
606 | return SVGPropertyAnimatorFactory::isKnownAttribute(attributeName) || isAnimatedPropertyAttribute(attributeName); |
607 | } |
608 | |
609 | bool SVGElement::isAnimatedStyleAttribute(const QualifiedName& attributeName) const |
610 | { |
611 | return SVGPropertyAnimatorFactory::isKnownAttribute(attributeName) || propertyRegistry().isAnimatedStylePropertyAttribute(attributeName); |
612 | } |
613 | |
614 | std::unique_ptr<SVGAttributeAnimator> SVGElement::createAnimator(const QualifiedName& attributeName, AnimationMode animationMode, CalcMode calcMode, bool isAccumulated, bool isAdditive) |
615 | { |
616 | // Property animator, e.g. "fill" or "fill-opacity". |
617 | if (auto animator = propertyAnimatorFactory().createAnimator(attributeName, animationMode, calcMode, isAccumulated, isAdditive)) |
618 | return animator; |
619 | |
620 | // Animated property animator. |
621 | auto animator = propertyRegistry().createAnimator(attributeName, animationMode, calcMode, isAccumulated, isAdditive); |
622 | if (!animator) |
623 | return animator; |
624 | for (auto* instance : instances()) |
625 | instance->propertyRegistry().appendAnimatedInstance(attributeName, *animator); |
626 | return animator; |
627 | } |
628 | |
629 | void SVGElement::animatorWillBeDeleted(const QualifiedName& attributeName) |
630 | { |
631 | propertyAnimatorFactory().animatorWillBeDeleted(attributeName); |
632 | } |
633 | |
634 | Optional<ElementStyle> SVGElement::resolveCustomStyle(const RenderStyle& parentStyle, const RenderStyle*) |
635 | { |
636 | // If the element is in a <use> tree we get the style from the definition tree. |
637 | if (auto styleElement = makeRefPtr(this->correspondingElement())) { |
638 | Optional<ElementStyle> style = styleElement->resolveStyle(&parentStyle); |
639 | StyleResolver::adjustSVGElementStyle(*this, *style->renderStyle); |
640 | return style; |
641 | } |
642 | |
643 | return resolveStyle(&parentStyle); |
644 | } |
645 | |
646 | MutableStyleProperties* SVGElement::animatedSMILStyleProperties() const |
647 | { |
648 | if (m_svgRareData) |
649 | return m_svgRareData->animatedSMILStyleProperties(); |
650 | return 0; |
651 | } |
652 | |
653 | MutableStyleProperties& SVGElement::ensureAnimatedSMILStyleProperties() |
654 | { |
655 | return ensureSVGRareData().ensureAnimatedSMILStyleProperties(); |
656 | } |
657 | |
658 | void SVGElement::setUseOverrideComputedStyle(bool value) |
659 | { |
660 | if (m_svgRareData) |
661 | m_svgRareData->setUseOverrideComputedStyle(value); |
662 | } |
663 | |
664 | const RenderStyle* SVGElement::computedStyle(PseudoId pseudoElementSpecifier) |
665 | { |
666 | if (!m_svgRareData || !m_svgRareData->useOverrideComputedStyle()) |
667 | return Element::computedStyle(pseudoElementSpecifier); |
668 | |
669 | const RenderStyle* parentStyle = nullptr; |
670 | if (auto parent = makeRefPtr(parentOrShadowHostElement())) { |
671 | if (auto renderer = parent->renderer()) |
672 | parentStyle = &renderer->style(); |
673 | } |
674 | |
675 | return m_svgRareData->overrideComputedStyle(*this, parentStyle); |
676 | } |
677 | |
678 | QualifiedName SVGElement::animatableAttributeForName(const AtomicString& localName) |
679 | { |
680 | static const auto animatableAttributes = makeNeverDestroyed([] { |
681 | static const QualifiedName* const names[] = { |
682 | &HTMLNames::classAttr.get(), |
683 | &SVGNames::amplitudeAttr.get(), |
684 | &SVGNames::azimuthAttr.get(), |
685 | &SVGNames::baseFrequencyAttr.get(), |
686 | &SVGNames::biasAttr.get(), |
687 | &SVGNames::clipPathUnitsAttr.get(), |
688 | &SVGNames::cxAttr.get(), |
689 | &SVGNames::cyAttr.get(), |
690 | &SVGNames::diffuseConstantAttr.get(), |
691 | &SVGNames::divisorAttr.get(), |
692 | &SVGNames::dxAttr.get(), |
693 | &SVGNames::dyAttr.get(), |
694 | &SVGNames::edgeModeAttr.get(), |
695 | &SVGNames::elevationAttr.get(), |
696 | &SVGNames::exponentAttr.get(), |
697 | &SVGNames::externalResourcesRequiredAttr.get(), |
698 | &SVGNames::filterUnitsAttr.get(), |
699 | &SVGNames::fxAttr.get(), |
700 | &SVGNames::fyAttr.get(), |
701 | &SVGNames::gradientTransformAttr.get(), |
702 | &SVGNames::gradientUnitsAttr.get(), |
703 | &SVGNames::heightAttr.get(), |
704 | &SVGNames::in2Attr.get(), |
705 | &SVGNames::inAttr.get(), |
706 | &SVGNames::interceptAttr.get(), |
707 | &SVGNames::k1Attr.get(), |
708 | &SVGNames::k2Attr.get(), |
709 | &SVGNames::k3Attr.get(), |
710 | &SVGNames::k4Attr.get(), |
711 | &SVGNames::kernelMatrixAttr.get(), |
712 | &SVGNames::kernelUnitLengthAttr.get(), |
713 | &SVGNames::lengthAdjustAttr.get(), |
714 | &SVGNames::limitingConeAngleAttr.get(), |
715 | &SVGNames::markerHeightAttr.get(), |
716 | &SVGNames::markerUnitsAttr.get(), |
717 | &SVGNames::markerWidthAttr.get(), |
718 | &SVGNames::maskContentUnitsAttr.get(), |
719 | &SVGNames::maskUnitsAttr.get(), |
720 | &SVGNames::methodAttr.get(), |
721 | &SVGNames::modeAttr.get(), |
722 | &SVGNames::numOctavesAttr.get(), |
723 | &SVGNames::offsetAttr.get(), |
724 | &SVGNames::operatorAttr.get(), |
725 | &SVGNames::orderAttr.get(), |
726 | &SVGNames::orientAttr.get(), |
727 | &SVGNames::pathLengthAttr.get(), |
728 | &SVGNames::patternContentUnitsAttr.get(), |
729 | &SVGNames::patternTransformAttr.get(), |
730 | &SVGNames::patternUnitsAttr.get(), |
731 | &SVGNames::pointsAtXAttr.get(), |
732 | &SVGNames::pointsAtYAttr.get(), |
733 | &SVGNames::pointsAtZAttr.get(), |
734 | &SVGNames::preserveAlphaAttr.get(), |
735 | &SVGNames::preserveAspectRatioAttr.get(), |
736 | &SVGNames::primitiveUnitsAttr.get(), |
737 | &SVGNames::radiusAttr.get(), |
738 | &SVGNames::rAttr.get(), |
739 | &SVGNames::refXAttr.get(), |
740 | &SVGNames::refYAttr.get(), |
741 | &SVGNames::resultAttr.get(), |
742 | &SVGNames::rotateAttr.get(), |
743 | &SVGNames::rxAttr.get(), |
744 | &SVGNames::ryAttr.get(), |
745 | &SVGNames::scaleAttr.get(), |
746 | &SVGNames::seedAttr.get(), |
747 | &SVGNames::slopeAttr.get(), |
748 | &SVGNames::spacingAttr.get(), |
749 | &SVGNames::specularConstantAttr.get(), |
750 | &SVGNames::specularExponentAttr.get(), |
751 | &SVGNames::spreadMethodAttr.get(), |
752 | &SVGNames::startOffsetAttr.get(), |
753 | &SVGNames::stdDeviationAttr.get(), |
754 | &SVGNames::stitchTilesAttr.get(), |
755 | &SVGNames::surfaceScaleAttr.get(), |
756 | &SVGNames::tableValuesAttr.get(), |
757 | &SVGNames::targetAttr.get(), |
758 | &SVGNames::targetXAttr.get(), |
759 | &SVGNames::targetYAttr.get(), |
760 | &SVGNames::transformAttr.get(), |
761 | &SVGNames::typeAttr.get(), |
762 | &SVGNames::valuesAttr.get(), |
763 | &SVGNames::viewBoxAttr.get(), |
764 | &SVGNames::widthAttr.get(), |
765 | &SVGNames::x1Attr.get(), |
766 | &SVGNames::x2Attr.get(), |
767 | &SVGNames::xAttr.get(), |
768 | &SVGNames::xChannelSelectorAttr.get(), |
769 | &SVGNames::y1Attr.get(), |
770 | &SVGNames::y2Attr.get(), |
771 | &SVGNames::yAttr.get(), |
772 | &SVGNames::yChannelSelectorAttr.get(), |
773 | &SVGNames::zAttr.get(), |
774 | &SVGNames::hrefAttr.get(), |
775 | }; |
776 | HashMap<AtomicString, QualifiedName> map; |
777 | for (auto& name : names) { |
778 | auto addResult = map.add(name->localName(), *name); |
779 | ASSERT_UNUSED(addResult, addResult.isNewEntry); |
780 | } |
781 | return map; |
782 | }()); |
783 | return animatableAttributes.get().get(localName); |
784 | } |
785 | |
786 | #ifndef NDEBUG |
787 | |
788 | bool SVGElement::isAnimatableAttribute(const QualifiedName& name) const |
789 | { |
790 | if (animatableAttributeForName(name.localName()) == name) |
791 | return !filterOutAnimatableAttribute(name); |
792 | return false; |
793 | } |
794 | |
795 | bool SVGElement::filterOutAnimatableAttribute(const QualifiedName&) const |
796 | { |
797 | return false; |
798 | } |
799 | |
800 | #endif |
801 | |
802 | String SVGElement::title() const |
803 | { |
804 | // According to spec, for stand-alone SVG documents we should not return a title when |
805 | // hovering over the rootmost SVG element (the first <title> element is the title of |
806 | // the document, not a tooltip) so we instantly return. |
807 | if (isOutermostSVGSVGElement() && document().topDocument().isSVGDocument()) |
808 | return String(); |
809 | auto firstTitle = childrenOfType<SVGTitleElement>(*this).first(); |
810 | return firstTitle ? const_cast<SVGTitleElement*>(firstTitle)->innerText() : String(); |
811 | } |
812 | |
813 | bool SVGElement::rendererIsNeeded(const RenderStyle& style) |
814 | { |
815 | // http://www.w3.org/TR/SVG/extend.html#PrivateData |
816 | // Prevent anything other than SVG renderers from appearing in our render tree |
817 | // Spec: SVG allows inclusion of elements from foreign namespaces anywhere |
818 | // with the SVG content. In general, the SVG user agent will include the unknown |
819 | // elements in the DOM but will otherwise ignore unknown elements. |
820 | if (!parentOrShadowHostElement() || parentOrShadowHostElement()->isSVGElement()) |
821 | return StyledElement::rendererIsNeeded(style); |
822 | |
823 | return false; |
824 | } |
825 | |
826 | CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& attrName) |
827 | { |
828 | if (!attrName.namespaceURI().isNull()) |
829 | return CSSPropertyInvalid; |
830 | |
831 | static const auto properties = makeNeverDestroyed(createAttributeNameToCSSPropertyIDMap()); |
832 | return properties.get().get(attrName.localName().impl()); |
833 | } |
834 | |
835 | bool SVGElement::isPresentationAttribute(const QualifiedName& name) const |
836 | { |
837 | if (cssPropertyIdForSVGAttributeName(name) > 0) |
838 | return true; |
839 | return StyledElement::isPresentationAttribute(name); |
840 | } |
841 | |
842 | void SVGElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style) |
843 | { |
844 | CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name); |
845 | if (propertyID > 0) |
846 | addPropertyToPresentationAttributeStyle(style, propertyID, value); |
847 | } |
848 | |
849 | void SVGElement::svgAttributeChanged(const QualifiedName& attrName) |
850 | { |
851 | CSSPropertyID propId = cssPropertyIdForSVGAttributeName(attrName); |
852 | if (propId > 0) { |
853 | invalidateInstances(); |
854 | return; |
855 | } |
856 | |
857 | if (attrName == HTMLNames::classAttr) { |
858 | classAttributeChanged(className()); |
859 | invalidateInstances(); |
860 | return; |
861 | } |
862 | |
863 | if (attrName == HTMLNames::idAttr) { |
864 | auto renderer = this->renderer(); |
865 | // Notify resources about id changes, this is important as we cache resources by id in SVGDocumentExtensions |
866 | if (is<RenderSVGResourceContainer>(renderer)) |
867 | downcast<RenderSVGResourceContainer>(*renderer).idChanged(); |
868 | if (isConnected()) |
869 | buildPendingResourcesIfNeeded(); |
870 | invalidateInstances(); |
871 | return; |
872 | } |
873 | |
874 | SVGLangSpace::svgAttributeChanged(attrName); |
875 | } |
876 | |
877 | Node::InsertedIntoAncestorResult SVGElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
878 | { |
879 | StyledElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
880 | updateRelativeLengthsInformation(); |
881 | buildPendingResourcesIfNeeded(); |
882 | return InsertedIntoAncestorResult::Done; |
883 | } |
884 | |
885 | void SVGElement::buildPendingResourcesIfNeeded() |
886 | { |
887 | if (!needsPendingResourceHandling() || !isConnected() || isInShadowTree()) |
888 | return; |
889 | |
890 | SVGDocumentExtensions& extensions = document().accessSVGExtensions(); |
891 | String resourceId = getIdAttribute(); |
892 | if (!extensions.isIdOfPendingResource(resourceId)) |
893 | return; |
894 | |
895 | // Mark pending resources as pending for removal. |
896 | extensions.markPendingResourcesForRemoval(resourceId); |
897 | |
898 | // Rebuild pending resources for each client of a pending resource that is being removed. |
899 | while (auto clientElement = extensions.removeElementFromPendingResourcesForRemovalMap(resourceId)) { |
900 | ASSERT(clientElement->hasPendingResources()); |
901 | if (clientElement->hasPendingResources()) { |
902 | clientElement->buildPendingResource(); |
903 | extensions.clearHasPendingResourcesIfPossible(*clientElement); |
904 | } |
905 | } |
906 | } |
907 | |
908 | void SVGElement::childrenChanged(const ChildChange& change) |
909 | { |
910 | StyledElement::childrenChanged(change); |
911 | |
912 | if (change.source == ChildChangeSource::Parser) |
913 | return; |
914 | invalidateInstances(); |
915 | } |
916 | |
917 | RefPtr<DeprecatedCSSOMValue> SVGElement::getPresentationAttribute(const String& name) |
918 | { |
919 | if (!hasAttributesWithoutUpdate()) |
920 | return 0; |
921 | |
922 | QualifiedName attributeName(nullAtom(), name, nullAtom()); |
923 | const Attribute* attribute = findAttributeByName(attributeName); |
924 | if (!attribute) |
925 | return 0; |
926 | |
927 | auto style = MutableStyleProperties::create(SVGAttributeMode); |
928 | CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(attribute->name()); |
929 | style->setProperty(propertyID, attribute->value()); |
930 | auto cssValue = style->getPropertyCSSValue(propertyID); |
931 | if (!cssValue) |
932 | return nullptr; |
933 | return cssValue->createDeprecatedCSSOMWrapper(style->ensureCSSStyleDeclaration()); |
934 | } |
935 | |
936 | bool SVGElement::instanceUpdatesBlocked() const |
937 | { |
938 | return m_svgRareData && m_svgRareData->instanceUpdatesBlocked(); |
939 | } |
940 | |
941 | void SVGElement::setInstanceUpdatesBlocked(bool value) |
942 | { |
943 | // Catch any callers that calls setInstanceUpdatesBlocked(true) twice in a row. |
944 | // That probably indicates nested use of InstanceUpdateBlocker and a bug. |
945 | ASSERT(!value || !instanceUpdatesBlocked()); |
946 | |
947 | if (m_svgRareData) |
948 | m_svgRareData->setInstanceUpdatesBlocked(value); |
949 | } |
950 | |
951 | AffineTransform SVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope) const |
952 | { |
953 | // To be overridden by SVGGraphicsElement (or as special case SVGTextElement and SVGPatternElement) |
954 | return AffineTransform(); |
955 | } |
956 | |
957 | void SVGElement::updateRelativeLengthsInformation(bool hasRelativeLengths, SVGElement* element) |
958 | { |
959 | // If we're not yet in a document, this function will be called again from insertedIntoAncestor(). Do nothing now. |
960 | if (!isConnected()) |
961 | return; |
962 | |
963 | // An element wants to notify us that its own relative lengths state changed. |
964 | // Register it in the relative length map, and register us in the parent relative length map. |
965 | // Register the parent in the grandparents map, etc. Repeat procedure until the root of the SVG tree. |
966 | |
967 | if (hasRelativeLengths) |
968 | m_elementsWithRelativeLengths.add(element); |
969 | else { |
970 | if (!m_elementsWithRelativeLengths.contains(element)) { |
971 | // We were never registered. Do nothing. |
972 | return; |
973 | } |
974 | |
975 | m_elementsWithRelativeLengths.remove(element); |
976 | } |
977 | |
978 | if (!element->isSVGGraphicsElement()) |
979 | return; |
980 | |
981 | // Find first styled parent node, and notify it that we've changed our relative length state. |
982 | auto node = makeRefPtr(parentNode()); |
983 | while (node) { |
984 | if (!node->isSVGElement()) |
985 | break; |
986 | |
987 | // Register us in the parent element map. |
988 | downcast<SVGElement>(*node).updateRelativeLengthsInformation(hasRelativeLengths, this); |
989 | break; |
990 | } |
991 | } |
992 | |
993 | bool SVGElement::hasFocusEventListeners() const |
994 | { |
995 | Element* eventTarget = const_cast<SVGElement*>(this); |
996 | return eventTarget->hasEventListeners(eventNames().focusinEvent) |
997 | || eventTarget->hasEventListeners(eventNames().focusoutEvent) |
998 | || eventTarget->hasEventListeners(eventNames().focusEvent) |
999 | || eventTarget->hasEventListeners(eventNames().blurEvent); |
1000 | } |
1001 | |
1002 | bool SVGElement::isMouseFocusable() const |
1003 | { |
1004 | if (!isFocusable()) |
1005 | return false; |
1006 | Element* eventTarget = const_cast<SVGElement*>(this); |
1007 | return hasFocusEventListeners() |
1008 | || eventTarget->hasEventListeners(eventNames().keydownEvent) |
1009 | || eventTarget->hasEventListeners(eventNames().keyupEvent) |
1010 | || eventTarget->hasEventListeners(eventNames().keypressEvent); |
1011 | } |
1012 | |
1013 | void SVGElement::accessKeyAction(bool sendMouseEvents) |
1014 | { |
1015 | dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); |
1016 | } |
1017 | |
1018 | void SVGElement::invalidateInstances() |
1019 | { |
1020 | if (instanceUpdatesBlocked()) |
1021 | return; |
1022 | |
1023 | auto& instances = this->instances(); |
1024 | while (!instances.isEmpty()) { |
1025 | auto instance = makeRefPtr(*instances.begin()); |
1026 | if (auto useElement = instance->correspondingUseElement()) |
1027 | useElement->invalidateShadowTree(); |
1028 | instance->setCorrespondingElement(nullptr); |
1029 | } while (!instances.isEmpty()); |
1030 | } |
1031 | |
1032 | } |
1033 | |