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 | |
50 | namespace WebCore { |
51 | |
52 | WTF_MAKE_ISO_ALLOCATED_IMPL(SVGSVGElement); |
53 | |
54 | inline 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 | |
72 | Ref<SVGSVGElement> SVGSVGElement::create(const QualifiedName& tagName, Document& document) |
73 | { |
74 | return adoptRef(*new SVGSVGElement(tagName, document)); |
75 | } |
76 | |
77 | Ref<SVGSVGElement> SVGSVGElement::create(Document& document) |
78 | { |
79 | return create(SVGNames::svgTag, document); |
80 | } |
81 | |
82 | SVGSVGElement::~SVGSVGElement() |
83 | { |
84 | if (m_viewSpec) |
85 | m_viewSpec->resetContextElement(); |
86 | document().unregisterForDocumentSuspensionCallbacks(*this); |
87 | document().accessSVGExtensions().removeTimeContainer(*this); |
88 | } |
89 | |
90 | void SVGSVGElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
91 | { |
92 | oldDocument.unregisterForDocumentSuspensionCallbacks(*this); |
93 | document().registerForDocumentSuspensionCallbacks(*this); |
94 | SVGGraphicsElement::didMoveToNewDocument(oldDocument, newDocument); |
95 | } |
96 | |
97 | const 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 | |
104 | void SVGSVGElement::setContentScriptType(const AtomicString& type) |
105 | { |
106 | setAttributeWithoutSynchronization(SVGNames::contentScriptTypeAttr, type); |
107 | } |
108 | |
109 | const 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 | |
116 | void SVGSVGElement::setContentStyleType(const AtomicString& type) |
117 | { |
118 | setAttributeWithoutSynchronization(SVGNames::contentStyleTypeAttr, type); |
119 | } |
120 | |
121 | Ref<SVGRect> SVGSVGElement::viewport() const |
122 | { |
123 | // FIXME: Not implemented. |
124 | return SVGRect::create(); |
125 | } |
126 | |
127 | float SVGSVGElement::pixelUnitToMillimeterX() const |
128 | { |
129 | // There are 25.4 millimeters in an inch. |
130 | return 25.4f / cssPixelsPerInch; |
131 | } |
132 | |
133 | float SVGSVGElement::pixelUnitToMillimeterY() const |
134 | { |
135 | // There are 25.4 millimeters in an inch. |
136 | return 25.4f / cssPixelsPerInch; |
137 | } |
138 | |
139 | float SVGSVGElement::screenPixelToMillimeterX() const |
140 | { |
141 | return pixelUnitToMillimeterX(); |
142 | } |
143 | |
144 | float SVGSVGElement::screenPixelToMillimeterY() const |
145 | { |
146 | return pixelUnitToMillimeterY(); |
147 | } |
148 | |
149 | SVGViewSpec& SVGSVGElement::currentView() |
150 | { |
151 | if (!m_viewSpec) |
152 | m_viewSpec = SVGViewSpec::create(*this); |
153 | return *m_viewSpec; |
154 | } |
155 | |
156 | RefPtr<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 | |
166 | float 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 | |
174 | void SVGSVGElement::setCurrentScale(float scale) |
175 | { |
176 | if (auto frame = frameForCurrentScale()) |
177 | frame->setPageZoomFactor(scale); |
178 | } |
179 | |
180 | void SVGSVGElement::setCurrentTranslate(const FloatPoint& translation) |
181 | { |
182 | if (m_currentTranslate->value() == translation) |
183 | return; |
184 | m_currentTranslate->setValue(translation); |
185 | updateCurrentTranslate(); |
186 | } |
187 | |
188 | void SVGSVGElement::updateCurrentTranslate() |
189 | { |
190 | if (RenderObject* object = renderer()) |
191 | object->setNeedsLayout(); |
192 | if (parentNode() == &document() && document().renderView()) |
193 | document().renderView()->repaint(); |
194 | } |
195 | |
196 | void 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 | |
263 | void 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 | |
286 | unsigned SVGSVGElement::suspendRedraw(unsigned) |
287 | { |
288 | return 0; |
289 | } |
290 | |
291 | void SVGSVGElement::unsuspendRedraw(unsigned) |
292 | { |
293 | } |
294 | |
295 | void SVGSVGElement::unsuspendRedrawAll() |
296 | { |
297 | } |
298 | |
299 | void SVGSVGElement::forceRedraw() |
300 | { |
301 | } |
302 | |
303 | Ref<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 | |
313 | static bool checkIntersectionWithoutUpdatingLayout(SVGElement& element, SVGRect& rect) |
314 | { |
315 | return RenderSVGModelObject::checkIntersection(element.renderer(), rect.value()); |
316 | } |
317 | |
318 | static bool checkEnclosureWithoutUpdatingLayout(SVGElement& element, SVGRect& rect) |
319 | { |
320 | return RenderSVGModelObject::checkEnclosure(element.renderer(), rect.value()); |
321 | } |
322 | |
323 | Ref<NodeList> SVGSVGElement::getIntersectionList(SVGRect& rect, SVGElement* referenceElement) |
324 | { |
325 | document().updateLayoutIgnorePendingStylesheets(); |
326 | return collectIntersectionOrEnclosureList(rect, referenceElement, checkIntersectionWithoutUpdatingLayout); |
327 | } |
328 | |
329 | Ref<NodeList> SVGSVGElement::getEnclosureList(SVGRect& rect, SVGElement* referenceElement) |
330 | { |
331 | document().updateLayoutIgnorePendingStylesheets(); |
332 | return collectIntersectionOrEnclosureList(rect, referenceElement, checkEnclosureWithoutUpdatingLayout); |
333 | } |
334 | |
335 | bool 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 | |
343 | bool 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 | |
351 | void SVGSVGElement::deselectAll() |
352 | { |
353 | if (auto frame = makeRefPtr(document().frame())) |
354 | frame->selection().clear(); |
355 | } |
356 | |
357 | Ref<SVGNumber> SVGSVGElement::createSVGNumber() |
358 | { |
359 | return SVGNumber::create(); |
360 | } |
361 | |
362 | Ref<SVGLength> SVGSVGElement::createSVGLength() |
363 | { |
364 | return SVGLength::create(); |
365 | } |
366 | |
367 | Ref<SVGAngle> SVGSVGElement::createSVGAngle() |
368 | { |
369 | return SVGAngle::create(); |
370 | } |
371 | |
372 | Ref<SVGPoint> SVGSVGElement::createSVGPoint() |
373 | { |
374 | return SVGPoint::create(); |
375 | } |
376 | |
377 | Ref<SVGMatrix> SVGSVGElement::createSVGMatrix() |
378 | { |
379 | return SVGMatrix::create(); |
380 | } |
381 | |
382 | Ref<SVGRect> SVGSVGElement::createSVGRect() |
383 | { |
384 | return SVGRect::create(); |
385 | } |
386 | |
387 | Ref<SVGTransform> SVGSVGElement::createSVGTransform() |
388 | { |
389 | return SVGTransform::create(SVGTransformValue::SVG_TRANSFORM_MATRIX); |
390 | } |
391 | |
392 | Ref<SVGTransform> SVGSVGElement::createSVGTransformFromMatrix(SVGMatrix& matrix) |
393 | { |
394 | return SVGTransform::create(matrix.value()); |
395 | } |
396 | |
397 | AffineTransform 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 | |
444 | bool 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 | |
457 | RenderPtr<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 | |
464 | Node::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 | |
480 | void 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 | |
489 | void SVGSVGElement::pauseAnimations() |
490 | { |
491 | if (!m_timeContainer->isPaused()) |
492 | m_timeContainer->pause(); |
493 | } |
494 | |
495 | void SVGSVGElement::unpauseAnimations() |
496 | { |
497 | if (m_timeContainer->isPaused()) |
498 | m_timeContainer->resume(); |
499 | } |
500 | |
501 | bool SVGSVGElement::animationsPaused() const |
502 | { |
503 | return m_timeContainer->isPaused(); |
504 | } |
505 | |
506 | bool SVGSVGElement::hasActiveAnimation() const |
507 | { |
508 | return m_timeContainer->isActive(); |
509 | } |
510 | |
511 | float SVGSVGElement::getCurrentTime() const |
512 | { |
513 | return narrowPrecisionToFloat(m_timeContainer->elapsed().value()); |
514 | } |
515 | |
516 | void SVGSVGElement::setCurrentTime(float seconds) |
517 | { |
518 | if (!std::isfinite(seconds)) |
519 | return; |
520 | m_timeContainer->setElapsed(std::max(seconds, 0.0f)); |
521 | } |
522 | |
523 | bool SVGSVGElement::selfHasRelativeLengths() const |
524 | { |
525 | return x().isRelative() |
526 | || y().isRelative() |
527 | || width().isRelative() |
528 | || height().isRelative() |
529 | || hasAttribute(SVGNames::viewBoxAttr); |
530 | } |
531 | |
532 | FloatRect 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 | |
556 | FloatSize 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 | |
577 | bool SVGSVGElement::hasIntrinsicWidth() const |
578 | { |
579 | return width().unitType() != LengthTypePercentage; |
580 | } |
581 | |
582 | bool SVGSVGElement::hasIntrinsicHeight() const |
583 | { |
584 | return height().unitType() != LengthTypePercentage; |
585 | } |
586 | |
587 | Length 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 | |
596 | Length 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 | |
605 | AffineTransform 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 | |
615 | SVGViewElement* SVGSVGElement::findViewAnchor(const String& fragmentIdentifier) const |
616 | { |
617 | auto* anchorElement = document().findAnchor(fragmentIdentifier); |
618 | return is<SVGViewElement>(anchorElement) ? downcast<SVGViewElement>(anchorElement): nullptr; |
619 | } |
620 | |
621 | SVGSVGElement* SVGSVGElement::findRootAnchor(const SVGViewElement* viewElement) const |
622 | { |
623 | auto* viewportElement = SVGLocatable::nearestViewportElement(viewElement); |
624 | return is<SVGSVGElement>(viewportElement) ? downcast<SVGSVGElement>(viewportElement) : nullptr; |
625 | } |
626 | |
627 | SVGSVGElement* SVGSVGElement::findRootAnchor(const String& fragmentIdentifier) const |
628 | { |
629 | if (auto* viewElement = findViewAnchor(fragmentIdentifier)) |
630 | return findRootAnchor(viewElement); |
631 | return nullptr; |
632 | } |
633 | |
634 | bool 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 = ¤tView(); // 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 | |
682 | void 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 | |
705 | void 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 | |
726 | void SVGSVGElement::prepareForDocumentSuspension() |
727 | { |
728 | pauseAnimations(); |
729 | } |
730 | |
731 | void 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 |
738 | Element* 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 | |
755 | bool SVGSVGElement::isValid() const |
756 | { |
757 | return SVGTests::isValid(); |
758 | } |
759 | |
760 | } |
761 | |