| /* |
| * 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 Apple Inc. All rights reserved. |
| * Copyright (C) 2008 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au> |
| * |
| * 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 "core/svg/SVGElement.h" |
| |
| #include "bindings/core/v8/ScriptEventListener.h" |
| #include "core/HTMLNames.h" |
| #include "core/SVGNames.h" |
| #include "core/XMLNames.h" |
| #include "core/animation/AnimationStack.h" |
| #include "core/animation/DocumentAnimations.h" |
| #include "core/animation/ElementAnimations.h" |
| #include "core/animation/InterpolationEnvironment.h" |
| #include "core/animation/InvalidatableInterpolation.h" |
| #include "core/css/CSSCursorImageValue.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/events/Event.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/HTMLElement.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/svg/LayoutSVGResourceContainer.h" |
| #include "core/svg/SVGCursorElement.h" |
| #include "core/svg/SVGDocumentExtensions.h" |
| #include "core/svg/SVGElementRareData.h" |
| #include "core/svg/SVGGraphicsElement.h" |
| #include "core/svg/SVGSVGElement.h" |
| #include "core/svg/SVGTitleElement.h" |
| #include "core/svg/SVGUseElement.h" |
| #include "core/svg/properties/SVGProperty.h" |
| #include "wtf/AutoReset.h" |
| #include "wtf/Threading.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| using namespace SVGNames; |
| |
| SVGElement::SVGElement(const QualifiedName& tagName, |
| Document& document, |
| ConstructionType constructionType) |
| : Element(tagName, &document, constructionType), |
| #if ENABLE(ASSERT) |
| m_inRelativeLengthClientsInvalidation(false), |
| #endif |
| m_SVGRareData(nullptr), |
| m_className(SVGAnimatedString::create(this, HTMLNames::classAttr)) { |
| addToPropertyMap(m_className); |
| setHasCustomStyleCallbacks(); |
| } |
| |
| SVGElement::~SVGElement() { |
| ASSERT(isConnected() || !hasRelativeLengths()); |
| } |
| |
| void SVGElement::detachLayoutTree(const AttachContext& context) { |
| Element::detachLayoutTree(context); |
| if (SVGElement* element = correspondingElement()) |
| element->removeInstanceMapping(this); |
| } |
| |
| void SVGElement::attachLayoutTree(const AttachContext& context) { |
| Element::attachLayoutTree(context); |
| if (SVGElement* element = correspondingElement()) |
| element->mapInstanceToElement(this); |
| } |
| |
| short SVGElement::tabIndex() const { |
| if (supportsFocus()) |
| return Element::tabIndex(); |
| return -1; |
| } |
| |
| void SVGElement::willRecalcStyle(StyleRecalcChange change) { |
| if (!hasSVGRareData()) |
| 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 > NoChange || needsStyleRecalc()) |
| svgRareData()->setNeedsOverrideComputedStyleUpdate(); |
| } |
| |
| void SVGElement::buildPendingResourcesIfNeeded() { |
| Document& document = this->document(); |
| if (!needsPendingResourceHandling() || !isConnected() || inUseShadowTree()) |
| return; |
| |
| SVGDocumentExtensions& extensions = document.accessSVGExtensions(); |
| AtomicString resourceId = getIdAttribute(); |
| if (!extensions.hasPendingResource(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 ( |
| Element* clientElement = |
| extensions.removeElementFromPendingResourcesForRemoval(resourceId)) { |
| ASSERT(clientElement->hasPendingResources()); |
| if (clientElement->hasPendingResources()) { |
| // FIXME: Ideally we'd always resolve pending resources async instead of |
| // inside insertedInto and svgAttributeChanged. For now we only do it for |
| // <use> since that would stamp out DOM. |
| if (isSVGUseElement(clientElement)) |
| toSVGUseElement(clientElement)->invalidateShadowTree(); |
| else |
| clientElement->buildPendingResource(); |
| extensions.clearHasPendingResourcesIfPossible(clientElement); |
| } |
| } |
| } |
| |
| SVGElementRareData* SVGElement::ensureSVGRareData() { |
| if (hasSVGRareData()) |
| return svgRareData(); |
| |
| m_SVGRareData = new SVGElementRareData(this); |
| return m_SVGRareData.get(); |
| } |
| |
| bool SVGElement::isOutermostSVGSVGElement() const { |
| if (!isSVGSVGElement(*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 (isSVGForeignObjectElement(*parentNode())) |
| return true; |
| |
| // 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 (inUseShadowTree() && parentOrShadowHostElement() && |
| parentOrShadowHostElement()->isSVGElement()) |
| return false; |
| |
| // 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 AtomicString& value) { |
| if (error == SVGParseStatus::NoError) |
| return; |
| // Don't report any errors on attribute removal. |
| if (value.isNull()) |
| return; |
| document().accessSVGExtensions().reportError( |
| error.format(tagName(), name, value)); |
| } |
| |
| String SVGElement::title() const { |
| // According to spec, we should not return titles when hovering over root |
| // <svg> elements (those <title> elements are the title of the document, not a |
| // tooltip) so we instantly return. |
| if (isOutermostSVGSVGElement()) |
| return String(); |
| |
| if (inUseShadowTree()) { |
| String useTitle(ownerShadowHost()->title()); |
| if (!useTitle.isEmpty()) |
| return useTitle; |
| } |
| |
| // If we aren't an instance in a <use> or the <use> title was not found, then |
| // find the first <title> child of this element. |
| // If a title child was found, return the text contents. |
| if (Element* titleElement = Traversal<SVGTitleElement>::firstChild(*this)) |
| return titleElement->innerText(); |
| |
| // Otherwise return a null/empty string. |
| return String(); |
| } |
| |
| bool SVGElement::instanceUpdatesBlocked() const { |
| return hasSVGRareData() && svgRareData()->instanceUpdatesBlocked(); |
| } |
| |
| void SVGElement::setInstanceUpdatesBlocked(bool value) { |
| if (hasSVGRareData()) |
| svgRareData()->setInstanceUpdatesBlocked(value); |
| } |
| |
| void SVGElement::setWebAnimationsPending() { |
| document().accessSVGExtensions().addWebAnimationsPendingSVGElement(*this); |
| ensureSVGRareData()->setWebAnimatedAttributesDirty(true); |
| ensureUniqueElementData().m_animatedSVGAttributesAreDirty = true; |
| } |
| |
| static bool isSVGAttributeHandle(const PropertyHandle& propertyHandle) { |
| return propertyHandle.isSVGAttribute(); |
| } |
| |
| void SVGElement::applyActiveWebAnimations() { |
| ActiveInterpolationsMap activeInterpolationsMap = |
| AnimationStack::activeInterpolations( |
| &elementAnimations()->animationStack(), nullptr, nullptr, |
| KeyframeEffectReadOnly::DefaultPriority, isSVGAttributeHandle); |
| for (auto& entry : activeInterpolationsMap) { |
| const QualifiedName& attribute = entry.key.svgAttribute(); |
| InterpolationEnvironment environment( |
| *this, propertyFromAttribute(attribute)->baseValueBase()); |
| InvalidatableInterpolation::applyStack(entry.value, environment); |
| } |
| svgRareData()->setWebAnimatedAttributesDirty(false); |
| } |
| |
| static inline void notifyAnimValChanged(SVGElement* targetElement, |
| const QualifiedName& attributeName) { |
| targetElement->invalidateSVGAttributes(); |
| targetElement->svgAttributeChanged(attributeName); |
| } |
| |
| template <typename T> |
| static void forSelfAndInstances(SVGElement* element, T callback) { |
| SVGElement::InstanceUpdateBlocker blocker(element); |
| callback(element); |
| for (SVGElement* instance : element->instancesForElement()) |
| callback(instance); |
| } |
| |
| void SVGElement::setWebAnimatedAttribute(const QualifiedName& attribute, |
| SVGPropertyBase* value) { |
| forSelfAndInstances(this, [&attribute, &value](SVGElement* element) { |
| if (SVGAnimatedPropertyBase* animatedProperty = |
| element->propertyFromAttribute(attribute)) { |
| animatedProperty->setAnimatedValue(value); |
| notifyAnimValChanged(element, attribute); |
| } |
| }); |
| ensureSVGRareData()->webAnimatedAttributes().add(&attribute); |
| } |
| |
| void SVGElement::clearWebAnimatedAttributes() { |
| if (!hasSVGRareData()) |
| return; |
| for (const QualifiedName* attribute : |
| svgRareData()->webAnimatedAttributes()) { |
| forSelfAndInstances(this, [&attribute](SVGElement* element) { |
| if (SVGAnimatedPropertyBase* animatedProperty = |
| element->propertyFromAttribute(*attribute)) { |
| animatedProperty->animationEnded(); |
| notifyAnimValChanged(element, *attribute); |
| } |
| }); |
| } |
| svgRareData()->webAnimatedAttributes().clear(); |
| } |
| |
| void SVGElement::setAnimatedAttribute(const QualifiedName& attribute, |
| SVGPropertyBase* value) { |
| forSelfAndInstances(this, [&attribute, &value](SVGElement* element) { |
| if (SVGAnimatedPropertyBase* animatedProperty = |
| element->propertyFromAttribute(attribute)) |
| animatedProperty->setAnimatedValue(value); |
| }); |
| } |
| |
| void SVGElement::invalidateAnimatedAttribute(const QualifiedName& attribute) { |
| forSelfAndInstances(this, [&attribute](SVGElement* element) { |
| notifyAnimValChanged(element, attribute); |
| }); |
| } |
| |
| void SVGElement::clearAnimatedAttribute(const QualifiedName& attribute) { |
| forSelfAndInstances(this, [&attribute](SVGElement* element) { |
| if (SVGAnimatedPropertyBase* animatedProperty = |
| element->propertyFromAttribute(attribute)) |
| animatedProperty->animationEnded(); |
| }); |
| } |
| |
| AffineTransform SVGElement::localCoordinateSpaceTransform(CTMScope) const { |
| // To be overriden by SVGGraphicsElement (or as special case SVGTextElement |
| // and SVGPatternElement) |
| return AffineTransform(); |
| } |
| |
| Node::InsertionNotificationRequest SVGElement::insertedInto( |
| ContainerNode* rootParent) { |
| Element::insertedInto(rootParent); |
| updateRelativeLengthsInformation(); |
| buildPendingResourcesIfNeeded(); |
| return InsertionDone; |
| } |
| |
| void SVGElement::removedFrom(ContainerNode* rootParent) { |
| bool wasInDocument = rootParent->isConnected(); |
| |
| if (wasInDocument && hasRelativeLengths()) { |
| // The root of the subtree being removed should take itself out from its |
| // parent's relative length set. For the other nodes in the subtree we don't |
| // need to do anything: they will get their own removedFrom() notification |
| // and just clear their sets. |
| if (rootParent->isSVGElement() && !parentNode()) { |
| ASSERT(toSVGElement(rootParent) |
| ->m_elementsWithRelativeLengths.contains(this)); |
| toSVGElement(rootParent)->updateRelativeLengthsInformation(false, this); |
| } |
| |
| m_elementsWithRelativeLengths.clear(); |
| } |
| |
| ASSERT_WITH_SECURITY_IMPLICATION( |
| !rootParent->isSVGElement() || |
| !toSVGElement(rootParent)->m_elementsWithRelativeLengths.contains(this)); |
| |
| Element::removedFrom(rootParent); |
| |
| if (wasInDocument) { |
| rebuildAllIncomingReferences(); |
| removeAllIncomingReferences(); |
| } |
| |
| invalidateInstances(); |
| } |
| |
| void SVGElement::childrenChanged(const ChildrenChange& change) { |
| Element::childrenChanged(change); |
| |
| // Invalidate all instances associated with us. |
| if (!change.byParser) |
| invalidateInstances(); |
| } |
| |
| CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName( |
| const QualifiedName& attrName) { |
| if (!attrName.namespaceURI().isNull()) |
| return CSSPropertyInvalid; |
| |
| static HashMap<StringImpl*, CSSPropertyID>* propertyNameToIdMap = 0; |
| if (!propertyNameToIdMap) { |
| propertyNameToIdMap = new HashMap<StringImpl*, CSSPropertyID>; |
| // This is a list of all base CSS and SVG CSS properties which are exposed |
| // as SVG XML attributes |
| const QualifiedName* const attrNames[] = { |
| &alignment_baselineAttr, |
| &baseline_shiftAttr, |
| &buffered_renderingAttr, |
| &clipAttr, |
| &clip_pathAttr, |
| &clip_ruleAttr, |
| &SVGNames::colorAttr, |
| &color_interpolationAttr, |
| &color_interpolation_filtersAttr, |
| &color_renderingAttr, |
| &cursorAttr, |
| &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, |
| &image_renderingAttr, |
| &letter_spacingAttr, |
| &lighting_colorAttr, |
| &marker_endAttr, |
| &marker_midAttr, |
| &marker_startAttr, |
| &maskAttr, |
| &mask_typeAttr, |
| &opacityAttr, |
| &overflowAttr, |
| &paint_orderAttr, |
| &pointer_eventsAttr, |
| &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, |
| &transform_originAttr, |
| &unicode_bidiAttr, |
| &vector_effectAttr, |
| &visibilityAttr, |
| &word_spacingAttr, |
| &writing_modeAttr, |
| }; |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(attrNames); i++) { |
| CSSPropertyID propertyId = cssPropertyID(attrNames[i]->localName()); |
| ASSERT(propertyId > 0); |
| propertyNameToIdMap->set(attrNames[i]->localName().impl(), propertyId); |
| } |
| } |
| |
| return propertyNameToIdMap->get(attrName.localName().impl()); |
| } |
| |
| void SVGElement::updateRelativeLengthsInformation(bool clientHasRelativeLengths, |
| SVGElement* clientElement) { |
| ASSERT(clientElement); |
| |
| // If we're not yet in a document, this function will be called again from |
| // insertedInto(). 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. |
| for (Node& currentNode : NodeTraversal::inclusiveAncestorsOf(*this)) { |
| if (!currentNode.isSVGElement()) |
| break; |
| SVGElement& currentElement = toSVGElement(currentNode); |
| ASSERT(!currentElement.m_inRelativeLengthClientsInvalidation); |
| |
| bool hadRelativeLengths = currentElement.hasRelativeLengths(); |
| if (clientHasRelativeLengths) |
| currentElement.m_elementsWithRelativeLengths.add(clientElement); |
| else |
| currentElement.m_elementsWithRelativeLengths.remove(clientElement); |
| |
| // If the relative length state hasn't changed, we can stop propagating the |
| // notification. |
| if (hadRelativeLengths == currentElement.hasRelativeLengths()) |
| return; |
| |
| clientElement = ¤tElement; |
| clientHasRelativeLengths = clientElement->hasRelativeLengths(); |
| } |
| |
| // Register root SVG elements for top level viewport change notifications. |
| if (isSVGSVGElement(*clientElement)) { |
| SVGDocumentExtensions& svgExtensions = accessDocumentSVGExtensions(); |
| if (clientElement->hasRelativeLengths()) |
| svgExtensions.addSVGRootWithRelativeLengthDescendents( |
| toSVGSVGElement(clientElement)); |
| else |
| svgExtensions.removeSVGRootWithRelativeLengthDescendents( |
| toSVGSVGElement(clientElement)); |
| } |
| } |
| |
| void SVGElement::invalidateRelativeLengthClients( |
| SubtreeLayoutScope* layoutScope) { |
| if (!isConnected()) |
| return; |
| |
| ASSERT(!m_inRelativeLengthClientsInvalidation); |
| #if ENABLE(ASSERT) |
| AutoReset<bool> inRelativeLengthClientsInvalidationChange( |
| &m_inRelativeLengthClientsInvalidation, true); |
| #endif |
| |
| if (LayoutObject* layoutObject = this->layoutObject()) { |
| if (hasRelativeLengths() && layoutObject->isSVGResourceContainer()) |
| toLayoutSVGResourceContainer(layoutObject) |
| ->invalidateCacheAndMarkForLayout(layoutScope); |
| else if (selfHasRelativeLengths()) |
| layoutObject->setNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::Unknown, MarkContainerChain, layoutScope); |
| } |
| |
| for (SVGElement* element : m_elementsWithRelativeLengths) { |
| if (element != this) |
| element->invalidateRelativeLengthClients(layoutScope); |
| } |
| } |
| |
| SVGSVGElement* SVGElement::ownerSVGElement() const { |
| ContainerNode* n = parentOrShadowHostNode(); |
| while (n) { |
| if (isSVGSVGElement(*n)) |
| return toSVGSVGElement(n); |
| |
| n = n->parentOrShadowHostNode(); |
| } |
| |
| return nullptr; |
| } |
| |
| SVGElement* SVGElement::viewportElement() const { |
| // This function needs shadow tree support - as LayoutSVGContainer uses this |
| // function to determine the "overflow" property. <use> on <symbol> wouldn't |
| // work otherwhise. |
| ContainerNode* n = parentOrShadowHostNode(); |
| while (n) { |
| if (isSVGSVGElement(*n) || isSVGImageElement(*n) || isSVGSymbolElement(*n)) |
| return toSVGElement(n); |
| |
| n = n->parentOrShadowHostNode(); |
| } |
| |
| return nullptr; |
| } |
| |
| SVGDocumentExtensions& SVGElement::accessDocumentSVGExtensions() { |
| // This function is provided for use by SVGAnimatedProperty to avoid |
| // global inclusion of core/dom/Document.h in SVG code. |
| return document().accessSVGExtensions(); |
| } |
| |
| void SVGElement::mapInstanceToElement(SVGElement* instance) { |
| ASSERT(instance); |
| ASSERT(instance->inUseShadowTree()); |
| |
| HeapHashSet<WeakMember<SVGElement>>& instances = |
| ensureSVGRareData()->elementInstances(); |
| ASSERT(!instances.contains(instance)); |
| |
| instances.add(instance); |
| } |
| |
| void SVGElement::removeInstanceMapping(SVGElement* instance) { |
| ASSERT(instance); |
| ASSERT(instance->inUseShadowTree()); |
| |
| if (!hasSVGRareData()) |
| return; |
| |
| HeapHashSet<WeakMember<SVGElement>>& instances = |
| svgRareData()->elementInstances(); |
| |
| instances.remove(instance); |
| } |
| |
| static HeapHashSet<WeakMember<SVGElement>>& emptyInstances() { |
| DEFINE_STATIC_LOCAL(HeapHashSet<WeakMember<SVGElement>>, emptyInstances, |
| (new HeapHashSet<WeakMember<SVGElement>>)); |
| return emptyInstances; |
| } |
| |
| const HeapHashSet<WeakMember<SVGElement>>& SVGElement::instancesForElement() |
| const { |
| if (!hasSVGRareData()) |
| return emptyInstances(); |
| return svgRareData()->elementInstances(); |
| } |
| |
| void SVGElement::setCursorElement(SVGCursorElement* cursorElement) { |
| SVGElementRareData* rareData = ensureSVGRareData(); |
| if (SVGCursorElement* oldCursorElement = rareData->cursorElement()) { |
| if (cursorElement == oldCursorElement) |
| return; |
| oldCursorElement->removeReferencedElement(this); |
| } |
| rareData->setCursorElement(cursorElement); |
| } |
| |
| void SVGElement::setCursorImageValue( |
| const CSSCursorImageValue* cursorImageValue) { |
| ensureSVGRareData()->setCursorImageValue(cursorImageValue); |
| } |
| |
| SVGElement* SVGElement::correspondingElement() const { |
| ASSERT(!hasSVGRareData() || !svgRareData()->correspondingElement() || |
| containingShadowRoot()); |
| return hasSVGRareData() ? svgRareData()->correspondingElement() : 0; |
| } |
| |
| SVGUseElement* SVGElement::correspondingUseElement() const { |
| if (ShadowRoot* root = containingShadowRoot()) { |
| if (isSVGUseElement(root->host()) && |
| (root->type() == ShadowRootType::UserAgent)) |
| return &toSVGUseElement(root->host()); |
| } |
| return nullptr; |
| } |
| |
| void SVGElement::setCorrespondingElement(SVGElement* correspondingElement) { |
| ensureSVGRareData()->setCorrespondingElement(correspondingElement); |
| } |
| |
| bool SVGElement::inUseShadowTree() const { |
| return correspondingUseElement(); |
| } |
| |
| void SVGElement::parseAttribute(const QualifiedName& name, |
| const AtomicString& oldValue, |
| const AtomicString& value) { |
| if (SVGAnimatedPropertyBase* property = propertyFromAttribute(name)) { |
| SVGParsingError parseError = property->setBaseValueAsString(value); |
| reportAttributeParsingError(parseError, name, value); |
| return; |
| } |
| |
| if (name == HTMLNames::classAttr) { |
| // SVG animation has currently requires special storage of values so we set |
| // the className here. svgAttributeChanged actually causes the resulting |
| // style updates (instead of Element::parseAttribute). We don't |
| // tell Element about the change to avoid parsing the class list twice |
| SVGParsingError parseError = m_className->setBaseValueAsString(value); |
| reportAttributeParsingError(parseError, name, value); |
| } else if (name == tabindexAttr) { |
| Element::parseAttribute(name, oldValue, value); |
| } else { |
| // standard events |
| const AtomicString& eventName = |
| HTMLElement::eventNameForAttributeName(name); |
| if (!eventName.isNull()) |
| setAttributeEventListener( |
| eventName, createAttributeEventListener(this, name, value, |
| eventParameterName())); |
| else |
| Element::parseAttribute(name, oldValue, value); |
| } |
| } |
| |
| typedef HashMap<QualifiedName, AnimatedPropertyType> AttributeToPropertyTypeMap; |
| AnimatedPropertyType SVGElement::animatedPropertyTypeForCSSAttribute( |
| const QualifiedName& attributeName) { |
| DEFINE_STATIC_LOCAL(AttributeToPropertyTypeMap, cssPropertyMap, ()); |
| |
| if (cssPropertyMap.isEmpty()) { |
| // Fill the map for the first use. |
| struct AttrToTypeEntry { |
| const QualifiedName& attr; |
| const AnimatedPropertyType propType; |
| }; |
| const AttrToTypeEntry attrToTypes[] = { |
| {alignment_baselineAttr, AnimatedString}, |
| {baseline_shiftAttr, AnimatedString}, |
| {buffered_renderingAttr, AnimatedString}, |
| {clip_pathAttr, AnimatedString}, |
| {clip_ruleAttr, AnimatedString}, |
| {SVGNames::colorAttr, AnimatedColor}, |
| {color_interpolationAttr, AnimatedString}, |
| {color_interpolation_filtersAttr, AnimatedString}, |
| {color_renderingAttr, AnimatedString}, |
| {cursorAttr, AnimatedString}, |
| {displayAttr, AnimatedString}, |
| {dominant_baselineAttr, AnimatedString}, |
| {fillAttr, AnimatedColor}, |
| {fill_opacityAttr, AnimatedNumber}, |
| {fill_ruleAttr, AnimatedString}, |
| {filterAttr, AnimatedString}, |
| {flood_colorAttr, AnimatedColor}, |
| {flood_opacityAttr, AnimatedNumber}, |
| {font_familyAttr, AnimatedString}, |
| {font_sizeAttr, AnimatedLength}, |
| {font_stretchAttr, AnimatedString}, |
| {font_styleAttr, AnimatedString}, |
| {font_variantAttr, AnimatedString}, |
| {font_weightAttr, AnimatedString}, |
| {image_renderingAttr, AnimatedString}, |
| {letter_spacingAttr, AnimatedLength}, |
| {lighting_colorAttr, AnimatedColor}, |
| {marker_endAttr, AnimatedString}, |
| {marker_midAttr, AnimatedString}, |
| {marker_startAttr, AnimatedString}, |
| {maskAttr, AnimatedString}, |
| {mask_typeAttr, AnimatedString}, |
| {opacityAttr, AnimatedNumber}, |
| {overflowAttr, AnimatedString}, |
| {paint_orderAttr, AnimatedString}, |
| {pointer_eventsAttr, AnimatedString}, |
| {shape_renderingAttr, AnimatedString}, |
| {stop_colorAttr, AnimatedColor}, |
| {stop_opacityAttr, AnimatedNumber}, |
| {strokeAttr, AnimatedColor}, |
| {stroke_dasharrayAttr, AnimatedLengthList}, |
| {stroke_dashoffsetAttr, AnimatedLength}, |
| {stroke_linecapAttr, AnimatedString}, |
| {stroke_linejoinAttr, AnimatedString}, |
| {stroke_miterlimitAttr, AnimatedNumber}, |
| {stroke_opacityAttr, AnimatedNumber}, |
| {stroke_widthAttr, AnimatedLength}, |
| {text_anchorAttr, AnimatedString}, |
| {text_decorationAttr, AnimatedString}, |
| {text_renderingAttr, AnimatedString}, |
| {vector_effectAttr, AnimatedString}, |
| {visibilityAttr, AnimatedString}, |
| {word_spacingAttr, AnimatedLength}, |
| }; |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(attrToTypes); i++) |
| cssPropertyMap.set(attrToTypes[i].attr, attrToTypes[i].propType); |
| } |
| // If the attribute is not present in the map, this will return the "empty |
| // value" per HashTraits - which is AnimatedUnknown. |
| DCHECK_EQ(HashTraits<AnimatedPropertyType>::emptyValue(), AnimatedUnknown); |
| return cssPropertyMap.get(attributeName); |
| } |
| |
| void SVGElement::addToPropertyMap(SVGAnimatedPropertyBase* property) { |
| m_attributeToPropertyMap.set(property->attributeName(), property); |
| } |
| |
| SVGAnimatedPropertyBase* SVGElement::propertyFromAttribute( |
| const QualifiedName& attributeName) const { |
| AttributeToPropertyMap::const_iterator it = |
| m_attributeToPropertyMap.find<SVGAttributeHashTranslator>(attributeName); |
| if (it == m_attributeToPropertyMap.end()) |
| return nullptr; |
| |
| return it->value.get(); |
| } |
| |
| bool SVGElement::isAnimatableCSSProperty(const QualifiedName& attrName) { |
| return animatedPropertyTypeForCSSAttribute(attrName) != AnimatedUnknown; |
| } |
| |
| bool SVGElement::isPresentationAttribute(const QualifiedName& name) const { |
| return cssPropertyIdForSVGAttributeName(name) > 0; |
| } |
| |
| void SVGElement::collectStyleForPresentationAttribute( |
| const QualifiedName& name, |
| const AtomicString& value, |
| MutableStylePropertySet* style) { |
| CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name); |
| if (propertyID > 0) |
| addPropertyToPresentationAttributeStyle(style, propertyID, value); |
| } |
| |
| bool SVGElement::haveLoadedRequiredResources() { |
| for (SVGElement* child = Traversal<SVGElement>::firstChild(*this); child; |
| child = Traversal<SVGElement>::nextSibling(*child)) { |
| if (!child->haveLoadedRequiredResources()) |
| return false; |
| } |
| return true; |
| } |
| |
| static inline void collectInstancesForSVGElement( |
| SVGElement* element, |
| HeapHashSet<WeakMember<SVGElement>>& instances) { |
| ASSERT(element); |
| if (element->containingShadowRoot()) |
| return; |
| |
| ASSERT(!element->instanceUpdatesBlocked()); |
| |
| instances = element->instancesForElement(); |
| } |
| |
| void SVGElement::addedEventListener( |
| const AtomicString& eventType, |
| RegisteredEventListener& registeredListener) { |
| // Add event listener to regular DOM element |
| Node::addedEventListener(eventType, registeredListener); |
| |
| // Add event listener to all shadow tree DOM element instances |
| HeapHashSet<WeakMember<SVGElement>> instances; |
| collectInstancesForSVGElement(this, instances); |
| AddEventListenerOptionsResolved options = registeredListener.options(); |
| EventListener* listener = registeredListener.listener(); |
| for (SVGElement* element : instances) { |
| bool result = |
| element->Node::addEventListenerInternal(eventType, listener, options); |
| DCHECK(result); |
| } |
| } |
| |
| void SVGElement::removedEventListener( |
| const AtomicString& eventType, |
| const RegisteredEventListener& registeredListener) { |
| Node::removedEventListener(eventType, registeredListener); |
| |
| // Remove event listener from all shadow tree DOM element instances |
| HeapHashSet<WeakMember<SVGElement>> instances; |
| collectInstancesForSVGElement(this, instances); |
| EventListenerOptions options = registeredListener.options(); |
| const EventListener* listener = registeredListener.listener(); |
| for (SVGElement* shadowTreeElement : instances) { |
| ASSERT(shadowTreeElement); |
| |
| shadowTreeElement->Node::removeEventListenerInternal(eventType, listener, |
| options); |
| } |
| } |
| |
| static bool hasLoadListener(Element* element) { |
| if (element->hasEventListeners(EventTypeNames::load)) |
| return true; |
| |
| for (element = element->parentOrShadowHostElement(); element; |
| element = element->parentOrShadowHostElement()) { |
| EventListenerVector* entry = |
| element->getEventListeners(EventTypeNames::load); |
| if (!entry) |
| continue; |
| for (size_t i = 0; i < entry->size(); ++i) { |
| if (entry->at(i).capture()) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool SVGElement::sendSVGLoadEventIfPossible() { |
| if (!haveLoadedRequiredResources()) |
| return false; |
| if ((isStructurallyExternal() || isSVGSVGElement(*this)) && |
| hasLoadListener(this)) |
| dispatchEvent(Event::create(EventTypeNames::load)); |
| return true; |
| } |
| |
| void SVGElement::sendSVGLoadEventToSelfAndAncestorChainIfPossible() { |
| // Let Document::implicitClose() dispatch the 'load' to the outermost SVG |
| // root. |
| if (isOutermostSVGSVGElement()) |
| return; |
| |
| // Save the next parent to dispatch to in case dispatching the event mutates |
| // the tree. |
| Element* parent = parentOrShadowHostElement(); |
| if (!sendSVGLoadEventIfPossible()) |
| return; |
| |
| // If document/window 'load' has been sent already, then only deliver to |
| // the element in question. |
| if (document().loadEventFinished()) |
| return; |
| |
| if (!parent || !parent->isSVGElement()) |
| return; |
| |
| toSVGElement(parent)->sendSVGLoadEventToSelfAndAncestorChainIfPossible(); |
| } |
| |
| void SVGElement::attributeChanged(const QualifiedName& name, |
| const AtomicString& oldValue, |
| const AtomicString& newValue, |
| AttributeModificationReason) { |
| Element::attributeChanged(name, oldValue, newValue); |
| |
| if (name == HTMLNames::idAttr) |
| rebuildAllIncomingReferences(); |
| |
| // 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. |
| if (name == HTMLNames::styleAttr) |
| return; |
| |
| svgAttributeBaseValChanged(name); |
| } |
| |
| void SVGElement::svgAttributeChanged(const QualifiedName& attrName) { |
| CSSPropertyID propId = SVGElement::cssPropertyIdForSVGAttributeName(attrName); |
| if (propId > 0) { |
| invalidateInstances(); |
| return; |
| } |
| |
| if (attrName == HTMLNames::classAttr) { |
| classAttributeChanged(AtomicString(m_className->currentValue()->value())); |
| invalidateInstances(); |
| return; |
| } |
| |
| if (attrName == HTMLNames::idAttr) { |
| LayoutObject* object = layoutObject(); |
| // Notify resources about id changes, this is important as we cache |
| // resources by id in SVGDocumentExtensions |
| if (object && object->isSVGResourceContainer()) |
| toLayoutSVGResourceContainer(object)->idChanged(); |
| if (isConnected()) |
| buildPendingResourcesIfNeeded(); |
| invalidateInstances(); |
| return; |
| } |
| } |
| |
| void SVGElement::svgAttributeBaseValChanged(const QualifiedName& attribute) { |
| svgAttributeChanged(attribute); |
| |
| if (!hasSVGRareData() || svgRareData()->webAnimatedAttributes().isEmpty()) |
| return; |
| |
| // TODO(alancutter): Only mark attributes as dirty if their animation depends |
| // on the underlying value. |
| svgRareData()->setWebAnimatedAttributesDirty(true); |
| elementData()->m_animatedSVGAttributesAreDirty = true; |
| } |
| |
| void SVGElement::ensureAttributeAnimValUpdated() { |
| if (!RuntimeEnabledFeatures::webAnimationsSVGEnabled()) |
| return; |
| |
| if ((hasSVGRareData() && svgRareData()->webAnimatedAttributesDirty()) || |
| (elementAnimations() && |
| DocumentAnimations::needsAnimationTimingUpdate(document()))) { |
| DocumentAnimations::updateAnimationTimingIfNeeded(document()); |
| applyActiveWebAnimations(); |
| } |
| } |
| |
| void SVGElement::synchronizeAnimatedSVGAttribute( |
| const QualifiedName& name) const { |
| if (!elementData() || !elementData()->m_animatedSVGAttributesAreDirty) |
| return; |
| |
| // We const_cast here because we have deferred baseVal mutation animation |
| // updates to this point in time. |
| const_cast<SVGElement*>(this)->ensureAttributeAnimValUpdated(); |
| |
| if (name == anyQName()) { |
| AttributeToPropertyMap::const_iterator::ValuesIterator it = |
| m_attributeToPropertyMap.values().begin(); |
| AttributeToPropertyMap::const_iterator::ValuesIterator end = |
| m_attributeToPropertyMap.values().end(); |
| for (; it != end; ++it) { |
| if ((*it)->needsSynchronizeAttribute()) |
| (*it)->synchronizeAttribute(); |
| } |
| |
| elementData()->m_animatedSVGAttributesAreDirty = false; |
| } else { |
| SVGAnimatedPropertyBase* property = m_attributeToPropertyMap.get(name); |
| if (property && property->needsSynchronizeAttribute()) |
| property->synchronizeAttribute(); |
| } |
| } |
| |
| PassRefPtr<ComputedStyle> SVGElement::customStyleForLayoutObject() { |
| if (!correspondingElement()) |
| return document().ensureStyleResolver().styleForElement(this); |
| |
| const ComputedStyle* style = nullptr; |
| if (Element* parent = parentOrShadowHostElement()) { |
| if (LayoutObject* layoutObject = parent->layoutObject()) |
| style = layoutObject->style(); |
| } |
| |
| return document().ensureStyleResolver().styleForElement( |
| correspondingElement(), style, DisallowStyleSharing); |
| } |
| |
| MutableStylePropertySet* SVGElement::animatedSMILStyleProperties() const { |
| if (hasSVGRareData()) |
| return svgRareData()->animatedSMILStyleProperties(); |
| return nullptr; |
| } |
| |
| MutableStylePropertySet* SVGElement::ensureAnimatedSMILStyleProperties() { |
| return ensureSVGRareData()->ensureAnimatedSMILStyleProperties(); |
| } |
| |
| void SVGElement::setUseOverrideComputedStyle(bool value) { |
| if (hasSVGRareData()) |
| svgRareData()->setUseOverrideComputedStyle(value); |
| } |
| |
| const ComputedStyle* SVGElement::ensureComputedStyle( |
| PseudoId pseudoElementSpecifier) { |
| if (!hasSVGRareData() || !svgRareData()->useOverrideComputedStyle()) |
| return Element::ensureComputedStyle(pseudoElementSpecifier); |
| |
| const ComputedStyle* parentStyle = nullptr; |
| if (Element* parent = parentOrShadowHostElement()) { |
| if (LayoutObject* layoutObject = parent->layoutObject()) |
| parentStyle = layoutObject->style(); |
| } |
| |
| return svgRareData()->overrideComputedStyle(this, parentStyle); |
| } |
| |
| bool SVGElement::hasFocusEventListeners() const { |
| return hasEventListeners(EventTypeNames::focusin) || |
| hasEventListeners(EventTypeNames::focusout) || |
| hasEventListeners(EventTypeNames::focus) || |
| hasEventListeners(EventTypeNames::blur); |
| } |
| |
| void SVGElement::markForLayoutAndParentResourceInvalidation( |
| LayoutObject* layoutObject) { |
| ASSERT(layoutObject); |
| LayoutSVGResourceContainer::markForLayoutAndParentResourceInvalidation( |
| layoutObject, true); |
| } |
| |
| void SVGElement::invalidateInstances() { |
| if (instanceUpdatesBlocked()) |
| return; |
| |
| const HeapHashSet<WeakMember<SVGElement>>& set = instancesForElement(); |
| if (set.isEmpty()) |
| return; |
| |
| // Mark all use elements referencing 'element' for rebuilding |
| for (SVGElement* instance : set) { |
| instance->setCorrespondingElement(0); |
| |
| if (SVGUseElement* element = instance->correspondingUseElement()) { |
| if (element->isConnected()) |
| element->invalidateShadowTree(); |
| } |
| } |
| |
| svgRareData()->elementInstances().clear(); |
| } |
| |
| void SVGElement::setNeedsStyleRecalcForInstances( |
| StyleChangeType changeType, |
| const StyleChangeReasonForTracing& reason) { |
| const HeapHashSet<WeakMember<SVGElement>>& set = instancesForElement(); |
| if (set.isEmpty()) |
| return; |
| |
| for (SVGElement* instance : set) |
| instance->setNeedsStyleRecalc(changeType, reason); |
| } |
| |
| SVGElement::InstanceUpdateBlocker::InstanceUpdateBlocker( |
| SVGElement* targetElement) |
| : m_targetElement(targetElement) { |
| if (m_targetElement) |
| m_targetElement->setInstanceUpdatesBlocked(true); |
| } |
| |
| SVGElement::InstanceUpdateBlocker::~InstanceUpdateBlocker() { |
| if (m_targetElement) |
| m_targetElement->setInstanceUpdatesBlocked(false); |
| } |
| |
| #if DCHECK_IS_ON() |
| bool SVGElement::isAnimatableAttribute(const QualifiedName& name) const { |
| // This static is atomically initialized to dodge a warning about |
| // a race when dumping debug data for a layer. |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(HashSet<QualifiedName>, animatableAttributes, |
| new HashSet<QualifiedName>({ |
| 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::filterUnitsAttr, |
| SVGNames::fxAttr, |
| SVGNames::fyAttr, |
| SVGNames::gradientTransformAttr, |
| SVGNames::gradientUnitsAttr, |
| SVGNames::heightAttr, |
| SVGNames::hrefAttr, |
| 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, |
| })); |
| |
| if (name == classAttr) |
| return true; |
| |
| return animatableAttributes.contains(name); |
| } |
| #endif // DCHECK_IS_ON() |
| |
| SVGElementSet* SVGElement::setOfIncomingReferences() const { |
| if (!hasSVGRareData()) |
| return nullptr; |
| return &svgRareData()->incomingReferences(); |
| } |
| |
| void SVGElement::addReferenceTo(SVGElement* targetElement) { |
| ASSERT(targetElement); |
| |
| ensureSVGRareData()->outgoingReferences().add(targetElement); |
| targetElement->ensureSVGRareData()->incomingReferences().add(this); |
| } |
| |
| void SVGElement::rebuildAllIncomingReferences() { |
| if (!hasSVGRareData()) |
| return; |
| |
| const SVGElementSet& incomingReferences = svgRareData()->incomingReferences(); |
| |
| // Iterate on a snapshot as |incomingReferences| may be altered inside loop. |
| HeapVector<Member<SVGElement>> incomingReferencesSnapshot; |
| copyToVector(incomingReferences, incomingReferencesSnapshot); |
| |
| // Force rebuilding the |sourceElement| so it knows about this change. |
| for (SVGElement* sourceElement : incomingReferencesSnapshot) { |
| // Before rebuilding |sourceElement| ensure it was not removed from under |
| // us. |
| if (incomingReferences.contains(sourceElement)) |
| sourceElement->svgAttributeChanged(SVGNames::hrefAttr); |
| } |
| } |
| |
| void SVGElement::removeAllIncomingReferences() { |
| if (!hasSVGRareData()) |
| return; |
| |
| SVGElementSet& incomingReferences = svgRareData()->incomingReferences(); |
| for (SVGElement* sourceElement : incomingReferences) { |
| ASSERT(sourceElement->hasSVGRareData()); |
| sourceElement->ensureSVGRareData()->outgoingReferences().remove(this); |
| } |
| incomingReferences.clear(); |
| } |
| |
| void SVGElement::removeAllOutgoingReferences() { |
| if (!hasSVGRareData()) |
| return; |
| |
| SVGElementSet& outgoingReferences = svgRareData()->outgoingReferences(); |
| for (SVGElement* targetElement : outgoingReferences) { |
| ASSERT(targetElement->hasSVGRareData()); |
| targetElement->ensureSVGRareData()->incomingReferences().remove(this); |
| } |
| outgoingReferences.clear(); |
| } |
| |
| DEFINE_TRACE(SVGElement) { |
| visitor->trace(m_elementsWithRelativeLengths); |
| visitor->trace(m_attributeToPropertyMap); |
| visitor->trace(m_SVGRareData); |
| visitor->trace(m_className); |
| Element::trace(visitor); |
| } |
| |
| const AtomicString& SVGElement::eventParameterName() { |
| DEFINE_STATIC_LOCAL(const AtomicString, evtString, ("evt")); |
| return evtString; |
| } |
| |
| } // namespace blink |