#include "config.h"
#if ENABLE(SVG)
#include "SVGSVGElement.h"
#include "AffineTransform.h"
#include "Attribute.h"
#include "CSSHelper.h"
#include "Document.h"
#include "EventListener.h"
#include "EventNames.h"
#include "FloatConversion.h"
#include "FloatRect.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "HTMLNames.h"
#include "RenderObject.h"
#include "RenderPart.h"
#include "RenderSVGResource.h"
#include "RenderSVGModelObject.h"
#include "RenderSVGRoot.h"
#include "RenderSVGViewportContainer.h"
#include "SMILTimeContainer.h"
#include "SVGAngle.h"
#include "SVGElementInstance.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 "StaticNodeList.h"
#include <wtf/StdLibExtras.h>
namespace WebCore {
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)
BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGSVGElement)
REGISTER_LOCAL_ANIMATED_PROPERTY(x)
REGISTER_LOCAL_ANIMATED_PROPERTY(y)
REGISTER_LOCAL_ANIMATED_PROPERTY(width)
REGISTER_LOCAL_ANIMATED_PROPERTY(height)
REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
REGISTER_LOCAL_ANIMATED_PROPERTY(viewBox)
REGISTER_LOCAL_ANIMATED_PROPERTY(preserveAspectRatio)
REGISTER_PARENT_ANIMATED_PROPERTIES(SVGStyledLocatableElement)
REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
END_REGISTER_ANIMATED_PROPERTIES
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))
{
ASSERT(hasTagName(SVGNames::svgTag));
registerAnimatedPropertiesForSVGSVGElement();
doc->registerForPageCacheSuspensionCallbacks(this);
}
PassRefPtr<SVGSVGElement> SVGSVGElement::create(const QualifiedName& tagName, Document* document)
{
return adoptRef(new SVGSVGElement(tagName, document));
}
SVGSVGElement::~SVGSVGElement()
{
document()->unregisterForPageCacheSuspensionCallbacks(this);
document()->accessSVGExtensions()->removeTimeContainer(this);
}
void SVGSVGElement::didMoveToNewDocument(Document* oldDocument)
{
if (oldDocument)
oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
document()->registerForPageCacheSuspensionCallbacks(this);
SVGStyledLocatableElement::didMoveToNewDocument(oldDocument);
}
const AtomicString& SVGSVGElement::contentScriptType() const
{
DEFINE_STATIC_LOCAL(const AtomicString, defaultValue, ("text/ecmascript"));
const AtomicString& n = fastGetAttribute(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 = fastGetAttribute(SVGNames::contentStyleTypeAttr);
return n.isNull() ? defaultValue : n;
}
void SVGSVGElement::setContentStyleType(const AtomicString& type)
{
setAttribute(SVGNames::contentStyleTypeAttr, type);
}
FloatRect SVGSVGElement::viewport() const
{
return FloatRect();
}
float SVGSVGElement::pixelUnitToMillimeterX() const
{
return (2.54f / cssPixelsPerInch) * 10.0f;
}
float SVGSVGElement::pixelUnitToMillimeterY() const
{
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
{
if (!inDocument() || !isOutermostSVGSVGElement())
return 1;
Frame* frame = document()->frame();
if (!frame)
return 1;
FrameTree* frameTree = frame->tree();
ASSERT(frameTree);
return frameTree->parent() ? 1 : frame->pageZoomFactor();
}
void SVGSVGElement::setCurrentScale(float scale)
{
if (!inDocument() || !isOutermostSVGSVGElement())
return;
Frame* frame = document()->frame();
if (!frame)
return;
FrameTree* frameTree = frame->tree();
ASSERT(frameTree);
if (frameTree->parent())
return;
frame->setPageZoomFactor(scale);
}
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::parseAttribute(Attribute* attr)
{
SVGParsingError parseError = NoError;
if (!nearestViewportElement()) {
bool setListener = true;
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::construct(LengthModeWidth, attr->value(), parseError));
else if (attr->name() == SVGNames::yAttr)
setYBaseValue(SVGLength::construct(LengthModeHeight, attr->value(), parseError));
else if (attr->name() == SVGNames::widthAttr)
setWidthBaseValue(SVGLength::construct(LengthModeWidth, attr->value(), parseError, ForbidNegativeLengths));
else if (attr->name() == SVGNames::heightAttr)
setHeightBaseValue(SVGLength::construct(LengthModeHeight, attr->value(), parseError, ForbidNegativeLengths));
else if (SVGTests::parseAttribute(attr)
|| SVGLangSpace::parseAttribute(attr)
|| SVGExternalResourcesRequired::parseAttribute(attr)
|| SVGFitToViewBox::parseAttribute(document(), attr)
|| SVGZoomAndPan::parseAttribute(attr)) {
} else
SVGStyledLocatableElement::parseAttribute(attr);
reportAttributeParsingError(parseError, attr);
}
void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
{
bool updateRelativeLengthsOrViewBox = false;
if (attrName == SVGNames::widthAttr
|| attrName == SVGNames::heightAttr
|| attrName == SVGNames::xAttr
|| attrName == SVGNames::yAttr) {
updateRelativeLengthsOrViewBox = true;
updateRelativeLengthsInformation();
}
if (SVGFitToViewBox::isKnownAttribute(attrName)) {
updateRelativeLengthsOrViewBox = true;
if (RenderObject* object = renderer())
object->setNeedsTransformUpdate();
}
SVGElementInstance::InvalidationGuard invalidationGuard(this);
if (SVGTests::handleAttributeChange(this, attrName))
return;
if (updateRelativeLengthsOrViewBox
|| SVGLangSpace::isKnownAttribute(attrName)
|| SVGExternalResourcesRequired::isKnownAttribute(attrName)
|| SVGZoomAndPan::isKnownAttribute(attrName)) {
if (renderer())
RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer());
return;
}
SVGStyledElement::svgAttributeChanged(attrName);
}
unsigned SVGSVGElement::suspendRedraw(unsigned )
{
return 0;
}
void SVGSVGElement::unsuspendRedraw(unsigned )
{
}
void SVGSVGElement::unsuspendRedrawAll()
{
}
void SVGSVGElement::forceRedraw()
{
}
PassRefPtr<NodeList> SVGSVGElement::collectIntersectionOrEnclosureList(const FloatRect& rect, SVGElement* referenceElement, CollectIntersectionOrEnclosure collect) const
{
Vector<RefPtr<Node> > nodes;
Node* node = traverseNextNode(referenceElement ? referenceElement : this);
while (node) {
if (node->isSVGElement()) {
if (collect == CollectIntersectionList) {
if (checkIntersection(static_cast<SVGElement*>(node), rect))
nodes.append(node);
} else {
if (checkEnclosure(static_cast<SVGElement*>(node), rect))
nodes.append(node);
}
}
node = node->traverseNextNode(referenceElement ? referenceElement : this);
}
return StaticNodeList::adopt(nodes);
}
PassRefPtr<NodeList> SVGSVGElement::getIntersectionList(const FloatRect& rect, SVGElement* referenceElement) const
{
return collectIntersectionOrEnclosureList(rect, referenceElement, CollectIntersectionList);
}
PassRefPtr<NodeList> SVGSVGElement::getEnclosureList(const FloatRect& rect, SVGElement* referenceElement) const
{
return collectIntersectionOrEnclosureList(rect, referenceElement, CollectEnclosureList);
}
bool SVGSVGElement::checkIntersection(SVGElement* element, const FloatRect& rect) const
{
if (!element)
return false;
return RenderSVGModelObject::checkIntersection(element->renderer(), rect);
}
bool SVGSVGElement::checkEnclosure(SVGElement* element, const FloatRect& rect) const
{
if (!element)
return false;
return RenderSVGModelObject::checkEnclosure(element->renderer(), rect);
}
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 (hasAttribute(SVGNames::viewBoxAttr)) {
FloatSize size = currentViewportSize();
viewBoxTransform = viewBoxToViewTransform(size.width(), size.height());
}
AffineTransform transform;
if (!isOutermostSVGSVGElement()) {
SVGLengthContext lengthContext(this);
transform.translate(x().value(lengthContext), y().value(lengthContext));
} else if (mode == SVGLocatable::ScreenScope) {
if (RenderObject* renderer = this->renderer()) {
FloatPoint location;
if (renderer->isSVGRoot())
location = toRenderSVGRoot(renderer)->localToBorderBoxTransform().mapPoint(location);
location = renderer->localToAbsolute(location, false, true);
transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f());
if (FrameView* view = document()->view()) {
LayoutSize scrollOffset = view->scrollOffset();
transform.translate(-scrollOffset.width(), -scrollOffset.height());
}
}
}
return transform.multiply(viewBoxTransform);
}
RenderObject* SVGSVGElement::createRenderer(RenderArena* arena, RenderStyle*)
{
if (isOutermostSVGSVGElement())
return new (arena) RenderSVGRoot(this);
return new (arena) RenderSVGViewportContainer(this);
}
Node::InsertionNotificationRequest SVGSVGElement::insertedInto(Node* rootParent)
{
if (rootParent->inDocument())
document()->accessSVGExtensions()->addTimeContainer(this);
return SVGStyledLocatableElement::insertedInto(rootParent);
}
void SVGSVGElement::removedFrom(Node* rootParent)
{
if (rootParent->inDocument())
document()->accessSVGExtensions()->removeTimeContainer(this);
SVGStyledLocatableElement::removedFrom(rootParent);
}
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)
{
if (isnan(seconds))
return;
seconds = max(seconds, 0.0f);
m_timeContainer->setElapsed(seconds);
}
bool SVGSVGElement::selfHasRelativeLengths() const
{
return x().isRelative()
|| y().isRelative()
|| width().isRelative()
|| height().isRelative()
|| hasAttribute(SVGNames::viewBoxAttr);
}
FloatRect SVGSVGElement::currentViewBoxRect() const
{
if (useCurrentView()) {
if (SVGViewSpec* view = currentView()) return view->viewBox();
return FloatRect();
}
FloatRect useViewBox = viewBox();
if (!useViewBox.isEmpty())
return useViewBox;
if (!renderer() || !renderer()->isSVGRoot())
return FloatRect();
if (!toRenderSVGRoot(renderer())->isEmbeddedThroughSVGImage())
return FloatRect();
Length intrinsicWidth = this->intrinsicWidth();
Length intrinsicHeight = this->intrinsicHeight();
if (!intrinsicWidth.isFixed() || !intrinsicHeight.isFixed())
return FloatRect();
return FloatRect(FloatPoint(), FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0)));
}
FloatSize SVGSVGElement::currentViewportSize() const
{
Length intrinsicWidth = this->intrinsicWidth();
Length intrinsicHeight = this->intrinsicHeight();
if (intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
return FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
if (!renderer())
return FloatSize();
if (renderer()->isSVGRoot()) {
LayoutRect contentBoxRect = toRenderSVGRoot(renderer())->contentBoxRect();
return FloatSize(contentBoxRect.width() / renderer()->style()->effectiveZoom(), contentBoxRect.height() / renderer()->style()->effectiveZoom());
}
FloatRect viewportRect = toRenderSVGViewportContainer(renderer())->viewport();
return FloatSize(viewportRect.width() / renderer()->style()->effectiveZoom(), viewportRect.height() / renderer()->style()->effectiveZoom());
}
bool SVGSVGElement::widthAttributeEstablishesViewport() const
{
if (!renderer() || renderer()->isSVGViewportContainer())
return true;
RenderSVGRoot* root = toRenderSVGRoot(renderer());
if (root->isEmbeddedThroughFrameContainingSVGDocument())
return !root->hasReplacedLogicalWidth() && !document()->frame()->ownerRenderer()->hasReplacedLogicalWidth();
if (root->isEmbeddedThroughSVGImage() || document()->documentElement() != this)
return !root->hasReplacedLogicalWidth();
return true;
}
bool SVGSVGElement::heightAttributeEstablishesViewport() const
{
if (!renderer() || renderer()->isSVGViewportContainer())
return true;
RenderSVGRoot* root = toRenderSVGRoot(renderer());
if (root->isEmbeddedThroughFrameContainingSVGDocument())
return !root->hasReplacedLogicalHeight() && !document()->frame()->ownerRenderer()->hasReplacedLogicalHeight();
if (root->isEmbeddedThroughSVGImage() || document()->documentElement() != this)
return !root->hasReplacedLogicalHeight();
return true;
}
Length SVGSVGElement::intrinsicWidth(ConsiderCSSMode mode) const
{
if (widthAttributeEstablishesViewport() || mode == IgnoreCSSProperties) {
if (width().unitType() == LengthTypePercentage)
return Length(width().valueAsPercentage() * 100, Percent);
SVGLengthContext lengthContext(this);
return Length(width().value(lengthContext), Fixed);
}
ASSERT(renderer());
return renderer()->style()->width();
}
Length SVGSVGElement::intrinsicHeight(ConsiderCSSMode mode) const
{
if (heightAttributeEstablishesViewport() || mode == IgnoreCSSProperties) {
if (height().unitType() == LengthTypePercentage)
return Length(height().valueAsPercentage() * 100, Percent);
SVGLengthContext lengthContext(this);
return Length(height().value(lengthContext), Fixed);
}
ASSERT(renderer());
return renderer()->style()->height();
}
AffineTransform SVGSVGElement::viewBoxToViewTransform(float viewWidth, float viewHeight) const
{
AffineTransform ctm = SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), preserveAspectRatio(), viewWidth, viewHeight);
if (useCurrentView() && currentView()) {
AffineTransform transform;
if (currentView()->transform().concatenate(transform))
ctm *= transform;
}
return ctm;
}
void SVGSVGElement::setupInitialView(const String& fragmentIdentifier, Element* anchorNode)
{
bool hadUseCurrentView = m_useCurrentView;
if (fragmentIdentifier.startsWith("xpointer(")) {
setUseCurrentView(false);
} else if (fragmentIdentifier.startsWith("svgView(")) {
if (currentView()->parseViewSpec(fragmentIdentifier))
setUseCurrentView(true);
} else if (anchorNode && anchorNode->hasTagName(SVGNames::viewTag)) {
if (SVGViewElement* viewElement = anchorNode->hasTagName(SVGNames::viewTag) ? static_cast<SVGViewElement*>(anchorNode) : 0) {
SVGElement* element = SVGLocatable::nearestViewportElement(viewElement);
if (element->hasTagName(SVGNames::svgTag)) {
SVGSVGElement* svg = static_cast<SVGSVGElement*>(element);
svg->inheritViewAttributes(viewElement);
setUseCurrentView(true);
}
}
}
if (!hadUseCurrentView) {
if (!m_useCurrentView)
return;
} else if (!m_useCurrentView)
currentView()->setTransform(emptyString());
if (RenderObject* object = renderer())
RenderSVGResource::markForLayoutAndParentResourceInvalidation(object);
}
void SVGSVGElement::inheritViewAttributes(SVGViewElement* viewElement)
{
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::documentWillSuspendForPageCache()
{
pauseAnimations();
}
void SVGSVGElement::documentDidResumeFromPageCache()
{
unpauseAnimations();
}
Element* SVGSVGElement::getElementById(const AtomicString& id) const
{
Element* element = treeScope()->getElementById(id);
if (element && element->isDescendantOf(this))
return element;
for (Node* node = traverseNextNode(this); node; node = node->traverseNextNode(this)) {
if (!node->isElementNode())
continue;
Element* element = static_cast<Element*>(node);
if (element->getIdAttribute() == id)
return element;
}
return 0;
}
}
#endif // ENABLE(SVG)