/*
 * 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-2017 Apple Inc. All rights reserved.
 * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * 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 "config.h"
#include "SVGSVGElement.h"

#include "CSSHelper.h"
#include "DOMWrapperWorld.h"
#include "ElementIterator.h"
#include "EventNames.h"
#include "FrameSelection.h"
#include "MainFrame.h"
#include "RenderSVGResource.h"
#include "RenderSVGRoot.h"
#include "RenderSVGViewportContainer.h"
#include "RenderView.h"
#include "SMILTimeContainer.h"
#include "SVGAngle.h"
#include "SVGDocumentExtensions.h"
#include "SVGLength.h"
#include "SVGMatrix.h"
#include "SVGNumber.h"
#include "SVGPoint.h"
#include "SVGRect.h"
#include "SVGStaticPropertyTearOff.h"
#include "SVGTransform.h"
#include "SVGViewElement.h"
#include "SVGViewSpec.h"
#include "StaticNodeList.h"

namespace WebCore {

// Animated property definitions
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, ASCIILiteral("100%"))
    , m_height(LengthModeHeight, ASCIILiteral("100%"))
    , m_timeContainer(SMILTimeContainer::create(*this))
{
    ASSERT(hasTagName(SVGNames::svgTag));
    registerAnimatedPropertiesForSVGSVGElement();
    document.registerForDocumentSuspensionCallbacks(this);
}

Ref<SVGSVGElement> SVGSVGElement::create(const QualifiedName& tagName, Document& document)
{
    return adoptRef(*new SVGSVGElement(tagName, document));
}

Ref<SVGSVGElement> SVGSVGElement::create(Document& document)
{
    return create(SVGNames::svgTag, document);
}

SVGSVGElement::~SVGSVGElement()
{
    if (m_viewSpec)
        m_viewSpec->resetContextElement();
    document().unregisterForDocumentSuspensionCallbacks(this);
    document().accessSVGExtensions().removeTimeContainer(this);
}

void SVGSVGElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
{
    oldDocument.unregisterForDocumentSuspensionCallbacks(this);
    document().registerForDocumentSuspensionCallbacks(this);
    SVGGraphicsElement::didMoveToNewDocument(oldDocument, newDocument);
}

const AtomicString& SVGSVGElement::contentScriptType() const
{
    static NeverDestroyed<AtomicString> defaultScriptType { "text/ecmascript" };
    const AtomicString& type = attributeWithoutSynchronization(SVGNames::contentScriptTypeAttr);
    return type.isNull() ? defaultScriptType.get() : type;
}

void SVGSVGElement::setContentScriptType(const AtomicString& type)
{
    setAttributeWithoutSynchronization(SVGNames::contentScriptTypeAttr, type);
}

const AtomicString& SVGSVGElement::contentStyleType() const
{
    static NeverDestroyed<AtomicString> defaultStyleType { "text/css" };
    const AtomicString& type = attributeWithoutSynchronization(SVGNames::contentStyleTypeAttr);
    return type.isNull() ? defaultStyleType.get() : type;
}

void SVGSVGElement::setContentStyleType(const AtomicString& type)
{
    setAttributeWithoutSynchronization(SVGNames::contentStyleTypeAttr, type);
}

Ref<SVGRect> SVGSVGElement::viewport() const
{
    // FIXME: Not implemented.
    return SVGRect::create();
}

float SVGSVGElement::pixelUnitToMillimeterX() const
{
    // There are 25.4 millimeters in an inch.
    return 25.4f / cssPixelsPerInch;
}

float SVGSVGElement::pixelUnitToMillimeterY() const
{
    // There are 25.4 millimeters in an inch.
    return 25.4f / cssPixelsPerInch;
}

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;
}

RefPtr<Frame> SVGSVGElement::frameForCurrentScale() const
{
    // The behavior of currentScale() is undefined when we're dealing with non-standalone SVG documents.
    // If the document is embedded, the scaling is handled by the host renderer.
    if (!isConnected() || !isOutermostSVGSVGElement())
        return nullptr;
    auto frame = makeRefPtr(document().frame());
    return frame && frame->isMainFrame() ? frame : nullptr;
}

float SVGSVGElement::currentScale() const
{
    // When asking from inside an embedded SVG document, a scale value of 1 seems reasonable, as it doesn't
    // know anything about the parent scale.
    auto frame = frameForCurrentScale();
    return frame ? frame->pageZoomFactor() : 1;
}

void SVGSVGElement::setCurrentScale(float scale)
{
    if (auto frame = frameForCurrentScale())
        frame->setPageZoomFactor(scale);
}

Ref<SVGPoint> SVGSVGElement::currentTranslate()
{
    return SVGStaticPropertyTearOff<SVGSVGElement, SVGPoint>::create(*this, m_currentTranslate, &SVGSVGElement::updateCurrentTranslate);
}

void SVGSVGElement::setCurrentTranslate(const FloatPoint& translation)
{
    if (m_currentTranslate == translation)
        return;
    m_currentTranslate = 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)
{
    if (!nearestViewportElement()) {
        // For these events, the outermost <svg> element works like a <body> element does,
        // setting certain event handlers directly on the window object.
        if (name == HTMLNames::onunloadAttr) {
            document().setWindowAttributeEventListener(eventNames().unloadEvent, name, value, mainThreadNormalWorld());
            return;
        }
        if (name == HTMLNames::onresizeAttr) {
            document().setWindowAttributeEventListener(eventNames().resizeEvent, name, value, mainThreadNormalWorld());
            return;
        }
        if (name == HTMLNames::onscrollAttr) {
            document().setWindowAttributeEventListener(eventNames().scrollEvent, name, value, mainThreadNormalWorld());
            return;
        }
        if (name == SVGNames::onzoomAttr) {
            document().setWindowAttributeEventListener(eventNames().zoomEvent, name, value, mainThreadNormalWorld());
            return;
        }
    }

    // For these events, any <svg> element works like a <body> element does,
    // setting certain event handlers directly on the window object.
    // FIXME: Why different from the events above that work only on the outermost <svg> element?
    if (name == HTMLNames::onabortAttr) {
        document().setWindowAttributeEventListener(eventNames().abortEvent, name, value, mainThreadNormalWorld());
        return;
    }
    if (name == HTMLNames::onerrorAttr) {
        document().setWindowAttributeEventListener(eventNames().errorEvent, name, value, mainThreadNormalWorld());
        return;
    }

    SVGParsingError parseError = NoError;

    if (name == SVGNames::xAttr)
        setXBaseValue(SVGLengthValue::construct(LengthModeWidth, value, parseError));
    else if (name == SVGNames::yAttr)
        setYBaseValue(SVGLengthValue::construct(LengthModeHeight, value, parseError));
    else if (name == SVGNames::widthAttr) {
        auto length = SVGLengthValue::construct(LengthModeWidth, value, parseError, ForbidNegativeLengths);
        if (parseError != NoError || value.isEmpty()) {
            // FIXME: This is definitely the correct behavior for a missing/removed attribute.
            // Not sure it's correct for the empty string or for something that can't be parsed.
            length = SVGLengthValue(LengthModeWidth, ASCIILiteral("100%"));
        }
        setWidthBaseValue(length);
    } else if (name == SVGNames::heightAttr) {
        auto length = SVGLengthValue::construct(LengthModeHeight, value, parseError, ForbidNegativeLengths);
        if (parseError != NoError || value.isEmpty()) {
            // FIXME: This is definitely the correct behavior for a removed attribute.
            // Not sure it's correct for the empty string or for something that can't be parsed.
            length = SVGLengthValue(LengthModeHeight, ASCIILiteral("100%"));
        }
        setHeightBaseValue(length);
    }

    reportAttributeParsingError(parseError, name, value);

    SVGExternalResourcesRequired::parseAttribute(name, value);
    SVGFitToViewBox::parseAttribute(this, name, value);
    SVGZoomAndPan::parseAttribute(*this, name, value);
    SVGGraphicsElement::parseAttribute(name, value);
}

void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
{
    bool updateRelativeLengthsOrViewBox = false;
    if (attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr || attrName == SVGNames::xAttr || attrName == SVGNames::yAttr) {
        invalidateSVGPresentationAttributeStyle();
        updateRelativeLengthsOrViewBox = true;
    }

    if (SVGFitToViewBox::isKnownAttribute(attrName)) {
        updateRelativeLengthsOrViewBox = true;
        if (auto* renderer = this->renderer())
            renderer->setNeedsTransformUpdate();
    }

    InstanceInvalidationGuard guard(*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()
{
}

Ref<NodeList> SVGSVGElement::collectIntersectionOrEnclosureList(SVGRect& rect, SVGElement* referenceElement, bool (*checkFunction)(SVGElement&, SVGRect&))
{
    Vector<Ref<Element>> elements;
    for (auto& element : descendantsOfType<SVGElement>(referenceElement ? *referenceElement : *this)) {
        if (checkFunction(element, rect))
            elements.append(element);
    }
    return StaticElementList::create(WTFMove(elements));
}

static bool checkIntersectionWithoutUpdatingLayout(SVGElement& element, SVGRect& rect)
{
    return RenderSVGModelObject::checkIntersection(element.renderer(), rect.propertyReference());
}

static bool checkEnclosureWithoutUpdatingLayout(SVGElement& element, SVGRect& rect)
{
    return RenderSVGModelObject::checkEnclosure(element.renderer(), rect.propertyReference());
}

Ref<NodeList> SVGSVGElement::getIntersectionList(SVGRect& rect, SVGElement* referenceElement)
{
    document().updateLayoutIgnorePendingStylesheets();
    return collectIntersectionOrEnclosureList(rect, referenceElement, checkIntersectionWithoutUpdatingLayout);
}

Ref<NodeList> SVGSVGElement::getEnclosureList(SVGRect& rect, SVGElement* referenceElement)
{
    document().updateLayoutIgnorePendingStylesheets();
    return collectIntersectionOrEnclosureList(rect, referenceElement, checkEnclosureWithoutUpdatingLayout);
}

bool SVGSVGElement::checkIntersection(RefPtr<SVGElement>&& element, SVGRect& rect)
{
    if (!element)
        return false;
    element->document().updateLayoutIgnorePendingStylesheets();
    return checkIntersectionWithoutUpdatingLayout(*element, rect);
}

bool SVGSVGElement::checkEnclosure(RefPtr<SVGElement>&& element, SVGRect& rect)
{
    if (!element)
        return false;
    element->document().updateLayoutIgnorePendingStylesheets();
    return checkEnclosureWithoutUpdatingLayout(*element, rect);
}

void SVGSVGElement::deselectAll()
{
    if (auto frame = makeRefPtr(document().frame()))
        frame->selection().clear();
}

Ref<SVGNumber> SVGSVGElement::createSVGNumber()
{
    return SVGNumber::create();
}

Ref<SVGLength> SVGSVGElement::createSVGLength()
{
    return SVGLength::create();
}

Ref<SVGAngle> SVGSVGElement::createSVGAngle()
{
    return SVGAngle::create();
}

Ref<SVGPoint> SVGSVGElement::createSVGPoint()
{
    return SVGPoint::create();
}

Ref<SVGMatrix> SVGSVGElement::createSVGMatrix()
{
    return SVGMatrix::create();
}

Ref<SVGRect> SVGSVGElement::createSVGRect()
{
    return SVGRect::create();
}

Ref<SVGTransform> SVGSVGElement::createSVGTransform()
{
    return SVGTransform::create(SVGTransformValue { SVGTransformValue::SVG_TRANSFORM_MATRIX });
}

Ref<SVGTransform> SVGSVGElement::createSVGTransformFromMatrix(SVGMatrix& matrix)
{
    return SVGTransform::create(SVGTransformValue { matrix.propertyReference() });
}

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 (auto* renderer = this->renderer()) {
            FloatPoint location;
            float zoomFactor = 1;

            // At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform
            // to map an element from SVG viewport coordinates to CSS box coordinates.
            // RenderSVGRoot's localToAbsolute method expects CSS box coordinates.
            // We also need to adjust for the zoom level factored into CSS coordinates (bug #96361).
            if (is<RenderSVGRoot>(*renderer)) {
                location = downcast<RenderSVGRoot>(*renderer).localToBorderBoxTransform().mapPoint(location);
                zoomFactor = 1 / renderer->style().effectiveZoom();
            }

            // Translate in our CSS parent coordinate space
            // FIXME: This doesn't work correctly with CSS transforms.
            location = renderer->localToAbsolute(location, UseTransforms);
            location.scale(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 (auto view = makeRefPtr(document().view())) {
                LayoutPoint scrollPosition = view->scrollPosition();
                scrollPosition.scale(zoomFactor);
                transform.translate(-scrollPosition);
            }
        }
    }

    return transform.multiply(viewBoxTransform);
}

bool SVGSVGElement::rendererIsNeeded(const RenderStyle& style)
{
    if (!isValid())
        return false;
    // FIXME: We should respect display: none on the documentElement svg element
    // but many things in FrameView and SVGImage depend on the RenderSVGRoot when
    // they should instead depend on the RenderView.
    // https://bugs.webkit.org/show_bug.cgi?id=103493
    if (document().documentElement() == this)
        return true;
    return StyledElement::rendererIsNeeded(style);
}

RenderPtr<RenderElement> SVGSVGElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
{
    if (isOutermostSVGSVGElement())
        return createRenderer<RenderSVGRoot>(*this, WTFMove(style));
    return createRenderer<RenderSVGViewportContainer>(*this, WTFMove(style));
}

Node::InsertedIntoAncestorResult SVGSVGElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
{
    if (insertionType.connectedToDocument) {
        document().accessSVGExtensions().addTimeContainer(this);
        if (!document().accessSVGExtensions().areAnimationsPaused())
            unpauseAnimations();

        // 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() && !m_timeContainer->isStarted())
            m_timeContainer->begin();
    }
    return SVGGraphicsElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
}

void SVGSVGElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
{
    if (removalType.disconnectedFromDocument) {
        document().accessSVGExtensions().removeTimeContainer(this);
        pauseAnimations();
    }
    SVGGraphicsElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
}

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();
}

bool SVGSVGElement::hasActiveAnimation() const
{
    return m_timeContainer->isActive();
}

float SVGSVGElement::getCurrentTime() const
{
    return narrowPrecisionToFloat(m_timeContainer->elapsed().value());
}

void SVGSVGElement::setCurrentTime(float seconds)
{
    if (!std::isfinite(seconds))
        return;
    m_timeContainer->setElapsed(std::max(seconds, 0.0f));
}

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 viewBox = this->viewBox();
    if (!viewBox.isEmpty())
        return viewBox;

    if (!is<RenderSVGRoot>(renderer()))
        return { };
    if (!downcast<RenderSVGRoot>(*renderer()).isEmbeddedThroughSVGImage())
        return { };

    Length intrinsicWidth = this->intrinsicWidth();
    Length intrinsicHeight = this->intrinsicHeight();
    if (!intrinsicWidth.isFixed() || !intrinsicHeight.isFixed())
        return { };

    // 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 { 0, 0, floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0) };
}

FloatSize SVGSVGElement::currentViewportSize() const
{
    FloatSize viewportSize;

    if (renderer()) {
        if (is<RenderSVGRoot>(*renderer())) {
            auto& root = downcast<RenderSVGRoot>(*renderer());
            viewportSize = root.contentBoxRect().size() / root.style().effectiveZoom();
        } else
            viewportSize = downcast<RenderSVGViewportContainer>(*renderer()).viewport().size();
    }

    if (!viewportSize.isEmpty())
        return viewportSize;

    if (!(hasIntrinsicWidth() && hasIntrinsicHeight()))
        return { };

    return FloatSize(floatValueForLength(intrinsicWidth(), 0), floatValueForLength(intrinsicHeight(), 0));
}

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 transform = SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), m_viewSpec->preserveAspectRatio(), viewWidth, viewHeight);
    m_viewSpec->transformBaseValue().concatenate(transform);
    return transform;
}

SVGViewElement* SVGSVGElement::findViewAnchor(const String& fragmentIdentifier) const
{
    auto* anchorElement = document().findAnchor(fragmentIdentifier);
    return is<SVGViewElement>(anchorElement) ? downcast<SVGViewElement>(anchorElement): nullptr;
}

SVGSVGElement* SVGSVGElement::findRootAnchor(const SVGViewElement* viewElement) const
{
    auto* viewportElement = SVGLocatable::nearestViewportElement(viewElement);
    return is<SVGSVGElement>(viewportElement) ? downcast<SVGSVGElement>(viewportElement) : nullptr;
}

SVGSVGElement* SVGSVGElement::findRootAnchor(const String& fragmentIdentifier) const
{
    if (auto* viewElement = findViewAnchor(fragmentIdentifier))
        return findRootAnchor(viewElement);
    return nullptr;
}

bool SVGSVGElement::scrollToFragment(const String& fragmentIdentifier)
{
    auto renderer = this->renderer();
    auto view = m_viewSpec;
    if (view)
        view->reset();

    bool hadUseCurrentView = m_useCurrentView;
    m_useCurrentView = false;

    if (fragmentIdentifier.startsWith("xpointer(")) {
        // FIXME: XPointer references are ignored (https://bugs.webkit.org/show_bug.cgi?id=17491)
        if (renderer && hadUseCurrentView)
            RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
        return false;
    }

    if (fragmentIdentifier.startsWith("svgView(")) {
        if (!view)
            view = &currentView(); // Create the SVGViewSpec.
        if (view->parseViewSpec(fragmentIdentifier))
            m_useCurrentView = true;
        else
            view->reset();
        if (renderer && (hadUseCurrentView || m_useCurrentView))
            RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
        return m_useCurrentView;
    }

    // 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.
    if (auto* viewElement = findViewAnchor(fragmentIdentifier)) {
        if (auto* rootElement = findRootAnchor(viewElement)) {
            rootElement->inheritViewAttributes(*viewElement);
            if (auto* renderer = rootElement->renderer())
                RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
            m_currentViewFragmentIdentifier = fragmentIdentifier;
            return true;
        }
    }

    // FIXME: We need to decide which <svg> to focus on, and zoom to it.
    // FIXME: We need to actually "highlight" the viewTarget(s).
    return false;
}

void SVGSVGElement::resetScrollAnchor()
{
    if (!m_useCurrentView && m_currentViewFragmentIdentifier.isEmpty())
        return;

    if (m_viewSpec)
        m_viewSpec->reset();

    if (!m_currentViewFragmentIdentifier.isEmpty()) {
        if (auto* rootElement = findRootAnchor(m_currentViewFragmentIdentifier)) {
            SVGViewSpec& view = rootElement->currentView();
            view.setViewBoxBaseValue(viewBox());
            view.setPreserveAspectRatioBaseValue(preserveAspectRatioBaseValue());
            view.setZoomAndPanBaseValue(zoomAndPan());
            m_currentViewFragmentIdentifier = { };
        }
    }

    m_useCurrentView = false;
    if (renderer())
        RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer());
}

void SVGSVGElement::inheritViewAttributes(const 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::prepareForDocumentSuspension()
{
    pauseAnimations();
}

void SVGSVGElement::resumeFromDocumentSuspension()
{
    unpauseAnimations();
}

// getElementById on SVGSVGElement is restricted to only the child subtree defined by the <svg> element.
// See http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGSVGElement
Element* SVGSVGElement::getElementById(const AtomicString& id)
{
    if (id.isNull())
        return nullptr;

    auto element = makeRefPtr(treeScope().getElementById(id));
    if (element && element->isDescendantOf(*this))
        return element.get();
    if (treeScope().containsMultipleElementsWithId(id)) {
        for (auto* element : *treeScope().getAllElementsById(id)) {
            if (element->isDescendantOf(*this))
                return element;
        }
    }
    return nullptr;
}

bool SVGSVGElement::isValid() const
{
    return SVGTests::isValid();
}

}
