| /* |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) 2004, 2005, 2006, 2008 Rob Buis <buis@kde.org> |
| * Copyright (C) 2008-2019 Apple Inc. All rights reserved. |
| * Copyright (C) 2008 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au> |
| * Copyright (C) 2013 Samsung Electronics. All rights reserved. |
| * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "SVGElement.h" |
| |
| #include "CSSPropertyParser.h" |
| #include "DeprecatedCSSOMValue.h" |
| #include "Document.h" |
| #include "ElementIterator.h" |
| #include "Event.h" |
| #include "EventNames.h" |
| #include "HTMLElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLParserIdioms.h" |
| #include "RenderObject.h" |
| #include "RenderSVGResource.h" |
| #include "RenderSVGResourceFilter.h" |
| #include "RenderSVGResourceMasker.h" |
| #include "SVGDocumentExtensions.h" |
| #include "SVGElementRareData.h" |
| #include "SVGGraphicsElement.h" |
| #include "SVGImageElement.h" |
| #include "SVGNames.h" |
| #include "SVGPropertyAnimatorFactory.h" |
| #include "SVGRenderStyle.h" |
| #include "SVGRenderSupport.h" |
| #include "SVGSVGElement.h" |
| #include "SVGTitleElement.h" |
| #include "SVGUseElement.h" |
| #include "ShadowRoot.h" |
| #include "XMLNames.h" |
| #include <wtf/Assertions.h> |
| #include <wtf/HashMap.h> |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/StdLibExtras.h> |
| #include <wtf/text/WTFString.h> |
| |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(SVGElement); |
| |
| static NEVER_INLINE HashMap<AtomStringImpl*, CSSPropertyID> createAttributeNameToCSSPropertyIDMap() |
| { |
| using namespace HTMLNames; |
| using namespace SVGNames; |
| |
| // This list should include all base CSS and SVG CSS properties which are exposed as SVG XML attributes. |
| static const QualifiedName* const attributeNames[] = { |
| &alignment_baselineAttr.get(), |
| &baseline_shiftAttr.get(), |
| &buffered_renderingAttr.get(), |
| &clipAttr.get(), |
| &clip_pathAttr.get(), |
| &clip_ruleAttr.get(), |
| &SVGNames::colorAttr.get(), |
| &color_interpolationAttr.get(), |
| &color_interpolation_filtersAttr.get(), |
| &color_profileAttr.get(), |
| &color_renderingAttr.get(), |
| &cursorAttr.get(), |
| &cxAttr.get(), |
| &cyAttr.get(), |
| &SVGNames::directionAttr.get(), |
| &displayAttr.get(), |
| &dominant_baselineAttr.get(), |
| &enable_backgroundAttr.get(), |
| &fillAttr.get(), |
| &fill_opacityAttr.get(), |
| &fill_ruleAttr.get(), |
| &filterAttr.get(), |
| &flood_colorAttr.get(), |
| &flood_opacityAttr.get(), |
| &font_familyAttr.get(), |
| &font_sizeAttr.get(), |
| &font_stretchAttr.get(), |
| &font_styleAttr.get(), |
| &font_variantAttr.get(), |
| &font_weightAttr.get(), |
| &glyph_orientation_horizontalAttr.get(), |
| &glyph_orientation_verticalAttr.get(), |
| &image_renderingAttr.get(), |
| &SVGNames::heightAttr.get(), |
| &kerningAttr.get(), |
| &letter_spacingAttr.get(), |
| &lighting_colorAttr.get(), |
| &marker_endAttr.get(), |
| &marker_midAttr.get(), |
| &marker_startAttr.get(), |
| &maskAttr.get(), |
| &mask_typeAttr.get(), |
| &opacityAttr.get(), |
| &overflowAttr.get(), |
| &paint_orderAttr.get(), |
| &pointer_eventsAttr.get(), |
| &rAttr.get(), |
| &rxAttr.get(), |
| &ryAttr.get(), |
| &shape_renderingAttr.get(), |
| &stop_colorAttr.get(), |
| &stop_opacityAttr.get(), |
| &strokeAttr.get(), |
| &stroke_dasharrayAttr.get(), |
| &stroke_dashoffsetAttr.get(), |
| &stroke_linecapAttr.get(), |
| &stroke_linejoinAttr.get(), |
| &stroke_miterlimitAttr.get(), |
| &stroke_opacityAttr.get(), |
| &stroke_widthAttr.get(), |
| &text_anchorAttr.get(), |
| &text_decorationAttr.get(), |
| &text_renderingAttr.get(), |
| &unicode_bidiAttr.get(), |
| &vector_effectAttr.get(), |
| &visibilityAttr.get(), |
| &SVGNames::widthAttr.get(), |
| &word_spacingAttr.get(), |
| &writing_modeAttr.get(), |
| &xAttr.get(), |
| &yAttr.get(), |
| }; |
| |
| HashMap<AtomStringImpl*, CSSPropertyID> map; |
| |
| for (auto& name : attributeNames) { |
| const AtomString& localName = name->localName(); |
| map.add(localName.impl(), cssPropertyID(localName)); |
| } |
| |
| // FIXME: When CSS supports "transform-origin" this special case can be removed, |
| // and we can add transform_originAttr to the table above instead. |
| map.add(transform_originAttr->localName().impl(), CSSPropertyTransformOrigin); |
| |
| return map; |
| } |
| |
| SVGElement::SVGElement(const QualifiedName& tagName, Document& document) |
| : StyledElement(tagName, document, CreateSVGElement) |
| , SVGLangSpace(this) |
| , m_propertyAnimatorFactory(makeUnique<SVGPropertyAnimatorFactory>()) |
| { |
| static std::once_flag onceFlag; |
| std::call_once(onceFlag, [] { |
| PropertyRegistry::registerProperty<HTMLNames::classAttr, &SVGElement::m_className>(); |
| }); |
| } |
| |
| SVGElement::~SVGElement() |
| { |
| if (m_svgRareData) { |
| for (SVGElement* instance : m_svgRareData->instances()) |
| instance->m_svgRareData->setCorrespondingElement(nullptr); |
| if (auto correspondingElement = makeRefPtr(m_svgRareData->correspondingElement())) |
| correspondingElement->m_svgRareData->instances().remove(this); |
| |
| m_svgRareData = nullptr; |
| } |
| document().accessSVGExtensions().rebuildAllElementReferencesForTarget(*this); |
| document().accessSVGExtensions().removeAllElementReferencesForTarget(*this); |
| } |
| |
| void SVGElement::willRecalcStyle(Style::Change change) |
| { |
| if (!m_svgRareData || styleResolutionShouldRecompositeLayer()) |
| return; |
| // If the style changes because of a regular property change (not induced by SMIL animations themselves) |
| // reset the "computed style without SMIL style properties", so the base value change gets reflected. |
| if (change > Style::NoChange || needsStyleRecalc()) |
| m_svgRareData->setNeedsOverrideComputedStyleUpdate(); |
| } |
| |
| SVGElementRareData& SVGElement::ensureSVGRareData() |
| { |
| if (!m_svgRareData) |
| m_svgRareData = makeUnique<SVGElementRareData>(); |
| return *m_svgRareData; |
| } |
| |
| bool SVGElement::isOutermostSVGSVGElement() const |
| { |
| if (!is<SVGSVGElement>(*this)) |
| return false; |
| |
| // If we're living in a shadow tree, we're a <svg> element that got created as replacement |
| // for a <symbol> element or a cloned <svg> element in the referenced tree. In that case |
| // we're always an inner <svg> element. |
| if (isInShadowTree() && parentOrShadowHostElement() && parentOrShadowHostElement()->isSVGElement()) |
| return false; |
| |
| // Element may not be in the document, pretend we're outermost for viewport(), getCTM(), etc. |
| if (!parentNode()) |
| return true; |
| |
| // We act like an outermost SVG element, if we're a direct child of a <foreignObject> element. |
| if (parentNode()->hasTagName(SVGNames::foreignObjectTag)) |
| return true; |
| |
| // This is true whenever this is the outermost SVG, even if there are HTML elements outside it |
| return !parentNode()->isSVGElement(); |
| } |
| |
| void SVGElement::reportAttributeParsingError(SVGParsingError error, const QualifiedName& name, const AtomString& value) |
| { |
| if (error == NoError) |
| return; |
| |
| String errorString = "<" + tagName() + "> attribute " + name.toString() + "=\"" + value + "\""; |
| SVGDocumentExtensions& extensions = document().accessSVGExtensions(); |
| |
| if (error == NegativeValueForbiddenError) { |
| extensions.reportError("Invalid negative value for " + errorString); |
| return; |
| } |
| |
| if (error == ParsingAttributeFailedError) { |
| extensions.reportError("Invalid value for " + errorString); |
| return; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void SVGElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| if (removalType.disconnectedFromDocument) |
| updateRelativeLengthsInformation(false, this); |
| |
| StyledElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| |
| if (removalType.disconnectedFromDocument) { |
| document().accessSVGExtensions().clearTargetDependencies(*this); |
| document().accessSVGExtensions().removeAllElementReferencesForTarget(*this); |
| } |
| invalidateInstances(); |
| } |
| |
| SVGSVGElement* SVGElement::ownerSVGElement() const |
| { |
| ContainerNode* node = parentOrShadowHostNode(); |
| while (node) { |
| if (is<SVGSVGElement>(*node)) |
| return downcast<SVGSVGElement>(node); |
| |
| node = node->parentOrShadowHostNode(); |
| } |
| |
| return nullptr; |
| } |
| |
| SVGElement* SVGElement::viewportElement() const |
| { |
| // This function needs shadow tree support - as RenderSVGContainer uses this function |
| // to determine the "overflow" property. <use> on <symbol> wouldn't work otherwhise. |
| ContainerNode* node = parentOrShadowHostNode(); |
| while (node) { |
| if (is<SVGSVGElement>(*node) || is<SVGImageElement>(*node) || node->hasTagName(SVGNames::symbolTag)) |
| return downcast<SVGElement>(node); |
| |
| node = node->parentOrShadowHostNode(); |
| } |
| |
| return nullptr; |
| } |
| |
| const HashSet<SVGElement*>& SVGElement::instances() const |
| { |
| if (!m_svgRareData) { |
| static NeverDestroyed<HashSet<SVGElement*>> emptyInstances; |
| return emptyInstances; |
| } |
| return m_svgRareData->instances(); |
| } |
| |
| bool SVGElement::getBoundingBox(FloatRect& rect, SVGLocatable::StyleUpdateStrategy styleUpdateStrategy) |
| { |
| if (is<SVGGraphicsElement>(*this)) { |
| rect = downcast<SVGGraphicsElement>(*this).getBBox(styleUpdateStrategy); |
| return true; |
| } |
| return false; |
| } |
| |
| SVGElement* SVGElement::correspondingElement() const |
| { |
| return m_svgRareData ? m_svgRareData->correspondingElement() : nullptr; |
| } |
| |
| RefPtr<SVGUseElement> SVGElement::correspondingUseElement() const |
| { |
| auto* root = containingShadowRoot(); |
| if (!root) |
| return nullptr; |
| if (root->mode() != ShadowRootMode::UserAgent) |
| return nullptr; |
| auto* host = root->host(); |
| if (!is<SVGUseElement>(host)) |
| return nullptr; |
| return &downcast<SVGUseElement>(*host); |
| } |
| |
| void SVGElement::setCorrespondingElement(SVGElement* correspondingElement) |
| { |
| if (m_svgRareData) { |
| if (auto oldCorrespondingElement = makeRefPtr(m_svgRareData->correspondingElement())) |
| oldCorrespondingElement->m_svgRareData->instances().remove(this); |
| } |
| if (m_svgRareData || correspondingElement) |
| ensureSVGRareData().setCorrespondingElement(correspondingElement); |
| if (correspondingElement) |
| correspondingElement->ensureSVGRareData().instances().add(this); |
| } |
| |
| void SVGElement::parseAttribute(const QualifiedName& name, const AtomString& value) |
| { |
| if (name == HTMLNames::classAttr) { |
| m_className->setBaseValInternal(value); |
| return; |
| } |
| |
| if (name == HTMLNames::tabindexAttr) { |
| if (value.isEmpty()) |
| clearTabIndexExplicitlyIfNeeded(); |
| else if (auto optionalTabIndex = parseHTMLInteger(value)) |
| setTabIndexExplicitly(optionalTabIndex.value()); |
| return; |
| } |
| |
| auto& eventName = HTMLElement::eventNameForEventHandlerAttribute(name); |
| if (!eventName.isNull()) { |
| setAttributeEventListener(eventName, name, value); |
| return; |
| } |
| |
| SVGLangSpace::parseAttribute(name, value); |
| } |
| |
| bool SVGElement::haveLoadedRequiredResources() |
| { |
| for (auto& child : childrenOfType<SVGElement>(*this)) { |
| if (!child.haveLoadedRequiredResources()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool SVGElement::addEventListener(const AtomString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options) |
| { |
| // Add event listener to regular DOM element |
| if (!Node::addEventListener(eventType, listener.copyRef(), options)) |
| return false; |
| |
| if (containingShadowRoot()) |
| return true; |
| |
| // Add event listener to all shadow tree DOM element instances |
| ASSERT(!instanceUpdatesBlocked()); |
| for (auto* instance : instances()) { |
| ASSERT(instance->correspondingElement() == this); |
| bool result = instance->Node::addEventListener(eventType, listener.copyRef(), options); |
| ASSERT_UNUSED(result, result); |
| } |
| |
| return true; |
| } |
| |
| bool SVGElement::removeEventListener(const AtomString& eventType, EventListener& listener, const ListenerOptions& options) |
| { |
| if (containingShadowRoot()) |
| return Node::removeEventListener(eventType, listener, options); |
| |
| // EventTarget::removeEventListener creates a Ref around the given EventListener |
| // object when creating a temporary RegisteredEventListener object used to look up the |
| // event listener in a cache. If we want to be able to call removeEventListener() multiple |
| // times on different nodes, we have to delay its immediate destruction, which would happen |
| // after the first call below. |
| Ref<EventListener> protector(listener); |
| |
| // Remove event listener from regular DOM element |
| if (!Node::removeEventListener(eventType, listener, options)) |
| return false; |
| |
| // Remove event listener from all shadow tree DOM element instances |
| ASSERT(!instanceUpdatesBlocked()); |
| for (auto& instance : instances()) { |
| ASSERT(instance->correspondingElement() == this); |
| |
| if (instance->Node::removeEventListener(eventType, listener, options)) |
| continue; |
| |
| // This case can only be hit for event listeners created from markup |
| ASSERT(listener.wasCreatedFromMarkup()); |
| |
| // If the event listener 'listener' has been created from markup and has been fired before |
| // then JSLazyEventListener::parseCode() has been called and m_jsFunction of that listener |
| // has been created (read: it's not 0 anymore). During shadow tree creation, the event |
| // listener DOM attribute has been cloned, and another event listener has been setup in |
| // the shadow tree. If that event listener has not been used yet, m_jsFunction is still 0, |
| // and tryRemoveEventListener() above will fail. Work around that very rare problem. |
| ASSERT(instance->eventTargetData()); |
| instance->eventTargetData()->eventListenerMap.removeFirstEventListenerCreatedFromMarkup(eventType); |
| } |
| |
| return true; |
| } |
| |
| static bool hasLoadListener(Element* element) |
| { |
| if (element->hasEventListeners(eventNames().loadEvent)) |
| return true; |
| |
| for (element = element->parentOrShadowHostElement(); element; element = element->parentOrShadowHostElement()) { |
| if (element->hasCapturingEventListeners(eventNames().loadEvent)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void SVGElement::sendSVGLoadEventIfPossible(bool sendParentLoadEvents) |
| { |
| if (!isConnected() || !document().frame()) |
| return; |
| |
| RefPtr<SVGElement> currentTarget = this; |
| while (currentTarget && currentTarget->haveLoadedRequiredResources()) { |
| RefPtr<Element> parent; |
| if (sendParentLoadEvents) |
| parent = currentTarget->parentOrShadowHostElement(); // save the next parent to dispatch too incase dispatching the event changes the tree |
| if (hasLoadListener(currentTarget.get())) |
| currentTarget->dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
| currentTarget = (parent && parent->isSVGElement()) ? static_pointer_cast<SVGElement>(parent) : RefPtr<SVGElement>(); |
| SVGElement* element = currentTarget.get(); |
| if (!element || !element->isOutermostSVGSVGElement()) |
| continue; |
| |
| // Consider <svg onload="foo()"><image xlink:href="foo.png" externalResourcesRequired="true"/></svg>. |
| // If foo.png is not yet loaded, the first SVGLoad event will go to the <svg> element, sent through |
| // Document::implicitClose(). Then the SVGLoad event will fire for <image>, once its loaded. |
| ASSERT(sendParentLoadEvents); |
| |
| // If the load event was not sent yet by Document::implicitClose(), but the <image> from the example |
| // above, just appeared, don't send the SVGLoad event to the outermost <svg>, but wait for the document |
| // to be "ready to render", first. |
| if (!document().loadEventFinished()) |
| break; |
| } |
| } |
| |
| void SVGElement::sendSVGLoadEventIfPossibleAsynchronously() |
| { |
| svgLoadEventTimer()->startOneShot(0_s); |
| } |
| |
| void SVGElement::svgLoadEventTimerFired() |
| { |
| sendSVGLoadEventIfPossible(); |
| } |
| |
| Timer* SVGElement::svgLoadEventTimer() |
| { |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| void SVGElement::finishParsingChildren() |
| { |
| StyledElement::finishParsingChildren(); |
| |
| // The outermost SVGSVGElement SVGLoad event is fired through Document::dispatchWindowLoadEvent. |
| if (isOutermostSVGSVGElement()) |
| return; |
| |
| // finishParsingChildren() is called when the close tag is reached for an element (e.g. </svg>) |
| // we send SVGLoad events here if we can, otherwise they'll be sent when any required loads finish |
| sendSVGLoadEventIfPossible(); |
| |
| // Notify all the elements which have references to this element to rebuild their shadow and render |
| // trees, e.g. a <use> element references a target element before this target element is defined. |
| invalidateInstances(); |
| } |
| |
| bool SVGElement::childShouldCreateRenderer(const Node& child) const |
| { |
| if (!child.isSVGElement()) |
| return false; |
| auto& svgChild = downcast<SVGElement>(child); |
| |
| static const QualifiedName* const invalidTextContent[] { |
| #if ENABLE(SVG_FONTS) |
| &SVGNames::altGlyphTag.get(), |
| #endif |
| &SVGNames::textPathTag.get(), |
| &SVGNames::trefTag.get(), |
| &SVGNames::tspanTag.get(), |
| }; |
| auto& name = svgChild.localName(); |
| for (auto* tag : invalidTextContent) { |
| if (name == tag->localName()) |
| return false; |
| } |
| |
| return svgChild.isValid(); |
| } |
| |
| void SVGElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason) |
| { |
| StyledElement::attributeChanged(name, oldValue, newValue); |
| |
| if (name == HTMLNames::idAttr) |
| document().accessSVGExtensions().rebuildAllElementReferencesForTarget(*this); |
| |
| // Changes to the style attribute are processed lazily (see Element::getAttribute() and related methods), |
| // so we don't want changes to the style attribute to result in extra work here except invalidateInstances(). |
| if (name == HTMLNames::styleAttr) |
| invalidateInstances(); |
| else |
| svgAttributeChanged(name); |
| } |
| |
| void SVGElement::synchronizeAttribute(const QualifiedName& name) |
| { |
| // If the value of the property has changed, serialize the new value to the attribute. |
| if (auto value = propertyRegistry().synchronize(name)) |
| setSynchronizedLazyAttribute(name, *value); |
| } |
| |
| void SVGElement::synchronizeAllAttributes() |
| { |
| // SVGPropertyRegistry::synchronizeAllAttributes() returns the new values of |
| // the properties which have changed but not committed yet. |
| auto map = propertyRegistry().synchronizeAllAttributes(); |
| for (const auto& entry : map) |
| setSynchronizedLazyAttribute(entry.key, entry.value); |
| } |
| |
| void SVGElement::synchronizeAllAnimatedSVGAttribute(SVGElement& svgElement) |
| { |
| svgElement.synchronizeAllAttributes(); |
| } |
| |
| void SVGElement::commitPropertyChange(SVGProperty* property) |
| { |
| // We want to dirty the top-level property when a descendant changes. For example |
| // a change in an SVGLength item in SVGLengthList should set the dirty flag on |
| // SVGLengthList and not the SVGLength. |
| property->setDirty(); |
| |
| invalidateSVGAttributes(); |
| svgAttributeChanged(propertyRegistry().propertyAttributeName(*property)); |
| } |
| |
| void SVGElement::commitPropertyChange(SVGAnimatedProperty& animatedProperty) |
| { |
| QualifiedName attributeName = propertyRegistry().animatedPropertyAttributeName(animatedProperty); |
| ASSERT(attributeName != nullQName()); |
| |
| // A change in a style property, e.g SVGRectElement::x should be serialized to |
| // the attribute immediately. Otherwise it is okay to be lazy in this regard. |
| if (!propertyRegistry().isAnimatedStylePropertyAttribute(attributeName)) |
| animatedProperty.setDirty(); |
| else |
| setSynchronizedLazyAttribute(attributeName, animatedProperty.baseValAsString()); |
| |
| invalidateSVGAttributes(); |
| svgAttributeChanged(attributeName); |
| } |
| |
| bool SVGElement::isAnimatedPropertyAttribute(const QualifiedName& attributeName) const |
| { |
| return propertyRegistry().isAnimatedPropertyAttribute(attributeName); |
| } |
| |
| bool SVGElement::isAnimatedAttribute(const QualifiedName& attributeName) const |
| { |
| return SVGPropertyAnimatorFactory::isKnownAttribute(attributeName) || isAnimatedPropertyAttribute(attributeName); |
| } |
| |
| bool SVGElement::isAnimatedStyleAttribute(const QualifiedName& attributeName) const |
| { |
| return SVGPropertyAnimatorFactory::isKnownAttribute(attributeName) || propertyRegistry().isAnimatedStylePropertyAttribute(attributeName); |
| } |
| |
| RefPtr<SVGAttributeAnimator> SVGElement::createAnimator(const QualifiedName& attributeName, AnimationMode animationMode, CalcMode calcMode, bool isAccumulated, bool isAdditive) |
| { |
| // Property animator, e.g. "fill" or "fill-opacity". |
| if (auto animator = propertyAnimatorFactory().createAnimator(attributeName, animationMode, calcMode, isAccumulated, isAdditive)) |
| return animator; |
| |
| // Animated property animator. |
| auto animator = propertyRegistry().createAnimator(attributeName, animationMode, calcMode, isAccumulated, isAdditive); |
| if (!animator) |
| return animator; |
| for (auto* instance : instances()) |
| instance->propertyRegistry().appendAnimatedInstance(attributeName, *animator); |
| return animator; |
| } |
| |
| void SVGElement::animatorWillBeDeleted(const QualifiedName& attributeName) |
| { |
| propertyAnimatorFactory().animatorWillBeDeleted(attributeName); |
| } |
| |
| Optional<ElementStyle> SVGElement::resolveCustomStyle(const RenderStyle& parentStyle, const RenderStyle*) |
| { |
| // If the element is in a <use> tree we get the style from the definition tree. |
| if (auto styleElement = makeRefPtr(this->correspondingElement())) { |
| Optional<ElementStyle> style = styleElement->resolveStyle(&parentStyle); |
| StyleResolver::adjustSVGElementStyle(*this, *style->renderStyle); |
| return style; |
| } |
| |
| return resolveStyle(&parentStyle); |
| } |
| |
| MutableStyleProperties* SVGElement::animatedSMILStyleProperties() const |
| { |
| if (m_svgRareData) |
| return m_svgRareData->animatedSMILStyleProperties(); |
| return 0; |
| } |
| |
| MutableStyleProperties& SVGElement::ensureAnimatedSMILStyleProperties() |
| { |
| return ensureSVGRareData().ensureAnimatedSMILStyleProperties(); |
| } |
| |
| void SVGElement::setUseOverrideComputedStyle(bool value) |
| { |
| if (m_svgRareData) |
| m_svgRareData->setUseOverrideComputedStyle(value); |
| } |
| |
| const RenderStyle* SVGElement::computedStyle(PseudoId pseudoElementSpecifier) |
| { |
| if (!m_svgRareData || !m_svgRareData->useOverrideComputedStyle()) |
| return Element::computedStyle(pseudoElementSpecifier); |
| |
| const RenderStyle* parentStyle = nullptr; |
| if (auto parent = makeRefPtr(parentOrShadowHostElement())) { |
| if (auto renderer = parent->renderer()) |
| parentStyle = &renderer->style(); |
| } |
| |
| return m_svgRareData->overrideComputedStyle(*this, parentStyle); |
| } |
| |
| QualifiedName SVGElement::animatableAttributeForName(const AtomString& localName) |
| { |
| static const auto animatableAttributes = makeNeverDestroyed([] { |
| static const QualifiedName* const names[] = { |
| &HTMLNames::classAttr.get(), |
| &SVGNames::amplitudeAttr.get(), |
| &SVGNames::azimuthAttr.get(), |
| &SVGNames::baseFrequencyAttr.get(), |
| &SVGNames::biasAttr.get(), |
| &SVGNames::clipPathUnitsAttr.get(), |
| &SVGNames::cxAttr.get(), |
| &SVGNames::cyAttr.get(), |
| &SVGNames::diffuseConstantAttr.get(), |
| &SVGNames::divisorAttr.get(), |
| &SVGNames::dxAttr.get(), |
| &SVGNames::dyAttr.get(), |
| &SVGNames::edgeModeAttr.get(), |
| &SVGNames::elevationAttr.get(), |
| &SVGNames::exponentAttr.get(), |
| &SVGNames::externalResourcesRequiredAttr.get(), |
| &SVGNames::filterUnitsAttr.get(), |
| &SVGNames::fxAttr.get(), |
| &SVGNames::fyAttr.get(), |
| &SVGNames::gradientTransformAttr.get(), |
| &SVGNames::gradientUnitsAttr.get(), |
| &SVGNames::heightAttr.get(), |
| &SVGNames::in2Attr.get(), |
| &SVGNames::inAttr.get(), |
| &SVGNames::interceptAttr.get(), |
| &SVGNames::k1Attr.get(), |
| &SVGNames::k2Attr.get(), |
| &SVGNames::k3Attr.get(), |
| &SVGNames::k4Attr.get(), |
| &SVGNames::kernelMatrixAttr.get(), |
| &SVGNames::kernelUnitLengthAttr.get(), |
| &SVGNames::lengthAdjustAttr.get(), |
| &SVGNames::limitingConeAngleAttr.get(), |
| &SVGNames::markerHeightAttr.get(), |
| &SVGNames::markerUnitsAttr.get(), |
| &SVGNames::markerWidthAttr.get(), |
| &SVGNames::maskContentUnitsAttr.get(), |
| &SVGNames::maskUnitsAttr.get(), |
| &SVGNames::methodAttr.get(), |
| &SVGNames::modeAttr.get(), |
| &SVGNames::numOctavesAttr.get(), |
| &SVGNames::offsetAttr.get(), |
| &SVGNames::operatorAttr.get(), |
| &SVGNames::orderAttr.get(), |
| &SVGNames::orientAttr.get(), |
| &SVGNames::pathLengthAttr.get(), |
| &SVGNames::patternContentUnitsAttr.get(), |
| &SVGNames::patternTransformAttr.get(), |
| &SVGNames::patternUnitsAttr.get(), |
| &SVGNames::pointsAtXAttr.get(), |
| &SVGNames::pointsAtYAttr.get(), |
| &SVGNames::pointsAtZAttr.get(), |
| &SVGNames::preserveAlphaAttr.get(), |
| &SVGNames::preserveAspectRatioAttr.get(), |
| &SVGNames::primitiveUnitsAttr.get(), |
| &SVGNames::radiusAttr.get(), |
| &SVGNames::rAttr.get(), |
| &SVGNames::refXAttr.get(), |
| &SVGNames::refYAttr.get(), |
| &SVGNames::resultAttr.get(), |
| &SVGNames::rotateAttr.get(), |
| &SVGNames::rxAttr.get(), |
| &SVGNames::ryAttr.get(), |
| &SVGNames::scaleAttr.get(), |
| &SVGNames::seedAttr.get(), |
| &SVGNames::slopeAttr.get(), |
| &SVGNames::spacingAttr.get(), |
| &SVGNames::specularConstantAttr.get(), |
| &SVGNames::specularExponentAttr.get(), |
| &SVGNames::spreadMethodAttr.get(), |
| &SVGNames::startOffsetAttr.get(), |
| &SVGNames::stdDeviationAttr.get(), |
| &SVGNames::stitchTilesAttr.get(), |
| &SVGNames::surfaceScaleAttr.get(), |
| &SVGNames::tableValuesAttr.get(), |
| &SVGNames::targetAttr.get(), |
| &SVGNames::targetXAttr.get(), |
| &SVGNames::targetYAttr.get(), |
| &SVGNames::transformAttr.get(), |
| &SVGNames::typeAttr.get(), |
| &SVGNames::valuesAttr.get(), |
| &SVGNames::viewBoxAttr.get(), |
| &SVGNames::widthAttr.get(), |
| &SVGNames::x1Attr.get(), |
| &SVGNames::x2Attr.get(), |
| &SVGNames::xAttr.get(), |
| &SVGNames::xChannelSelectorAttr.get(), |
| &SVGNames::y1Attr.get(), |
| &SVGNames::y2Attr.get(), |
| &SVGNames::yAttr.get(), |
| &SVGNames::yChannelSelectorAttr.get(), |
| &SVGNames::zAttr.get(), |
| &SVGNames::hrefAttr.get(), |
| }; |
| HashMap<AtomString, QualifiedName> map; |
| for (auto& name : names) { |
| auto addResult = map.add(name->localName(), *name); |
| ASSERT_UNUSED(addResult, addResult.isNewEntry); |
| } |
| return map; |
| }()); |
| return animatableAttributes.get().get(localName); |
| } |
| |
| #ifndef NDEBUG |
| |
| bool SVGElement::isAnimatableAttribute(const QualifiedName& name) const |
| { |
| if (animatableAttributeForName(name.localName()) == name) |
| return !filterOutAnimatableAttribute(name); |
| return false; |
| } |
| |
| bool SVGElement::filterOutAnimatableAttribute(const QualifiedName&) const |
| { |
| return false; |
| } |
| |
| #endif |
| |
| String SVGElement::title() const |
| { |
| // According to spec, for stand-alone SVG documents we should not return a title when |
| // hovering over the rootmost SVG element (the first <title> element is the title of |
| // the document, not a tooltip) so we instantly return. |
| if (isOutermostSVGSVGElement() && document().topDocument().isSVGDocument()) |
| return String(); |
| auto firstTitle = childrenOfType<SVGTitleElement>(*this).first(); |
| return firstTitle ? const_cast<SVGTitleElement*>(firstTitle)->innerText() : String(); |
| } |
| |
| bool SVGElement::rendererIsNeeded(const RenderStyle& style) |
| { |
| // http://www.w3.org/TR/SVG/extend.html#PrivateData |
| // Prevent anything other than SVG renderers from appearing in our render tree |
| // Spec: SVG allows inclusion of elements from foreign namespaces anywhere |
| // with the SVG content. In general, the SVG user agent will include the unknown |
| // elements in the DOM but will otherwise ignore unknown elements. |
| if (!parentOrShadowHostElement() || parentOrShadowHostElement()->isSVGElement()) |
| return StyledElement::rendererIsNeeded(style); |
| |
| return false; |
| } |
| |
| CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& attrName) |
| { |
| if (!attrName.namespaceURI().isNull()) |
| return CSSPropertyInvalid; |
| |
| static const auto properties = makeNeverDestroyed(createAttributeNameToCSSPropertyIDMap()); |
| return properties.get().get(attrName.localName().impl()); |
| } |
| |
| bool SVGElement::isPresentationAttribute(const QualifiedName& name) const |
| { |
| if (cssPropertyIdForSVGAttributeName(name) > 0) |
| return true; |
| return StyledElement::isPresentationAttribute(name); |
| } |
| |
| void SVGElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style) |
| { |
| CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name); |
| if (propertyID > 0) |
| addPropertyToPresentationAttributeStyle(style, propertyID, value); |
| } |
| |
| void SVGElement::svgAttributeChanged(const QualifiedName& attrName) |
| { |
| CSSPropertyID propId = cssPropertyIdForSVGAttributeName(attrName); |
| if (propId > 0) { |
| invalidateInstances(); |
| return; |
| } |
| |
| if (attrName == HTMLNames::classAttr) { |
| classAttributeChanged(className()); |
| invalidateInstances(); |
| return; |
| } |
| |
| if (attrName == HTMLNames::idAttr) { |
| auto renderer = this->renderer(); |
| // Notify resources about id changes, this is important as we cache resources by id in SVGDocumentExtensions |
| if (is<RenderSVGResourceContainer>(renderer)) |
| downcast<RenderSVGResourceContainer>(*renderer).idChanged(); |
| if (isConnected()) |
| buildPendingResourcesIfNeeded(); |
| invalidateInstances(); |
| return; |
| } |
| |
| SVGLangSpace::svgAttributeChanged(attrName); |
| } |
| |
| Node::InsertedIntoAncestorResult SVGElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| StyledElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| updateRelativeLengthsInformation(); |
| buildPendingResourcesIfNeeded(); |
| return InsertedIntoAncestorResult::Done; |
| } |
| |
| void SVGElement::buildPendingResourcesIfNeeded() |
| { |
| if (!needsPendingResourceHandling() || !isConnected() || isInShadowTree()) |
| return; |
| |
| SVGDocumentExtensions& extensions = document().accessSVGExtensions(); |
| String resourceId = getIdAttribute(); |
| if (!extensions.isIdOfPendingResource(resourceId)) |
| return; |
| |
| // Mark pending resources as pending for removal. |
| extensions.markPendingResourcesForRemoval(resourceId); |
| |
| // Rebuild pending resources for each client of a pending resource that is being removed. |
| while (auto clientElement = extensions.removeElementFromPendingResourcesForRemovalMap(resourceId)) { |
| ASSERT(clientElement->hasPendingResources()); |
| if (clientElement->hasPendingResources()) { |
| clientElement->buildPendingResource(); |
| extensions.clearHasPendingResourcesIfPossible(*clientElement); |
| } |
| } |
| } |
| |
| void SVGElement::childrenChanged(const ChildChange& change) |
| { |
| StyledElement::childrenChanged(change); |
| |
| if (change.source == ChildChangeSource::Parser) |
| return; |
| invalidateInstances(); |
| } |
| |
| RefPtr<DeprecatedCSSOMValue> SVGElement::getPresentationAttribute(const String& name) |
| { |
| if (!hasAttributesWithoutUpdate()) |
| return 0; |
| |
| QualifiedName attributeName(nullAtom(), name, nullAtom()); |
| const Attribute* attribute = findAttributeByName(attributeName); |
| if (!attribute) |
| return 0; |
| |
| auto style = MutableStyleProperties::create(SVGAttributeMode); |
| CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(attribute->name()); |
| style->setProperty(propertyID, attribute->value()); |
| auto cssValue = style->getPropertyCSSValue(propertyID); |
| if (!cssValue) |
| return nullptr; |
| return cssValue->createDeprecatedCSSOMWrapper(style->ensureCSSStyleDeclaration()); |
| } |
| |
| bool SVGElement::instanceUpdatesBlocked() const |
| { |
| return m_svgRareData && m_svgRareData->instanceUpdatesBlocked(); |
| } |
| |
| void SVGElement::setInstanceUpdatesBlocked(bool value) |
| { |
| // Catch any callers that calls setInstanceUpdatesBlocked(true) twice in a row. |
| // That probably indicates nested use of InstanceUpdateBlocker and a bug. |
| ASSERT(!value || !instanceUpdatesBlocked()); |
| |
| if (m_svgRareData) |
| m_svgRareData->setInstanceUpdatesBlocked(value); |
| } |
| |
| AffineTransform SVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope) const |
| { |
| // To be overridden by SVGGraphicsElement (or as special case SVGTextElement and SVGPatternElement) |
| return AffineTransform(); |
| } |
| |
| void SVGElement::updateRelativeLengthsInformation(bool hasRelativeLengths, SVGElement* element) |
| { |
| // If we're not yet in a document, this function will be called again from insertedIntoAncestor(). Do nothing now. |
| if (!isConnected()) |
| return; |
| |
| // An element wants to notify us that its own relative lengths state changed. |
| // Register it in the relative length map, and register us in the parent relative length map. |
| // Register the parent in the grandparents map, etc. Repeat procedure until the root of the SVG tree. |
| |
| if (hasRelativeLengths) |
| m_elementsWithRelativeLengths.add(element); |
| else { |
| if (!m_elementsWithRelativeLengths.contains(element)) { |
| // We were never registered. Do nothing. |
| return; |
| } |
| |
| m_elementsWithRelativeLengths.remove(element); |
| } |
| |
| if (!element->isSVGGraphicsElement()) |
| return; |
| |
| // Find first styled parent node, and notify it that we've changed our relative length state. |
| auto node = makeRefPtr(parentNode()); |
| while (node) { |
| if (!node->isSVGElement()) |
| break; |
| |
| // Register us in the parent element map. |
| downcast<SVGElement>(*node).updateRelativeLengthsInformation(hasRelativeLengths, this); |
| break; |
| } |
| } |
| |
| void SVGElement::accessKeyAction(bool sendMouseEvents) |
| { |
| dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); |
| } |
| |
| void SVGElement::invalidateInstances() |
| { |
| if (instanceUpdatesBlocked()) |
| return; |
| |
| auto& instances = this->instances(); |
| while (!instances.isEmpty()) { |
| auto instance = makeRefPtr(*instances.begin()); |
| if (auto useElement = instance->correspondingUseElement()) |
| useElement->invalidateShadowTree(); |
| instance->setCorrespondingElement(nullptr); |
| } while (!instances.isEmpty()); |
| } |
| |
| } |