diff options
Diffstat (limited to 'WebCore/svg/SVGUseElement.cpp')
-rw-r--r-- | WebCore/svg/SVGUseElement.cpp | 277 |
1 files changed, 174 insertions, 103 deletions
diff --git a/WebCore/svg/SVGUseElement.cpp b/WebCore/svg/SVGUseElement.cpp index 2ce1bd1..a69334c 100644 --- a/WebCore/svg/SVGUseElement.cpp +++ b/WebCore/svg/SVGUseElement.cpp @@ -1,51 +1,54 @@ /* - Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> - 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org> - Copyright (C) Research In Motion Limited 2009-2010. 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. -*/ + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org> + * Copyright (C) Research In Motion Limited 2009-2010. 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" #if ENABLE(SVG) #include "SVGUseElement.h" +#include "Attribute.h" #include "CSSStyleSelector.h" -#include "CString.h" #include "Document.h" #include "Event.h" #include "EventListener.h" #include "HTMLNames.h" -#include "MappedAttribute.h" #include "NodeRenderStyle.h" #include "RegisteredEventListener.h" +#include "RenderSVGResource.h" #include "RenderSVGShadowTreeRootContainer.h" #include "SVGElementInstance.h" #include "SVGElementInstanceList.h" #include "SVGGElement.h" #include "SVGLength.h" #include "SVGPreserveAspectRatio.h" -#include "SVGShadowTreeElements.h" #include "SVGSMILElement.h" #include "SVGSVGElement.h" +#include "SVGShadowTreeElements.h" #include "SVGSymbolElement.h" #include "XLinkNames.h" +#include "XMLDocumentParser.h" #include "XMLSerializer.h" +#include <wtf/text/StringConcatenate.h> + // Dump SVGElementInstance object tree - useful to debug instanceRoot problems // #define DUMP_INSTANCE_TREE @@ -54,23 +57,21 @@ namespace WebCore { -SVGUseElement::SVGUseElement(const QualifiedName& tagName, Document* doc) - : SVGStyledTransformableElement(tagName, doc) - , SVGTests() - , SVGLangSpace() - , SVGExternalResourcesRequired() - , SVGURIReference() +inline SVGUseElement::SVGUseElement(const QualifiedName& tagName, Document* document) + : SVGStyledTransformableElement(tagName, document) , m_x(LengthModeWidth) , m_y(LengthModeHeight) , m_width(LengthModeWidth) , m_height(LengthModeHeight) + , m_updatesBlocked(false) , m_isPendingResource(false) , m_needsShadowTreeRecreation(false) { } -SVGUseElement::~SVGUseElement() +PassRefPtr<SVGUseElement> SVGUseElement::create(const QualifiedName& tagName, Document* document) { + return adoptRef(new SVGUseElement(tagName, document)); } SVGElementInstance* SVGUseElement::instanceRoot() const @@ -91,7 +92,7 @@ SVGElementInstance* SVGUseElement::animatedInstanceRoot() const return 0; } -void SVGUseElement::parseMappedAttribute(MappedAttribute* attr) +void SVGUseElement::parseMappedAttribute(Attribute* attr) { if (attr->name() == SVGNames::xAttr) setXBaseValue(SVGLength(LengthModeWidth, attr->value())); @@ -121,22 +122,32 @@ void SVGUseElement::parseMappedAttribute(MappedAttribute* attr) void SVGUseElement::insertedIntoDocument() { // This functions exists to assure assumptions made in the code regarding SVGElementInstance creation/destruction are satisfied. - SVGElement::insertedIntoDocument(); - ASSERT(!m_targetElementInstance); + SVGStyledTransformableElement::insertedIntoDocument(); + ASSERT(!m_targetElementInstance || ((document()->isSVGDocument() || document()->isXHTMLDocument()) && !static_cast<XMLDocumentParser*>(document()->parser())->wellFormed())); ASSERT(!m_isPendingResource); } void SVGUseElement::removedFromDocument() { + SVGStyledTransformableElement::removedFromDocument(); m_targetElementInstance = 0; - SVGElement::removedFromDocument(); } void SVGUseElement::svgAttributeChanged(const QualifiedName& attrName) { SVGStyledTransformableElement::svgAttributeChanged(attrName); - if (!renderer()) + bool isXYAttribute = attrName == SVGNames::xAttr || attrName == SVGNames::yAttr; + bool isWidthHeightAttribute = attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr; + + if (isXYAttribute || isWidthHeightAttribute) + updateRelativeLengthsInformation(); + + if (SVGTests::handleAttributeChange(this, attrName)) + return; + + RenderObject* object = renderer(); + if (!object) return; if (SVGURIReference::isKnownAttribute(attrName)) { @@ -150,12 +161,12 @@ void SVGUseElement::svgAttributeChanged(const QualifiedName& attrName) return; } - if (attrName == SVGNames::xAttr || attrName == SVGNames::yAttr) { + if (isXYAttribute) { updateContainerOffsets(); return; } - if (attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr) { + if (isWidthHeightAttribute) { updateContainerSizes(); return; } @@ -166,12 +177,15 @@ void SVGUseElement::svgAttributeChanged(const QualifiedName& attrName) return; } - if (SVGTests::isKnownAttribute(attrName) - || SVGLangSpace::isKnownAttribute(attrName) - || SVGExternalResourcesRequired::isKnownAttribute(attrName) - || SVGStyledTransformableElement::isKnownAttribute(attrName)) { - invalidateShadowTree(); + if (SVGStyledTransformableElement::isKnownAttribute(attrName)) { + object->setNeedsTransformUpdate(); + RenderSVGResource::markForLayoutAndParentResourceInvalidation(object); + return; } + + if (SVGLangSpace::isKnownAttribute(attrName) + || SVGExternalResourcesRequired::isKnownAttribute(attrName)) + invalidateShadowTree(); } void SVGUseElement::synchronizeProperty(const QualifiedName& attrName) @@ -246,8 +260,8 @@ void SVGUseElement::updateContainerSizes() // Update whole subtree, scanning for shadow container elements, that correspond to <svg>/<symbol> tags updateContainerSize(this, m_targetElementInstance.get()); - if (renderer()) - renderer()->setNeedsLayout(true); + if (RenderObject* object = renderer()) + RenderSVGResource::markForLayoutAndParentResourceInvalidation(object); } static void updateContainerOffset(SVGElementInstance* targetInstance) @@ -286,7 +300,7 @@ void SVGUseElement::updateContainerOffsets() SVGElement* shadowRoot = m_targetElementInstance->shadowTreeElement(); ASSERT(shadowRoot); - Node* parentNode = shadowRoot->parentNode(); + ContainerNode* parentNode = shadowRoot->parentNode(); ASSERT(parentNode); ASSERT(parentNode->isSVGElement()); ASSERT(parentNode->hasTagName(SVGNames::gTag)); @@ -298,29 +312,33 @@ void SVGUseElement::updateContainerOffsets() // Update whole subtree, scanning for shadow container elements, marking a cloned use subtree updateContainerOffset(m_targetElementInstance.get()); - if (renderer()) - renderer()->setNeedsLayout(true); + if (RenderObject* object = renderer()) + RenderSVGResource::markForLayoutAndParentResourceInvalidation(object); } void SVGUseElement::recalcStyle(StyleChange change) { // Eventually mark shadow root element needing style recalc - if (needsStyleRecalc() && m_targetElementInstance) { + if (needsStyleRecalc() && m_targetElementInstance && !m_updatesBlocked) { if (SVGElement* shadowRoot = m_targetElementInstance->shadowTreeElement()) shadowRoot->setNeedsStyleRecalc(); } SVGStyledTransformableElement::recalcStyle(change); - bool needsStyleUpdate = !m_needsShadowTreeRecreation; - if (m_needsShadowTreeRecreation) { - static_cast<RenderSVGShadowTreeRootContainer*>(renderer())->markShadowTreeForRecreation(); - m_needsShadowTreeRecreation = false; - } + // Assure that the shadow tree has not been marked for recreation, while we're building it. + if (m_updatesBlocked) + ASSERT(!m_needsShadowTreeRecreation); RenderSVGShadowTreeRootContainer* shadowRoot = static_cast<RenderSVGShadowTreeRootContainer*>(renderer()); if (!shadowRoot) return; + + bool needsStyleUpdate = !m_needsShadowTreeRecreation; + if (m_needsShadowTreeRecreation) { + shadowRoot->markShadowTreeForRecreation(); + m_needsShadowTreeRecreation = false; + } shadowRoot->updateFromElement(); @@ -339,7 +357,7 @@ void dumpInstanceTree(unsigned int& depth, String& text, SVGElementInstance* tar SVGElement* shadowTreeElement = targetInstance->shadowTreeElement(); ASSERT(shadowTreeElement); - String elementId = element->getIDAttribute(); + String elementId = element->getIdAttribute(); String elementNodeName = element->nodeName(); String shadowTreeElementNodeName = shadowTreeElement->nodeName(); String parentNodeName = element->parentNode() ? element->parentNode()->nodeName() : "null"; @@ -356,7 +374,7 @@ void dumpInstanceTree(unsigned int& depth, String& text, SVGElementInstance* tar text += " "; const HashSet<SVGElementInstance*>& elementInstances = element->instancesForElement(); - text += String::format("Corresponding element is associated with %i instance(s):\n", elementInstances.size()); + text += makeString("Corresponding element is associated with ", String::number(elementInstances.size()), " instance(s):\n"); const HashSet<SVGElementInstance*>::const_iterator end = elementInstances.end(); for (HashSet<SVGElementInstance*>::const_iterator it = elementInstances.begin(); it != end; ++it) { @@ -414,7 +432,7 @@ void SVGUseElement::buildPendingResource() ASSERT(!m_targetElementInstance); if (!targetElement) { - if (m_isPendingResource) + if (m_isPendingResource || id.isEmpty()) return; m_isPendingResource = true; @@ -432,13 +450,38 @@ void SVGUseElement::buildPendingResource() void SVGUseElement::buildShadowAndInstanceTree(SVGShadowTreeRootElement* shadowRoot) { + struct ShadowTreeUpdateBlocker { + ShadowTreeUpdateBlocker(SVGUseElement* currentUseElement) + : useElement(currentUseElement) + { + useElement->setUpdatesBlocked(true); + } + + ~ShadowTreeUpdateBlocker() + { + useElement->setUpdatesBlocked(false); + } + + SVGUseElement* useElement; + }; + + // When cloning the target nodes, they may decide to synchronize style and/or animated SVG attributes. + // That causes calls to SVGElementInstance::updateAllInstancesOfElement(), which mark the shadow tree for recreation. + // Solution: block any updates to the shadow tree while we're building it. + ShadowTreeUpdateBlocker blocker(this); + String id = SVGURIReference::getTarget(href()); Element* targetElement = document()->getElementById(id); - ASSERT(targetElement); + if (!targetElement) { + // The only time we should get here is when the use element has not been + // given a resource to target. + ASSERT(m_resourceId.isEmpty()); + return; + } // Do not build the shadow/instance tree for <use> elements living in a shadow tree. // The will be expanded soon anyway - see expandUseElementsInShadowTree(). - Node* parent = parentNode(); + ContainerNode* parent = parentNode(); while (parent) { if (parent->isShadowNode()) return; @@ -544,6 +587,9 @@ void SVGUseElement::buildShadowAndInstanceTree(SVGShadowTreeRootElement* shadowR // Update container offset/size updateContainerOffsets(); updateContainerSizes(); + + // Update relative length information + updateRelativeLengthsInformation(); } RenderObject* SVGUseElement::createRenderer(RenderArena* arena, RenderStyle*) @@ -582,21 +628,36 @@ static bool isDirectReference(Node* n) n->hasTagName(SVGNames::textTag); } -Path SVGUseElement::toClipPath() const +void SVGUseElement::toClipPath(Path& path) const { + ASSERT(path.isEmpty()); + Node* n = m_targetElementInstance ? m_targetElementInstance->shadowTreeElement() : 0; if (!n) - return Path(); + return; if (n->isSVGElement() && static_cast<SVGElement*>(n)->isStyledTransformable()) { if (!isDirectReference(n)) // Spec: Indirect references are an error (14.3.5) document()->accessSVGExtensions()->reportError("Not allowed to use indirect reference in <clip-path>"); - else - return static_cast<SVGStyledTransformableElement*>(n)->toClipPath(); + else { + static_cast<SVGStyledTransformableElement*>(n)->toClipPath(path); + path.translate(FloatSize(x().value(this), y().value(this))); + path.transform(animatedLocalTransform()); + } } +} + +RenderObject* SVGUseElement::rendererClipChild() const +{ + Node* n = m_targetElementInstance ? m_targetElementInstance->shadowTreeElement() : 0; + if (!n) + return 0; + + if (n->isSVGElement() && isDirectReference(n)) + return static_cast<SVGElement*>(n)->renderer(); - return Path(); + return 0; } void SVGUseElement::buildInstanceTree(SVGElement* target, SVGElementInstance* targetInstance, bool& foundProblem) @@ -657,7 +718,8 @@ void SVGUseElement::handleDeepUseReferencing(SVGUseElement* use, SVGElementInsta while (instance) { SVGElement* element = instance->correspondingElement(); - if (element->getIDAttribute() == id) { + // FIXME: This should probably be using getIdAttribute instead of idForStyleResolution. + if (element->hasID() && element->idForStyleResolution() == id) { foundProblem = true; return; } @@ -683,7 +745,7 @@ void SVGUseElement::removeDisallowedElementsFromSubtree(Node* subtree) if (isDisallowedElement(node)) { Node* next = node->traverseNextSibling(subtree); // The subtree is not in document so this won't generate events that could mutate the tree. - node->parent()->removeChild(node, ec); + node->parentNode()->removeChild(node, ec); node = next; } else node = node->traverseNextNode(subtree); @@ -736,36 +798,17 @@ void SVGUseElement::expandUseElementsInShadowTree(SVGShadowTreeRootElement* shad target = static_cast<SVGElement*>(targetElement); // Don't ASSERT(target) here, it may be "pending", too. - if (target) { - // Setup sub-shadow tree root node - RefPtr<SVGShadowTreeContainerElement> cloneParent = new SVGShadowTreeContainerElement(document()); - - // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the - // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. - transferUseAttributesToReplacedElement(use, cloneParent.get()); - - ExceptionCode ec = 0; - - // For instance <use> on <foreignObject> (direct case). - if (isDisallowedElement(target)) { - // We still have to setup the <use> replacment (<g>). Otherwhise - // associateInstancesWithShadowTreeElements() makes wrong assumptions. - // Replace <use> with referenced content. - ASSERT(use->parentNode()); - use->parentNode()->replaceChild(cloneParent.release(), use, ec); - ASSERT(!ec); - return; - } + // Setup sub-shadow tree root node + RefPtr<SVGShadowTreeContainerElement> cloneParent = SVGShadowTreeContainerElement::create(document()); + use->cloneChildNodes(cloneParent.get()); - RefPtr<Element> newChild = target->cloneElementWithChildren(); + // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the + // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. + transferUseAttributesToReplacedElement(use, cloneParent.get()); - // We don't walk the target tree element-by-element, and clone each element, - // but instead use cloneElementWithChildren(). This is an optimization for the common - // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). - // Though if there are disallowed elements in the subtree, we have to remove them. - // For instance: <use> on <g> containing <foreignObject> (indirect case). - if (subtreeContainsDisallowedElement(newChild.get())) - removeDisallowedElementsFromSubtree(newChild.get()); + ExceptionCode ec = 0; + if (target && !isDisallowedElement(target)) { + RefPtr<Element> newChild = target->cloneElementWithChildren(); SVGElement* newChildPtr = 0; if (newChild->isSVGElement()) @@ -774,16 +817,24 @@ void SVGUseElement::expandUseElementsInShadowTree(SVGShadowTreeRootElement* shad cloneParent->appendChild(newChild.release(), ec); ASSERT(!ec); + } - // Replace <use> with referenced content. - ASSERT(use->parentNode()); - use->parentNode()->replaceChild(cloneParent.release(), use, ec); - ASSERT(!ec); + // We don't walk the target tree element-by-element, and clone each element, + // but instead use cloneElementWithChildren(). This is an optimization for the common + // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). + // Though if there are disallowed elements in the subtree, we have to remove them. + // For instance: <use> on <g> containing <foreignObject> (indirect case). + if (subtreeContainsDisallowedElement(cloneParent.get())) + removeDisallowedElementsFromSubtree(cloneParent.get()); - // Immediately stop here, and restart expanding. - expandUseElementsInShadowTree(shadowRoot, shadowRoot); - return; - } + // Replace <use> with referenced content. + ASSERT(use->parentNode()); + use->parentNode()->replaceChild(cloneParent.release(), use, ec); + ASSERT(!ec); + + // Immediately stop here, and restart expanding. + expandUseElementsInShadowTree(shadowRoot, shadowRoot); + return; } for (RefPtr<Node> child = element->firstChild(); child; child = child->nextSibling()) @@ -799,7 +850,7 @@ void SVGUseElement::expandSymbolElementsInShadowTree(SVGShadowTreeRootElement* s // height are provided on the 'use' element, then these attributes will be transferred to // the generated 'svg'. If attributes width and/or height are not specified, the generated // 'svg' element will use values of 100% for these attributes. - RefPtr<SVGSVGElement> svgElement = new SVGSVGElement(SVGNames::svgTag, document()); + RefPtr<SVGSVGElement> svgElement = SVGSVGElement::create(SVGNames::svgTag, document()); // Transfer all attributes from <symbol> to the new <svg> element svgElement->attributes()->setAttributes(*element->attributes()); @@ -899,6 +950,9 @@ void SVGUseElement::associateInstancesWithShadowTreeElements(Node* target, SVGEl while (node && !node->isSVGElement()) node = node->nextSibling(); + if (!node) + break; + associateInstancesWithShadowTreeElements(node, instance); node = node->nextSibling(); } @@ -937,6 +991,10 @@ SVGElementInstance* SVGUseElement::instanceForShadowTreeElement(Node* element, S void SVGUseElement::invalidateShadowTree() { + // Don't mutate the shadow tree while we're building it. + if (m_updatesBlocked) + return; + m_needsShadowTreeRecreation = true; setNeedsStyleRecalc(); } @@ -966,9 +1024,22 @@ void SVGUseElement::transferUseAttributesToReplacedElement(SVGElement* from, SVG ASSERT(!ec); } -bool SVGUseElement::hasRelativeValues() const +bool SVGUseElement::selfHasRelativeLengths() const { - return x().isRelative() || y().isRelative() || width().isRelative() || height().isRelative(); + if (x().isRelative() + || y().isRelative() + || width().isRelative() + || height().isRelative()) + return true; + + if (!m_targetElementInstance) + return false; + + SVGElement* element = m_targetElementInstance->correspondingElement(); + if (!element || !element->isStyled()) + return false; + + return static_cast<SVGStyledElement*>(element)->hasRelativeLengths(); } } |