1/*
2 * Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Apple Inc. All rights reserved.
5 * Copyright (C) 2014 Google, Inc.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#include "config.h"
24
25#include "core/svg/SVGSVGElement.h"
26
27#include "bindings/core/v8/ScriptEventListener.h"
28#include "core/HTMLNames.h"
29#include "core/SVGNames.h"
30#include "core/css/CSSHelper.h"
31#include "core/dom/Document.h"
32#include "core/dom/ElementTraversal.h"
33#include "core/dom/StaticNodeList.h"
34#include "core/editing/FrameSelection.h"
35#include "core/events/EventListener.h"
36#include "core/frame/LocalFrame.h"
37#include "core/page/FrameTree.h"
38#include "core/frame/FrameView.h"
39#include "core/frame/UseCounter.h"
40#include "core/rendering/RenderObject.h"
41#include "core/rendering/RenderPart.h"
42#include "core/rendering/svg/RenderSVGModelObject.h"
43#include "core/rendering/svg/RenderSVGResource.h"
44#include "core/rendering/svg/RenderSVGRoot.h"
45#include "core/rendering/svg/RenderSVGViewportContainer.h"
46#include "core/svg/SVGAngleTearOff.h"
47#include "core/svg/SVGNumberTearOff.h"
48#include "core/svg/SVGPreserveAspectRatio.h"
49#include "core/svg/SVGRectTearOff.h"
50#include "core/svg/SVGTransform.h"
51#include "core/svg/SVGTransformList.h"
52#include "core/svg/SVGTransformTearOff.h"
53#include "core/svg/SVGViewElement.h"
54#include "core/svg/SVGViewSpec.h"
55#include "core/svg/animation/SMILTimeContainer.h"
56#include "platform/FloatConversion.h"
57#include "platform/LengthFunctions.h"
58#include "platform/geometry/FloatRect.h"
59#include "platform/transforms/AffineTransform.h"
60#include "wtf/StdLibExtras.h"
61
62namespace blink {
63
64inline SVGSVGElement::SVGSVGElement(Document& doc)
65    : SVGGraphicsElement(SVGNames::svgTag, doc)
66    , SVGFitToViewBox(this)
67    , m_x(SVGAnimatedLength::create(this, SVGNames::xAttr, SVGLength::create(LengthModeWidth), AllowNegativeLengths))
68    , m_y(SVGAnimatedLength::create(this, SVGNames::yAttr, SVGLength::create(LengthModeHeight), AllowNegativeLengths))
69    , m_width(SVGAnimatedLength::create(this, SVGNames::widthAttr, SVGLength::create(LengthModeWidth), ForbidNegativeLengths))
70    , m_height(SVGAnimatedLength::create(this, SVGNames::heightAttr, SVGLength::create(LengthModeHeight), ForbidNegativeLengths))
71    , m_useCurrentView(false)
72    , m_timeContainer(SMILTimeContainer::create(*this))
73    , m_translation(SVGPoint::create())
74{
75    m_width->setDefaultValueAsString("100%");
76    m_height->setDefaultValueAsString("100%");
77
78    addToPropertyMap(m_x);
79    addToPropertyMap(m_y);
80    addToPropertyMap(m_width);
81    addToPropertyMap(m_height);
82
83    UseCounter::count(doc, UseCounter::SVGSVGElement);
84}
85
86DEFINE_NODE_FACTORY(SVGSVGElement)
87
88SVGSVGElement::~SVGSVGElement()
89{
90#if !ENABLE(OILPAN)
91    if (m_viewSpec)
92        m_viewSpec->detachContextElement();
93
94    // There are cases where removedFromDocument() is not called.
95    // see ContainerNode::removeAllChildren, called by its destructor.
96    // With Oilpan, either removedFrom is called or the document
97    // is dead as well and there is no reason to clear the extensions.
98    document().accessSVGExtensions().removeTimeContainer(this);
99
100    ASSERT(inDocument() || !accessDocumentSVGExtensions().isSVGRootWithRelativeLengthDescendents(this));
101#endif
102}
103
104PassRefPtr<SVGRectTearOff> SVGSVGElement::viewport() const
105{
106    // FIXME: This method doesn't follow the spec and is basically untested. Parent documents are not considered here.
107    // As we have no test coverage for this, we're going to disable it completly for now.
108    return SVGRectTearOff::create(SVGRect::create(), 0, PropertyIsNotAnimVal);
109}
110
111float SVGSVGElement::pixelUnitToMillimeterX() const
112{
113    return 1 / cssPixelsPerMillimeter;
114}
115
116float SVGSVGElement::pixelUnitToMillimeterY() const
117{
118    return 1 / cssPixelsPerMillimeter;
119}
120
121float SVGSVGElement::screenPixelToMillimeterX() const
122{
123    return pixelUnitToMillimeterX();
124}
125
126float SVGSVGElement::screenPixelToMillimeterY() const
127{
128    return pixelUnitToMillimeterY();
129}
130
131SVGViewSpec* SVGSVGElement::currentView()
132{
133    if (!m_viewSpec)
134        m_viewSpec = SVGViewSpec::create(this);
135    return m_viewSpec.get();
136}
137
138float SVGSVGElement::currentScale() const
139{
140    if (!inDocument() || !isOutermostSVGSVGElement())
141        return 1;
142
143    LocalFrame* frame = document().frame();
144    if (!frame)
145        return 1;
146
147    const FrameTree& frameTree = frame->tree();
148
149    // The behaviour of currentScale() is undefined, when we're dealing with non-standalone SVG documents.
150    // If the svg is embedded, the scaling is handled by the host renderer, so when asking from inside
151    // the SVG document, a scale value of 1 seems reasonable, as it doesn't know anything about the parent scale.
152    return frameTree.parent() ? 1 : frame->pageZoomFactor();
153}
154
155void SVGSVGElement::setCurrentScale(float scale)
156{
157    if (!inDocument() || !isOutermostSVGSVGElement())
158        return;
159
160    LocalFrame* frame = document().frame();
161    if (!frame)
162        return;
163
164    const FrameTree& frameTree = frame->tree();
165
166    // The behaviour of setCurrentScale() is undefined, when we're dealing with non-standalone SVG documents.
167    // We choose the ignore this call, it's pretty useless to support calling setCurrentScale() from within
168    // an embedded SVG document, for the same reasons as in currentScale() - needs resolution by SVG WG.
169    if (frameTree.parent())
170        return;
171
172    frame->setPageZoomFactor(scale);
173}
174
175class SVGCurrentTranslateTearOff : public SVGPointTearOff {
176public:
177    static PassRefPtr<SVGCurrentTranslateTearOff> create(SVGSVGElement* contextElement)
178    {
179        return adoptRef(new SVGCurrentTranslateTearOff(contextElement));
180    }
181
182    virtual void commitChange() OVERRIDE
183    {
184        ASSERT(contextElement());
185        toSVGSVGElement(contextElement())->updateCurrentTranslate();
186    }
187
188private:
189    SVGCurrentTranslateTearOff(SVGSVGElement* contextElement)
190        : SVGPointTearOff(contextElement->m_translation, contextElement, PropertyIsNotAnimVal)
191    {
192    }
193};
194
195PassRefPtr<SVGPointTearOff> SVGSVGElement::currentTranslateFromJavascript()
196{
197    return SVGCurrentTranslateTearOff::create(this);
198}
199
200void SVGSVGElement::setCurrentTranslate(const FloatPoint& point)
201{
202    m_translation->setValue(point);
203    updateCurrentTranslate();
204}
205
206void SVGSVGElement::updateCurrentTranslate()
207{
208    if (RenderObject* object = renderer())
209        object->setNeedsLayoutAndFullPaintInvalidation();
210}
211
212void SVGSVGElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
213{
214    SVGParsingError parseError = NoError;
215
216    if (!nearestViewportElement()) {
217        bool setListener = true;
218
219        // Only handle events if we're the outermost <svg> element
220        if (name == HTMLNames::onunloadAttr)
221            document().setWindowAttributeEventListener(EventTypeNames::unload, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
222        else if (name == HTMLNames::onresizeAttr)
223            document().setWindowAttributeEventListener(EventTypeNames::resize, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
224        else if (name == HTMLNames::onscrollAttr)
225            document().setWindowAttributeEventListener(EventTypeNames::scroll, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
226        else if (name == SVGNames::onzoomAttr)
227            document().setWindowAttributeEventListener(EventTypeNames::zoom, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
228        else
229            setListener = false;
230
231        if (setListener)
232            return;
233    }
234
235    if (name == HTMLNames::onabortAttr) {
236        document().setWindowAttributeEventListener(EventTypeNames::abort, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
237    } else if (name == HTMLNames::onerrorAttr) {
238        document().setWindowAttributeEventListener(EventTypeNames::error, createAttributeEventListener(document().frame(), name, value, eventParameterName()));
239    } else if (name == SVGNames::xAttr) {
240        m_x->setBaseValueAsString(value, parseError);
241    } else if (name == SVGNames::yAttr) {
242        m_y->setBaseValueAsString(value, parseError);
243    } else if (name == SVGNames::widthAttr) {
244        m_width->setBaseValueAsString(value, parseError);
245    } else if (name == SVGNames::heightAttr) {
246        m_height->setBaseValueAsString(value, parseError);
247    } else if (SVGFitToViewBox::parseAttribute(name, value, document(), parseError)) {
248    } else if (SVGZoomAndPan::parseAttribute(name, value)) {
249    } else {
250        SVGGraphicsElement::parseAttribute(name, value);
251    }
252
253    reportAttributeParsingError(parseError, name, value);
254}
255
256bool SVGSVGElement::isPresentationAttribute(const QualifiedName& name) const
257{
258    if (isOutermostSVGSVGElement() && (name == SVGNames::widthAttr || name == SVGNames::heightAttr))
259        return true;
260    return SVGGraphicsElement::isPresentationAttribute(name);
261}
262
263void SVGSVGElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
264{
265    if (isOutermostSVGSVGElement() && (name == SVGNames::widthAttr || name == SVGNames::heightAttr)) {
266        RefPtr<SVGLength> length = SVGLength::create(LengthModeOther);
267        TrackExceptionState exceptionState;
268        length->setValueAsString(value, exceptionState);
269        if (!exceptionState.hadException()) {
270            if (name == SVGNames::widthAttr)
271                addPropertyToPresentationAttributeStyle(style, CSSPropertyWidth, value);
272            else if (name == SVGNames::heightAttr)
273                addPropertyToPresentationAttributeStyle(style, CSSPropertyHeight, value);
274        }
275    } else {
276        SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style);
277    }
278}
279
280void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
281{
282    bool updateRelativeLengthsOrViewBox = false;
283    bool widthChanged = attrName == SVGNames::widthAttr;
284    bool heightChanged = attrName == SVGNames::heightAttr;
285    if (widthChanged || heightChanged
286        || attrName == SVGNames::xAttr
287        || attrName == SVGNames::yAttr) {
288        updateRelativeLengthsOrViewBox = true;
289        updateRelativeLengthsInformation();
290        invalidateRelativeLengthClients();
291
292        // At the SVG/HTML boundary (aka RenderSVGRoot), the width and
293        // height attributes can affect the replaced size so we need
294        // to mark it for updating.
295        //
296        // FIXME: For width/height animated as XML attributes on SVG
297        // roots, there is an attribute synchronization missing. See
298        // http://crbug.com/364807
299        if (widthChanged || heightChanged) {
300            RenderObject* renderObject = renderer();
301            if (renderObject && renderObject->isSVGRoot()) {
302                invalidateSVGPresentationAttributeStyle();
303                setNeedsStyleRecalc(LocalStyleChange);
304            }
305        }
306    }
307
308    if (SVGFitToViewBox::isKnownAttribute(attrName)) {
309        updateRelativeLengthsOrViewBox = true;
310        if (RenderObject* object = renderer())
311            object->setNeedsTransformUpdate();
312    }
313
314    SVGElement::InvalidationGuard invalidationGuard(this);
315
316    if (updateRelativeLengthsOrViewBox
317        || SVGZoomAndPan::isKnownAttribute(attrName)) {
318        if (renderer())
319            RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer());
320        return;
321    }
322
323    SVGGraphicsElement::svgAttributeChanged(attrName);
324}
325
326// FloatRect::intersects does not consider horizontal or vertical lines (because of isEmpty()).
327static bool intersectsAllowingEmpty(const FloatRect& r1, const FloatRect& r2)
328{
329    if (r1.width() < 0 || r1.height() < 0 || r2.width() < 0 || r2.height() < 0)
330        return false;
331
332    return r1.x() < r2.maxX() && r2.x() < r1.maxX()
333        && r1.y() < r2.maxY() && r2.y() < r1.maxY();
334}
335
336// One of the element types that can cause graphics to be drawn onto the target canvas.
337// Specifically: circle, ellipse, image, line, path, polygon, polyline, rect, text and use.
338static bool isIntersectionOrEnclosureTarget(RenderObject* renderer)
339{
340    return renderer->isSVGShape()
341        || renderer->isSVGText()
342        || renderer->isSVGImage()
343        || isSVGUseElement(*renderer->node());
344}
345
346bool SVGSVGElement::checkIntersectionOrEnclosure(const SVGElement& element, const FloatRect& rect,
347    CheckIntersectionOrEnclosure mode) const
348{
349    RenderObject* renderer = element.renderer();
350    ASSERT(!renderer || renderer->style());
351    if (!renderer || renderer->style()->pointerEvents() == PE_NONE)
352        return false;
353
354    if (!isIntersectionOrEnclosureTarget(renderer))
355        return false;
356
357    AffineTransform ctm = toSVGGraphicsElement(element).computeCTM(AncestorScope, DisallowStyleUpdate, this);
358    FloatRect mappedRepaintRect = ctm.mapRect(renderer->paintInvalidationRectInLocalCoordinates());
359
360    bool result = false;
361    switch (mode) {
362    case CheckIntersection:
363        result = intersectsAllowingEmpty(rect, mappedRepaintRect);
364        break;
365    case CheckEnclosure:
366        result = rect.contains(mappedRepaintRect);
367        break;
368    default:
369        ASSERT_NOT_REACHED();
370        break;
371    }
372
373    return result;
374}
375
376PassRefPtrWillBeRawPtr<StaticNodeList> SVGSVGElement::collectIntersectionOrEnclosureList(const FloatRect& rect,
377    SVGElement* referenceElement, CheckIntersectionOrEnclosure mode) const
378{
379    WillBeHeapVector<RefPtrWillBeMember<Node> > nodes;
380
381    const SVGElement* root = this;
382    if (referenceElement) {
383        // Only the common subtree needs to be traversed.
384        if (contains(referenceElement)) {
385            root = referenceElement;
386        } else if (!isDescendantOf(referenceElement)) {
387            // No common subtree.
388            return StaticNodeList::adopt(nodes);
389        }
390    }
391
392    for (SVGGraphicsElement* element = Traversal<SVGGraphicsElement>::firstWithin(*root); element;
393        element = Traversal<SVGGraphicsElement>::next(*element, root)) {
394        if (checkIntersectionOrEnclosure(*element, rect, mode))
395            nodes.append(element);
396    }
397
398    return StaticNodeList::adopt(nodes);
399}
400
401PassRefPtrWillBeRawPtr<StaticNodeList> SVGSVGElement::getIntersectionList(PassRefPtr<SVGRectTearOff> rect, SVGElement* referenceElement) const
402{
403    document().updateLayoutIgnorePendingStylesheets();
404
405    return collectIntersectionOrEnclosureList(rect->target()->value(), referenceElement, CheckIntersection);
406}
407
408PassRefPtrWillBeRawPtr<StaticNodeList> SVGSVGElement::getEnclosureList(PassRefPtr<SVGRectTearOff> rect, SVGElement* referenceElement) const
409{
410    document().updateLayoutIgnorePendingStylesheets();
411
412    return collectIntersectionOrEnclosureList(rect->target()->value(), referenceElement, CheckEnclosure);
413}
414
415bool SVGSVGElement::checkIntersection(SVGElement* element, PassRefPtr<SVGRectTearOff> rect) const
416{
417    ASSERT(element);
418    document().updateLayoutIgnorePendingStylesheets();
419
420    return checkIntersectionOrEnclosure(*element, rect->target()->value(), CheckIntersection);
421}
422
423bool SVGSVGElement::checkEnclosure(SVGElement* element, PassRefPtr<SVGRectTearOff> rect) const
424{
425    ASSERT(element);
426    document().updateLayoutIgnorePendingStylesheets();
427
428    return checkIntersectionOrEnclosure(*element, rect->target()->value(), CheckEnclosure);
429}
430
431void SVGSVGElement::deselectAll()
432{
433    if (LocalFrame* frame = document().frame())
434        frame->selection().clear();
435}
436
437PassRefPtr<SVGNumberTearOff> SVGSVGElement::createSVGNumber()
438{
439    return SVGNumberTearOff::create(SVGNumber::create(0.0f), 0, PropertyIsNotAnimVal);
440}
441
442PassRefPtr<SVGLengthTearOff> SVGSVGElement::createSVGLength()
443{
444    return SVGLengthTearOff::create(SVGLength::create(), 0, PropertyIsNotAnimVal);
445}
446
447PassRefPtr<SVGAngleTearOff> SVGSVGElement::createSVGAngle()
448{
449    return SVGAngleTearOff::create(SVGAngle::create(), 0, PropertyIsNotAnimVal);
450}
451
452PassRefPtr<SVGPointTearOff> SVGSVGElement::createSVGPoint()
453{
454    return SVGPointTearOff::create(SVGPoint::create(), 0, PropertyIsNotAnimVal);
455}
456
457PassRefPtr<SVGMatrixTearOff> SVGSVGElement::createSVGMatrix()
458{
459    return SVGMatrixTearOff::create(AffineTransform());
460}
461
462PassRefPtr<SVGRectTearOff> SVGSVGElement::createSVGRect()
463{
464    return SVGRectTearOff::create(SVGRect::create(), 0, PropertyIsNotAnimVal);
465}
466
467PassRefPtr<SVGTransformTearOff> SVGSVGElement::createSVGTransform()
468{
469    return SVGTransformTearOff::create(SVGTransform::create(SVG_TRANSFORM_MATRIX), 0, PropertyIsNotAnimVal);
470}
471
472PassRefPtr<SVGTransformTearOff> SVGSVGElement::createSVGTransformFromMatrix(PassRefPtr<SVGMatrixTearOff> matrix)
473{
474    return SVGTransformTearOff::create(SVGTransform::create(matrix->value()), 0, PropertyIsNotAnimVal);
475}
476
477AffineTransform SVGSVGElement::localCoordinateSpaceTransform(SVGElement::CTMScope mode) const
478{
479    AffineTransform viewBoxTransform;
480    if (!hasEmptyViewBox()) {
481        FloatSize size = currentViewportSize();
482        viewBoxTransform = viewBoxToViewTransform(size.width(), size.height());
483    }
484
485    AffineTransform transform;
486    if (!isOutermostSVGSVGElement()) {
487        SVGLengthContext lengthContext(this);
488        transform.translate(m_x->currentValue()->value(lengthContext), m_y->currentValue()->value(lengthContext));
489    } else if (mode == SVGElement::ScreenScope) {
490        if (RenderObject* renderer = this->renderer()) {
491            FloatPoint location;
492            float zoomFactor = 1;
493
494            // At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform
495            // to map an element from SVG viewport coordinates to CSS box coordinates.
496            // RenderSVGRoot's localToAbsolute method expects CSS box coordinates.
497            // We also need to adjust for the zoom level factored into CSS coordinates (bug #96361).
498            if (renderer->isSVGRoot()) {
499                location = toRenderSVGRoot(renderer)->localToBorderBoxTransform().mapPoint(location);
500                zoomFactor = 1 / renderer->style()->effectiveZoom();
501            }
502
503            // Translate in our CSS parent coordinate space
504            // FIXME: This doesn't work correctly with CSS transforms.
505            location = renderer->localToAbsolute(location, UseTransforms);
506            location.scale(zoomFactor, zoomFactor);
507
508            // Be careful here! localToBorderBoxTransform() included the x/y offset coming from the viewBoxToViewTransform(),
509            // so we have to subtract it here (original cause of bug #27183)
510            transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f());
511
512            // Respect scroll offset.
513            if (FrameView* view = document().view()) {
514                LayoutSize scrollOffset = view->scrollOffset();
515                scrollOffset.scale(zoomFactor);
516                transform.translate(-scrollOffset.width(), -scrollOffset.height());
517            }
518        }
519    }
520
521    return transform.multiply(viewBoxTransform);
522}
523
524bool SVGSVGElement::rendererIsNeeded(const RenderStyle& style)
525{
526    // FIXME: We should respect display: none on the documentElement svg element
527    // but many things in FrameView and SVGImage depend on the RenderSVGRoot when
528    // they should instead depend on the RenderView.
529    // https://bugs.webkit.org/show_bug.cgi?id=103493
530    if (document().documentElement() == this)
531        return true;
532    return Element::rendererIsNeeded(style);
533}
534
535RenderObject* SVGSVGElement::createRenderer(RenderStyle*)
536{
537    if (isOutermostSVGSVGElement())
538        return new RenderSVGRoot(this);
539
540    return new RenderSVGViewportContainer(this);
541}
542
543Node::InsertionNotificationRequest SVGSVGElement::insertedInto(ContainerNode* rootParent)
544{
545    if (rootParent->inDocument()) {
546        UseCounter::count(document(), UseCounter::SVGSVGElementInDocument);
547        if (rootParent->document().isXMLDocument())
548            UseCounter::count(document(), UseCounter::SVGSVGElementInXMLDocument);
549
550        document().accessSVGExtensions().addTimeContainer(this);
551
552        // Animations are started at the end of document parsing and after firing the load event,
553        // but if we miss that train (deferred programmatic element insertion for example) we need
554        // to initialize the time container here.
555        if (!document().parsing() && !document().processingLoadEvent() && document().loadEventFinished() && !timeContainer()->isStarted())
556            timeContainer()->begin();
557    }
558    return SVGGraphicsElement::insertedInto(rootParent);
559}
560
561void SVGSVGElement::removedFrom(ContainerNode* rootParent)
562{
563    if (rootParent->inDocument()) {
564        SVGDocumentExtensions& svgExtensions = document().accessSVGExtensions();
565        svgExtensions.removeTimeContainer(this);
566        svgExtensions.removeSVGRootWithRelativeLengthDescendents(this);
567    }
568
569    SVGGraphicsElement::removedFrom(rootParent);
570}
571
572void SVGSVGElement::pauseAnimations()
573{
574    if (!m_timeContainer->isPaused())
575        m_timeContainer->pause();
576}
577
578void SVGSVGElement::unpauseAnimations()
579{
580    if (m_timeContainer->isPaused())
581        m_timeContainer->resume();
582}
583
584bool SVGSVGElement::animationsPaused() const
585{
586    return m_timeContainer->isPaused();
587}
588
589float SVGSVGElement::getCurrentTime() const
590{
591    return narrowPrecisionToFloat(m_timeContainer->elapsed().value());
592}
593
594void SVGSVGElement::setCurrentTime(float seconds)
595{
596    if (std::isnan(seconds))
597        return;
598    seconds = max(seconds, 0.0f);
599    m_timeContainer->setElapsed(seconds);
600}
601
602bool SVGSVGElement::selfHasRelativeLengths() const
603{
604    return m_x->currentValue()->isRelative()
605        || m_y->currentValue()->isRelative()
606        || m_width->currentValue()->isRelative()
607        || m_height->currentValue()->isRelative()
608        || hasAttribute(SVGNames::viewBoxAttr);
609}
610
611FloatRect SVGSVGElement::currentViewBoxRect() const
612{
613    if (m_useCurrentView)
614        return m_viewSpec ? m_viewSpec->viewBox()->currentValue()->value() : FloatRect();
615
616    FloatRect useViewBox = viewBox()->currentValue()->value();
617    if (!useViewBox.isEmpty())
618        return useViewBox;
619    if (!renderer() || !renderer()->isSVGRoot())
620        return FloatRect();
621    if (!toRenderSVGRoot(renderer())->isEmbeddedThroughSVGImage())
622        return FloatRect();
623
624    // If no viewBox is specified but non-relative width/height values, then we
625    // should always synthesize a viewBox if we're embedded through a SVGImage.
626    return FloatRect(FloatPoint(), FloatSize(floatValueForLength(intrinsicWidth(), 0), floatValueForLength(intrinsicHeight(), 0)));
627}
628
629FloatSize SVGSVGElement::currentViewportSize() const
630{
631    if (!renderer())
632        return FloatSize();
633
634    if (renderer()->isSVGRoot()) {
635        LayoutRect contentBoxRect = toRenderSVGRoot(renderer())->contentBoxRect();
636        return FloatSize(contentBoxRect.width() / renderer()->style()->effectiveZoom(), contentBoxRect.height() / renderer()->style()->effectiveZoom());
637    }
638
639    FloatRect viewportRect = toRenderSVGViewportContainer(renderer())->viewport();
640    return FloatSize(viewportRect.width(), viewportRect.height());
641}
642
643bool SVGSVGElement::hasIntrinsicWidth() const
644{
645    return width()->currentValue()->unitType() != LengthTypePercentage;
646}
647
648bool SVGSVGElement::hasIntrinsicHeight() const
649{
650    return height()->currentValue()->unitType() != LengthTypePercentage;
651}
652
653Length SVGSVGElement::intrinsicWidth() const
654{
655    if (width()->currentValue()->unitType() == LengthTypePercentage)
656        return Length(0, Fixed);
657
658    SVGLengthContext lengthContext(this);
659    return Length(width()->currentValue()->value(lengthContext), Fixed);
660}
661
662Length SVGSVGElement::intrinsicHeight() const
663{
664    if (height()->currentValue()->unitType() == LengthTypePercentage)
665        return Length(0, Fixed);
666
667    SVGLengthContext lengthContext(this);
668    return Length(height()->currentValue()->value(lengthContext), Fixed);
669}
670
671AffineTransform SVGSVGElement::viewBoxToViewTransform(float viewWidth, float viewHeight) const
672{
673    if (!m_useCurrentView || !m_viewSpec)
674        return SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), preserveAspectRatio()->currentValue(), viewWidth, viewHeight);
675
676    AffineTransform ctm = SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), m_viewSpec->preserveAspectRatio()->currentValue(), viewWidth, viewHeight);
677    RefPtr<SVGTransformList> transformList = m_viewSpec->transform();
678    if (transformList->isEmpty())
679        return ctm;
680
681    AffineTransform transform;
682    if (transformList->concatenate(transform))
683        ctm *= transform;
684
685    return ctm;
686}
687
688void SVGSVGElement::setupInitialView(const String& fragmentIdentifier, Element* anchorNode)
689{
690    RenderObject* renderer = this->renderer();
691    SVGViewSpec* view = m_viewSpec.get();
692    if (view)
693        view->reset();
694
695    bool hadUseCurrentView = m_useCurrentView;
696    m_useCurrentView = false;
697
698    if (fragmentIdentifier.startsWith("xpointer(")) {
699        // FIXME: XPointer references are ignored (https://bugs.webkit.org/show_bug.cgi?id=17491)
700        if (renderer && hadUseCurrentView)
701            RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
702        return;
703    }
704
705    if (fragmentIdentifier.startsWith("svgView(")) {
706        if (!view)
707            view = currentView(); // Create the SVGViewSpec.
708
709        if (view->parseViewSpec(fragmentIdentifier))
710            m_useCurrentView = true;
711        else
712            view->reset();
713
714        if (renderer && (hadUseCurrentView || m_useCurrentView))
715            RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
716        return;
717    }
718
719    // Spec: If the SVG fragment identifier addresses a ‘view’ element within an SVG document (e.g., MyDrawing.svg#MyView
720    // or MyDrawing.svg#xpointer(id('MyView'))) then the closest ancestor ‘svg’ element is displayed in the viewport.
721    // Any view specification attributes included on the given ‘view’ element override the corresponding view specification
722    // attributes on the closest ancestor ‘svg’ element.
723    if (isSVGViewElement(anchorNode)) {
724        SVGViewElement& viewElement = toSVGViewElement(*anchorNode);
725
726        if (SVGSVGElement* svg = viewElement.ownerSVGElement()) {
727            svg->inheritViewAttributes(&viewElement);
728
729            if (RenderObject* renderer = svg->renderer())
730                RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
731        }
732    }
733
734    // FIXME: We need to decide which <svg> to focus on, and zoom to it.
735    // FIXME: We need to actually "highlight" the viewTarget(s).
736}
737
738void SVGSVGElement::inheritViewAttributes(SVGViewElement* viewElement)
739{
740    SVGViewSpec* view = currentView();
741    m_useCurrentView = true;
742
743    if (viewElement->hasAttribute(SVGNames::viewBoxAttr))
744        view->viewBox()->baseValue()->setValue(viewElement->viewBox()->currentValue()->value());
745    else
746        view->viewBox()->baseValue()->setValue(viewBox()->currentValue()->value());
747
748    if (viewElement->hasAttribute(SVGNames::preserveAspectRatioAttr)) {
749        view->preserveAspectRatio()->baseValue()->setAlign(viewElement->preserveAspectRatio()->currentValue()->align());
750        view->preserveAspectRatio()->baseValue()->setMeetOrSlice(viewElement->preserveAspectRatio()->currentValue()->meetOrSlice());
751    } else {
752        view->preserveAspectRatio()->baseValue()->setAlign(preserveAspectRatio()->currentValue()->align());
753        view->preserveAspectRatio()->baseValue()->setMeetOrSlice(preserveAspectRatio()->currentValue()->meetOrSlice());
754    }
755
756    if (viewElement->hasAttribute(SVGNames::zoomAndPanAttr))
757        view->setZoomAndPan(viewElement->zoomAndPan());
758    else
759        view->setZoomAndPan(zoomAndPan());
760}
761
762void SVGSVGElement::finishParsingChildren()
763{
764    SVGGraphicsElement::finishParsingChildren();
765
766    // The outermost SVGSVGElement SVGLoad event is fired through Document::dispatchWindowLoadEvent.
767    if (isOutermostSVGSVGElement())
768        return;
769
770    // finishParsingChildren() is called when the close tag is reached for an element (e.g. </svg>)
771    // we send SVGLoad events here if we can, otherwise they'll be sent when any required loads finish
772    sendSVGLoadEventIfPossible();
773}
774
775void SVGSVGElement::trace(Visitor* visitor)
776{
777    visitor->trace(m_timeContainer);
778    visitor->trace(m_viewSpec);
779    SVGGraphicsElement::trace(visitor);
780}
781
782} // namespace blink
783