blob: 73bddfc0bd4f4625d3947890011c6ed5d1696afd [file] [log] [blame]
/*
* 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.
* Copyright (C) 2014 Google, Inc.
*
* 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 "core/svg/SVGSVGElement.h"
#include "bindings/core/v8/ScriptEventListener.h"
#include "core/HTMLNames.h"
#include "core/SVGNames.h"
#include "core/css/CSSHelper.h"
#include "core/dom/Document.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/StaticNodeList.h"
#include "core/dom/StyleChangeReason.h"
#include "core/editing/FrameSelection.h"
#include "core/events/EventListener.h"
#include "core/frame/Deprecation.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/svg/LayoutSVGModelObject.h"
#include "core/layout/svg/LayoutSVGRoot.h"
#include "core/layout/svg/LayoutSVGViewportContainer.h"
#include "core/page/FrameTree.h"
#include "core/svg/SVGAngleTearOff.h"
#include "core/svg/SVGDocumentExtensions.h"
#include "core/svg/SVGNumberTearOff.h"
#include "core/svg/SVGPreserveAspectRatio.h"
#include "core/svg/SVGRectTearOff.h"
#include "core/svg/SVGTransform.h"
#include "core/svg/SVGTransformList.h"
#include "core/svg/SVGTransformTearOff.h"
#include "core/svg/SVGViewElement.h"
#include "core/svg/SVGViewSpec.h"
#include "core/svg/animation/SMILTimeContainer.h"
#include "platform/FloatConversion.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/FloatRect.h"
#include "platform/transforms/AffineTransform.h"
#include "wtf/StdLibExtras.h"
namespace blink {
inline SVGSVGElement::SVGSVGElement(Document& doc)
: SVGGraphicsElement(SVGNames::svgTag, doc)
, SVGFitToViewBox(this)
, m_x(SVGAnimatedLength::create(this, SVGNames::xAttr, SVGLength::create(SVGLengthMode::Width)))
, m_y(SVGAnimatedLength::create(this, SVGNames::yAttr, SVGLength::create(SVGLengthMode::Height)))
, m_width(SVGAnimatedLength::create(this, SVGNames::widthAttr, SVGLength::create(SVGLengthMode::Width)))
, m_height(SVGAnimatedLength::create(this, SVGNames::heightAttr, SVGLength::create(SVGLengthMode::Height)))
, m_useCurrentView(false)
, m_timeContainer(SMILTimeContainer::create(*this))
, m_translation(SVGPoint::create())
, m_currentScale(1)
{
m_width->setDefaultValueAsString("100%");
m_height->setDefaultValueAsString("100%");
addToPropertyMap(m_x);
addToPropertyMap(m_y);
addToPropertyMap(m_width);
addToPropertyMap(m_height);
UseCounter::count(doc, UseCounter::SVGSVGElement);
}
DEFINE_NODE_FACTORY(SVGSVGElement)
SVGSVGElement::~SVGSVGElement()
{
}
SVGRectTearOff* SVGSVGElement::viewport() const
{
// FIXME: This method doesn't follow the spec and is basically untested. Parent documents are not considered here.
// As we have no test coverage for this, we're going to disable it completly for now.
return SVGRectTearOff::create(SVGRect::create(), 0, PropertyIsNotAnimVal);
}
SVGViewSpec* SVGSVGElement::currentView()
{
if (!m_viewSpec)
m_viewSpec = SVGViewSpec::create(this);
return m_viewSpec.get();
}
float SVGSVGElement::currentScale() const
{
if (!inShadowIncludingDocument() || !isOutermostSVGSVGElement())
return 1;
return m_currentScale;
}
void SVGSVGElement::setCurrentScale(float scale)
{
ASSERT(std::isfinite(scale));
if (!inShadowIncludingDocument() || !isOutermostSVGSVGElement())
return;
m_currentScale = scale;
updateUserTransform();
}
class SVGCurrentTranslateTearOff : public SVGPointTearOff {
public:
static SVGCurrentTranslateTearOff* create(SVGSVGElement* contextElement)
{
return new SVGCurrentTranslateTearOff(contextElement);
}
void commitChange() override
{
ASSERT(contextElement());
toSVGSVGElement(contextElement())->updateUserTransform();
}
private:
SVGCurrentTranslateTearOff(SVGSVGElement* contextElement)
: SVGPointTearOff(contextElement->m_translation, contextElement, PropertyIsNotAnimVal)
{
}
};
SVGPointTearOff* SVGSVGElement::currentTranslateFromJavascript()
{
return SVGCurrentTranslateTearOff::create(this);
}
void SVGSVGElement::setCurrentTranslate(const FloatPoint& point)
{
m_translation->setValue(point);
updateUserTransform();
}
void SVGSVGElement::updateUserTransform()
{
if (LayoutObject* object = layoutObject())
object->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::Unknown);
}
bool SVGSVGElement::zoomAndPanEnabled() const
{
const SVGZoomAndPan* currentViewSpec = this;
if (m_useCurrentView)
currentViewSpec = m_viewSpec.get();
return currentViewSpec && currentViewSpec->zoomAndPan() == SVGZoomAndPanMagnify;
}
void SVGSVGElement::parseAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& value)
{
if (!nearestViewportElement()) {
bool setListener = true;
// Only handle events if we're the outermost <svg> element
if (name == HTMLNames::onunloadAttr) {
document().setWindowAttributeEventListener(EventTypeNames::unload, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
} else if (name == HTMLNames::onresizeAttr) {
document().setWindowAttributeEventListener(EventTypeNames::resize, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
} else if (name == HTMLNames::onscrollAttr) {
document().setWindowAttributeEventListener(EventTypeNames::scroll, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
} else {
setListener = false;
}
if (setListener)
return;
}
if (name == HTMLNames::onabortAttr) {
document().setWindowAttributeEventListener(EventTypeNames::abort, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
} else if (name == HTMLNames::onerrorAttr) {
document().setWindowAttributeEventListener(EventTypeNames::error, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
} else if (SVGZoomAndPan::parseAttribute(name, value)) {
} else if (name == SVGNames::widthAttr || name == SVGNames::heightAttr) {
SVGAnimatedLength* property = name == SVGNames::widthAttr ? m_width : m_height;
SVGParsingError parseError;
if (!value.isNull())
parseError = property->setBaseValueAsString(value);
if (parseError != SVGParseStatus::NoError || value.isNull())
property->setDefaultValueAsString("100%");
reportAttributeParsingError(parseError, name, value);
} else {
SVGElement::parseAttribute(name, oldValue, value);
}
}
bool SVGSVGElement::isPresentationAttribute(const QualifiedName& name) const
{
if (isOutermostSVGSVGElement() && (name == SVGNames::widthAttr || name == SVGNames::heightAttr))
return true;
else if (name == SVGNames::xAttr || name == SVGNames::yAttr)
return true;
return SVGGraphicsElement::isPresentationAttribute(name);
}
bool SVGSVGElement::isPresentationAttributeWithSVGDOM(const QualifiedName& attrName) const
{
if (attrName == SVGNames::xAttr || attrName == SVGNames::yAttr)
return true;
return SVGGraphicsElement::isPresentationAttributeWithSVGDOM(attrName);
}
void SVGSVGElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
{
SVGAnimatedPropertyBase* property = propertyFromAttribute(name);
if (property == m_x) {
addPropertyToPresentationAttributeStyle(style, CSSPropertyX, m_x->currentValue()->asCSSPrimitiveValue());
} else if (property == m_y) {
addPropertyToPresentationAttributeStyle(style, CSSPropertyY, m_y->currentValue()->asCSSPrimitiveValue());
} else if (isOutermostSVGSVGElement() && (property == m_width || property == m_height)) {
if (property == m_width)
addPropertyToPresentationAttributeStyle(style, CSSPropertyWidth, m_width->currentValue()->asCSSPrimitiveValue());
else if (property == m_height)
addPropertyToPresentationAttributeStyle(style, CSSPropertyHeight, m_height->currentValue()->asCSSPrimitiveValue());
} else {
SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style);
}
}
void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
{
bool updateRelativeLengthsOrViewBox = false;
bool widthOrHeightChanged = attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr;
if (widthOrHeightChanged
|| attrName == SVGNames::xAttr
|| attrName == SVGNames::yAttr) {
updateRelativeLengthsOrViewBox = true;
updateRelativeLengthsInformation();
invalidateRelativeLengthClients();
// At the SVG/HTML boundary (aka LayoutSVGRoot), the width and
// height attributes can affect the replaced size so we need
// to mark it for updating.
if (widthOrHeightChanged) {
LayoutObject* layoutObject = this->layoutObject();
if (layoutObject && layoutObject->isSVGRoot()) {
invalidateSVGPresentationAttributeStyle();
setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::SVGContainerSizeChange));
}
} else {
invalidateSVGPresentationAttributeStyle();
setNeedsStyleRecalc(LocalStyleChange,
StyleChangeReasonForTracing::fromAttribute(attrName));
}
}
if (SVGFitToViewBox::isKnownAttribute(attrName)) {
updateRelativeLengthsOrViewBox = true;
invalidateRelativeLengthClients();
if (LayoutObject* object = layoutObject())
object->setNeedsTransformUpdate();
}
if (updateRelativeLengthsOrViewBox
|| SVGZoomAndPan::isKnownAttribute(attrName)) {
SVGElement::InvalidationGuard invalidationGuard(this);
if (layoutObject())
markForLayoutAndParentResourceInvalidation(layoutObject());
return;
}
SVGGraphicsElement::svgAttributeChanged(attrName);
}
// FloatRect::intersects does not consider horizontal or vertical lines (because of isEmpty()).
static bool intersectsAllowingEmpty(const FloatRect& r1, const FloatRect& r2)
{
if (r1.width() < 0 || r1.height() < 0 || r2.width() < 0 || r2.height() < 0)
return false;
return r1.x() < r2.maxX() && r2.x() < r1.maxX()
&& r1.y() < r2.maxY() && r2.y() < r1.maxY();
}
// One of the element types that can cause graphics to be drawn onto the target canvas.
// Specifically: circle, ellipse, image, line, path, polygon, polyline, rect, text and use.
static bool isIntersectionOrEnclosureTarget(LayoutObject* layoutObject)
{
return layoutObject->isSVGShape()
|| layoutObject->isSVGText()
|| layoutObject->isSVGImage()
|| isSVGUseElement(*layoutObject->node());
}
bool SVGSVGElement::checkIntersectionOrEnclosure(const SVGElement& element, const FloatRect& rect,
CheckIntersectionOrEnclosure mode) const
{
LayoutObject* layoutObject = element.layoutObject();
ASSERT(!layoutObject || layoutObject->style());
if (!layoutObject || layoutObject->style()->pointerEvents() == PE_NONE)
return false;
if (!isIntersectionOrEnclosureTarget(layoutObject))
return false;
AffineTransform ctm = toSVGGraphicsElement(element).computeCTM(AncestorScope, DisallowStyleUpdate, this);
FloatRect mappedRepaintRect = ctm.mapRect(layoutObject->paintInvalidationRectInLocalSVGCoordinates());
bool result = false;
switch (mode) {
case CheckIntersection:
result = intersectsAllowingEmpty(rect, mappedRepaintRect);
break;
case CheckEnclosure:
result = rect.contains(mappedRepaintRect);
break;
default:
ASSERT_NOT_REACHED();
break;
}
return result;
}
StaticNodeList* SVGSVGElement::collectIntersectionOrEnclosureList(const FloatRect& rect,
SVGElement* referenceElement, CheckIntersectionOrEnclosure mode) const
{
HeapVector<Member<Node>> nodes;
const SVGElement* root = this;
if (referenceElement) {
// Only the common subtree needs to be traversed.
if (contains(referenceElement)) {
root = referenceElement;
} else if (!isDescendantOf(referenceElement)) {
// No common subtree.
return StaticNodeList::adopt(nodes);
}
}
for (SVGGraphicsElement& element : Traversal<SVGGraphicsElement>::descendantsOf(*root)) {
if (checkIntersectionOrEnclosure(element, rect, mode))
nodes.append(&element);
}
return StaticNodeList::adopt(nodes);
}
StaticNodeList* SVGSVGElement::getIntersectionList(SVGRectTearOff* rect, SVGElement* referenceElement) const
{
document().updateStyleAndLayoutIgnorePendingStylesheets();
return collectIntersectionOrEnclosureList(rect->target()->value(), referenceElement, CheckIntersection);
}
StaticNodeList* SVGSVGElement::getEnclosureList(SVGRectTearOff* rect, SVGElement* referenceElement) const
{
document().updateStyleAndLayoutIgnorePendingStylesheets();
return collectIntersectionOrEnclosureList(rect->target()->value(), referenceElement, CheckEnclosure);
}
bool SVGSVGElement::checkIntersection(SVGElement* element, SVGRectTearOff* rect) const
{
ASSERT(element);
document().updateStyleAndLayoutIgnorePendingStylesheets();
return checkIntersectionOrEnclosure(*element, rect->target()->value(), CheckIntersection);
}
bool SVGSVGElement::checkEnclosure(SVGElement* element, SVGRectTearOff* rect) const
{
ASSERT(element);
document().updateStyleAndLayoutIgnorePendingStylesheets();
return checkIntersectionOrEnclosure(*element, rect->target()->value(), CheckEnclosure);
}
void SVGSVGElement::deselectAll()
{
if (LocalFrame* frame = document().frame())
frame->selection().clear();
}
SVGNumberTearOff* SVGSVGElement::createSVGNumber()
{
return SVGNumberTearOff::create(SVGNumber::create(0.0f), 0, PropertyIsNotAnimVal);
}
SVGLengthTearOff* SVGSVGElement::createSVGLength()
{
return SVGLengthTearOff::create(SVGLength::create(), 0, PropertyIsNotAnimVal);
}
SVGAngleTearOff* SVGSVGElement::createSVGAngle()
{
return SVGAngleTearOff::create(SVGAngle::create(), 0, PropertyIsNotAnimVal);
}
SVGPointTearOff* SVGSVGElement::createSVGPoint()
{
return SVGPointTearOff::create(SVGPoint::create(), 0, PropertyIsNotAnimVal);
}
SVGMatrixTearOff* SVGSVGElement::createSVGMatrix()
{
return SVGMatrixTearOff::create(AffineTransform());
}
SVGRectTearOff* SVGSVGElement::createSVGRect()
{
return SVGRectTearOff::create(SVGRect::create(), 0, PropertyIsNotAnimVal);
}
SVGTransformTearOff* SVGSVGElement::createSVGTransform()
{
return SVGTransformTearOff::create(SVGTransform::create(SVG_TRANSFORM_MATRIX), 0, PropertyIsNotAnimVal);
}
SVGTransformTearOff* SVGSVGElement::createSVGTransformFromMatrix(SVGMatrixTearOff* matrix)
{
return SVGTransformTearOff::create(SVGTransform::create(matrix->value()), 0, PropertyIsNotAnimVal);
}
AffineTransform SVGSVGElement::localCoordinateSpaceTransform(SVGElement::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(m_x->currentValue()->value(lengthContext), m_y->currentValue()->value(lengthContext));
} else if (mode == SVGElement::ScreenScope) {
if (LayoutObject* layoutObject = this->layoutObject()) {
FloatPoint location;
float zoomFactor = 1;
// At the SVG/HTML boundary (aka LayoutSVGRoot), we apply the localToBorderBoxTransform
// to map an element from SVG viewport coordinates to CSS box coordinates.
// LayoutSVGRoot's localToAbsolute method expects CSS box coordinates.
// We also need to adjust for the zoom level factored into CSS coordinates (bug #96361).
if (layoutObject->isSVGRoot()) {
location = toLayoutSVGRoot(layoutObject)->localToBorderBoxTransform().mapPoint(location);
zoomFactor = 1 / layoutObject->style()->effectiveZoom();
}
// Translate in our CSS parent coordinate space
// FIXME: This doesn't work correctly with CSS transforms.
location = layoutObject->localToAbsolute(location, UseTransforms);
location.scale(zoomFactor, zoomFactor);
// Be careful here! localToBorderBoxTransform() included the x/y offset coming from the viewBoxToViewTransform(),
// 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()) {
LayoutSize scrollOffset(view->scrollOffset());
scrollOffset.scale(zoomFactor);
transform.translate(-scrollOffset.width(), -scrollOffset.height());
}
}
}
return transform.multiply(viewBoxTransform);
}
bool SVGSVGElement::layoutObjectIsNeeded(const ComputedStyle& style)
{
// FIXME: We should respect display: none on the documentElement svg element
// but many things in FrameView and SVGImage depend on the LayoutSVGRoot when
// they should instead depend on the LayoutView.
// https://bugs.webkit.org/show_bug.cgi?id=103493
if (document().documentElement() == this)
return true;
return Element::layoutObjectIsNeeded(style);
}
LayoutObject* SVGSVGElement::createLayoutObject(const ComputedStyle&)
{
if (isOutermostSVGSVGElement())
return new LayoutSVGRoot(this);
return new LayoutSVGViewportContainer(this);
}
Node::InsertionNotificationRequest SVGSVGElement::insertedInto(ContainerNode* rootParent)
{
if (rootParent->inShadowIncludingDocument()) {
UseCounter::count(document(), UseCounter::SVGSVGElementInDocument);
if (rootParent->document().isXMLDocument())
UseCounter::count(document(), UseCounter::SVGSVGElementInXMLDocument);
if (RuntimeEnabledFeatures::smilEnabled()) {
document().accessSVGExtensions().addTimeContainer(this);
// Animations are started at the end of document parsing and after firing the load event,
// but if we miss that train (deferred programmatic element insertion for example) we need
// to initialize the time container here.
if (!document().parsing() && !document().processingLoadEvent() && document().loadEventFinished() && !timeContainer()->isStarted())
timeContainer()->begin();
}
}
return SVGGraphicsElement::insertedInto(rootParent);
}
void SVGSVGElement::removedFrom(ContainerNode* rootParent)
{
if (rootParent->inShadowIncludingDocument()) {
SVGDocumentExtensions& svgExtensions = document().accessSVGExtensions();
svgExtensions.removeTimeContainer(this);
svgExtensions.removeSVGRootWithRelativeLengthDescendents(this);
}
SVGGraphicsElement::removedFrom(rootParent);
}
void SVGSVGElement::pauseAnimations()
{
ASSERT(RuntimeEnabledFeatures::smilEnabled());
if (!m_timeContainer->isPaused())
m_timeContainer->pause();
}
void SVGSVGElement::unpauseAnimations()
{
ASSERT(RuntimeEnabledFeatures::smilEnabled());
if (m_timeContainer->isPaused())
m_timeContainer->resume();
}
bool SVGSVGElement::animationsPaused() const
{
ASSERT(RuntimeEnabledFeatures::smilEnabled());
return m_timeContainer->isPaused();
}
float SVGSVGElement::getCurrentTime() const
{
ASSERT(RuntimeEnabledFeatures::smilEnabled());
return narrowPrecisionToFloat(m_timeContainer->elapsed().value());
}
void SVGSVGElement::setCurrentTime(float seconds)
{
ASSERT(RuntimeEnabledFeatures::smilEnabled());
ASSERT(std::isfinite(seconds));
seconds = max(seconds, 0.0f);
m_timeContainer->setElapsed(seconds);
}
bool SVGSVGElement::selfHasRelativeLengths() const
{
return m_x->currentValue()->isRelative()
|| m_y->currentValue()->isRelative()
|| m_width->currentValue()->isRelative()
|| m_height->currentValue()->isRelative();
}
FloatRect SVGSVGElement::currentViewBoxRect() const
{
if (m_useCurrentView)
return m_viewSpec ? m_viewSpec->viewBox()->currentValue()->value() : FloatRect();
FloatRect useViewBox = viewBox()->currentValue()->value();
if (!useViewBox.isEmpty())
return useViewBox;
if (!layoutObject() || !layoutObject()->isSVGRoot())
return FloatRect();
if (!toLayoutSVGRoot(layoutObject())->isEmbeddedThroughSVGImage())
return FloatRect();
// If no viewBox is specified but non-relative width/height values, then we
// should always synthesize a viewBox if we're embedded through a SVGImage.
return FloatRect(FloatPoint(), FloatSize(intrinsicWidth(), intrinsicHeight()));
}
FloatSize SVGSVGElement::currentViewportSize() const
{
if (!layoutObject())
return FloatSize();
if (layoutObject()->isSVGRoot()) {
LayoutRect contentBoxRect = toLayoutSVGRoot(layoutObject())->contentBoxRect();
return FloatSize(contentBoxRect.width() / layoutObject()->style()->effectiveZoom(), contentBoxRect.height() / layoutObject()->style()->effectiveZoom());
}
FloatRect viewportRect = toLayoutSVGViewportContainer(layoutObject())->viewport();
return FloatSize(viewportRect.width(), viewportRect.height());
}
bool SVGSVGElement::hasIntrinsicWidth() const
{
return width()->currentValue()->typeWithCalcResolved() != CSSPrimitiveValue::UnitType::Percentage;
}
bool SVGSVGElement::hasIntrinsicHeight() const
{
return height()->currentValue()->typeWithCalcResolved() != CSSPrimitiveValue::UnitType::Percentage;
}
float SVGSVGElement::intrinsicWidth() const
{
if (width()->currentValue()->typeWithCalcResolved() == CSSPrimitiveValue::UnitType::Percentage)
return 0;
SVGLengthContext lengthContext(this);
return width()->currentValue()->value(lengthContext);
}
float SVGSVGElement::intrinsicHeight() const
{
if (height()->currentValue()->typeWithCalcResolved() == CSSPrimitiveValue::UnitType::Percentage)
return 0;
SVGLengthContext lengthContext(this);
return height()->currentValue()->value(lengthContext);
}
AffineTransform SVGSVGElement::viewBoxToViewTransform(float viewWidth, float viewHeight) const
{
if (!m_useCurrentView || !m_viewSpec)
return SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), preserveAspectRatio()->currentValue(), viewWidth, viewHeight);
AffineTransform ctm = SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), m_viewSpec->preserveAspectRatio()->currentValue(), viewWidth, viewHeight);
SVGTransformList* transformList = m_viewSpec->transform();
if (transformList->isEmpty())
return ctm;
AffineTransform transform;
if (transformList->concatenate(transform))
ctm *= transform;
return ctm;
}
void SVGSVGElement::setupInitialView(const String& fragmentIdentifier, Element* anchorNode)
{
LayoutObject* layoutObject = this->layoutObject();
SVGViewSpec* view = m_viewSpec.get();
if (view)
view->reset();
bool hadUseCurrentView = m_useCurrentView;
m_useCurrentView = false;
if (fragmentIdentifier.startsWith("svgView(")) {
if (!view)
view = currentView(); // Create the SVGViewSpec.
view->inheritViewAttributesFromElement(this);
if (view->parseViewSpec(fragmentIdentifier)) {
UseCounter::count(document(), UseCounter::SVGSVGElementFragmentSVGView);
m_useCurrentView = true;
} else {
view->reset();
}
if (layoutObject && (hadUseCurrentView || m_useCurrentView))
markForLayoutAndParentResourceInvalidation(layoutObject);
return;
}
// Spec: If the SVG fragment identifier addresses a 'view' element within an SVG document (e.g., MyDrawing.svg#MyView
// or MyDrawing.svg#xpointer(id('MyView'))) then the closest ancestor 'svg' element is displayed in the viewport.
// Any view specification attributes included on the given 'view' element override the corresponding view specification
// attributes on the closest ancestor 'svg' element.
// TODO(ed): The spec text above is a bit unclear.
// Should the transform from outermost svg to nested svg be applied to "display"
// the inner svg in the viewport, then let the view element override the inner
// svg's view specification attributes. Should it fill/override the outer viewport?
if (isSVGViewElement(anchorNode)) {
SVGViewElement& viewElement = toSVGViewElement(*anchorNode);
if (SVGSVGElement* svg = viewElement.ownerSVGElement()) {
svg->inheritViewAttributes(&viewElement);
if (LayoutObject* layoutObject = svg->layoutObject())
markForLayoutAndParentResourceInvalidation(layoutObject);
return;
}
}
// If we previously had a view and didn't get a new one, we need to
// layout again.
if (layoutObject && hadUseCurrentView)
markForLayoutAndParentResourceInvalidation(layoutObject);
// FIXME: We need to decide which <svg> to focus on, and zoom to it.
// FIXME: We need to actually "highlight" the viewTarget(s).
}
void SVGSVGElement::inheritViewAttributes(SVGViewElement* viewElement)
{
SVGViewSpec* view = currentView();
m_useCurrentView = true;
UseCounter::count(document(), UseCounter::SVGSVGElementFragmentSVGViewElement);
view->inheritViewAttributesFromElement(this);
view->inheritViewAttributesFromElement(viewElement);
}
void SVGSVGElement::finishParsingChildren()
{
SVGGraphicsElement::finishParsingChildren();
// The outermost SVGSVGElement SVGLoad event is fired through
// LocalDOMWindow::dispatchWindowLoadEvent.
if (isOutermostSVGSVGElement())
return;
// finishParsingChildren() is called when the close tag is reached for an element (e.g. </svg>)
// we send SVGLoad events here if we can, otherwise they'll be sent when any required loads finish
sendSVGLoadEventIfPossible();
}
DEFINE_TRACE(SVGSVGElement)
{
visitor->trace(m_x);
visitor->trace(m_y);
visitor->trace(m_width);
visitor->trace(m_height);
visitor->trace(m_translation);
visitor->trace(m_timeContainer);
visitor->trace(m_viewSpec);
SVGGraphicsElement::trace(visitor);
SVGFitToViewBox::trace(visitor);
}
} // namespace blink