Use "= default" to denote default constructor or destructor
[WebKit-https.git] / Source / WebCore / rendering / svg / RenderSVGRoot.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2004, 2005, 2007, 2008, 2009 Rob Buis <buis@kde.org>
4  * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5  * Copyright (C) 2009 Google, Inc.
6  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #include "config.h"
25 #include "RenderSVGRoot.h"
26
27 #include "Frame.h"
28 #include "GraphicsContext.h"
29 #include "HitTestResult.h"
30 #include "LayoutRepainter.h"
31 #include "Page.h"
32 #include "RenderIterator.h"
33 #include "RenderLayer.h"
34 #include "RenderSVGResource.h"
35 #include "RenderSVGResourceContainer.h"
36 #include "RenderSVGResourceFilter.h"
37 #include "RenderView.h"
38 #include "SVGImage.h"
39 #include "SVGRenderingContext.h"
40 #include "SVGResources.h"
41 #include "SVGResourcesCache.h"
42 #include "SVGSVGElement.h"
43 #include "SVGViewSpec.h"
44 #include "TransformState.h"
45 #include <wtf/StackStats.h>
46
47 namespace WebCore {
48
49 RenderSVGRoot::RenderSVGRoot(SVGSVGElement& element, RenderStyle&& style)
50     : RenderReplaced(element, WTFMove(style))
51     , m_objectBoundingBoxValid(false)
52     , m_isLayoutSizeChanged(false)
53     , m_needsBoundariesOrTransformUpdate(true)
54     , m_hasSVGShadow(false)
55     , m_hasBoxDecorations(false)
56 {
57 }
58
59 RenderSVGRoot::~RenderSVGRoot() = default;
60
61 SVGSVGElement& RenderSVGRoot::svgSVGElement() const
62 {
63     return downcast<SVGSVGElement>(nodeForNonAnonymous());
64 }
65
66 void RenderSVGRoot::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio) const
67 {
68     // Spec: http://www.w3.org/TR/SVG/coords.html#IntrinsicSizing
69     // SVG needs to specify how to calculate some intrinsic sizing properties to enable inclusion within other languages.
70
71     // The intrinsic aspect ratio of the viewport of SVG content is necessary for example, when including SVG from an ‘object’
72     // element in HTML styled with CSS. It is possible (indeed, common) for an SVG graphic to have an intrinsic aspect ratio but
73     // not to have an intrinsic width or height. The intrinsic aspect ratio must be calculated based upon the following rules:
74     // - The aspect ratio is calculated by dividing a width by a height.
75     // - If the ‘width’ and ‘height’ of the rootmost ‘svg’ element are both specified with unit identifiers (in, mm, cm, pt, pc,
76     //   px, em, ex) or in user units, then the aspect ratio is calculated from the ‘width’ and ‘height’ attributes after
77     //   resolving both values to user units.
78     intrinsicSize.setWidth(floatValueForLength(svgSVGElement().intrinsicWidth(), 0));
79     intrinsicSize.setHeight(floatValueForLength(svgSVGElement().intrinsicHeight(), 0));
80
81
82     if (!intrinsicSize.isEmpty())
83         intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height());
84     else {
85         // - If either/both of the ‘width’ and ‘height’ of the rootmost ‘svg’ element are in percentage units (or omitted), the
86         //   aspect ratio is calculated from the width and height values of the ‘viewBox’ specified for the current SVG document
87         //   fragment. If the ‘viewBox’ is not correctly specified, or set to 'none', the intrinsic aspect ratio cannot be
88         //   calculated and is considered unspecified.
89         FloatSize viewBoxSize = svgSVGElement().viewBox().size();
90         if (!viewBoxSize.isEmpty()) {
91             // The viewBox can only yield an intrinsic ratio, not an intrinsic size.
92             intrinsicRatio = viewBoxSize.width() / static_cast<double>(viewBoxSize.height());
93         }
94     }
95 }
96
97 bool RenderSVGRoot::isEmbeddedThroughSVGImage() const
98 {
99     return isInSVGImage(&svgSVGElement());
100 }
101
102 bool RenderSVGRoot::isEmbeddedThroughFrameContainingSVGDocument() const
103 {
104     // If our frame has an owner renderer, we're embedded through eg. object/embed/iframe,
105     // but we only negotiate if we're in an SVG document.
106     if (!frame().ownerRenderer())
107         return false;
108     return frame().document()->isSVGDocument();
109 }
110
111 LayoutUnit RenderSVGRoot::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
112 {
113     // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size.
114     if (!m_containerSize.isEmpty())
115         return m_containerSize.width();
116
117     if (isEmbeddedThroughFrameContainingSVGDocument())
118         return containingBlock()->availableLogicalWidth();
119
120     // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG.
121     return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred);
122 }
123
124 LayoutUnit RenderSVGRoot::computeReplacedLogicalHeight(std::optional<LayoutUnit> estimatedUsedWidth) const
125 {
126     // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size.
127     if (!m_containerSize.isEmpty())
128         return m_containerSize.height();
129
130     if (isEmbeddedThroughFrameContainingSVGDocument())
131         return containingBlock()->availableLogicalHeight(IncludeMarginBorderPadding);
132
133     // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG.
134     return RenderReplaced::computeReplacedLogicalHeight(estimatedUsedWidth);
135 }
136
137 void RenderSVGRoot::layout()
138 {
139     StackStats::LayoutCheckPoint layoutCheckPoint;
140     ASSERT(needsLayout());
141
142     m_resourcesNeedingToInvalidateClients.clear();
143
144     // Arbitrary affine transforms are incompatible with LayoutState.
145     LayoutStateDisabler layoutStateDisabler(view());
146
147     bool needsLayout = selfNeedsLayout();
148     LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && needsLayout);
149
150     LayoutSize oldSize = size();
151     updateLogicalWidth();
152     updateLogicalHeight();
153     buildLocalToBorderBoxTransform();
154
155     m_isLayoutSizeChanged = needsLayout || (svgSVGElement().hasRelativeLengths() && oldSize != size());
156     SVGRenderSupport::layoutChildren(*this, needsLayout || SVGRenderSupport::filtersForceContainerLayout(*this));
157
158     if (!m_resourcesNeedingToInvalidateClients.isEmpty()) {
159         // Invalidate resource clients, which may mark some nodes for layout.
160         for (auto& resource :  m_resourcesNeedingToInvalidateClients) {
161             resource->removeAllClientsFromCache();
162             SVGResourcesCache::clientStyleChanged(*resource, StyleDifferenceLayout, resource->style());
163         }
164
165         m_isLayoutSizeChanged = false;
166         SVGRenderSupport::layoutChildren(*this, false);
167     }
168
169     // At this point LayoutRepainter already grabbed the old bounds,
170     // recalculate them now so repaintAfterLayout() uses the new bounds.
171     if (m_needsBoundariesOrTransformUpdate) {
172         updateCachedBoundaries();
173         m_needsBoundariesOrTransformUpdate = false;
174     }
175
176     clearOverflow();
177     if (!shouldApplyViewportClip()) {
178         FloatRect contentRepaintRect = repaintRectInLocalCoordinates();
179         contentRepaintRect = m_localToBorderBoxTransform.mapRect(contentRepaintRect);
180         addVisualOverflow(enclosingLayoutRect(contentRepaintRect));
181     }
182
183     updateLayerTransform();
184     m_hasBoxDecorations = isDocumentElementRenderer() ? hasVisibleBoxDecorationStyle() : hasVisibleBoxDecorations();
185     invalidateBackgroundObscurationStatus();
186
187     repainter.repaintAfterLayout();
188
189     clearNeedsLayout();
190 }
191
192 bool RenderSVGRoot::shouldApplyViewportClip() const
193 {
194     // the outermost svg is clipped if auto, and svg document roots are always clipped
195     // When the svg is stand-alone (isDocumentElement() == true) the viewport clipping should always
196     // be applied, noting that the window scrollbars should be hidden if overflow=hidden.
197     return style().overflowX() == OHIDDEN
198         || style().overflowX() == OAUTO
199         || style().overflowX() == OSCROLL
200         || this->isDocumentElementRenderer();
201 }
202
203 void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
204 {
205     // An empty viewport disables rendering.
206     if (borderBoxRect().isEmpty())
207         return;
208
209     // Don't paint, if the context explicitly disabled it.
210     if (paintInfo.context().paintingDisabled())
211         return;
212
213     // SVG outlines are painted during PaintPhaseForeground.
214     if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline)
215         return;
216
217     // An empty viewBox also disables rendering.
218     // (http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute)
219     if (svgSVGElement().hasEmptyViewBox())
220         return;
221
222     // Don't paint if we don't have kids, except if we have filters we should paint those.
223     if (!firstChild()) {
224         auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this);
225         if (!resources || !resources->filter()) {
226             if (paintInfo.phase == PaintPhaseForeground)
227                 page().addRelevantUnpaintedObject(this, visualOverflowRect());
228             return;
229         }
230     }
231
232     if (paintInfo.phase == PaintPhaseForeground)
233         page().addRelevantRepaintedObject(this, visualOverflowRect());
234
235     // Make a copy of the PaintInfo because applyTransform will modify the damage rect.
236     PaintInfo childPaintInfo(paintInfo);
237     childPaintInfo.context().save();
238
239     // Apply initial viewport clip
240     if (shouldApplyViewportClip())
241         childPaintInfo.context().clip(snappedIntRect(overflowClipRect(paintOffset)));
242
243     // Convert from container offsets (html renderers) to a relative transform (svg renderers).
244     // Transform from our paint container's coordinate system to our local coords.
245     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset);
246     childPaintInfo.applyTransform(AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y()) * localToBorderBoxTransform());
247
248     // SVGRenderingContext must be destroyed before we restore the childPaintInfo.context(), because a filter may have
249     // changed the context and it is only reverted when the SVGRenderingContext destructor finishes applying the filter.
250     {
251         SVGRenderingContext renderingContext;
252         bool continueRendering = true;
253         if (childPaintInfo.phase == PaintPhaseForeground) {
254             renderingContext.prepareToRenderSVGContent(*this, childPaintInfo);
255             continueRendering = renderingContext.isRenderingPrepared();
256         }
257
258         if (continueRendering) {
259             childPaintInfo.updateSubtreePaintRootForChildren(this);
260             for (auto& child : childrenOfType<RenderElement>(*this))
261                 child.paint(childPaintInfo, location());
262         }
263     }
264
265     childPaintInfo.context().restore();
266 }
267
268 void RenderSVGRoot::willBeDestroyed()
269 {
270     RenderBlock::removePercentHeightDescendant(const_cast<RenderSVGRoot&>(*this));
271
272     SVGResourcesCache::clientDestroyed(*this);
273     RenderReplaced::willBeDestroyed();
274 }
275
276 void RenderSVGRoot::insertedIntoTree()
277 {
278     RenderReplaced::insertedIntoTree();
279     SVGResourcesCache::clientWasAddedToTree(*this);
280 }
281
282 void RenderSVGRoot::willBeRemovedFromTree()
283 {
284     SVGResourcesCache::clientWillBeRemovedFromTree(*this);
285     RenderReplaced::willBeRemovedFromTree();
286 }
287
288 void RenderSVGRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
289 {
290     if (diff == StyleDifferenceLayout)
291         setNeedsBoundariesUpdate();
292
293     // Box decorations may have appeared/disappeared - recompute status.
294     if (diff == StyleDifferenceRepaint)
295         m_hasBoxDecorations = hasVisibleBoxDecorationStyle();
296
297     RenderReplaced::styleDidChange(diff, oldStyle);
298     SVGResourcesCache::clientStyleChanged(*this, diff, style());
299 }
300
301 void RenderSVGRoot::addChild(RenderPtr<RenderObject> newChild, RenderObject* beforeChild)
302 {
303     auto& child = *newChild;
304     RenderReplaced::addChild(WTFMove(newChild), beforeChild);
305     SVGResourcesCache::clientWasAddedToTree(child);
306 }
307
308 RenderPtr<RenderObject> RenderSVGRoot::takeChild(RenderObject& child)
309 {
310     SVGResourcesCache::clientWillBeRemovedFromTree(child);
311     return RenderReplaced::takeChild(child);
312 }
313
314 // RenderBox methods will expect coordinates w/o any transforms in coordinates
315 // relative to our borderBox origin.  This method gives us exactly that.
316 void RenderSVGRoot::buildLocalToBorderBoxTransform()
317 {
318     float scale = style().effectiveZoom();
319     FloatPoint translate = svgSVGElement().currentTranslateValue();
320     LayoutSize borderAndPadding(borderLeft() + paddingLeft(), borderTop() + paddingTop());
321     m_localToBorderBoxTransform = svgSVGElement().viewBoxToViewTransform(contentWidth() / scale, contentHeight() / scale);
322     if (borderAndPadding.isZero() && scale == 1 && translate == FloatPoint::zero())
323         return;
324     m_localToBorderBoxTransform = AffineTransform(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y()) * m_localToBorderBoxTransform;
325 }
326
327 const AffineTransform& RenderSVGRoot::localToParentTransform() const
328 {
329     // Slightly optimized version of m_localToParentTransform = AffineTransform::translation(x(), y()) * m_localToBorderBoxTransform;
330     m_localToParentTransform = m_localToBorderBoxTransform;
331     if (x())
332         m_localToParentTransform.setE(m_localToParentTransform.e() + roundToInt(x()));
333     if (y())
334         m_localToParentTransform.setF(m_localToParentTransform.f() + roundToInt(y()));
335     return m_localToParentTransform;
336 }
337
338 LayoutRect RenderSVGRoot::clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const
339 {
340     if (style().visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent())
341         return LayoutRect();
342
343     FloatRect contentRepaintRect = m_localToBorderBoxTransform.mapRect(repaintRectInLocalCoordinates());
344     contentRepaintRect.intersect(snappedIntRect(borderBoxRect()));
345
346     LayoutRect repaintRect = enclosingLayoutRect(contentRepaintRect);
347     if (m_hasBoxDecorations || hasRenderOverflow())
348         repaintRect.unite(unionRect(localSelectionRect(false), visualOverflowRect()));
349
350     return RenderReplaced::computeRectForRepaint(enclosingIntRect(repaintRect), repaintContainer);
351 }
352
353 FloatRect RenderSVGRoot::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) const
354 {
355     // Apply our local transforms (except for x/y translation), then our shadow, 
356     // and then call RenderBox's method to handle all the normal CSS Box model bits
357     FloatRect adjustedRect = m_localToBorderBoxTransform.mapRect(repaintRect);
358
359     const SVGRenderStyle& svgStyle = style().svgStyle();
360     if (const ShadowData* shadow = svgStyle.shadow())
361         shadow->adjustRectForShadow(adjustedRect);
362
363     // Apply initial viewport clip
364     if (shouldApplyViewportClip())
365         adjustedRect.intersect(snappedIntRect(borderBoxRect()));
366
367     if (m_hasBoxDecorations || hasRenderOverflow()) {
368         // The selectionRect can project outside of the overflowRect, so take their union
369         // for repainting to avoid selection painting glitches.
370         LayoutRect decoratedRepaintRect = unionRect(localSelectionRect(false), visualOverflowRect());
371         adjustedRect.unite(decoratedRepaintRect);
372     }
373     return RenderReplaced::computeRectForRepaint(enclosingIntRect(adjustedRect), repaintContainer, {fixed, false});
374 }
375
376 // This method expects local CSS box coordinates.
377 // Callers with local SVG viewport coordinates should first apply the localToBorderBoxTransform
378 // to convert from SVG viewport coordinates to local CSS box coordinates.
379 void RenderSVGRoot::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags mode, bool* wasFixed) const
380 {
381     ASSERT(mode & ~IsFixed); // We should have no fixed content in the SVG rendering tree.
382     ASSERT(mode & UseTransforms); // mapping a point through SVG w/o respecting trasnforms is useless.
383
384     RenderReplaced::mapLocalToContainer(repaintContainer, transformState, mode | ApplyContainerFlip, wasFixed);
385 }
386
387 const RenderObject* RenderSVGRoot::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const
388 {
389     return RenderReplaced::pushMappingToContainer(ancestorToStopAt, geometryMap);
390 }
391
392 void RenderSVGRoot::updateCachedBoundaries()
393 {
394     SVGRenderSupport::computeContainerBoundingBoxes(*this, m_objectBoundingBox, m_objectBoundingBoxValid, m_strokeBoundingBox, m_repaintBoundingBoxExcludingShadow);
395     SVGRenderSupport::intersectRepaintRectWithResources(*this, m_repaintBoundingBoxExcludingShadow);
396     m_repaintBoundingBoxExcludingShadow.inflate(horizontalBorderAndPaddingExtent());
397
398     m_repaintBoundingBox = m_repaintBoundingBoxExcludingShadow;
399     SVGRenderSupport::intersectRepaintRectWithShadows(*this, m_repaintBoundingBox);
400 }
401
402 bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
403 {
404     LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset);
405     LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location());
406
407     // Test SVG content if the point is in our content box or it is inside the visualOverflowRect and the overflow is visible.
408     // FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint.
409     if (contentBoxRect().contains(pointInBorderBox) || (!shouldApplyViewportClip() && visualOverflowRect().contains(pointInParent))) {
410         FloatPoint localPoint = localToParentTransform().inverse().value_or(AffineTransform()).mapPoint(FloatPoint(pointInParent));
411
412         for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
413             // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
414             if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
415                 updateHitTestResult(result, pointInBorderBox);
416                 if (result.addNodeToListBasedTestResult(child->node(), request, locationInContainer) == HitTestProgress::Stop)
417                     return true;
418             }
419         }
420     }
421
422     // If we didn't early exit above, we've just hit the container <svg> element. Unlike SVG 1.1, 2nd Edition allows container elements to be hit.
423     if ((hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) && visibleToHitTesting()) {
424         // Only return true here, if the last hit testing phase 'BlockBackground' is executed. If we'd return true in the 'Foreground' phase,
425         // hit testing would stop immediately. For SVG only trees this doesn't matter. Though when we have a <foreignObject> subtree we need
426         // to be able to detect hits on the background of a <div> element. If we'd return true here in the 'Foreground' phase, we are not able 
427         // to detect these hits anymore.
428         LayoutRect boundsRect(accumulatedOffset + location(), size());
429         if (locationInContainer.intersects(boundsRect)) {
430             updateHitTestResult(result, pointInBorderBox);
431             if (result.addNodeToListBasedTestResult(&svgSVGElement(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
432                 return true;
433         }
434     }
435
436     return false;
437 }
438
439 bool RenderSVGRoot::hasRelativeDimensions() const
440 {
441     return svgSVGElement().intrinsicHeight().isPercentOrCalculated() || svgSVGElement().intrinsicWidth().isPercentOrCalculated();
442 }
443
444 void RenderSVGRoot::addResourceForClientInvalidation(RenderSVGResourceContainer* resource)
445 {
446     RenderSVGRoot* svgRoot = SVGRenderSupport::findTreeRootObject(*resource);
447     if (!svgRoot)
448         return;
449     svgRoot->m_resourcesNeedingToInvalidateClients.add(resource);
450 }
451
452 }