#include "config.h"
#include "SVGSVGElement.h"
#include "AffineTransform.h"
#include "Attribute.h"
#include "CSSHelper.h"
#include "Document.h"
#include "ElementIterator.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 "RenderSVGResource.h"
#include "RenderSVGModelObject.h"
#include "RenderSVGRoot.h"
#include "RenderSVGViewportContainer.h"
#include "RenderView.h"
#include "RenderWidget.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 "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(SVGGraphicsElement)
END_REGISTER_ANIMATED_PROPERTIES
inline SVGSVGElement::SVGSVGElement(const QualifiedName& tagName, Document& document)
: SVGGraphicsElement(tagName, document)
, 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();
document.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);
SVGGraphicsElement::didMoveToNewDocument(oldDocument);
}
const AtomicString& SVGSVGElement::contentScriptType() const
{
DEPRECATED_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
{
DEPRECATED_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;
return frame->tree().parent() ? 1 : frame->pageZoomFactor();
}
void SVGSVGElement::setCurrentScale(float scale)
{
if (!inDocument() || !isOutermostSVGSVGElement())
return;
Frame* frame = document().frame();
if (!frame)
return;
if (frame->tree().parent())
return;
frame->setPageZoomFactor(scale);
}
void SVGSVGElement::setCurrentTranslate(const FloatPoint& translation)
{
m_translation = translation;
updateCurrentTranslate();
}
void SVGSVGElement::updateCurrentTranslate()
{
if (RenderObject* object = renderer())
object->setNeedsLayout();
if (parentNode() == &document() && document().renderView())
document().renderView()->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, name, value);
else if (name == HTMLNames::onresizeAttr)
document().setWindowAttributeEventListener(eventNames().resizeEvent, name, value);
else if (name == HTMLNames::onscrollAttr)
document().setWindowAttributeEventListener(eventNames().scrollEvent, name, value);
else if (name == SVGNames::onzoomAttr)
document().setWindowAttributeEventListener(eventNames().zoomEvent, name, value);
else
setListener = false;
if (setListener)
return;
}
if (name == HTMLNames::onabortAttr)
document().setWindowAttributeEventListener(eventNames().abortEvent, name, value);
else if (name == HTMLNames::onerrorAttr)
document().setWindowAttributeEventListener(eventNames().errorEvent, 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 (SVGLangSpace::parseAttribute(name, value)
|| SVGExternalResourcesRequired::parseAttribute(name, value)
|| SVGFitToViewBox::parseAttribute(this, name, value)
|| SVGZoomAndPan::parseAttribute(this, name, value)) {
} else
SVGGraphicsElement::parseAttribute(name, value);
reportAttributeParsingError(parseError, name, value);
}
bool SVGSVGElement::isPresentationAttribute(const QualifiedName& name) const
{
if (isOutermostSVGSVGElement() && (name == SVGNames::widthAttr || name == SVGNames::heightAttr))
return true;
return SVGGraphicsElement::isPresentationAttribute(name);
}
void SVGSVGElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
{
if (isOutermostSVGSVGElement() && (name == SVGNames::widthAttr || name == SVGNames::heightAttr)) {
if (name == SVGNames::widthAttr)
addPropertyToPresentationAttributeStyle(style, CSSPropertyWidth, value);
else if (name == SVGNames::heightAttr)
addPropertyToPresentationAttributeStyle(style, CSSPropertyHeight, value);
} else
SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style);
}
void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
{
bool updateRelativeLengthsOrViewBox = false;
bool widthChanged = attrName == SVGNames::widthAttr;
bool heightChanged = attrName == SVGNames::heightAttr;
if (widthChanged || heightChanged
|| attrName == SVGNames::xAttr
|| attrName == SVGNames::yAttr) {
updateRelativeLengthsOrViewBox = true;
updateRelativeLengthsInformation();
if (widthChanged || heightChanged) {
synchronizeAllAttributes();
RenderObject* renderObject = renderer();
if (renderObject && renderObject->isSVGRoot()) {
invalidateSVGPresentationAttributeStyle();
setNeedsStyleRecalc();
}
}
}
if (SVGFitToViewBox::isKnownAttribute(attrName)) {
updateRelativeLengthsOrViewBox = true;
if (RenderObject* object = renderer())
object->setNeedsTransformUpdate();
}
SVGElementInstance::InvalidationGuard invalidationGuard(this);
if (updateRelativeLengthsOrViewBox
|| SVGLangSpace::isKnownAttribute(attrName)
|| SVGExternalResourcesRequired::isKnownAttribute(attrName)
|| SVGZoomAndPan::isKnownAttribute(attrName)) {
if (auto renderer = this->renderer())
RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
return;
}
SVGGraphicsElement::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<Ref<Element>> elements;
for (auto& svgElement : descendantsOfType<SVGElement>(*(referenceElement ? referenceElement : this))) {
if (collect == CollectIntersectionList) {
if (RenderSVGModelObject::checkIntersection(svgElement.renderer(), rect))
elements.append(const_cast<SVGElement&>(svgElement));
} else {
if (RenderSVGModelObject::checkEnclosure(svgElement.renderer(), rect))
elements.append(const_cast<SVGElement&>(svgElement));
}
}
return StaticElementList::adopt(elements);
}
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(const SVGElement* element, const FloatRect& rect) const
{
if (!element)
return false;
return RenderSVGModelObject::checkIntersection(element->renderer(), rect);
}
bool SVGSVGElement::checkEnclosure(const 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();
}
SVGPoint SVGSVGElement::createSVGPoint()
{
return SVGPoint();
}
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 RenderStyle& style)
{
if (document().documentElement() == this)
return true;
return StyledElement::rendererIsNeeded(style);
}
RenderPtr<RenderElement> SVGSVGElement::createElementRenderer(PassRef<RenderStyle> style)
{
if (isOutermostSVGSVGElement())
return createRenderer<RenderSVGRoot>(*this, WTF::move(style));
return createRenderer<RenderSVGViewportContainer>(*this, WTF::move(style));
}
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 SVGGraphicsElement::insertedInto(rootParent);
}
void SVGSVGElement::removedFrom(ContainerNode& rootParent)
{
if (rootParent.inDocument())
document().accessSVGExtensions()->removeTimeContainer(this);
SVGGraphicsElement::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 = std::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
{
if (hasIntrinsicWidth() && hasIntrinsicHeight()) {
Length intrinsicWidth = this->intrinsicWidth();
Length intrinsicHeight = this->intrinsicHeight();
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::hasIntrinsicWidth() const
{
return width().unitType() != LengthTypePercentage;
}
bool SVGSVGElement::hasIntrinsicHeight() const
{
return height().unitType() != LengthTypePercentage;
}
Length SVGSVGElement::intrinsicWidth() const
{
if (width().unitType() == LengthTypePercentage)
return Length(0, Fixed);
SVGLengthContext lengthContext(this);
return Length(width().value(lengthContext), Fixed);
}
Length SVGSVGElement::intrinsicHeight() const
{
if (height().unitType() == LengthTypePercentage)
return Length(0, Fixed);
SVGLengthContext lengthContext(this);
return Length(height().value(lengthContext), Fixed);
}
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)
{
auto 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 && isSVGViewElement(anchorNode)) {
if (SVGViewElement* viewElement = toSVGViewElement(anchorNode)) {
SVGElement* element = SVGLocatable::nearestViewportElement(viewElement);
if (element->hasTagName(SVGNames::svgTag)) {
SVGSVGElement* svg = static_cast<SVGSVGElement*>(element);
svg->inheritViewAttributes(viewElement);
if (RenderElement* 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 String& id)
{
Element* element = treeScope().getElementById(id);
if (element && element->isDescendantOf(this))
return element;
for (auto& element : descendantsOfType<Element>(*this)) {
if (element.getIdAttribute() == id)
return &element;
}
return 0;
}
}