/* * Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <zimmermann@kde.org> * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Rob Buis <buis@kde.org> * Copyright (C) 2007 Apple Inc. 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 "SVGSVGElement.h" #include "AffineTransform.h" #include "Attribute.h" #include "CSSHelper.h" #include "CSSPropertyNames.h" #include "Document.h" #include "EventListener.h" #include "EventNames.h" #include "FloatConversion.h" #include "FloatRect.h" #include "FrameView.h" #include "HTMLNames.h" #include "RenderSVGResource.h" #include "RenderSVGRoot.h" #include "RenderSVGViewportContainer.h" #include "SMILTimeContainer.h" #include "SVGAngle.h" #include "SVGNames.h" #include "SVGPreserveAspectRatio.h" #include "SVGTransform.h" #include "SVGTransformList.h" #include "SVGViewElement.h" #include "SVGViewSpec.h" #include "SVGZoomEvent.h" #include "ScriptEventListener.h" #include "SelectionController.h" #include <wtf/StdLibExtras.h> namespace WebCore { // Animated property definitions DEFINE_ANIMATED_LENGTH(SVGSVGElement, SVGNames::xAttr, X, x) DEFINE_ANIMATED_LENGTH(SVGSVGElement, SVGNames::yAttr, Y, y) DEFINE_ANIMATED_LENGTH(SVGSVGElement, SVGNames::widthAttr, Width, width) DEFINE_ANIMATED_LENGTH(SVGSVGElement, SVGNames::heightAttr, Height, height) DEFINE_ANIMATED_BOOLEAN(SVGSVGElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired) DEFINE_ANIMATED_PRESERVEASPECTRATIO(SVGSVGElement, SVGNames::preserveAspectRatioAttr, PreserveAspectRatio, preserveAspectRatio) DEFINE_ANIMATED_RECT(SVGSVGElement, SVGNames::viewBoxAttr, ViewBox, viewBox) inline SVGSVGElement::SVGSVGElement(const QualifiedName& tagName, Document* doc) : SVGStyledLocatableElement(tagName, doc) , m_x(LengthModeWidth) , m_y(LengthModeHeight) , m_width(LengthModeWidth, "100%") , m_height(LengthModeHeight, "100%") , m_useCurrentView(false) , m_timeContainer(SMILTimeContainer::create(this)) , m_scale(1) , m_viewSpec(0) , m_containerSize(300, 150) , m_hasSetContainerSize(false) { doc->registerForDocumentActivationCallbacks(this); } PassRefPtr<SVGSVGElement> SVGSVGElement::create(const QualifiedName& tagName, Document* document) { return adoptRef(new SVGSVGElement(tagName, document)); } SVGSVGElement::~SVGSVGElement() { document()->unregisterForDocumentActivationCallbacks(this); // There are cases where removedFromDocument() is not called. // see ContainerNode::removeAllChildren, called by its destructor. document()->accessSVGExtensions()->removeTimeContainer(this); } void SVGSVGElement::willMoveToNewOwnerDocument() { document()->unregisterForDocumentActivationCallbacks(this); SVGStyledLocatableElement::willMoveToNewOwnerDocument(); } void SVGSVGElement::didMoveToNewOwnerDocument() { document()->registerForDocumentActivationCallbacks(this); SVGStyledLocatableElement::didMoveToNewOwnerDocument(); } const AtomicString& SVGSVGElement::contentScriptType() const { DEFINE_STATIC_LOCAL(const AtomicString, defaultValue, ("text/ecmascript")); const AtomicString& n = getAttribute(SVGNames::contentScriptTypeAttr); return n.isNull() ? defaultValue : n; } void SVGSVGElement::setContentScriptType(const AtomicString& type) { setAttribute(SVGNames::contentScriptTypeAttr, type); } const AtomicString& SVGSVGElement::contentStyleType() const { DEFINE_STATIC_LOCAL(const AtomicString, defaultValue, ("text/css")); const AtomicString& n = getAttribute(SVGNames::contentStyleTypeAttr); return n.isNull() ? defaultValue : n; } void SVGSVGElement::setContentStyleType(const AtomicString& type) { setAttribute(SVGNames::contentStyleTypeAttr, type); } FloatRect SVGSVGElement::viewport() const { FloatRect viewRectangle; if (!isOutermostSVG()) viewRectangle.setLocation(FloatPoint(x().value(this), y().value(this))); viewRectangle.setSize(FloatSize(width().value(this), height().value(this))); return viewBoxToViewTransform(viewRectangle.width(), viewRectangle.height()).mapRect(viewRectangle); } int SVGSVGElement::relativeWidthValue() const { SVGLength w = width(); if (w.unitType() != LengthTypePercentage) return 0; return static_cast<int>(w.valueAsPercentage() * m_containerSize.width()); } int SVGSVGElement::relativeHeightValue() const { SVGLength h = height(); if (h.unitType() != LengthTypePercentage) return 0; return static_cast<int>(h.valueAsPercentage() * m_containerSize.height()); } float SVGSVGElement::pixelUnitToMillimeterX() const { // 2.54 / cssPixelsPerInch gives CM. return (2.54f / cssPixelsPerInch) * 10.0f; } float SVGSVGElement::pixelUnitToMillimeterY() const { // 2.54 / cssPixelsPerInch gives CM. return (2.54f / cssPixelsPerInch) * 10.0f; } float SVGSVGElement::screenPixelToMillimeterX() const { return pixelUnitToMillimeterX(); } float SVGSVGElement::screenPixelToMillimeterY() const { return pixelUnitToMillimeterY(); } bool SVGSVGElement::useCurrentView() const { return m_useCurrentView; } void SVGSVGElement::setUseCurrentView(bool currentView) { m_useCurrentView = currentView; } SVGViewSpec* SVGSVGElement::currentView() const { if (!m_viewSpec) m_viewSpec = adoptPtr(new SVGViewSpec(const_cast<SVGSVGElement*>(this))); return m_viewSpec.get(); } float SVGSVGElement::currentScale() const { // Only the page zoom factor is relevant for SVG if (Frame* frame = document()->frame()) return frame->pageZoomFactor(); return m_scale; } void SVGSVGElement::setCurrentScale(float scale) { if (Frame* frame = document()->frame()) { // Calling setCurrentScale() on the outermost <svg> element in a standalone SVG document // is allowed to change the page zoom factor, influencing the document size, scrollbars etc. if (parentNode() == document()) frame->setPageZoomFactor(scale); return; } m_scale = scale; if (RenderObject* object = renderer()) RenderSVGResource::markForLayoutAndParentResourceInvalidation(object); } void SVGSVGElement::setCurrentTranslate(const FloatPoint& translation) { m_translation = translation; updateCurrentTranslate(); } void SVGSVGElement::updateCurrentTranslate() { if (RenderObject* object = renderer()) object->setNeedsLayout(true); if (parentNode() == document() && document()->renderer()) document()->renderer()->repaint(); } void SVGSVGElement::parseMappedAttribute(Attribute* attr) { if (!nearestViewportElement()) { bool setListener = true; // Only handle events if we're the outermost <svg> element if (attr->name() == HTMLNames::onunloadAttr) document()->setWindowAttributeEventListener(eventNames().unloadEvent, createAttributeEventListener(document()->frame(), attr)); else if (attr->name() == HTMLNames::onresizeAttr) document()->setWindowAttributeEventListener(eventNames().resizeEvent, createAttributeEventListener(document()->frame(), attr)); else if (attr->name() == HTMLNames::onscrollAttr) document()->setWindowAttributeEventListener(eventNames().scrollEvent, createAttributeEventListener(document()->frame(), attr)); else if (attr->name() == SVGNames::onzoomAttr) document()->setWindowAttributeEventListener(eventNames().zoomEvent, createAttributeEventListener(document()->frame(), attr)); else setListener = false; if (setListener) return; } if (attr->name() == HTMLNames::onabortAttr) document()->setWindowAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(document()->frame(), attr)); else if (attr->name() == HTMLNames::onerrorAttr) document()->setWindowAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(document()->frame(), attr)); else if (attr->name() == SVGNames::xAttr) setXBaseValue(SVGLength(LengthModeWidth, attr->value())); else if (attr->name() == SVGNames::yAttr) setYBaseValue(SVGLength(LengthModeHeight, attr->value())); else if (attr->name() == SVGNames::widthAttr) { setWidthBaseValue(SVGLength(LengthModeWidth, attr->value())); addCSSProperty(attr, CSSPropertyWidth, attr->value()); if (widthBaseValue().value(this) < 0.0) document()->accessSVGExtensions()->reportError("A negative value for svg attribute <width> is not allowed"); } else if (attr->name() == SVGNames::heightAttr) { setHeightBaseValue(SVGLength(LengthModeHeight, attr->value())); addCSSProperty(attr, CSSPropertyHeight, attr->value()); if (heightBaseValue().value(this) < 0.0) document()->accessSVGExtensions()->reportError("A negative value for svg attribute <height> is not allowed"); } else { if (SVGTests::parseMappedAttribute(attr)) return; if (SVGLangSpace::parseMappedAttribute(attr)) return; if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) return; if (SVGFitToViewBox::parseMappedAttribute(document(), attr)) return; if (SVGZoomAndPan::parseMappedAttribute(attr)) return; SVGStyledLocatableElement::parseMappedAttribute(attr); } } // This hack will not handle the case where we're setting a width/height // on a root <svg> via svg.width.baseValue = when it has none. static void updateCSSForAttribute(SVGSVGElement* element, const QualifiedName& attrName, CSSPropertyID property, const SVGLength& value) { Attribute* attribute = element->attributes(false)->getAttributeItem(attrName); if (!attribute || !attribute->isMappedAttribute()) return; element->addCSSProperty(attribute, property, value.valueAsString()); } void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName) { SVGStyledElement::svgAttributeChanged(attrName); // FIXME: Ugly, ugly hack to around that parseMappedAttribute is not called // when svg.width.baseValue = 100 is evaluated. // Thus the CSS length value for width is not updated, and width() computeLogicalWidth() // calculations on RenderSVGRoot will be wrong. // https://bugs.webkit.org/show_bug.cgi?id=25387 bool updateRelativeLengths = false; if (attrName == SVGNames::widthAttr) { updateCSSForAttribute(this, attrName, CSSPropertyWidth, widthBaseValue()); updateRelativeLengths = true; } else if (attrName == SVGNames::heightAttr) { updateCSSForAttribute(this, attrName, CSSPropertyHeight, heightBaseValue()); updateRelativeLengths = true; } if (updateRelativeLengths || attrName == SVGNames::xAttr || attrName == SVGNames::yAttr || SVGFitToViewBox::isKnownAttribute(attrName)) { updateRelativeLengths = true; updateRelativeLengthsInformation(); } if (SVGTests::handleAttributeChange(this, attrName)) return; if (!renderer()) return; if (updateRelativeLengths || SVGLangSpace::isKnownAttribute(attrName) || SVGExternalResourcesRequired::isKnownAttribute(attrName) || SVGZoomAndPan::isKnownAttribute(attrName) || SVGStyledLocatableElement::isKnownAttribute(attrName)) RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer()); } void SVGSVGElement::synchronizeProperty(const QualifiedName& attrName) { SVGStyledElement::synchronizeProperty(attrName); if (attrName == anyQName()) { synchronizeX(); synchronizeY(); synchronizeWidth(); synchronizeHeight(); synchronizeExternalResourcesRequired(); synchronizeViewBox(); synchronizePreserveAspectRatio(); SVGTests::synchronizeProperties(this, attrName); return; } if (attrName == SVGNames::xAttr) synchronizeX(); else if (attrName == SVGNames::yAttr) synchronizeY(); else if (attrName == SVGNames::widthAttr) synchronizeWidth(); else if (attrName == SVGNames::heightAttr) synchronizeHeight(); else if (SVGExternalResourcesRequired::isKnownAttribute(attrName)) synchronizeExternalResourcesRequired(); else if (attrName == SVGNames::viewBoxAttr) synchronizeViewBox(); else if (attrName == SVGNames::preserveAspectRatioAttr) synchronizePreserveAspectRatio(); else if (SVGTests::isKnownAttribute(attrName)) SVGTests::synchronizeProperties(this, attrName); } AttributeToPropertyTypeMap& SVGSVGElement::attributeToPropertyTypeMap() { DEFINE_STATIC_LOCAL(AttributeToPropertyTypeMap, s_attributeToPropertyTypeMap, ()); return s_attributeToPropertyTypeMap; } void SVGSVGElement::fillAttributeToPropertyTypeMap() { AttributeToPropertyTypeMap& attributeToPropertyTypeMap = this->attributeToPropertyTypeMap(); attributeToPropertyTypeMap.set(SVGNames::xAttr, AnimatedLength); attributeToPropertyTypeMap.set(SVGNames::yAttr, AnimatedLength); attributeToPropertyTypeMap.set(SVGNames::widthAttr, AnimatedLength); attributeToPropertyTypeMap.set(SVGNames::heightAttr, AnimatedLength); attributeToPropertyTypeMap.set(SVGNames::viewBoxAttr, AnimatedRect); attributeToPropertyTypeMap.set(SVGNames::preserveAspectRatioAttr, AnimatedPreserveAspectRatio); } unsigned SVGSVGElement::suspendRedraw(unsigned /* maxWaitMilliseconds */) { // FIXME: Implement me (see bug 11275) return 0; } void SVGSVGElement::unsuspendRedraw(unsigned /* suspendHandleId */) { // FIXME: Implement me (see bug 11275) } void SVGSVGElement::unsuspendRedrawAll() { // FIXME: Implement me (see bug 11275) } void SVGSVGElement::forceRedraw() { // FIXME: Implement me (see bug 11275) } NodeList* SVGSVGElement::getIntersectionList(const FloatRect&, SVGElement*) { // FIXME: Implement me (see bug 11274) return 0; } NodeList* SVGSVGElement::getEnclosureList(const FloatRect&, SVGElement*) { // FIXME: Implement me (see bug 11274) return 0; } bool SVGSVGElement::checkIntersection(SVGElement*, const FloatRect& rect) { // TODO : take into account pointer-events? // FIXME: Why is element ignored?? // FIXME: Implement me (see bug 11274) return rect.intersects(getBBox()); } bool SVGSVGElement::checkEnclosure(SVGElement*, const FloatRect& rect) { // TODO : take into account pointer-events? // FIXME: Why is element ignored?? // FIXME: Implement me (see bug 11274) return rect.contains(getBBox()); } void SVGSVGElement::deselectAll() { if (Frame* frame = document()->frame()) frame->selection()->clear(); } float SVGSVGElement::createSVGNumber() { return 0.0f; } SVGLength SVGSVGElement::createSVGLength() { return SVGLength(); } SVGAngle SVGSVGElement::createSVGAngle() { return SVGAngle(); } FloatPoint SVGSVGElement::createSVGPoint() { return FloatPoint(); } SVGMatrix SVGSVGElement::createSVGMatrix() { return SVGMatrix(); } FloatRect SVGSVGElement::createSVGRect() { return FloatRect(); } SVGTransform SVGSVGElement::createSVGTransform() { return SVGTransform(SVGTransform::SVG_TRANSFORM_MATRIX); } SVGTransform SVGSVGElement::createSVGTransformFromMatrix(const SVGMatrix& matrix) { return SVGTransform(static_cast<const AffineTransform&>(matrix)); } AffineTransform SVGSVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope mode) const { AffineTransform viewBoxTransform; if (attributes()->getAttributeItem(SVGNames::viewBoxAttr)) viewBoxTransform = viewBoxToViewTransform(width().value(this), height().value(this)); AffineTransform transform; if (!isOutermostSVG()) transform.translate(x().value(this), y().value(this)); else if (mode == SVGLocatable::ScreenScope) { if (RenderObject* renderer = this->renderer()) { // Translate in our CSS parent coordinate space // FIXME: This doesn't work correctly with CSS transforms. FloatPoint location = renderer->localToAbsolute(FloatPoint(), false, true); // Be careful here! localToAbsolute() includes the x/y offset coming from the viewBoxToViewTransform(), because // RenderSVGRoot::localToBorderBoxTransform() (called through mapLocalToContainer(), called from localToAbsolute()) // also takes the viewBoxToViewTransform() into account, so we have to subtract it here (original cause of bug #27183) transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f()); // Respect scroll offset. if (FrameView* view = document()->view()) { IntSize scrollOffset = view->scrollOffset(); transform.translate(-scrollOffset.width(), -scrollOffset.height()); } } } return transform.multiply(viewBoxTransform); } RenderObject* SVGSVGElement::createRenderer(RenderArena* arena, RenderStyle*) { if (isOutermostSVG()) return new (arena) RenderSVGRoot(this); return new (arena) RenderSVGViewportContainer(this); } void SVGSVGElement::insertedIntoDocument() { document()->accessSVGExtensions()->addTimeContainer(this); SVGStyledLocatableElement::insertedIntoDocument(); } void SVGSVGElement::removedFromDocument() { document()->accessSVGExtensions()->removeTimeContainer(this); SVGStyledLocatableElement::removedFromDocument(); } void SVGSVGElement::pauseAnimations() { if (!m_timeContainer->isPaused()) m_timeContainer->pause(); } void SVGSVGElement::unpauseAnimations() { if (m_timeContainer->isPaused()) m_timeContainer->resume(); } bool SVGSVGElement::animationsPaused() const { return m_timeContainer->isPaused(); } float SVGSVGElement::getCurrentTime() const { return narrowPrecisionToFloat(m_timeContainer->elapsed().value()); } void SVGSVGElement::setCurrentTime(float /* seconds */) { // FIXME: Implement me, bug 12073 } bool SVGSVGElement::selfHasRelativeLengths() const { return x().isRelative() || y().isRelative() || width().isRelative() || height().isRelative() || hasAttribute(SVGNames::viewBoxAttr); } bool SVGSVGElement::isOutermostSVG() const { // Element may not be in the document, pretend we're outermost for viewport(), getCTM(), etc. if (!parentNode()) return true; #if ENABLE(SVG_FOREIGN_OBJECT) // We act like an outermost SVG element, if we're a direct child of a <foreignObject> element. if (parentNode()->hasTagName(SVGNames::foreignObjectTag)) return true; #endif // This is true whenever this is the outermost SVG, even if there are HTML elements outside it return !parentNode()->isSVGElement(); } AffineTransform SVGSVGElement::viewBoxToViewTransform(float viewWidth, float viewHeight) const { FloatRect viewBoxRect; if (useCurrentView()) { if (currentView()) // what if we should use it but it is not set? viewBoxRect = currentView()->viewBox(); } else viewBoxRect = viewBox(); AffineTransform ctm = SVGFitToViewBox::viewBoxToViewTransform(viewBoxRect, preserveAspectRatio(), viewWidth, viewHeight); if (useCurrentView() && currentView()) { AffineTransform transform; if (currentView()->transform().concatenate(transform)) ctm *= transform; } return ctm; } void SVGSVGElement::inheritViewAttributes(SVGViewElement* viewElement) { setUseCurrentView(true); if (viewElement->hasAttribute(SVGNames::viewBoxAttr)) currentView()->setViewBoxBaseValue(viewElement->viewBox()); else currentView()->setViewBoxBaseValue(viewBox()); SVGPreserveAspectRatio aspectRatio; if (viewElement->hasAttribute(SVGNames::preserveAspectRatioAttr)) aspectRatio = viewElement->preserveAspectRatioBaseValue(); else aspectRatio = preserveAspectRatioBaseValue(); currentView()->setPreserveAspectRatioBaseValue(aspectRatio); if (viewElement->hasAttribute(SVGNames::zoomAndPanAttr)) currentView()->setZoomAndPan(viewElement->zoomAndPan()); if (RenderObject* object = renderer()) RenderSVGResource::markForLayoutAndParentResourceInvalidation(object); } void SVGSVGElement::documentWillBecomeInactive() { pauseAnimations(); } void SVGSVGElement::documentDidBecomeActive() { unpauseAnimations(); } // getElementById on SVGSVGElement is restricted to only the child subtree defined by the <svg> element. // See http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGSVGElement Element* SVGSVGElement::getElementById(const AtomicString& id) const { Element* element = document()->getElementById(id); if (element && element->isDescendantOf(this)) return element; // Fall back to traversing our subtree. Duplicate ids are allowed, the first found will // be returned. for (Node* node = traverseNextNode(this); node; node = node->traverseNextNode(this)) { if (!node->isElementNode()) continue; Element* element = static_cast<Element*>(node); if (element->hasID() && element->getIdAttribute() == id) return element; } return 0; } } #endif // ENABLE(SVG)