| /* |
| * 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 "Document.h" |
| #include "ElementChildIterator.h" |
| #include "Event.h" |
| #include "EventNames.h" |
| #include "HTMLElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLParserIdioms.h" |
| #include "RenderAncestorIterator.h" |
| #include "RenderSVGResourceFilter.h" |
| #include "RenderSVGResourceMasker.h" |
| #include "SVGDocumentExtensions.h" |
| #include "SVGElementRareData.h" |
| #include "SVGElementTypeHelpers.h" |
| #include "SVGForeignObjectElement.h" |
| #include "SVGGraphicsElement.h" |
| #include "SVGImageElement.h" |
| #include "SVGNames.h" |
| #include "SVGPropertyAnimatorFactory.h" |
| #include "SVGRenderStyle.h" |
| #include "SVGRenderSupport.h" |
| #include "SVGResourceElementClient.h" |
| #include "SVGSVGElement.h" |
| #include "SVGTitleElement.h" |
| #include "SVGUseElement.h" |
| #include "ShadowRoot.h" |
| #include "StyleAdjuster.h" |
| #include "XMLNames.h" |
| #include <wtf/HashMap.h> |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/RobinHoodHashMap.h> |
| #include <wtf/StdLibExtras.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(SVGElement); |
| |
| static NEVER_INLINE MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, 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 constexpr std::array attributeNames { |
| &alignment_baselineAttr, |
| &baseline_shiftAttr, |
| &buffered_renderingAttr, |
| &clipAttr, |
| &clip_pathAttr, |
| &clip_ruleAttr, |
| &SVGNames::colorAttr, |
| &color_interpolationAttr, |
| &color_interpolation_filtersAttr, |
| &cursorAttr, |
| &cxAttr, |
| &cyAttr, |
| &SVGNames::directionAttr, |
| &displayAttr, |
| &dominant_baselineAttr, |
| &fillAttr, |
| &fill_opacityAttr, |
| &fill_ruleAttr, |
| &filterAttr, |
| &flood_colorAttr, |
| &flood_opacityAttr, |
| &font_familyAttr, |
| &font_sizeAttr, |
| &font_stretchAttr, |
| &font_styleAttr, |
| &font_variantAttr, |
| &font_weightAttr, |
| &glyph_orientation_horizontalAttr, |
| &glyph_orientation_verticalAttr, |
| &image_renderingAttr, |
| &SVGNames::heightAttr, |
| &kerningAttr, |
| &letter_spacingAttr, |
| &lighting_colorAttr, |
| &marker_endAttr, |
| &marker_midAttr, |
| &marker_startAttr, |
| &maskAttr, |
| &mask_typeAttr, |
| &opacityAttr, |
| &overflowAttr, |
| &paint_orderAttr, |
| &pointer_eventsAttr, |
| &rAttr, |
| &rxAttr, |
| &ryAttr, |
| &shape_renderingAttr, |
| &stop_colorAttr, |
| &stop_opacityAttr, |
| &strokeAttr, |
| &stroke_dasharrayAttr, |
| &stroke_dashoffsetAttr, |
| &stroke_linecapAttr, |
| &stroke_linejoinAttr, |
| &stroke_miterlimitAttr, |
| &stroke_opacityAttr, |
| &stroke_widthAttr, |
| &text_anchorAttr, |
| &text_decorationAttr, |
| &text_renderingAttr, |
| &unicode_bidiAttr, |
| &vector_effectAttr, |
| &visibilityAttr, |
| &SVGNames::widthAttr, |
| &word_spacingAttr, |
| &writing_modeAttr, |
| &xAttr, |
| &yAttr, |
| }; |
| |
| MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, CSSPropertyID> map; |
| |
| for (auto& name : attributeNames) { |
| auto& localName = name->get().localName(); |
| map.add(localName, 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(), CSSPropertyTransformOrigin); |
| |
| return map; |
| } |
| |
| SVGElement::SVGElement(const QualifiedName& tagName, Document& document, ConstructionType constructionType) |
| : StyledElement(tagName, document, constructionType) |
| , 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) { |
| RELEASE_ASSERT(m_svgRareData->referencingElements().computesEmpty()); |
| for (SVGElement& instance : copyToVectorOf<Ref<SVGElement>>(instances())) |
| instance.m_svgRareData->setCorrespondingElement(nullptr); |
| RELEASE_ASSERT(!m_svgRareData->correspondingElement()); |
| m_svgRareData = nullptr; |
| } |
| document().accessSVGExtensions().removeElementToRebuild(*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::Change::None || 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; |
| |
| // 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 (is<SVGForeignObjectElement>(*parentNode())) |
| return true; |
| |
| // If we're inside the shadow tree of a <use> element, we're always an inner <svg> element. |
| if (isInShadowTree() && is<SVGUseElement>(shadowHost())) |
| return false; |
| |
| // This is true whenever this is the outermost SVG, even if there are HTML elements outside it |
| return !is<SVGElement>(*parentNode()); |
| } |
| |
| 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) { |
| auto& extensions = document().accessSVGExtensions(); |
| if (m_svgRareData) { |
| for (auto& element : m_svgRareData->takeReferencingElements()) { |
| extensions.addElementToRebuild(element); |
| Ref { element }->clearTarget(); |
| } |
| RELEASE_ASSERT(m_svgRareData->referencingElements().computesEmpty()); |
| } |
| extensions.removeElementToRebuild(*this); |
| } |
| invalidateInstances(); |
| |
| if (removalType.treeScopeChanged && oldParentOfRemovedTree.isUserAgentShadowRoot()) |
| setCorrespondingElement(nullptr); |
| } |
| |
| 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 WeakHashSet<SVGElement>& SVGElement::instances() const |
| { |
| if (!m_svgRareData) { |
| static NeverDestroyed<WeakHashSet<SVGElement>> emptyInstances; |
| return emptyInstances; |
| } |
| return m_svgRareData->instances(); |
| } |
| |
| std::optional<FloatRect> SVGElement::getBoundingBox() const |
| { |
| if (is<SVGGraphicsElement>(*this)) { |
| if (auto renderer = this->renderer()) |
| return renderer->objectBoundingBox(); |
| } |
| return std::nullopt; |
| } |
| |
| Vector<Ref<SVGElement>> SVGElement::referencingElements() const |
| { |
| if (!m_svgRareData) |
| return { }; |
| return copyToVectorOf<Ref<SVGElement>>(m_svgRareData->referencingElements()); |
| } |
| |
| void SVGElement::addReferencingElement(SVGElement& element) |
| { |
| ensureSVGRareData().addReferencingElement(element); |
| auto& rareDataOfReferencingElement = element.ensureSVGRareData(); |
| RELEASE_ASSERT(!rareDataOfReferencingElement.referenceTarget()); |
| rareDataOfReferencingElement.setReferenceTarget(*this); |
| } |
| |
| void SVGElement::removeReferencingElement(SVGElement& element) |
| { |
| ensureSVGRareData().removeReferencingElement(element); |
| element.ensureSVGRareData().setReferenceTarget(nullptr); |
| } |
| |
| void SVGElement::removeElementReference() |
| { |
| if (!m_svgRareData) |
| return; |
| if (RefPtr destination = m_svgRareData->referenceTarget()) |
| destination->removeReferencingElement(*this); |
| } |
| |
| Vector<WeakPtr<SVGResourceElementClient>> SVGElement::referencingCSSClients() const |
| { |
| if (!m_svgRareData) |
| return { }; |
| return copyToVector(m_svgRareData->referencingCSSClients()); |
| } |
| |
| void SVGElement::addReferencingCSSClient(SVGResourceElementClient& client) |
| { |
| ensureSVGRareData().addReferencingCSSClient(client); |
| } |
| |
| void SVGElement::removeReferencingCSSClient(SVGResourceElementClient& client) |
| { |
| if (!m_svgRareData) |
| return; |
| ensureSVGRareData().removeReferencingCSSClient(client); |
| } |
| |
| 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 (RefPtr oldCorrespondingElement = m_svgRareData->correspondingElement()) |
| oldCorrespondingElement->m_svgRareData->removeInstance(*this); |
| } |
| if (m_svgRareData || correspondingElement) |
| ensureSVGRareData().setCorrespondingElement(correspondingElement); |
| if (correspondingElement) |
| correspondingElement->ensureSVGRareData().addInstance(*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()) |
| setTabIndexExplicitly(std::nullopt); |
| else if (auto optionalTabIndex = parseHTMLInteger(value)) |
| setTabIndexExplicitly(optionalTabIndex.value()); |
| return; |
| } |
| |
| auto& eventName = HTMLElement::eventNameForEventHandlerAttribute(name); |
| if (!eventName.isNull()) { |
| setAttributeEventListener(eventName, name, value); |
| return; |
| } |
| } |
| |
| 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 : copyToVectorOf<Ref<SVGElement>>(instances())) { |
| ASSERT(instance->correspondingElement() == this); |
| ASSERT(instance->isInUserAgentShadowTree()); |
| bool result = instance->Node::addEventListener(eventType, listener.copyRef(), options); |
| ASSERT_UNUSED(result, result); |
| } |
| |
| return true; |
| } |
| |
| bool SVGElement::removeEventListener(const AtomString& eventType, EventListener& listener, const EventListenerOptions& 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 : copyToVectorOf<Ref<SVGElement>>(instances())) { |
| ASSERT(instance->correspondingElement() == this); |
| ASSERT(instance->isInUserAgentShadowTree()); |
| |
| 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::sendLoadEventIfPossible() |
| { |
| if (!isConnected() || !document().frame()) |
| return; |
| |
| if (!haveLoadedRequiredResources() || !hasLoadListener(this)) |
| return; |
| |
| dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
| } |
| |
| void SVGElement::loadEventTimerFired() |
| { |
| sendLoadEventIfPossible(); |
| } |
| |
| Timer* SVGElement::loadEventTimer() |
| { |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| void SVGElement::finishParsingChildren() |
| { |
| StyledElement::finishParsingChildren(); |
| |
| if (isOutermostSVGSVGElement()) |
| return; |
| |
| // 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(); |
| } |
| |
| #if ENABLE(LAYER_BASED_SVG_ENGINE) |
| static MemoryCompactLookupOnlyRobinHoodHashSet<AtomString> createSVGLayerAwareElementSet() |
| { |
| // List of all SVG elements whose renderers support the layer aware layout / painting / hit-testing mode ('LBSE-mode'). |
| using namespace SVGNames; |
| MemoryCompactLookupOnlyRobinHoodHashSet<AtomString> set; |
| for (auto& tag : { rectTag.get() }) |
| set.add(tag.localName()); |
| return set; |
| } |
| |
| static inline bool isSVGLayerAwareElement(const SVGElement& element) |
| { |
| static NeverDestroyed<MemoryCompactLookupOnlyRobinHoodHashSet<AtomString>> set = createSVGLayerAwareElementSet(); |
| return set.get().contains(element.localName()); |
| } |
| #endif |
| |
| bool SVGElement::childShouldCreateRenderer(const Node& child) const |
| { |
| if (!child.isSVGElement()) |
| return false; |
| auto& svgChild = downcast<SVGElement>(child); |
| |
| #if ENABLE(LAYER_BASED_SVG_ENGINE) |
| // If the layer based SVG engine is enabled, all renderers that do not support the |
| // RenderLayer aware layout / painting / hit-testing mode ('LBSE-mode') have to be skipped. |
| // FIXME: [LBSE] Upstream support for all elements, and remove 'isSVGLayerAwareElement' check afterwards. |
| if (document().settings().layerBasedSVGEngineEnabled()) |
| return isSVGLayerAwareElement(svgChild); |
| #endif |
| |
| static const QualifiedName* const invalidTextContent[] { |
| &SVGNames::altGlyphTag.get(), |
| &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::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)) |
| propertyRegistry().setAnimatedPropertyDirty(attributeName, animatedProperty); |
| 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 : copyToVectorOf<Ref<SVGElement>>(instances())) |
| instance->propertyRegistry().appendAnimatedInstance(attributeName, *animator); |
| return animator; |
| } |
| |
| void SVGElement::animatorWillBeDeleted(const QualifiedName& attributeName) |
| { |
| propertyAnimatorFactory().animatorWillBeDeleted(attributeName); |
| } |
| |
| std::optional<Style::ElementStyle> SVGElement::resolveCustomStyle(const Style::ResolutionContext& resolutionContext, const RenderStyle*) |
| { |
| // If the element is in a <use> tree we get the style from the definition tree. |
| if (RefPtr styleElement = this->correspondingElement()) { |
| auto styleElementResolutionContext = resolutionContext; |
| // Can't use the state since we are going to another part of the tree. |
| styleElementResolutionContext.selectorMatchingState = nullptr; |
| auto style = styleElement->resolveStyle(styleElementResolutionContext); |
| Style::Adjuster::adjustSVGElementStyle(*style.renderStyle, *this); |
| return style; |
| } |
| |
| return resolveStyle(resolutionContext); |
| } |
| |
| 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 (RefPtr parent = parentOrShadowHostElement()) { |
| if (auto renderer = parent->renderer()) |
| parentStyle = &renderer->style(); |
| } |
| |
| return m_svgRareData->overrideComputedStyle(*this, parentStyle); |
| } |
| |
| QualifiedName SVGElement::animatableAttributeForName(const AtomString& localName) |
| { |
| static NeverDestroyed animatableAttributes = [] { |
| static constexpr std::array names { |
| &HTMLNames::classAttr, |
| &SVGNames::amplitudeAttr, |
| &SVGNames::azimuthAttr, |
| &SVGNames::baseFrequencyAttr, |
| &SVGNames::biasAttr, |
| &SVGNames::clipPathUnitsAttr, |
| &SVGNames::cxAttr, |
| &SVGNames::cyAttr, |
| &SVGNames::diffuseConstantAttr, |
| &SVGNames::divisorAttr, |
| &SVGNames::dxAttr, |
| &SVGNames::dyAttr, |
| &SVGNames::edgeModeAttr, |
| &SVGNames::elevationAttr, |
| &SVGNames::exponentAttr, |
| &SVGNames::externalResourcesRequiredAttr, |
| &SVGNames::filterUnitsAttr, |
| &SVGNames::fxAttr, |
| &SVGNames::fyAttr, |
| &SVGNames::gradientTransformAttr, |
| &SVGNames::gradientUnitsAttr, |
| &SVGNames::heightAttr, |
| &SVGNames::in2Attr, |
| &SVGNames::inAttr, |
| &SVGNames::interceptAttr, |
| &SVGNames::k1Attr, |
| &SVGNames::k2Attr, |
| &SVGNames::k3Attr, |
| &SVGNames::k4Attr, |
| &SVGNames::kernelMatrixAttr, |
| &SVGNames::kernelUnitLengthAttr, |
| &SVGNames::lengthAdjustAttr, |
| &SVGNames::limitingConeAngleAttr, |
| &SVGNames::markerHeightAttr, |
| &SVGNames::markerUnitsAttr, |
| &SVGNames::markerWidthAttr, |
| &SVGNames::maskContentUnitsAttr, |
| &SVGNames::maskUnitsAttr, |
| &SVGNames::methodAttr, |
| &SVGNames::modeAttr, |
| &SVGNames::numOctavesAttr, |
| &SVGNames::offsetAttr, |
| &SVGNames::operatorAttr, |
| &SVGNames::orderAttr, |
| &SVGNames::orientAttr, |
| &SVGNames::pathLengthAttr, |
| &SVGNames::patternContentUnitsAttr, |
| &SVGNames::patternTransformAttr, |
| &SVGNames::patternUnitsAttr, |
| &SVGNames::pointsAtXAttr, |
| &SVGNames::pointsAtYAttr, |
| &SVGNames::pointsAtZAttr, |
| &SVGNames::preserveAlphaAttr, |
| &SVGNames::preserveAspectRatioAttr, |
| &SVGNames::primitiveUnitsAttr, |
| &SVGNames::radiusAttr, |
| &SVGNames::rAttr, |
| &SVGNames::refXAttr, |
| &SVGNames::refYAttr, |
| &SVGNames::resultAttr, |
| &SVGNames::rotateAttr, |
| &SVGNames::rxAttr, |
| &SVGNames::ryAttr, |
| &SVGNames::scaleAttr, |
| &SVGNames::seedAttr, |
| &SVGNames::slopeAttr, |
| &SVGNames::spacingAttr, |
| &SVGNames::specularConstantAttr, |
| &SVGNames::specularExponentAttr, |
| &SVGNames::spreadMethodAttr, |
| &SVGNames::startOffsetAttr, |
| &SVGNames::stdDeviationAttr, |
| &SVGNames::stitchTilesAttr, |
| &SVGNames::surfaceScaleAttr, |
| &SVGNames::tableValuesAttr, |
| &SVGNames::targetAttr, |
| &SVGNames::targetXAttr, |
| &SVGNames::targetYAttr, |
| &SVGNames::transformAttr, |
| &SVGNames::typeAttr, |
| &SVGNames::valuesAttr, |
| &SVGNames::viewBoxAttr, |
| &SVGNames::widthAttr, |
| &SVGNames::x1Attr, |
| &SVGNames::x2Attr, |
| &SVGNames::xAttr, |
| &SVGNames::xChannelSelectorAttr, |
| &SVGNames::y1Attr, |
| &SVGNames::y2Attr, |
| &SVGNames::yAttr, |
| &SVGNames::yChannelSelectorAttr, |
| &SVGNames::zAttr, |
| &SVGNames::hrefAttr, |
| }; |
| MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName> map; |
| for (auto& name : names) { |
| auto addResult = map.add(name->get().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() || is<SVGElement>(*parentOrShadowHostElement())) |
| return StyledElement::rendererIsNeeded(style); |
| |
| return false; |
| } |
| |
| CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& attrName) |
| { |
| if (!attrName.namespaceURI().isNull()) |
| return CSSPropertyInvalid; |
| |
| static NeverDestroyed properties = createAttributeNameToCSSPropertyIDMap(); |
| return properties.get().get(attrName.localName()); |
| } |
| |
| bool SVGElement::hasPresentationalHintsForAttribute(const QualifiedName& name) const |
| { |
| if (cssPropertyIdForSVGAttributeName(name) > 0) |
| return true; |
| return StyledElement::hasPresentationalHintsForAttribute(name); |
| } |
| |
| void SVGElement::collectPresentationalHintsForAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style) |
| { |
| CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name); |
| if (propertyID > 0) |
| addPropertyToPresentationalHintStyle(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; |
| } |
| } |
| |
| Node::InsertedIntoAncestorResult SVGElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| StyledElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| updateRelativeLengthsInformation(); |
| |
| if (needsPendingResourceHandling() && insertionType.connectedToDocument && !isInShadowTree()) { |
| SVGDocumentExtensions& extensions = document().accessSVGExtensions(); |
| String resourceId = getIdAttribute(); |
| if (extensions.isIdOfPendingResource(resourceId)) |
| return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
| } |
| |
| hideNonce(); |
| |
| return InsertedIntoAncestorResult::Done; |
| } |
| |
| void SVGElement::didFinishInsertingNode() |
| { |
| buildPendingResourcesIfNeeded(); |
| } |
| |
| 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.takeElementFromPendingResourcesForRemovalMap(resourceId)) { |
| ASSERT(clientElement->hasPendingResources()); |
| if (clientElement->hasPendingResources()) { |
| clientElement->buildPendingResource(); |
| if (auto renderer = clientElement->renderer()) { |
| for (auto& ancestor : ancestorsOfType<RenderSVGResourceContainer>(*renderer)) |
| ancestor.markAllClientsForRepaint(); |
| } |
| extensions.clearHasPendingResourcesIfPossible(*clientElement); |
| } |
| } |
| } |
| |
| void SVGElement::childrenChanged(const ChildChange& change) |
| { |
| StyledElement::childrenChanged(change); |
| |
| if (change.source == ChildChange::Source::Parser) |
| return; |
| invalidateInstances(); |
| } |
| |
| 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 { |
| bool neverRegistered = !m_elementsWithRelativeLengths.contains(element); |
| if (neverRegistered) |
| return; |
| |
| m_elementsWithRelativeLengths.remove(element); |
| } |
| |
| if (is<SVGGraphicsElement>(element)) { |
| if (RefPtr parent = parentNode(); is<SVGElement>(parent)) |
| downcast<SVGElement>(*parent).updateRelativeLengthsInformation(hasRelativeLengths, *this); |
| } |
| } |
| |
| bool SVGElement::accessKeyAction(bool sendMouseEvents) |
| { |
| return dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); |
| } |
| |
| void SVGElement::invalidateInstances() |
| { |
| if (instanceUpdatesBlocked()) |
| return; |
| |
| for (auto& instance : copyToVectorOf<Ref<SVGElement>>(instances())) { |
| if (auto useElement = instance->correspondingUseElement()) |
| useElement->invalidateShadowTree(); |
| instance->setCorrespondingElement(nullptr); |
| } |
| } |
| |
| } |