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 "third_party/blink/renderer/core/svg/svg_svg_element.h"
24
25 #include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
26 #include "third_party/blink/renderer/core/css/css_resolution_units.h"
27 #include "third_party/blink/renderer/core/css/style_change_reason.h"
28 #include "third_party/blink/renderer/core/dom/document.h"
29 #include "third_party/blink/renderer/core/dom/element_traversal.h"
30 #include "third_party/blink/renderer/core/dom/events/event_listener.h"
31 #include "third_party/blink/renderer/core/dom/static_node_list.h"
32 #include "third_party/blink/renderer/core/dom/xml_document.h"
33 #include "third_party/blink/renderer/core/editing/frame_selection.h"
34 #include "third_party/blink/renderer/core/frame/deprecation.h"
35 #include "third_party/blink/renderer/core/frame/local_frame.h"
36 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
37 #include "third_party/blink/renderer/core/html_names.h"
38 #include "third_party/blink/renderer/core/layout/layout_object.h"
39 #include "third_party/blink/renderer/core/layout/svg/layout_svg_model_object.h"
40 #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
41 #include "third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.h"
42 #include "third_party/blink/renderer/core/svg/animation/smil_time_container.h"
43 #include "third_party/blink/renderer/core/svg/svg_angle_tear_off.h"
44 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
45 #include "third_party/blink/renderer/core/svg/svg_length_tear_off.h"
46 #include "third_party/blink/renderer/core/svg/svg_matrix_tear_off.h"
47 #include "third_party/blink/renderer/core/svg/svg_number_tear_off.h"
48 #include "third_party/blink/renderer/core/svg/svg_point_tear_off.h"
49 #include "third_party/blink/renderer/core/svg/svg_preserve_aspect_ratio.h"
50 #include "third_party/blink/renderer/core/svg/svg_rect_tear_off.h"
51 #include "third_party/blink/renderer/core/svg/svg_transform.h"
52 #include "third_party/blink/renderer/core/svg/svg_transform_list.h"
53 #include "third_party/blink/renderer/core/svg/svg_transform_tear_off.h"
54 #include "third_party/blink/renderer/core/svg/svg_view_element.h"
55 #include "third_party/blink/renderer/core/svg/svg_view_spec.h"
56 #include "third_party/blink/renderer/core/svg_names.h"
57 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
58 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
59 #include "third_party/blink/renderer/platform/heap/heap.h"
60 #include "third_party/blink/renderer/platform/transforms/affine_transform.h"
61 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
62 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
63
64 namespace blink {
65
SVGSVGElement(Document & doc)66 SVGSVGElement::SVGSVGElement(Document& doc)
67 : SVGGraphicsElement(svg_names::kSVGTag, doc),
68 SVGFitToViewBox(this),
69 x_(MakeGarbageCollected<SVGAnimatedLength>(
70 this,
71 svg_names::kXAttr,
72 SVGLengthMode::kWidth,
73 SVGLength::Initial::kUnitlessZero,
74 CSSPropertyID::kX)),
75 y_(MakeGarbageCollected<SVGAnimatedLength>(
76 this,
77 svg_names::kYAttr,
78 SVGLengthMode::kHeight,
79 SVGLength::Initial::kUnitlessZero,
80 CSSPropertyID::kY)),
81 width_(MakeGarbageCollected<SVGAnimatedLength>(
82 this,
83 svg_names::kWidthAttr,
84 SVGLengthMode::kWidth,
85 SVGLength::Initial::kPercent100,
86 CSSPropertyID::kWidth)),
87 height_(MakeGarbageCollected<SVGAnimatedLength>(
88 this,
89 svg_names::kHeightAttr,
90 SVGLengthMode::kHeight,
91 SVGLength::Initial::kPercent100,
92 CSSPropertyID::kHeight)),
93 time_container_(MakeGarbageCollected<SMILTimeContainer>(*this)),
94 translation_(MakeGarbageCollected<SVGPoint>()),
95 current_scale_(1) {
96 AddToPropertyMap(x_);
97 AddToPropertyMap(y_);
98 AddToPropertyMap(width_);
99 AddToPropertyMap(height_);
100
101 UseCounter::Count(doc, WebFeature::kSVGSVGElement);
102 }
103
104 SVGSVGElement::~SVGSVGElement() = default;
105
currentScale() const106 float SVGSVGElement::currentScale() const {
107 if (!isConnected() || !IsOutermostSVGSVGElement())
108 return 1;
109
110 return current_scale_;
111 }
112
setCurrentScale(float scale)113 void SVGSVGElement::setCurrentScale(float scale) {
114 DCHECK(std::isfinite(scale));
115 if (!isConnected() || !IsOutermostSVGSVGElement())
116 return;
117
118 current_scale_ = scale;
119 UpdateUserTransform();
120 }
121
122 class SVGCurrentTranslateTearOff : public SVGPointTearOff {
123 public:
SVGCurrentTranslateTearOff(SVGSVGElement * context_element)124 SVGCurrentTranslateTearOff(SVGSVGElement* context_element)
125 : SVGPointTearOff(context_element->translation_, context_element) {}
126
CommitChange()127 void CommitChange() override {
128 DCHECK(ContextElement());
129 To<SVGSVGElement>(ContextElement())->UpdateUserTransform();
130 }
131 };
132
currentTranslateFromJavascript()133 SVGPointTearOff* SVGSVGElement::currentTranslateFromJavascript() {
134 return MakeGarbageCollected<SVGCurrentTranslateTearOff>(this);
135 }
136
SetCurrentTranslate(const FloatPoint & point)137 void SVGSVGElement::SetCurrentTranslate(const FloatPoint& point) {
138 translation_->SetValue(point);
139 UpdateUserTransform();
140 }
141
UpdateUserTransform()142 void SVGSVGElement::UpdateUserTransform() {
143 if (LayoutObject* object = GetLayoutObject()) {
144 object->SetNeedsLayoutAndFullPaintInvalidation(
145 layout_invalidation_reason::kUnknown);
146 }
147 }
148
ZoomAndPanEnabled() const149 bool SVGSVGElement::ZoomAndPanEnabled() const {
150 SVGZoomAndPanType zoom_and_pan = zoomAndPan();
151 if (view_spec_ && view_spec_->ZoomAndPan() != kSVGZoomAndPanUnknown)
152 zoom_and_pan = view_spec_->ZoomAndPan();
153 return zoom_and_pan == kSVGZoomAndPanMagnify;
154 }
155
ParseAttribute(const AttributeModificationParams & params)156 void SVGSVGElement::ParseAttribute(const AttributeModificationParams& params) {
157 const QualifiedName& name = params.name;
158 const AtomicString& value = params.new_value;
159 if (!nearestViewportElement()) {
160 bool set_listener = true;
161
162 // Only handle events if we're the outermost <svg> element
163 if (name == html_names::kOnunloadAttr) {
164 GetDocument().SetWindowAttributeEventListener(
165 event_type_names::kUnload,
166 CreateAttributeEventListener(GetDocument().GetFrame(), name, value));
167 } else if (name == html_names::kOnresizeAttr) {
168 GetDocument().SetWindowAttributeEventListener(
169 event_type_names::kResize,
170 CreateAttributeEventListener(GetDocument().GetFrame(), name, value));
171 } else if (name == html_names::kOnscrollAttr) {
172 GetDocument().SetWindowAttributeEventListener(
173 event_type_names::kScroll,
174 CreateAttributeEventListener(GetDocument().GetFrame(), name, value));
175 } else {
176 set_listener = false;
177 }
178
179 if (set_listener)
180 return;
181 }
182
183 if (name == html_names::kOnabortAttr) {
184 GetDocument().SetWindowAttributeEventListener(
185 event_type_names::kAbort,
186 CreateAttributeEventListener(GetDocument().GetFrame(), name, value));
187 } else if (name == html_names::kOnerrorAttr) {
188 GetDocument().SetWindowAttributeEventListener(
189 event_type_names::kError,
190 CreateAttributeEventListener(
191 GetDocument().GetFrame(), name, value,
192 JSEventHandler::HandlerType::kOnErrorEventHandler));
193 } else if (SVGZoomAndPan::ParseAttribute(name, value)) {
194 } else {
195 SVGElement::ParseAttribute(params);
196 }
197 }
198
IsPresentationAttribute(const QualifiedName & name) const199 bool SVGSVGElement::IsPresentationAttribute(const QualifiedName& name) const {
200 if ((name == svg_names::kWidthAttr || name == svg_names::kHeightAttr) &&
201 !IsOutermostSVGSVGElement())
202 return false;
203 return SVGGraphicsElement::IsPresentationAttribute(name);
204 }
205
CollectStyleForPresentationAttribute(const QualifiedName & name,const AtomicString & value,MutableCSSPropertyValueSet * style)206 void SVGSVGElement::CollectStyleForPresentationAttribute(
207 const QualifiedName& name,
208 const AtomicString& value,
209 MutableCSSPropertyValueSet* style) {
210 SVGAnimatedPropertyBase* property = PropertyFromAttribute(name);
211 if (property == x_) {
212 AddPropertyToPresentationAttributeStyle(style, property->CssPropertyId(),
213 x_->CssValue());
214 } else if (property == y_) {
215 AddPropertyToPresentationAttributeStyle(style, property->CssPropertyId(),
216 y_->CssValue());
217 } else if (IsOutermostSVGSVGElement() &&
218 (property == width_ || property == height_)) {
219 if (property == width_) {
220 AddPropertyToPresentationAttributeStyle(style, property->CssPropertyId(),
221 width_->CssValue());
222 } else if (property == height_) {
223 AddPropertyToPresentationAttributeStyle(style, property->CssPropertyId(),
224 height_->CssValue());
225 }
226 } else {
227 SVGGraphicsElement::CollectStyleForPresentationAttribute(name, value,
228 style);
229 }
230 }
231
SvgAttributeChanged(const QualifiedName & attr_name)232 void SVGSVGElement::SvgAttributeChanged(const QualifiedName& attr_name) {
233 bool update_relative_lengths_or_view_box = false;
234 bool width_or_height_changed =
235 attr_name == svg_names::kWidthAttr || attr_name == svg_names::kHeightAttr;
236 if (width_or_height_changed || attr_name == svg_names::kXAttr ||
237 attr_name == svg_names::kYAttr) {
238 update_relative_lengths_or_view_box = true;
239 UpdateRelativeLengthsInformation();
240 InvalidateRelativeLengthClients();
241
242 // At the SVG/HTML boundary (aka LayoutSVGRoot), the width and
243 // height attributes can affect the replaced size so we need
244 // to mark it for updating.
245 if (width_or_height_changed) {
246 LayoutObject* layout_object = GetLayoutObject();
247 // If the element is not attached, we cannot be sure if it is (going to
248 // be) an outermost root, so always mark presentation attributes dirty in
249 // that case.
250 if (!layout_object || layout_object->IsSVGRoot()) {
251 InvalidateSVGPresentationAttributeStyle();
252 SetNeedsStyleRecalc(kLocalStyleChange,
253 StyleChangeReasonForTracing::Create(
254 style_change_reason::kSVGContainerSizeChange));
255 if (layout_object)
256 ToLayoutSVGRoot(layout_object)->IntrinsicSizingInfoChanged();
257 }
258 } else {
259 InvalidateSVGPresentationAttributeStyle();
260 SetNeedsStyleRecalc(
261 kLocalStyleChange,
262 StyleChangeReasonForTracing::FromAttribute(attr_name));
263 }
264 }
265
266 if (SVGFitToViewBox::IsKnownAttribute(attr_name)) {
267 update_relative_lengths_or_view_box = true;
268 InvalidateRelativeLengthClients();
269 if (LayoutObject* object = GetLayoutObject()) {
270 object->SetNeedsTransformUpdate();
271 if (attr_name == svg_names::kViewBoxAttr && object->IsSVGRoot())
272 ToLayoutSVGRoot(object)->IntrinsicSizingInfoChanged();
273 }
274 }
275
276 if (update_relative_lengths_or_view_box ||
277 SVGZoomAndPan::IsKnownAttribute(attr_name)) {
278 SVGElement::InvalidationGuard invalidation_guard(this);
279 if (auto* layout_object = GetLayoutObject())
280 MarkForLayoutAndParentResourceInvalidation(*layout_object);
281 return;
282 }
283
284 SVGGraphicsElement::SvgAttributeChanged(attr_name);
285 }
286
287 // FloatRect::intersects does not consider horizontal or vertical lines (because
288 // of isEmpty()).
IntersectsAllowingEmpty(const FloatRect & r1,const FloatRect & r2)289 static bool IntersectsAllowingEmpty(const FloatRect& r1, const FloatRect& r2) {
290 if (r1.Width() < 0 || r1.Height() < 0 || r2.Width() < 0 || r2.Height() < 0)
291 return false;
292
293 return r1.X() < r2.MaxX() && r2.X() < r1.MaxX() && r1.Y() < r2.MaxY() &&
294 r2.Y() < r1.MaxY();
295 }
296
297 // One of the element types that can cause graphics to be drawn onto the target
298 // canvas. Specifically: circle, ellipse, image, line, path, polygon, polyline,
299 // rect, text and use.
IsIntersectionOrEnclosureTarget(LayoutObject * layout_object)300 static bool IsIntersectionOrEnclosureTarget(LayoutObject* layout_object) {
301 return layout_object->IsSVGShape() || layout_object->IsSVGText() ||
302 layout_object->IsSVGImage() ||
303 IsA<SVGUseElement>(*layout_object->GetNode());
304 }
305
CheckIntersectionOrEnclosure(const SVGElement & element,const FloatRect & rect,GeometryMatchingMode mode) const306 bool SVGSVGElement::CheckIntersectionOrEnclosure(
307 const SVGElement& element,
308 const FloatRect& rect,
309 GeometryMatchingMode mode) const {
310 LayoutObject* layout_object = element.GetLayoutObject();
311 DCHECK(!layout_object || layout_object->Style());
312 if (!layout_object ||
313 layout_object->StyleRef().PointerEvents() == EPointerEvents::kNone)
314 return false;
315
316 if (!IsIntersectionOrEnclosureTarget(layout_object))
317 return false;
318
319 AffineTransform ctm =
320 To<SVGGraphicsElement>(element).ComputeCTM(kAncestorScope, this);
321 FloatRect mapped_repaint_rect =
322 ctm.MapRect(layout_object->VisualRectInLocalSVGCoordinates());
323
324 bool result = false;
325 switch (mode) {
326 case kCheckIntersection:
327 result = IntersectsAllowingEmpty(rect, mapped_repaint_rect);
328 break;
329 case kCheckEnclosure:
330 result = rect.Contains(mapped_repaint_rect);
331 break;
332 default:
333 NOTREACHED();
334 break;
335 }
336
337 return result;
338 }
339
DidMoveToNewDocument(Document & old_document)340 void SVGSVGElement::DidMoveToNewDocument(Document& old_document) {
341 SVGGraphicsElement::DidMoveToNewDocument(old_document);
342 if (TimeContainer()->IsStarted()) {
343 TimeContainer()->ResetDocumentTime();
344 }
345 }
346
CollectIntersectionOrEnclosureList(const FloatRect & rect,SVGElement * reference_element,GeometryMatchingMode mode) const347 StaticNodeList* SVGSVGElement::CollectIntersectionOrEnclosureList(
348 const FloatRect& rect,
349 SVGElement* reference_element,
350 GeometryMatchingMode mode) const {
351 HeapVector<Member<Node>> nodes;
352
353 const SVGElement* root = this;
354 if (reference_element) {
355 // Only the common subtree needs to be traversed.
356 if (contains(reference_element)) {
357 root = reference_element;
358 } else if (!IsDescendantOf(reference_element)) {
359 // No common subtree.
360 return StaticNodeList::Adopt(nodes);
361 }
362 }
363
364 for (SVGGraphicsElement& element :
365 Traversal<SVGGraphicsElement>::DescendantsOf(*root)) {
366 if (CheckIntersectionOrEnclosure(element, rect, mode))
367 nodes.push_back(&element);
368 }
369
370 return StaticNodeList::Adopt(nodes);
371 }
372
getIntersectionList(SVGRectTearOff * rect,SVGElement * reference_element) const373 StaticNodeList* SVGSVGElement::getIntersectionList(
374 SVGRectTearOff* rect,
375 SVGElement* reference_element) const {
376 GetDocument().UpdateStyleAndLayoutForNode(this,
377 DocumentUpdateReason::kJavaScript);
378
379 return CollectIntersectionOrEnclosureList(
380 rect->Target()->Value(), reference_element, kCheckIntersection);
381 }
382
getEnclosureList(SVGRectTearOff * rect,SVGElement * reference_element) const383 StaticNodeList* SVGSVGElement::getEnclosureList(
384 SVGRectTearOff* rect,
385 SVGElement* reference_element) const {
386 GetDocument().UpdateStyleAndLayoutForNode(this,
387 DocumentUpdateReason::kJavaScript);
388
389 return CollectIntersectionOrEnclosureList(rect->Target()->Value(),
390 reference_element, kCheckEnclosure);
391 }
392
checkIntersection(SVGElement * element,SVGRectTearOff * rect) const393 bool SVGSVGElement::checkIntersection(SVGElement* element,
394 SVGRectTearOff* rect) const {
395 DCHECK(element);
396 GetDocument().UpdateStyleAndLayoutForNode(this,
397 DocumentUpdateReason::kJavaScript);
398
399 return CheckIntersectionOrEnclosure(*element, rect->Target()->Value(),
400 kCheckIntersection);
401 }
402
checkEnclosure(SVGElement * element,SVGRectTearOff * rect) const403 bool SVGSVGElement::checkEnclosure(SVGElement* element,
404 SVGRectTearOff* rect) const {
405 DCHECK(element);
406 GetDocument().UpdateStyleAndLayoutForNode(this,
407 DocumentUpdateReason::kJavaScript);
408
409 return CheckIntersectionOrEnclosure(*element, rect->Target()->Value(),
410 kCheckEnclosure);
411 }
412
deselectAll()413 void SVGSVGElement::deselectAll() {
414 if (LocalFrame* frame = GetDocument().GetFrame())
415 frame->Selection().Clear();
416 }
417
createSVGNumber()418 SVGNumberTearOff* SVGSVGElement::createSVGNumber() {
419 return SVGNumberTearOff::CreateDetached();
420 }
421
createSVGLength()422 SVGLengthTearOff* SVGSVGElement::createSVGLength() {
423 return SVGLengthTearOff::CreateDetached();
424 }
425
createSVGAngle()426 SVGAngleTearOff* SVGSVGElement::createSVGAngle() {
427 return SVGAngleTearOff::CreateDetached();
428 }
429
createSVGPoint()430 SVGPointTearOff* SVGSVGElement::createSVGPoint() {
431 return SVGPointTearOff::CreateDetached(FloatPoint(0, 0));
432 }
433
createSVGMatrix()434 SVGMatrixTearOff* SVGSVGElement::createSVGMatrix() {
435 return MakeGarbageCollected<SVGMatrixTearOff>(AffineTransform());
436 }
437
createSVGRect()438 SVGRectTearOff* SVGSVGElement::createSVGRect() {
439 return SVGRectTearOff::CreateDetached(FloatRect(0, 0, 0, 0));
440 }
441
createSVGTransform()442 SVGTransformTearOff* SVGSVGElement::createSVGTransform() {
443 return SVGTransformTearOff::CreateDetached();
444 }
445
createSVGTransformFromMatrix(SVGMatrixTearOff * matrix)446 SVGTransformTearOff* SVGSVGElement::createSVGTransformFromMatrix(
447 SVGMatrixTearOff* matrix) {
448 return MakeGarbageCollected<SVGTransformTearOff>(matrix);
449 }
450
LocalCoordinateSpaceTransform(CTMScope mode) const451 AffineTransform SVGSVGElement::LocalCoordinateSpaceTransform(
452 CTMScope mode) const {
453 AffineTransform transform;
454 if (!IsOutermostSVGSVGElement()) {
455 SVGLengthContext length_context(this);
456 transform.Translate(x_->CurrentValue()->Value(length_context),
457 y_->CurrentValue()->Value(length_context));
458 } else if (mode == kScreenScope) {
459 if (LayoutObject* layout_object = GetLayoutObject()) {
460 TransformationMatrix matrix;
461 // Adjust for the zoom level factored into CSS coordinates (WK bug
462 // #96361).
463 matrix.Scale(1.0 / layout_object->StyleRef().EffectiveZoom());
464
465 // Apply transforms from our ancestor coordinate space, including any
466 // non-SVG ancestor transforms.
467 matrix.Multiply(layout_object->LocalToAbsoluteTransform());
468
469 // At the SVG/HTML boundary (aka LayoutSVGRoot), we need to apply the
470 // localToBorderBoxTransform to map an element from SVG viewport
471 // coordinates to CSS box coordinates.
472 matrix.Multiply(
473 ToLayoutSVGRoot(layout_object)->LocalToBorderBoxTransform());
474 // Drop any potential non-affine parts, because we're not able to convey
475 // that information further anyway until getScreenCTM returns a DOMMatrix
476 // (4x4 matrix.)
477 return matrix.ToAffineTransform();
478 }
479 }
480 if (!HasEmptyViewBox()) {
481 FloatSize size = CurrentViewportSize();
482 transform.Multiply(ViewBoxToViewTransform(size.Width(), size.Height()));
483 }
484 return transform;
485 }
486
LayoutObjectIsNeeded(const ComputedStyle & style) const487 bool SVGSVGElement::LayoutObjectIsNeeded(const ComputedStyle& style) const {
488 // FIXME: We should respect display: none on the documentElement svg element
489 // but many things in LocalFrameView and SVGImage depend on the LayoutSVGRoot
490 // when they should instead depend on the LayoutView.
491 // https://bugs.webkit.org/show_bug.cgi?id=103493
492 if (GetDocument().documentElement() == this)
493 return true;
494
495 // <svg> elements don't need an SVG parent to render, so we bypass
496 // SVGElement::layoutObjectIsNeeded.
497 return IsValid() && Element::LayoutObjectIsNeeded(style);
498 }
499
AttachLayoutTree(AttachContext & context)500 void SVGSVGElement::AttachLayoutTree(AttachContext& context) {
501 SVGGraphicsElement::AttachLayoutTree(context);
502
503 if (GetLayoutObject() && GetLayoutObject()->IsSVGRoot())
504 ToLayoutSVGRoot(GetLayoutObject())->IntrinsicSizingInfoChanged();
505 }
506
CreateLayoutObject(const ComputedStyle &,LegacyLayout)507 LayoutObject* SVGSVGElement::CreateLayoutObject(const ComputedStyle&,
508 LegacyLayout) {
509 if (IsOutermostSVGSVGElement())
510 return new LayoutSVGRoot(this);
511
512 return new LayoutSVGViewportContainer(this);
513 }
514
InsertedInto(ContainerNode & root_parent)515 Node::InsertionNotificationRequest SVGSVGElement::InsertedInto(
516 ContainerNode& root_parent) {
517 if (root_parent.isConnected()) {
518 UseCounter::Count(GetDocument(), WebFeature::kSVGSVGElementInDocument);
519 if (IsA<XMLDocument>(root_parent.GetDocument()))
520 UseCounter::Count(GetDocument(), WebFeature::kSVGSVGElementInXMLDocument);
521
522 GetDocument().AccessSVGExtensions().AddTimeContainer(this);
523
524 // Animations are started at the end of document parsing and after firing
525 // the load event, but if we miss that train (deferred programmatic
526 // element insertion for example) we need to initialize the time container
527 // here.
528 if (!GetDocument().Parsing() && GetDocument().LoadEventFinished() &&
529 !TimeContainer()->IsStarted())
530 TimeContainer()->Start();
531 }
532 return SVGGraphicsElement::InsertedInto(root_parent);
533 }
534
RemovedFrom(ContainerNode & root_parent)535 void SVGSVGElement::RemovedFrom(ContainerNode& root_parent) {
536 if (root_parent.isConnected()) {
537 SVGDocumentExtensions& svg_extensions = GetDocument().AccessSVGExtensions();
538 svg_extensions.RemoveTimeContainer(this);
539 svg_extensions.RemoveSVGRootWithRelativeLengthDescendents(this);
540 }
541
542 SVGGraphicsElement::RemovedFrom(root_parent);
543 }
544
pauseAnimations()545 void SVGSVGElement::pauseAnimations() {
546 if (!time_container_->IsPaused())
547 time_container_->Pause();
548 }
549
unpauseAnimations()550 void SVGSVGElement::unpauseAnimations() {
551 if (time_container_->IsPaused())
552 time_container_->Unpause();
553 }
554
animationsPaused() const555 bool SVGSVGElement::animationsPaused() const {
556 return time_container_->IsPaused();
557 }
558
getCurrentTime() const559 float SVGSVGElement::getCurrentTime() const {
560 return clampTo<float>(time_container_->Elapsed().InSecondsF());
561 }
562
setCurrentTime(float seconds)563 void SVGSVGElement::setCurrentTime(float seconds) {
564 DCHECK(std::isfinite(seconds));
565 time_container_->SetElapsed(SMILTime::FromSecondsD(std::max(seconds, 0.0f)));
566 }
567
SelfHasRelativeLengths() const568 bool SVGSVGElement::SelfHasRelativeLengths() const {
569 return x_->CurrentValue()->IsRelative() || y_->CurrentValue()->IsRelative() ||
570 width_->CurrentValue()->IsRelative() ||
571 height_->CurrentValue()->IsRelative();
572 }
573
ShouldSynthesizeViewBox() const574 bool SVGSVGElement::ShouldSynthesizeViewBox() const {
575 return GetLayoutObject() && GetLayoutObject()->IsSVGRoot() &&
576 ToLayoutSVGRoot(GetLayoutObject())->IsEmbeddedThroughSVGImage();
577 }
578
CurrentViewBoxRect() const579 FloatRect SVGSVGElement::CurrentViewBoxRect() const {
580 if (view_spec_ && view_spec_->ViewBox())
581 return view_spec_->ViewBox()->Value();
582
583 FloatRect use_view_box = viewBox()->CurrentValue()->Value();
584 if (!use_view_box.IsEmpty())
585 return use_view_box;
586 if (!ShouldSynthesizeViewBox())
587 return FloatRect();
588
589 // If no viewBox is specified but non-relative width/height values, then we
590 // should always synthesize a viewBox if we're embedded through a SVGImage.
591 FloatSize synthesized_view_box_size(IntrinsicWidth(), IntrinsicHeight());
592 if (!HasIntrinsicWidth())
593 synthesized_view_box_size.SetWidth(
594 width()->CurrentValue()->ScaleByPercentage(
595 CurrentViewportSize().Width()));
596 if (!HasIntrinsicHeight())
597 synthesized_view_box_size.SetHeight(
598 height()->CurrentValue()->ScaleByPercentage(
599 CurrentViewportSize().Height()));
600 return FloatRect(FloatPoint(), synthesized_view_box_size);
601 }
602
CurrentPreserveAspectRatio() const603 const SVGPreserveAspectRatio* SVGSVGElement::CurrentPreserveAspectRatio()
604 const {
605 if (view_spec_ && view_spec_->PreserveAspectRatio())
606 return view_spec_->PreserveAspectRatio();
607
608 if (!HasValidViewBox() && ShouldSynthesizeViewBox()) {
609 // If no (valid) viewBox is specified and we're embedded through SVGImage,
610 // then synthesize a pAR with the value 'none'.
611 auto* synthesized_par = MakeGarbageCollected<SVGPreserveAspectRatio>();
612 synthesized_par->SetAlign(
613 SVGPreserveAspectRatio::kSvgPreserveaspectratioNone);
614 return synthesized_par;
615 }
616 return preserveAspectRatio()->CurrentValue();
617 }
618
CurrentViewportSize() const619 FloatSize SVGSVGElement::CurrentViewportSize() const {
620 const LayoutObject* layout_object = GetLayoutObject();
621 if (!layout_object)
622 return FloatSize();
623
624 if (layout_object->IsSVGRoot()) {
625 LayoutSize content_size = ToLayoutSVGRoot(layout_object)->ContentSize();
626 float zoom = layout_object->StyleRef().EffectiveZoom();
627 return FloatSize(content_size.Width() / zoom, content_size.Height() / zoom);
628 }
629
630 FloatRect viewport_rect =
631 ToLayoutSVGViewportContainer(GetLayoutObject())->Viewport();
632 return viewport_rect.Size();
633 }
634
HasIntrinsicWidth() const635 bool SVGSVGElement::HasIntrinsicWidth() const {
636 // TODO(crbug.com/979895): This is the result of a refactoring, which might
637 // have revealed an existing bug that we are not handling math functions
638 // involving percentages correctly. Fix it if necessary.
639 return !width()->CurrentValue()->IsPercentage();
640 }
641
HasIntrinsicHeight() const642 bool SVGSVGElement::HasIntrinsicHeight() const {
643 // TODO(crbug.com/979895): This is the result of a refactoring, which might
644 // have revealed an existing bug that we are not handling math functions
645 // involving percentages correctly. Fix it if necessary.
646 return !height()->CurrentValue()->IsPercentage();
647 }
648
IntrinsicWidth() const649 float SVGSVGElement::IntrinsicWidth() const {
650 if (!HasIntrinsicWidth())
651 return 0;
652
653 SVGLengthContext length_context(this);
654 return width()->CurrentValue()->Value(length_context);
655 }
656
IntrinsicHeight() const657 float SVGSVGElement::IntrinsicHeight() const {
658 if (!HasIntrinsicHeight())
659 return 0;
660
661 SVGLengthContext length_context(this);
662 return height()->CurrentValue()->Value(length_context);
663 }
664
ViewBoxToViewTransform(float view_width,float view_height) const665 AffineTransform SVGSVGElement::ViewBoxToViewTransform(float view_width,
666 float view_height) const {
667 AffineTransform ctm = SVGFitToViewBox::ViewBoxToViewTransform(
668 CurrentViewBoxRect(), CurrentPreserveAspectRatio(), view_width,
669 view_height);
670 if (!view_spec_ || !view_spec_->Transform())
671 return ctm;
672
673 const SVGTransformList* transform_list = view_spec_->Transform();
674 if (transform_list->IsEmpty())
675 return ctm;
676
677 AffineTransform transform;
678 if (transform_list->Concatenate(transform))
679 ctm *= transform;
680
681 return ctm;
682 }
683
SetViewSpec(const SVGViewSpec * view_spec)684 void SVGSVGElement::SetViewSpec(const SVGViewSpec* view_spec) {
685 // Even if the viewspec object itself doesn't change, it could still
686 // have been mutated, so only treat a "no viewspec" -> "no viewspec"
687 // transition as a no-op.
688 if (!view_spec_ && !view_spec)
689 return;
690 view_spec_ = view_spec;
691 if (LayoutObject* layout_object = GetLayoutObject())
692 MarkForLayoutAndParentResourceInvalidation(*layout_object);
693 }
694
SetupInitialView(const String & fragment_identifier,Element * anchor_node)695 void SVGSVGElement::SetupInitialView(const String& fragment_identifier,
696 Element* anchor_node) {
697 if (fragment_identifier.StartsWith("svgView(")) {
698 SVGViewSpec* view_spec =
699 SVGViewSpec::CreateFromFragment(fragment_identifier);
700 if (view_spec) {
701 UseCounter::Count(GetDocument(),
702 WebFeature::kSVGSVGElementFragmentSVGView);
703 SetViewSpec(view_spec);
704 return;
705 }
706 }
707 if (auto* svg_view_element = DynamicTo<SVGViewElement>(anchor_node)) {
708 // Spec: If the SVG fragment identifier addresses a 'view' element within an
709 // SVG document (e.g., MyDrawing.svg#MyView) then the root 'svg' element is
710 // displayed in the SVG viewport. Any view specification attributes included
711 // on the given 'view' element override the corresponding view specification
712 // attributes on the root 'svg' element.
713 SVGViewSpec* view_spec =
714 SVGViewSpec::CreateForViewElement(*svg_view_element);
715 UseCounter::Count(GetDocument(),
716 WebFeature::kSVGSVGElementFragmentSVGViewElement);
717 SetViewSpec(view_spec);
718 return;
719 }
720 SetViewSpec(nullptr);
721 }
722
FinishParsingChildren()723 void SVGSVGElement::FinishParsingChildren() {
724 SVGGraphicsElement::FinishParsingChildren();
725
726 // The outermost SVGSVGElement SVGLoad event is fired through
727 // LocalDOMWindow::dispatchWindowLoadEvent.
728 if (IsOutermostSVGSVGElement())
729 return;
730
731 // finishParsingChildren() is called when the close tag is reached for an
732 // element (e.g. </svg>) we send SVGLoad events here if we can, otherwise
733 // they'll be sent when any required loads finish
734 SendSVGLoadEventIfPossible();
735 }
736
Trace(Visitor * visitor)737 void SVGSVGElement::Trace(Visitor* visitor) {
738 visitor->Trace(x_);
739 visitor->Trace(y_);
740 visitor->Trace(width_);
741 visitor->Trace(height_);
742 visitor->Trace(translation_);
743 visitor->Trace(time_container_);
744 visitor->Trace(view_spec_);
745 SVGGraphicsElement::Trace(visitor);
746 SVGFitToViewBox::Trace(visitor);
747 }
748
749 } // namespace blink
750