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-2019 Apple Inc. All rights reserved.
5 * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved.
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#include "SVGSVGElement.h"
25
26#include "CSSHelper.h"
27#include "DOMWrapperWorld.h"
28#include "ElementIterator.h"
29#include "EventNames.h"
30#include "Frame.h"
31#include "FrameSelection.h"
32#include "RenderSVGResource.h"
33#include "RenderSVGRoot.h"
34#include "RenderSVGViewportContainer.h"
35#include "RenderView.h"
36#include "SMILTimeContainer.h"
37#include "SVGAngle.h"
38#include "SVGDocumentExtensions.h"
39#include "SVGLength.h"
40#include "SVGMatrix.h"
41#include "SVGNumber.h"
42#include "SVGPoint.h"
43#include "SVGRect.h"
44#include "SVGTransform.h"
45#include "SVGViewElement.h"
46#include "SVGViewSpec.h"
47#include "StaticNodeList.h"
48#include <wtf/IsoMallocInlines.h>
49
50namespace WebCore {
51
52WTF_MAKE_ISO_ALLOCATED_IMPL(SVGSVGElement);
53
54inline SVGSVGElement::SVGSVGElement(const QualifiedName& tagName, Document& document)
55 : SVGGraphicsElement(tagName, document)
56 , SVGExternalResourcesRequired(this)
57 , SVGFitToViewBox(this)
58 , m_timeContainer(SMILTimeContainer::create(*this))
59{
60 ASSERT(hasTagName(SVGNames::svgTag));
61 document.registerForDocumentSuspensionCallbacks(*this);
62
63 static std::once_flag onceFlag;
64 std::call_once(onceFlag, [] {
65 PropertyRegistry::registerProperty<SVGNames::xAttr, &SVGSVGElement::m_x>();
66 PropertyRegistry::registerProperty<SVGNames::yAttr, &SVGSVGElement::m_y>();
67 PropertyRegistry::registerProperty<SVGNames::widthAttr, &SVGSVGElement::m_width>();
68 PropertyRegistry::registerProperty<SVGNames::heightAttr, &SVGSVGElement::m_height>();
69 });
70}
71
72Ref<SVGSVGElement> SVGSVGElement::create(const QualifiedName& tagName, Document& document)
73{
74 return adoptRef(*new SVGSVGElement(tagName, document));
75}
76
77Ref<SVGSVGElement> SVGSVGElement::create(Document& document)
78{
79 return create(SVGNames::svgTag, document);
80}
81
82SVGSVGElement::~SVGSVGElement()
83{
84 if (m_viewSpec)
85 m_viewSpec->resetContextElement();
86 document().unregisterForDocumentSuspensionCallbacks(*this);
87 document().accessSVGExtensions().removeTimeContainer(*this);
88}
89
90void SVGSVGElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
91{
92 oldDocument.unregisterForDocumentSuspensionCallbacks(*this);
93 document().registerForDocumentSuspensionCallbacks(*this);
94 SVGGraphicsElement::didMoveToNewDocument(oldDocument, newDocument);
95}
96
97const AtomicString& SVGSVGElement::contentScriptType() const
98{
99 static NeverDestroyed<AtomicString> defaultScriptType { "text/ecmascript" };
100 const AtomicString& type = attributeWithoutSynchronization(SVGNames::contentScriptTypeAttr);
101 return type.isNull() ? defaultScriptType.get() : type;
102}
103
104void SVGSVGElement::setContentScriptType(const AtomicString& type)
105{
106 setAttributeWithoutSynchronization(SVGNames::contentScriptTypeAttr, type);
107}
108
109const AtomicString& SVGSVGElement::contentStyleType() const
110{
111 static NeverDestroyed<AtomicString> defaultStyleType { "text/css" };
112 const AtomicString& type = attributeWithoutSynchronization(SVGNames::contentStyleTypeAttr);
113 return type.isNull() ? defaultStyleType.get() : type;
114}
115
116void SVGSVGElement::setContentStyleType(const AtomicString& type)
117{
118 setAttributeWithoutSynchronization(SVGNames::contentStyleTypeAttr, type);
119}
120
121Ref<SVGRect> SVGSVGElement::viewport() const
122{
123 // FIXME: Not implemented.
124 return SVGRect::create();
125}
126
127float SVGSVGElement::pixelUnitToMillimeterX() const
128{
129 // There are 25.4 millimeters in an inch.
130 return 25.4f / cssPixelsPerInch;
131}
132
133float SVGSVGElement::pixelUnitToMillimeterY() const
134{
135 // There are 25.4 millimeters in an inch.
136 return 25.4f / cssPixelsPerInch;
137}
138
139float SVGSVGElement::screenPixelToMillimeterX() const
140{
141 return pixelUnitToMillimeterX();
142}
143
144float SVGSVGElement::screenPixelToMillimeterY() const
145{
146 return pixelUnitToMillimeterY();
147}
148
149SVGViewSpec& SVGSVGElement::currentView()
150{
151 if (!m_viewSpec)
152 m_viewSpec = SVGViewSpec::create(*this);
153 return *m_viewSpec;
154}
155
156RefPtr<Frame> SVGSVGElement::frameForCurrentScale() const
157{
158 // The behavior of currentScale() is undefined when we're dealing with non-standalone SVG documents.
159 // If the document is embedded, the scaling is handled by the host renderer.
160 if (!isConnected() || !isOutermostSVGSVGElement())
161 return nullptr;
162 auto frame = makeRefPtr(document().frame());
163 return frame && frame->isMainFrame() ? frame : nullptr;
164}
165
166float SVGSVGElement::currentScale() const
167{
168 // When asking from inside an embedded SVG document, a scale value of 1 seems reasonable, as it doesn't
169 // know anything about the parent scale.
170 auto frame = frameForCurrentScale();
171 return frame ? frame->pageZoomFactor() : 1;
172}
173
174void SVGSVGElement::setCurrentScale(float scale)
175{
176 if (auto frame = frameForCurrentScale())
177 frame->setPageZoomFactor(scale);
178}
179
180void SVGSVGElement::setCurrentTranslate(const FloatPoint& translation)
181{
182 if (m_currentTranslate->value() == translation)
183 return;
184 m_currentTranslate->setValue(translation);
185 updateCurrentTranslate();
186}
187
188void SVGSVGElement::updateCurrentTranslate()
189{
190 if (RenderObject* object = renderer())
191 object->setNeedsLayout();
192 if (parentNode() == &document() && document().renderView())
193 document().renderView()->repaint();
194}
195
196void SVGSVGElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
197{
198 if (!nearestViewportElement()) {
199 // For these events, the outermost <svg> element works like a <body> element does,
200 // setting certain event handlers directly on the window object.
201 if (name == HTMLNames::onunloadAttr) {
202 document().setWindowAttributeEventListener(eventNames().unloadEvent, name, value, mainThreadNormalWorld());
203 return;
204 }
205 if (name == HTMLNames::onresizeAttr) {
206 document().setWindowAttributeEventListener(eventNames().resizeEvent, name, value, mainThreadNormalWorld());
207 return;
208 }
209 if (name == HTMLNames::onscrollAttr) {
210 document().setWindowAttributeEventListener(eventNames().scrollEvent, name, value, mainThreadNormalWorld());
211 return;
212 }
213 if (name == SVGNames::onzoomAttr) {
214 document().setWindowAttributeEventListener(eventNames().zoomEvent, name, value, mainThreadNormalWorld());
215 return;
216 }
217 }
218
219 // For these events, any <svg> element works like a <body> element does,
220 // setting certain event handlers directly on the window object.
221 // FIXME: Why different from the events above that work only on the outermost <svg> element?
222 if (name == HTMLNames::onabortAttr) {
223 document().setWindowAttributeEventListener(eventNames().abortEvent, name, value, mainThreadNormalWorld());
224 return;
225 }
226 if (name == HTMLNames::onerrorAttr) {
227 document().setWindowAttributeEventListener(eventNames().errorEvent, name, value, mainThreadNormalWorld());
228 return;
229 }
230
231 SVGParsingError parseError = NoError;
232
233 if (name == SVGNames::xAttr)
234 m_x->setBaseValInternal(SVGLengthValue::construct(LengthModeWidth, value, parseError));
235 else if (name == SVGNames::yAttr)
236 m_y->setBaseValInternal(SVGLengthValue::construct(LengthModeHeight, value, parseError));
237 else if (name == SVGNames::widthAttr) {
238 auto length = SVGLengthValue::construct(LengthModeWidth, value, parseError, ForbidNegativeLengths);
239 if (parseError != NoError || value.isEmpty()) {
240 // FIXME: This is definitely the correct behavior for a missing/removed attribute.
241 // Not sure it's correct for the empty string or for something that can't be parsed.
242 length = SVGLengthValue(LengthModeWidth, "100%"_s);
243 }
244 m_width->setBaseValInternal(length);
245 } else if (name == SVGNames::heightAttr) {
246 auto length = SVGLengthValue::construct(LengthModeHeight, value, parseError, ForbidNegativeLengths);
247 if (parseError != NoError || value.isEmpty()) {
248 // FIXME: This is definitely the correct behavior for a removed attribute.
249 // Not sure it's correct for the empty string or for something that can't be parsed.
250 length = SVGLengthValue(LengthModeHeight, "100%"_s);
251 }
252 m_height->setBaseValInternal(length);
253 }
254
255 reportAttributeParsingError(parseError, name, value);
256
257 SVGGraphicsElement::parseAttribute(name, value);
258 SVGExternalResourcesRequired::parseAttribute(name, value);
259 SVGFitToViewBox::parseAttribute(name, value);
260 SVGZoomAndPan::parseAttribute(name, value);
261}
262
263void SVGSVGElement::svgAttributeChanged(const QualifiedName& attrName)
264{
265 if (PropertyRegistry::isKnownAttribute(attrName)) {
266 InstanceInvalidationGuard guard(*this);
267 invalidateSVGPresentationAttributeStyle();
268
269 if (auto renderer = this->renderer())
270 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
271 return;
272 }
273
274 if (SVGFitToViewBox::isKnownAttribute(attrName)) {
275 if (auto* renderer = this->renderer()) {
276 renderer->setNeedsTransformUpdate();
277 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
278 }
279 return;
280 }
281
282 SVGGraphicsElement::svgAttributeChanged(attrName);
283 SVGExternalResourcesRequired::svgAttributeChanged(attrName);
284}
285
286unsigned SVGSVGElement::suspendRedraw(unsigned)
287{
288 return 0;
289}
290
291void SVGSVGElement::unsuspendRedraw(unsigned)
292{
293}
294
295void SVGSVGElement::unsuspendRedrawAll()
296{
297}
298
299void SVGSVGElement::forceRedraw()
300{
301}
302
303Ref<NodeList> SVGSVGElement::collectIntersectionOrEnclosureList(SVGRect& rect, SVGElement* referenceElement, bool (*checkFunction)(SVGElement&, SVGRect&))
304{
305 Vector<Ref<Element>> elements;
306 for (auto& element : descendantsOfType<SVGElement>(referenceElement ? *referenceElement : *this)) {
307 if (checkFunction(element, rect))
308 elements.append(element);
309 }
310 return StaticElementList::create(WTFMove(elements));
311}
312
313static bool checkIntersectionWithoutUpdatingLayout(SVGElement& element, SVGRect& rect)
314{
315 return RenderSVGModelObject::checkIntersection(element.renderer(), rect.value());
316}
317
318static bool checkEnclosureWithoutUpdatingLayout(SVGElement& element, SVGRect& rect)
319{
320 return RenderSVGModelObject::checkEnclosure(element.renderer(), rect.value());
321}
322
323Ref<NodeList> SVGSVGElement::getIntersectionList(SVGRect& rect, SVGElement* referenceElement)
324{
325 document().updateLayoutIgnorePendingStylesheets();
326 return collectIntersectionOrEnclosureList(rect, referenceElement, checkIntersectionWithoutUpdatingLayout);
327}
328
329Ref<NodeList> SVGSVGElement::getEnclosureList(SVGRect& rect, SVGElement* referenceElement)
330{
331 document().updateLayoutIgnorePendingStylesheets();
332 return collectIntersectionOrEnclosureList(rect, referenceElement, checkEnclosureWithoutUpdatingLayout);
333}
334
335bool SVGSVGElement::checkIntersection(RefPtr<SVGElement>&& element, SVGRect& rect)
336{
337 if (!element)
338 return false;
339 element->document().updateLayoutIgnorePendingStylesheets();
340 return checkIntersectionWithoutUpdatingLayout(*element, rect);
341}
342
343bool SVGSVGElement::checkEnclosure(RefPtr<SVGElement>&& element, SVGRect& rect)
344{
345 if (!element)
346 return false;
347 element->document().updateLayoutIgnorePendingStylesheets();
348 return checkEnclosureWithoutUpdatingLayout(*element, rect);
349}
350
351void SVGSVGElement::deselectAll()
352{
353 if (auto frame = makeRefPtr(document().frame()))
354 frame->selection().clear();
355}
356
357Ref<SVGNumber> SVGSVGElement::createSVGNumber()
358{
359 return SVGNumber::create();
360}
361
362Ref<SVGLength> SVGSVGElement::createSVGLength()
363{
364 return SVGLength::create();
365}
366
367Ref<SVGAngle> SVGSVGElement::createSVGAngle()
368{
369 return SVGAngle::create();
370}
371
372Ref<SVGPoint> SVGSVGElement::createSVGPoint()
373{
374 return SVGPoint::create();
375}
376
377Ref<SVGMatrix> SVGSVGElement::createSVGMatrix()
378{
379 return SVGMatrix::create();
380}
381
382Ref<SVGRect> SVGSVGElement::createSVGRect()
383{
384 return SVGRect::create();
385}
386
387Ref<SVGTransform> SVGSVGElement::createSVGTransform()
388{
389 return SVGTransform::create(SVGTransformValue::SVG_TRANSFORM_MATRIX);
390}
391
392Ref<SVGTransform> SVGSVGElement::createSVGTransformFromMatrix(SVGMatrix& matrix)
393{
394 return SVGTransform::create(matrix.value());
395}
396
397AffineTransform SVGSVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope mode) const
398{
399 AffineTransform viewBoxTransform;
400 if (!hasEmptyViewBox()) {
401 FloatSize size = currentViewportSize();
402 viewBoxTransform = viewBoxToViewTransform(size.width(), size.height());
403 }
404
405 AffineTransform transform;
406 if (!isOutermostSVGSVGElement()) {
407 SVGLengthContext lengthContext(this);
408 transform.translate(x().value(lengthContext), y().value(lengthContext));
409 } else if (mode == SVGLocatable::ScreenScope) {
410 if (auto* renderer = this->renderer()) {
411 FloatPoint location;
412 float zoomFactor = 1;
413
414 // At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform
415 // to map an element from SVG viewport coordinates to CSS box coordinates.
416 // RenderSVGRoot's localToAbsolute method expects CSS box coordinates.
417 // We also need to adjust for the zoom level factored into CSS coordinates (bug #96361).
418 if (is<RenderSVGRoot>(*renderer)) {
419 location = downcast<RenderSVGRoot>(*renderer).localToBorderBoxTransform().mapPoint(location);
420 zoomFactor = 1 / renderer->style().effectiveZoom();
421 }
422
423 // Translate in our CSS parent coordinate space
424 // FIXME: This doesn't work correctly with CSS transforms.
425 location = renderer->localToAbsolute(location, UseTransforms);
426 location.scale(zoomFactor);
427
428 // Be careful here! localToBorderBoxTransform() included the x/y offset coming from the viewBoxToViewTransform(),
429 // so we have to subtract it here (original cause of bug #27183)
430 transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f());
431
432 // Respect scroll offset.
433 if (auto view = makeRefPtr(document().view())) {
434 LayoutPoint scrollPosition = view->scrollPosition();
435 scrollPosition.scale(zoomFactor);
436 transform.translate(-scrollPosition);
437 }
438 }
439 }
440
441 return transform.multiply(viewBoxTransform);
442}
443
444bool SVGSVGElement::rendererIsNeeded(const RenderStyle& style)
445{
446 if (!isValid())
447 return false;
448 // FIXME: We should respect display: none on the documentElement svg element
449 // but many things in FrameView and SVGImage depend on the RenderSVGRoot when
450 // they should instead depend on the RenderView.
451 // https://bugs.webkit.org/show_bug.cgi?id=103493
452 if (document().documentElement() == this)
453 return true;
454 return StyledElement::rendererIsNeeded(style);
455}
456
457RenderPtr<RenderElement> SVGSVGElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
458{
459 if (isOutermostSVGSVGElement())
460 return createRenderer<RenderSVGRoot>(*this, WTFMove(style));
461 return createRenderer<RenderSVGViewportContainer>(*this, WTFMove(style));
462}
463
464Node::InsertedIntoAncestorResult SVGSVGElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
465{
466 if (insertionType.connectedToDocument) {
467 document().accessSVGExtensions().addTimeContainer(*this);
468 if (!document().accessSVGExtensions().areAnimationsPaused())
469 unpauseAnimations();
470
471 // Animations are started at the end of document parsing and after firing the load event,
472 // but if we miss that train (deferred programmatic element insertion for example) we need
473 // to initialize the time container here.
474 if (!document().parsing() && !document().processingLoadEvent() && document().loadEventFinished() && !m_timeContainer->isStarted())
475 m_timeContainer->begin();
476 }
477 return SVGGraphicsElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
478}
479
480void SVGSVGElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
481{
482 if (removalType.disconnectedFromDocument) {
483 document().accessSVGExtensions().removeTimeContainer(*this);
484 pauseAnimations();
485 }
486 SVGGraphicsElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
487}
488
489void SVGSVGElement::pauseAnimations()
490{
491 if (!m_timeContainer->isPaused())
492 m_timeContainer->pause();
493}
494
495void SVGSVGElement::unpauseAnimations()
496{
497 if (m_timeContainer->isPaused())
498 m_timeContainer->resume();
499}
500
501bool SVGSVGElement::animationsPaused() const
502{
503 return m_timeContainer->isPaused();
504}
505
506bool SVGSVGElement::hasActiveAnimation() const
507{
508 return m_timeContainer->isActive();
509}
510
511float SVGSVGElement::getCurrentTime() const
512{
513 return narrowPrecisionToFloat(m_timeContainer->elapsed().value());
514}
515
516void SVGSVGElement::setCurrentTime(float seconds)
517{
518 if (!std::isfinite(seconds))
519 return;
520 m_timeContainer->setElapsed(std::max(seconds, 0.0f));
521}
522
523bool SVGSVGElement::selfHasRelativeLengths() const
524{
525 return x().isRelative()
526 || y().isRelative()
527 || width().isRelative()
528 || height().isRelative()
529 || hasAttribute(SVGNames::viewBoxAttr);
530}
531
532FloatRect SVGSVGElement::currentViewBoxRect() const
533{
534 if (m_useCurrentView)
535 return m_viewSpec ? m_viewSpec->viewBox() : FloatRect();
536
537 FloatRect viewBox = this->viewBox();
538 if (!viewBox.isEmpty())
539 return viewBox;
540
541 if (!is<RenderSVGRoot>(renderer()))
542 return { };
543 if (!downcast<RenderSVGRoot>(*renderer()).isEmbeddedThroughSVGImage())
544 return { };
545
546 Length intrinsicWidth = this->intrinsicWidth();
547 Length intrinsicHeight = this->intrinsicHeight();
548 if (!intrinsicWidth.isFixed() || !intrinsicHeight.isFixed())
549 return { };
550
551 // If no viewBox is specified but non-relative width/height values, then we
552 // should always synthesize a viewBox if we're embedded through a SVGImage.
553 return { 0, 0, floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0) };
554}
555
556FloatSize SVGSVGElement::currentViewportSize() const
557{
558 FloatSize viewportSize;
559
560 if (renderer()) {
561 if (is<RenderSVGRoot>(*renderer())) {
562 auto& root = downcast<RenderSVGRoot>(*renderer());
563 viewportSize = root.contentBoxRect().size() / root.style().effectiveZoom();
564 } else
565 viewportSize = downcast<RenderSVGViewportContainer>(*renderer()).viewport().size();
566 }
567
568 if (!viewportSize.isEmpty())
569 return viewportSize;
570
571 if (!(hasIntrinsicWidth() && hasIntrinsicHeight()))
572 return { };
573
574 return FloatSize(floatValueForLength(intrinsicWidth(), 0), floatValueForLength(intrinsicHeight(), 0));
575}
576
577bool SVGSVGElement::hasIntrinsicWidth() const
578{
579 return width().unitType() != LengthTypePercentage;
580}
581
582bool SVGSVGElement::hasIntrinsicHeight() const
583{
584 return height().unitType() != LengthTypePercentage;
585}
586
587Length SVGSVGElement::intrinsicWidth() const
588{
589 if (width().unitType() == LengthTypePercentage)
590 return Length(0, Fixed);
591
592 SVGLengthContext lengthContext(this);
593 return Length(width().value(lengthContext), Fixed);
594}
595
596Length SVGSVGElement::intrinsicHeight() const
597{
598 if (height().unitType() == LengthTypePercentage)
599 return Length(0, Fixed);
600
601 SVGLengthContext lengthContext(this);
602 return Length(height().value(lengthContext), Fixed);
603}
604
605AffineTransform SVGSVGElement::viewBoxToViewTransform(float viewWidth, float viewHeight) const
606{
607 if (!m_useCurrentView || !m_viewSpec)
608 return SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), preserveAspectRatio(), viewWidth, viewHeight);
609
610 AffineTransform transform = SVGFitToViewBox::viewBoxToViewTransform(currentViewBoxRect(), m_viewSpec->preserveAspectRatio(), viewWidth, viewHeight);
611 transform *= m_viewSpec->transform()->concatenate();
612 return transform;
613}
614
615SVGViewElement* SVGSVGElement::findViewAnchor(const String& fragmentIdentifier) const
616{
617 auto* anchorElement = document().findAnchor(fragmentIdentifier);
618 return is<SVGViewElement>(anchorElement) ? downcast<SVGViewElement>(anchorElement): nullptr;
619}
620
621SVGSVGElement* SVGSVGElement::findRootAnchor(const SVGViewElement* viewElement) const
622{
623 auto* viewportElement = SVGLocatable::nearestViewportElement(viewElement);
624 return is<SVGSVGElement>(viewportElement) ? downcast<SVGSVGElement>(viewportElement) : nullptr;
625}
626
627SVGSVGElement* SVGSVGElement::findRootAnchor(const String& fragmentIdentifier) const
628{
629 if (auto* viewElement = findViewAnchor(fragmentIdentifier))
630 return findRootAnchor(viewElement);
631 return nullptr;
632}
633
634bool SVGSVGElement::scrollToFragment(const String& fragmentIdentifier)
635{
636 auto renderer = this->renderer();
637 auto view = m_viewSpec;
638 if (view)
639 view->reset();
640
641 bool hadUseCurrentView = m_useCurrentView;
642 m_useCurrentView = false;
643
644 if (fragmentIdentifier.startsWith("xpointer(")) {
645 // FIXME: XPointer references are ignored (https://bugs.webkit.org/show_bug.cgi?id=17491)
646 if (renderer && hadUseCurrentView)
647 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
648 return false;
649 }
650
651 if (fragmentIdentifier.startsWith("svgView(")) {
652 if (!view)
653 view = &currentView(); // Create the SVGViewSpec.
654 if (view->parseViewSpec(fragmentIdentifier))
655 m_useCurrentView = true;
656 else
657 view->reset();
658 if (renderer && (hadUseCurrentView || m_useCurrentView))
659 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
660 return m_useCurrentView;
661 }
662
663 // Spec: If the SVG fragment identifier addresses a "view" element within an SVG document (e.g., MyDrawing.svg#MyView
664 // or MyDrawing.svg#xpointer(id('MyView'))) then the closest ancestor "svg" element is displayed in the viewport.
665 // Any view specification attributes included on the given "view" element override the corresponding view specification
666 // attributes on the closest ancestor "svg" element.
667 if (auto* viewElement = findViewAnchor(fragmentIdentifier)) {
668 if (auto* rootElement = findRootAnchor(viewElement)) {
669 rootElement->inheritViewAttributes(*viewElement);
670 if (auto* renderer = rootElement->renderer())
671 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
672 m_currentViewFragmentIdentifier = fragmentIdentifier;
673 return true;
674 }
675 }
676
677 // FIXME: We need to decide which <svg> to focus on, and zoom to it.
678 // FIXME: We need to actually "highlight" the viewTarget(s).
679 return false;
680}
681
682void SVGSVGElement::resetScrollAnchor()
683{
684 if (!m_useCurrentView && m_currentViewFragmentIdentifier.isEmpty())
685 return;
686
687 if (m_viewSpec)
688 m_viewSpec->reset();
689
690 if (!m_currentViewFragmentIdentifier.isEmpty()) {
691 if (auto* rootElement = findRootAnchor(m_currentViewFragmentIdentifier)) {
692 SVGViewSpec& view = rootElement->currentView();
693 view.setViewBox(viewBox());
694 view.setPreserveAspectRatio(preserveAspectRatio());
695 view.setZoomAndPan(zoomAndPan());
696 m_currentViewFragmentIdentifier = { };
697 }
698 }
699
700 m_useCurrentView = false;
701 if (renderer())
702 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer());
703}
704
705void SVGSVGElement::inheritViewAttributes(const SVGViewElement& viewElement)
706{
707 SVGViewSpec& view = currentView();
708 m_useCurrentView = true;
709
710 if (viewElement.hasAttribute(SVGNames::viewBoxAttr))
711 view.setViewBox(viewElement.viewBox());
712 else
713 view.setViewBox(viewBox());
714
715 if (viewElement.hasAttribute(SVGNames::preserveAspectRatioAttr))
716 view.setPreserveAspectRatio(viewElement.preserveAspectRatio());
717 else
718 view.setPreserveAspectRatio(preserveAspectRatio());
719
720 if (viewElement.hasAttribute(SVGNames::zoomAndPanAttr))
721 view.setZoomAndPan(viewElement.zoomAndPan());
722 else
723 view.setZoomAndPan(zoomAndPan());
724}
725
726void SVGSVGElement::prepareForDocumentSuspension()
727{
728 pauseAnimations();
729}
730
731void SVGSVGElement::resumeFromDocumentSuspension()
732{
733 unpauseAnimations();
734}
735
736// getElementById on SVGSVGElement is restricted to only the child subtree defined by the <svg> element.
737// See http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGSVGElement
738Element* SVGSVGElement::getElementById(const AtomicString& id)
739{
740 if (id.isNull())
741 return nullptr;
742
743 auto element = makeRefPtr(treeScope().getElementById(id));
744 if (element && element->isDescendantOf(*this))
745 return element.get();
746 if (treeScope().containsMultipleElementsWithId(id)) {
747 for (auto* element : *treeScope().getAllElementsById(id)) {
748 if (element->isDescendantOf(*this))
749 return element;
750 }
751 }
752 return nullptr;
753}
754
755bool SVGSVGElement::isValid() const
756{
757 return SVGTests::isValid();
758}
759
760}
761