#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 "NodeTraversal.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 "SVGFitToViewBox.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(SVGStyledTransformableElement)
REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
END_REGISTER_ANIMATED_PROPERTIES
inline SVGSVGElement::SVGSVGElement(const QualifiedName& tagName, Document* doc)
: SVGStyledTransformableElement(tagName, doc)
, m_x(LengthModeWidth)
, m_y(LengthModeHeight)
, m_width(LengthModeWidth, "100%")
, m_height(LengthModeHeight, "100%")
, m_useCurrentView(false)
, m_zoomAndPan(SVGZoomAndPanMagnify)
, 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()
{
if (m_viewSpec)
m_viewSpec->resetContextElement();
document()->unregisterForPageCacheSuspensionCallbacks(this);
document()->accessSVGExtensions()->removeTimeContainer(this);
}
void SVGSVGElement::didMoveToNewDocument(Document* oldDocument)
{
if (oldDocument)
oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
document()->registerForPageCacheSuspensionCallbacks(this);
SVGStyledTransformableElement::didMoveToNewDocument(oldDocument);
}
const AtomicString& SVGSVGElement::contentScriptType() const
{
DEFINE_STATIC_LOCAL(const AtomicString, defaultValue, ("text/ecmascript", AtomicString::ConstructFromLiteral));
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", AtomicString::ConstructFromLiteral));
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();
}
SVGViewSpec* SVGSVGElement::currentView()
{
if (!m_viewSpec)
m_viewSpec = SVGViewSpec::create(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(const QualifiedName& name, const AtomicString& value)
{
SVGParsingError parseError = NoError;
if (!nearestViewportElement()) {
bool setListener = true;
if (name == HTMLNames::onunloadAttr)
document()->setWindowAttributeEventListener(eventNames().unloadEvent, createAttributeEventListener(document()->frame(), name, value));
else if (name == HTMLNames::onresizeAttr)
document()->setWindowAttributeEventListener(eventNames().resizeEvent, createAttributeEventListener(document()->frame(), name, value));
else if (name == HTMLNames::onscrollAttr)
document()->setWindowAttributeEventListener(eventNames().scrollEvent, createAttributeEventListener(document()->frame(), name, value));
else if (name == SVGNames::onzoomAttr)
document()->setWindowAttributeEventListener(eventNames().zoomEvent, createAttributeEventListener(document()->frame(), name, value));
else
setListener = false;
if (setListener)
return;
}
if (name == HTMLNames::onabortAttr)
document()->setWindowAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(document()->frame(), name, value));
else if (name == HTMLNames::onerrorAttr)
document()->setWindowAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(document()->frame(), name, value));
else if (name == SVGNames::xAttr)
setXBaseValue(SVGLength::construct(LengthModeWidth, value, parseError));
else if (name == SVGNames::yAttr)
setYBaseValue(SVGLength::construct(LengthModeHeight, value, parseError));
else if (name == SVGNames::widthAttr)
setWidthBaseValue(SVGLength::construct(LengthModeWidth, value, parseError, ForbidNegativeLengths));
else if (name == SVGNames::heightAttr)
setHeightBaseValue(SVGLength::construct(LengthModeHeight, value, parseError, ForbidNegativeLengths));
else if (SVGTests::parseAttribute(name, value)
|| SVGLangSpace::parseAttribute(name, value)
|| SVGExternalResourcesRequired::parseAttribute(name, value)
|| SVGFitToViewBox::parseAttribute(this, name, value)
|| SVGZoomAndPan::parseAttribute(this, name, value)) {
} else
SVGStyledTransformableElement::parseAttribute(name, value);
reportAttributeParsingError(parseError, name, value);
}
void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
{
bool updateRelativeLengthsOrViewBox = false;
bool widthChanged = attrName == SVGNames::widthAttr;
if (widthChanged
|| attrName == SVGNames::heightAttr
|| attrName == SVGNames::xAttr
|| attrName == SVGNames::yAttr) {
updateRelativeLengthsOrViewBox = true;
updateRelativeLengthsInformation();
if (widthChanged) {
RenderObject* renderObject = renderer();
if (renderObject && renderObject->isSVGRoot())
toRenderSVGRoot(renderObject)->setNeedsLayoutAndPrefWidthsRecalc();
}
}
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;
Element* element = ElementTraversal::next(referenceElement ? referenceElement : this);
while (element) {
if (element->isSVGElement()) {
SVGElement* svgElement = toSVGElement(element);
if (collect == CollectIntersectionList) {
if (checkIntersection(svgElement, rect))
nodes.append(element);
} else {
if (checkEnclosure(svgElement, rect))
nodes.append(element);
}
}
element = ElementTraversal::next(element, 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 (!hasEmptyViewBox()) {
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;
float zoomFactor = 1;
if (renderer->isSVGRoot()) {
location = toRenderSVGRoot(renderer)->localToBorderBoxTransform().mapPoint(location);
zoomFactor = 1 / renderer->style()->effectiveZoom();
}
location = renderer->localToAbsolute(location, UseTransforms);
location.scale(zoomFactor, zoomFactor);
transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f());
if (FrameView* view = document()->view()) {
LayoutSize scrollOffset = view->scrollOffset();
scrollOffset.scale(zoomFactor);
transform.translate(-scrollOffset.width(), -scrollOffset.height());
}
}
}
return transform.multiply(viewBoxTransform);
}
bool SVGSVGElement::rendererIsNeeded(const NodeRenderingContext& context)
{
if (document()->documentElement() == this)
return true;
return StyledElement::rendererIsNeeded(context);
}
RenderObject* SVGSVGElement::createRenderer(RenderArena* arena, RenderStyle*)
{
if (isOutermostSVGSVGElement())
return new (arena) RenderSVGRoot(this);
return new (arena) RenderSVGViewportContainer(this);
}
Node::InsertionNotificationRequest SVGSVGElement::insertedInto(ContainerNode* rootParent)
{
if (rootParent->inDocument()) {
document()->accessSVGExtensions()->addTimeContainer(this);
if (!document()->parsing() && !document()->processingLoadEvent() && document()->loadEventFinished() && !timeContainer()->isStarted())
timeContainer()->begin();
}
return SVGStyledTransformableElement::insertedInto(rootParent);
}
void SVGSVGElement::removedFrom(ContainerNode* rootParent)
{
if (rootParent->inDocument())
document()->accessSVGExtensions()->removeTimeContainer(this);
SVGStyledTransformableElement::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 (std::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 (m_useCurrentView)
return m_viewSpec ? m_viewSpec->viewBox() : 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(), viewportRect.height());
}
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
{
if (!m_useCurrentView || !m_viewSpec)
return SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), preserveAspectRatio(), viewWidth, viewHeight);
AffineTransform ctm = SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), m_viewSpec->preserveAspectRatio(), viewWidth, viewHeight);
const SVGTransformList& transformList = m_viewSpec->transformBaseValue();
if (transformList.isEmpty())
return ctm;
AffineTransform transform;
if (transformList.concatenate(transform))
ctm *= transform;
return ctm;
}
void SVGSVGElement::setupInitialView(const String& fragmentIdentifier, Element* anchorNode)
{
RenderObject* renderer = this->renderer();
SVGViewSpec* view = m_viewSpec.get();
if (view)
view->reset();
bool hadUseCurrentView = m_useCurrentView;
m_useCurrentView = false;
if (fragmentIdentifier.startsWith("xpointer(")) {
if (renderer && hadUseCurrentView)
RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
return;
}
if (fragmentIdentifier.startsWith("svgView(")) {
if (!view)
view = currentView();
if (view->parseViewSpec(fragmentIdentifier))
m_useCurrentView = true;
else
view->reset();
if (renderer && (hadUseCurrentView || m_useCurrentView))
RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
return;
}
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);
if (RenderObject* renderer = svg->renderer())
RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
}
}
return;
}
}
void SVGSVGElement::inheritViewAttributes(SVGViewElement* viewElement)
{
SVGViewSpec* view = currentView();
m_useCurrentView = true;
if (viewElement->hasAttribute(SVGNames::viewBoxAttr))
view->setViewBoxBaseValue(viewElement->viewBox());
else
view->setViewBoxBaseValue(viewBox());
if (viewElement->hasAttribute(SVGNames::preserveAspectRatioAttr))
view->setPreserveAspectRatioBaseValue(viewElement->preserveAspectRatioBaseValue());
else
view->setPreserveAspectRatioBaseValue(preserveAspectRatioBaseValue());
if (viewElement->hasAttribute(SVGNames::zoomAndPanAttr))
view->setZoomAndPanBaseValue(viewElement->zoomAndPan());
else
view->setZoomAndPanBaseValue(zoomAndPan());
}
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 = firstChild(); node; node = NodeTraversal::next(node, this)) {
if (!node->isElementNode())
continue;
Element* element = toElement(node);
if (element->getIdAttribute() == id)
return element;
}
return 0;
}
}
#endif // ENABLE(SVG)