Move more of SVG resources cache to using RenderElement.
[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
26 #if ENABLE(SVG)
27 #include "RenderSVGRoot.h"
28
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "Frame.h"
32 #include "GraphicsContext.h"
33 #include "HitTestResult.h"
34 #include "LayoutRepainter.h"
35 #include "Page.h"
36 #include "RenderSVGContainer.h"
37 #include "RenderSVGResource.h"
38 #include "RenderSVGResourceContainer.h"
39 #include "RenderView.h"
40 #include "SVGElement.h"
41 #include "SVGImage.h"
42 #include "SVGLength.h"
43 #include "SVGRenderingContext.h"
44 #include "SVGResources.h"
45 #include "SVGResourcesCache.h"
46 #include "SVGSVGElement.h"
47 #include "SVGViewSpec.h"
48 #include "TransformState.h"
49 #include <wtf/StackStats.h>
50
51 #if ENABLE(FILTERS)
52 #include "RenderSVGResourceFilter.h"
53 #endif
54
55 using namespace std;
56
57 namespace WebCore {
58
59 RenderSVGRoot::RenderSVGRoot(SVGSVGElement& element, PassRef<RenderStyle> style)
60     : RenderReplaced(element, std::move(style))
61     , m_objectBoundingBoxValid(false)
62     , m_isLayoutSizeChanged(false)
63     , m_needsBoundariesOrTransformUpdate(true)
64     , m_hasSVGShadow(false)
65 {
66 }
67
68 RenderSVGRoot::~RenderSVGRoot()
69 {
70 }
71
72 SVGSVGElement& RenderSVGRoot::svgSVGElement() const
73 {
74     return toSVGSVGElement(nodeForNonAnonymous());
75 }
76
77 void RenderSVGRoot::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const
78 {
79     // Spec: http://www.w3.org/TR/SVG/coords.html#IntrinsicSizing
80     // SVG needs to specify how to calculate some intrinsic sizing properties to enable inclusion within other languages.
81     // The intrinsic width and height of the viewport of SVG content must be determined from the ‘width’ and ‘height’ attributes.
82     // If either of these are not specified, a value of '100%' must be assumed. Note: the ‘width’ and ‘height’ attributes are not
83     // the same as the CSS width and height properties. Specifically, percentage values do not provide an intrinsic width or height,
84     // and do not indicate a percentage of the containing block. Rather, once the viewport is established, they indicate the portion
85     // of the viewport that is actually covered by image data.
86     Length intrinsicWidthAttribute = svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties);
87     Length intrinsicHeightAttribute = svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties);
88
89     // The intrinsic aspect ratio of the viewport of SVG content is necessary for example, when including SVG from an ‘object’
90     // element in HTML styled with CSS. It is possible (indeed, common) for an SVG graphic to have an intrinsic aspect ratio but
91     // not to have an intrinsic width or height. The intrinsic aspect ratio must be calculated based upon the following rules:
92     // - The aspect ratio is calculated by dividing a width by a height.
93     // - If the ‘width’ and ‘height’ of the rootmost ‘svg’ element are both specified with unit identifiers (in, mm, cm, pt, pc,
94     //   px, em, ex) or in user units, then the aspect ratio is calculated from the ‘width’ and ‘height’ attributes after
95     //   resolving both values to user units.
96     if (intrinsicWidthAttribute.isFixed() || intrinsicHeightAttribute.isFixed()) {
97         if (intrinsicWidthAttribute.isFixed())
98             intrinsicSize.setWidth(floatValueForLength(intrinsicWidthAttribute, 0));
99         if (intrinsicHeightAttribute.isFixed())
100             intrinsicSize.setHeight(floatValueForLength(intrinsicHeightAttribute, 0));
101         if (!intrinsicSize.isEmpty())
102             intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height());
103         return;
104     }
105
106     // - If either/both of the ‘width’ and ‘height’ of the rootmost ‘svg’ element are in percentage units (or omitted), the
107     //   aspect ratio is calculated from the width and height values of the ‘viewBox’ specified for the current SVG document
108     //   fragment. If the ‘viewBox’ is not correctly specified, or set to 'none', the intrinsic aspect ratio cannot be
109     //   calculated and is considered unspecified.
110     intrinsicSize = svgSVGElement().viewBox().size();
111     if (!intrinsicSize.isEmpty()) {
112         // The viewBox can only yield an intrinsic ratio, not an intrinsic size.
113         intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height());
114         intrinsicSize = FloatSize();
115         return;
116     }
117
118     // If our intrinsic size is in percentage units, return those to the caller through the intrinsicSize. Notify the caller
119     // about the special situation, by setting isPercentageIntrinsicSize=true, so it knows how to interpret the return values.
120     if (intrinsicWidthAttribute.isPercent() && intrinsicHeightAttribute.isPercent()) {
121         isPercentageIntrinsicSize = true;
122         intrinsicSize = FloatSize(intrinsicWidthAttribute.percent(), intrinsicHeightAttribute.percent());
123     }
124 }
125
126 bool RenderSVGRoot::isEmbeddedThroughSVGImage() const
127 {
128     return isInSVGImage(&svgSVGElement());
129 }
130
131 bool RenderSVGRoot::isEmbeddedThroughFrameContainingSVGDocument() const
132 {
133     // If our frame has an owner renderer, we're embedded through eg. object/embed/iframe,
134     // but we only negotiate if we're in an SVG document.
135     if (!frame().ownerRenderer())
136         return false;
137     return frame().document()->isSVGDocument();
138 }
139
140 static inline LayoutUnit resolveLengthAttributeForSVG(const Length& length, float scale, float maxSize, RenderView* renderView)
141 {
142     return static_cast<LayoutUnit>(valueForLength(length, maxSize, renderView) * (length.isFixed() ? scale : 1));
143 }
144
145 LayoutUnit RenderSVGRoot::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
146 {
147     // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size.
148     if (!m_containerSize.isEmpty())
149         return m_containerSize.width();
150
151     if (style().logicalWidth().isSpecified() || style().logicalMaxWidth().isSpecified())
152         return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred);
153
154     if (svgSVGElement().widthAttributeEstablishesViewport())
155         return resolveLengthAttributeForSVG(svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties), style().effectiveZoom(), containingBlock()->availableLogicalWidth(), &view());
156
157     // SVG embedded through object/embed/iframe.
158     if (isEmbeddedThroughFrameContainingSVGDocument())
159         return frame().ownerRenderer()->availableLogicalWidth();
160
161     // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG.
162     return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred);
163 }
164
165 LayoutUnit RenderSVGRoot::computeReplacedLogicalHeight() const
166 {
167     // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size.
168     if (!m_containerSize.isEmpty())
169         return m_containerSize.height();
170
171     if (style().logicalHeight().isSpecified() || style().logicalMaxHeight().isSpecified())
172         return RenderReplaced::computeReplacedLogicalHeight();
173
174     if (svgSVGElement().heightAttributeEstablishesViewport()) {
175         Length height = svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties);
176         if (height.isPercent()) {
177             RenderBlock* cb = containingBlock();
178             ASSERT(cb);
179             while (cb->isAnonymous() && !cb->isRenderView()) {
180                 cb = cb->containingBlock();
181                 cb->addPercentHeightDescendant(const_cast<RenderSVGRoot&>(*this));
182             }
183         } else
184             RenderBlock::removePercentHeightDescendant(const_cast<RenderSVGRoot&>(*this));
185
186         return resolveLengthAttributeForSVG(height, style().effectiveZoom(), containingBlock()->availableLogicalHeight(IncludeMarginBorderPadding), &view());
187     }
188
189     // SVG embedded through object/embed/iframe.
190     if (isEmbeddedThroughFrameContainingSVGDocument())
191         return frame().ownerRenderer()->availableLogicalHeight(IncludeMarginBorderPadding);
192
193     // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG.
194     return RenderReplaced::computeReplacedLogicalHeight();
195 }
196
197 void RenderSVGRoot::layout()
198 {
199     StackStats::LayoutCheckPoint layoutCheckPoint;
200     ASSERT(needsLayout());
201
202     m_resourcesNeedingToInvalidateClients.clear();
203
204     // Arbitrary affine transforms are incompatible with LayoutState.
205     LayoutStateDisabler layoutStateDisabler(&view());
206
207     bool needsLayout = selfNeedsLayout();
208     LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && needsLayout);
209
210     LayoutSize oldSize = size();
211     updateLogicalWidth();
212     updateLogicalHeight();
213     buildLocalToBorderBoxTransform();
214
215     m_isLayoutSizeChanged = needsLayout || (svgSVGElement().hasRelativeLengths() && oldSize != size());
216     SVGRenderSupport::layoutChildren(this, needsLayout || SVGRenderSupport::filtersForceContainerLayout(this));
217
218     if (!m_resourcesNeedingToInvalidateClients.isEmpty()) {
219         // Invalidate resource clients, which may mark some nodes for layout.
220         HashSet<RenderSVGResourceContainer*>::iterator end = m_resourcesNeedingToInvalidateClients.end();
221         for (HashSet<RenderSVGResourceContainer*>::iterator it = m_resourcesNeedingToInvalidateClients.begin(); it != end; ++it)
222             (*it)->removeAllClientsFromCache();
223
224         m_isLayoutSizeChanged = false;
225         SVGRenderSupport::layoutChildren(this, false);
226     }
227
228     // At this point LayoutRepainter already grabbed the old bounds,
229     // recalculate them now so repaintAfterLayout() uses the new bounds.
230     if (m_needsBoundariesOrTransformUpdate) {
231         updateCachedBoundaries();
232         m_needsBoundariesOrTransformUpdate = false;
233     }
234
235     updateLayerTransform();
236
237     repainter.repaintAfterLayout();
238
239     clearNeedsLayout();
240 }
241
242 void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
243 {
244     // An empty viewport disables rendering.
245     if (pixelSnappedBorderBoxRect().isEmpty())
246         return;
247
248     // Don't paint, if the context explicitly disabled it.
249     if (paintInfo.context->paintingDisabled())
250         return;
251
252     // An empty viewBox also disables rendering.
253     // (http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute)
254     if (svgSVGElement().hasEmptyViewBox())
255         return;
256
257     Page* page = frame().page();
258
259     // Don't paint if we don't have kids, except if we have filters we should paint those.
260     if (!firstChild()) {
261         SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
262         if (!resources || !resources->filter()) {
263             if (page && paintInfo.phase == PaintPhaseForeground)
264                 page->addRelevantUnpaintedObject(this, visualOverflowRect());
265             return;
266         }
267     }
268
269     if (page && paintInfo.phase == PaintPhaseForeground)
270         page->addRelevantRepaintedObject(this, visualOverflowRect());
271
272     // Make a copy of the PaintInfo because applyTransform will modify the damage rect.
273     PaintInfo childPaintInfo(paintInfo);
274     childPaintInfo.context->save();
275
276     // Apply initial viewport clip - not affected by overflow handling
277     childPaintInfo.context->clip(pixelSnappedIntRect(overflowClipRect(paintOffset, paintInfo.renderRegion)));
278
279     // Convert from container offsets (html renderers) to a relative transform (svg renderers).
280     // Transform from our paint container's coordinate system to our local coords.
281     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset);
282     childPaintInfo.applyTransform(AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y()) * localToBorderBoxTransform());
283
284     // SVGRenderingContext must be destroyed before we restore the childPaintInfo.context, because a filter may have
285     // changed the context and it is only reverted when the SVGRenderingContext destructor finishes applying the filter.
286     {
287         SVGRenderingContext renderingContext;
288         bool continueRendering = true;
289         if (childPaintInfo.phase == PaintPhaseForeground) {
290             renderingContext.prepareToRenderSVGContent(*this, childPaintInfo);
291             continueRendering = renderingContext.isRenderingPrepared();
292         }
293
294         if (continueRendering) {
295             childPaintInfo.updateSubtreePaintRootForChildren(this);
296             for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
297                 // FIXME: Can this ever have RenderText children?
298                 if (!child->isRenderElement())
299                     continue;
300                 toRenderElement(child)->paint(childPaintInfo, location());
301             }
302         }
303     }
304
305     childPaintInfo.context->restore();
306 }
307
308 void RenderSVGRoot::willBeDestroyed()
309 {
310     RenderBlock::removePercentHeightDescendant(const_cast<RenderSVGRoot&>(*this));
311
312     SVGResourcesCache::clientDestroyed(*this);
313     RenderReplaced::willBeDestroyed();
314 }
315
316 void RenderSVGRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
317 {
318     if (diff == StyleDifferenceLayout)
319         setNeedsBoundariesUpdate();
320     RenderReplaced::styleDidChange(diff, oldStyle);
321     SVGResourcesCache::clientStyleChanged(*this, diff, style());
322 }
323
324 void RenderSVGRoot::addChild(RenderObject* child, RenderObject* beforeChild)
325 {
326     RenderReplaced::addChild(child, beforeChild);
327     SVGResourcesCache::clientWasAddedToTree(*child);
328 }
329
330 void RenderSVGRoot::removeChild(RenderObject& child)
331 {
332     SVGResourcesCache::clientWillBeRemovedFromTree(child);
333     RenderReplaced::removeChild(child);
334 }
335
336 // RenderBox methods will expect coordinates w/o any transforms in coordinates
337 // relative to our borderBox origin.  This method gives us exactly that.
338 void RenderSVGRoot::buildLocalToBorderBoxTransform()
339 {
340     float scale = style().effectiveZoom();
341     SVGPoint translate = svgSVGElement().currentTranslate();
342     LayoutSize borderAndPadding(borderLeft() + paddingLeft(), borderTop() + paddingTop());
343     m_localToBorderBoxTransform = svgSVGElement().viewBoxToViewTransform(contentWidth() / scale, contentHeight() / scale);
344     if (borderAndPadding.isEmpty() && scale == 1 && translate == SVGPoint::zero())
345         return;
346     m_localToBorderBoxTransform = AffineTransform(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y()) * m_localToBorderBoxTransform;
347 }
348
349 const AffineTransform& RenderSVGRoot::localToParentTransform() const
350 {
351     // Slightly optimized version of m_localToParentTransform = AffineTransform::translation(x(), y()) * m_localToBorderBoxTransform;
352     m_localToParentTransform = m_localToBorderBoxTransform;
353     if (x())
354         m_localToParentTransform.setE(m_localToParentTransform.e() + roundToInt(x()));
355     if (y())
356         m_localToParentTransform.setF(m_localToParentTransform.f() + roundToInt(y()));
357     return m_localToParentTransform;
358 }
359
360 LayoutRect RenderSVGRoot::clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const
361 {
362     return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer);
363 }
364
365 void RenderSVGRoot::computeFloatRectForRepaint(const RenderLayerModelObject* repaintContainer, FloatRect& repaintRect, bool fixed) const
366 {
367     // Apply our local transforms (except for x/y translation), then our shadow, 
368     // and then call RenderBox's method to handle all the normal CSS Box model bits
369     repaintRect = m_localToBorderBoxTransform.mapRect(repaintRect);
370
371     const SVGRenderStyle* svgStyle = style().svgStyle();
372     if (const ShadowData* shadow = svgStyle->shadow())
373         shadow->adjustRectForShadow(repaintRect);
374
375     // Apply initial viewport clip - not affected by overflow settings
376     repaintRect.intersect(pixelSnappedBorderBoxRect());
377
378     LayoutRect rect = enclosingIntRect(repaintRect);
379     RenderReplaced::computeRectForRepaint(repaintContainer, rect, fixed);
380     repaintRect = rect;
381 }
382
383 // This method expects local CSS box coordinates.
384 // Callers with local SVG viewport coordinates should first apply the localToBorderBoxTransform
385 // to convert from SVG viewport coordinates to local CSS box coordinates.
386 void RenderSVGRoot::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags mode, bool* wasFixed) const
387 {
388     ASSERT(mode & ~IsFixed); // We should have no fixed content in the SVG rendering tree.
389     ASSERT(mode & UseTransforms); // mapping a point through SVG w/o respecting trasnforms is useless.
390
391     RenderReplaced::mapLocalToContainer(repaintContainer, transformState, mode | ApplyContainerFlip, wasFixed);
392 }
393
394 const RenderObject* RenderSVGRoot::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const
395 {
396     return RenderReplaced::pushMappingToContainer(ancestorToStopAt, geometryMap);
397 }
398
399 void RenderSVGRoot::updateCachedBoundaries()
400 {
401     SVGRenderSupport::computeContainerBoundingBoxes(this, m_objectBoundingBox, m_objectBoundingBoxValid, m_strokeBoundingBox, m_repaintBoundingBoxExcludingShadow);
402     SVGRenderSupport::intersectRepaintRectWithResources(*this, m_repaintBoundingBoxExcludingShadow);
403     m_repaintBoundingBoxExcludingShadow.inflate(borderAndPaddingWidth());
404
405     m_repaintBoundingBox = m_repaintBoundingBoxExcludingShadow;
406     SVGRenderSupport::intersectRepaintRectWithShadows(this, m_repaintBoundingBox);
407 }
408
409 bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
410 {
411     LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset);
412     LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location());
413
414     // Only test SVG content if the point is in our content box.
415     // FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint.
416     if (contentBoxRect().contains(pointInBorderBox)) {
417         FloatPoint localPoint = localToParentTransform().inverse().mapPoint(FloatPoint(pointInParent));
418
419         for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
420             // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
421             if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
422                 updateHitTestResult(result, pointInBorderBox);
423                 if (!result.addNodeToRectBasedTestResult(child->node(), request, locationInContainer))
424                     return true;
425             }
426         }
427     }
428
429     // 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.
430     if (hitTestAction == HitTestBlockBackground && visibleToHitTesting()) {
431         // Only return true here, if the last hit testing phase 'BlockBackground' is executed. If we'd return true in the 'Foreground' phase,
432         // hit testing would stop immediately. For SVG only trees this doesn't matter. Though when we have a <foreignObject> subtree we need
433         // 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 
434         // to detect these hits anymore.
435         LayoutRect boundsRect(accumulatedOffset + location(), size());
436         if (locationInContainer.intersects(boundsRect)) {
437             updateHitTestResult(result, pointInBorderBox);
438             if (!result.addNodeToRectBasedTestResult(&svgSVGElement(), request, locationInContainer, boundsRect))
439                 return true;
440         }
441     }
442
443     return false;
444 }
445
446 bool RenderSVGRoot::hasRelativeDimensions() const
447 {
448     return svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties).isPercent() || svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties).isPercent();
449 }
450
451 bool RenderSVGRoot::hasRelativeIntrinsicLogicalWidth() const
452 {
453     return svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties).isPercent();
454 }
455
456 bool RenderSVGRoot::hasRelativeLogicalHeight() const
457 {
458     return svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties).isPercent();
459 }
460
461 void RenderSVGRoot::addResourceForClientInvalidation(RenderSVGResourceContainer* resource)
462 {
463     RenderObject* svgRoot = resource->parent();
464     while (svgRoot && !svgRoot->isSVGRoot())
465         svgRoot = svgRoot->parent();
466     if (!svgRoot)
467         return;
468     toRenderSVGRoot(svgRoot)->m_resourcesNeedingToInvalidateClients.add(resource);
469 }
470
471 }
472
473 #endif // ENABLE(SVG)