Use modern for-loops in WebCore/svg.
[WebKit-https.git] / Source / WebCore / svg / graphics / SVGImage.cpp
1 /*
2  * Copyright (C) 2006 Eric Seidel <eric@webkit.org>
3  * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
4  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
26  */
27
28 #include "config.h"
29 #include "SVGImage.h"
30
31 #include "Chrome.h"
32 #include "DocumentLoader.h"
33 #include "ElementIterator.h"
34 #include "FrameLoader.h"
35 #include "FrameView.h"
36 #include "ImageBuffer.h"
37 #include "ImageObserver.h"
38 #include "IntRect.h"
39 #include "MainFrame.h"
40 #include "PageConfiguration.h"
41 #include "RenderSVGRoot.h"
42 #include "RenderStyle.h"
43 #include "SVGDocument.h"
44 #include "SVGForeignObjectElement.h"
45 #include "SVGImageClients.h"
46 #include "SVGSVGElement.h"
47 #include "Settings.h"
48
49 namespace WebCore {
50
51 SVGImage::SVGImage(ImageObserver* observer)
52     : Image(observer)
53 {
54 }
55
56 SVGImage::~SVGImage()
57 {
58     if (m_page) {
59         // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
60         std::unique_ptr<Page> currentPage = WTF::move(m_page);
61         currentPage->mainFrame().loader().frameDetached(); // Break both the loader and view references to the frame
62     }
63
64     // Verify that page teardown destroyed the Chrome
65     ASSERT(!m_chromeClient || !m_chromeClient->image());
66 }
67
68 inline SVGSVGElement* SVGImage::rootElement() const
69 {
70     if (!m_page)
71         return nullptr;
72     return downcast<SVGDocument>(*m_page->mainFrame().document()).rootElement();
73 }
74
75 bool SVGImage::hasSingleSecurityOrigin() const
76 {
77     SVGSVGElement* rootElement = this->rootElement();
78     if (!rootElement)
79         return true;
80
81     // Don't allow foreignObject elements since they can leak information with arbitrary HTML (like spellcheck or control theme).
82     if (descendantsOfType<SVGForeignObjectElement>(*rootElement).first())
83         return false;
84
85     // Because SVG image rendering disallows external resources and links,
86     // these images effectively are restricted to a single security origin.
87     return true;
88 }
89
90 void SVGImage::setContainerSize(const FloatSize& size)
91 {
92     if (!usesContainerSize())
93         return;
94
95     SVGSVGElement* rootElement = this->rootElement();
96     if (!rootElement)
97         return;
98     auto* renderer = downcast<RenderSVGRoot>(rootElement->renderer());
99     if (!renderer)
100         return;
101
102     FrameView* view = frameView();
103     view->resize(this->containerSize());
104
105     renderer->setContainerSize(IntSize(size));
106 }
107
108 IntSize SVGImage::containerSize() const
109 {
110     SVGSVGElement* rootElement = this->rootElement();
111     if (!rootElement)
112         return IntSize();
113
114     auto* renderer = downcast<RenderSVGRoot>(rootElement->renderer());
115     if (!renderer)
116         return IntSize();
117
118     // If a container size is available it has precedence.
119     IntSize containerSize = renderer->containerSize();
120     if (!containerSize.isEmpty())
121         return containerSize;
122
123     // Assure that a container size is always given for a non-identity zoom level.
124     ASSERT(renderer->style().effectiveZoom() == 1);
125
126     FloatSize currentSize;
127     if (rootElement->hasIntrinsicWidth() && rootElement->hasIntrinsicHeight())
128         currentSize = rootElement->currentViewportSize();
129     else
130         currentSize = rootElement->currentViewBoxRect().size();
131
132     if (!currentSize.isEmpty())
133         return IntSize(static_cast<int>(ceilf(currentSize.width())), static_cast<int>(ceilf(currentSize.height())));
134
135     // As last resort, use CSS default intrinsic size.
136     return IntSize(300, 150);
137 }
138
139 void SVGImage::drawForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& dstRect,
140     const FloatRect& srcRect, ColorSpace colorSpace, CompositeOperator compositeOp, BlendMode blendMode)
141 {
142     if (!m_page)
143         return;
144
145     ImageObserver* observer = imageObserver();
146     ASSERT(observer);
147
148     // Temporarily reset image observer, we don't want to receive any changeInRect() calls due to this relayout.
149     setImageObserver(0);
150
151     IntSize roundedContainerSize = roundedIntSize(containerSize);
152     setContainerSize(roundedContainerSize);
153
154     FloatRect scaledSrc = srcRect;
155     scaledSrc.scale(1 / zoom);
156
157     // Compensate for the container size rounding by adjusting the source rect.
158     FloatSize adjustedSrcSize = scaledSrc.size();
159     adjustedSrcSize.scale(roundedContainerSize.width() / containerSize.width(), roundedContainerSize.height() / containerSize.height());
160     scaledSrc.setSize(adjustedSrcSize);
161
162     draw(context, dstRect, scaledSrc, colorSpace, compositeOp, blendMode, ImageOrientationDescription());
163
164     setImageObserver(observer);
165 }
166
167 #if USE(CAIRO)
168 // Passes ownership of the native image to the caller so PassNativeImagePtr needs
169 // to be a smart pointer type.
170 PassNativeImagePtr SVGImage::nativeImageForCurrentFrame()
171 {
172     if (!m_page)
173         return 0;
174
175     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(size(), 1);
176     if (!buffer) // failed to allocate image
177         return 0;
178
179     draw(buffer->context(), rect(), rect(), ColorSpaceDeviceRGB, CompositeSourceOver, BlendModeNormal, ImageOrientationDescription());
180
181     // FIXME: WK(Bug 113657): We should use DontCopyBackingStore here.
182     return buffer->copyImage(CopyBackingStore)->nativeImageForCurrentFrame();
183 }
184 #endif
185
186 void SVGImage::drawPatternForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& srcRect,
187     const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace colorSpace, CompositeOperator compositeOp, const FloatRect& dstRect, BlendMode blendMode)
188 {
189     FloatRect zoomedContainerRect = FloatRect(FloatPoint(), containerSize);
190     zoomedContainerRect.scale(zoom);
191
192     // The ImageBuffer size needs to be scaled to match the final resolution.
193     AffineTransform transform = context->getCTM();
194     FloatSize imageBufferScale = FloatSize(transform.xScale(), transform.yScale());
195     ASSERT(imageBufferScale.width());
196     ASSERT(imageBufferScale.height());
197
198     FloatRect imageBufferSize = zoomedContainerRect;
199     imageBufferSize.scale(imageBufferScale.width(), imageBufferScale.height());
200
201     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(imageBufferSize.size()), 1, ColorSpaceDeviceRGB, context, true);
202     if (!buffer) // Failed to allocate buffer.
203         return;
204     drawForContainer(buffer->context(), containerSize, zoom, imageBufferSize, zoomedContainerRect, ColorSpaceDeviceRGB, CompositeSourceOver, BlendModeNormal);
205     if (context->drawLuminanceMask())
206         buffer->convertToLuminanceMask();
207
208     RefPtr<Image> image = buffer->copyImage(DontCopyBackingStore, Unscaled);
209     image->setSpaceSize(spaceSize());
210
211     // Adjust the source rect and transform due to the image buffer's scaling.
212     FloatRect scaledSrcRect = srcRect;
213     scaledSrcRect.scale(imageBufferScale.width(), imageBufferScale.height());
214     AffineTransform unscaledPatternTransform(patternTransform);
215     unscaledPatternTransform.scale(1 / imageBufferScale.width(), 1 / imageBufferScale.height());
216
217     context->setDrawLuminanceMask(false);
218     image->drawPattern(context, scaledSrcRect, unscaledPatternTransform, phase, colorSpace, compositeOp, dstRect, blendMode);
219 }
220
221 void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator compositeOp, BlendMode blendMode, ImageOrientationDescription)
222 {
223     if (!m_page)
224         return;
225
226     FrameView* view = frameView();
227     ASSERT(view);
228
229     GraphicsContextStateSaver stateSaver(*context);
230     context->setCompositeOperation(compositeOp, blendMode);
231     context->clip(enclosingIntRect(dstRect));
232
233     float alpha = context->alpha();
234     bool compositingRequiresTransparencyLayer = compositeOp != CompositeSourceOver || blendMode != BlendModeNormal || alpha < 1;
235     if (compositingRequiresTransparencyLayer) {
236         context->beginTransparencyLayer(alpha);
237         context->setCompositeOperation(CompositeSourceOver, BlendModeNormal);
238     }
239
240     FloatSize scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
241     
242     // We can only draw the entire frame, clipped to the rect we want. So compute where the top left
243     // of the image would be if we were drawing without clipping, and translate accordingly.
244     FloatSize topLeftOffset(srcRect.location().x() * scale.width(), srcRect.location().y() * scale.height());
245     FloatPoint destOffset = dstRect.location() - topLeftOffset;
246
247     context->translate(destOffset.x(), destOffset.y());
248     context->scale(scale);
249
250     view->resize(containerSize());
251
252     if (view->needsLayout())
253         view->layout();
254
255     view->paint(context, intersection(context->clipBounds(), enclosingIntRect(srcRect)));
256
257     if (compositingRequiresTransparencyLayer)
258         context->endTransparencyLayer();
259
260     stateSaver.restore();
261
262     if (!m_url.isEmpty())
263         view->scrollToFragment(m_url);
264
265     if (imageObserver())
266         imageObserver()->didDraw(this);
267 }
268
269 RenderBox* SVGImage::embeddedContentBox() const
270 {
271     SVGSVGElement* rootElement = this->rootElement();
272     if (!rootElement)
273         return nullptr;
274     return downcast<RenderBox>(rootElement->renderer());
275 }
276
277 FrameView* SVGImage::frameView() const
278 {
279     if (!m_page)
280         return nullptr;
281     return m_page->mainFrame().view();
282 }
283
284 bool SVGImage::hasRelativeWidth() const
285 {
286     SVGSVGElement* rootElement = this->rootElement();
287     if (!rootElement)
288         return false;
289     return rootElement->intrinsicWidth().isPercentOrCalculated();
290 }
291
292 bool SVGImage::hasRelativeHeight() const
293 {
294     SVGSVGElement* rootElement = this->rootElement();
295     if (!rootElement)
296         return false;
297     return rootElement->intrinsicHeight().isPercentOrCalculated();
298 }
299
300 void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
301 {
302     SVGSVGElement* rootElement = this->rootElement();
303     if (!rootElement)
304         return;
305
306     intrinsicWidth = rootElement->intrinsicWidth();
307     intrinsicHeight = rootElement->intrinsicHeight();
308     if (rootElement->preserveAspectRatio().align() == SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
309         return;
310
311     intrinsicRatio = rootElement->viewBox().size();
312     if (intrinsicRatio.isEmpty() && intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
313         intrinsicRatio = FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
314 }
315
316 // FIXME: support catchUpIfNecessary.
317 void SVGImage::startAnimation(CatchUpAnimation)
318 {
319     SVGSVGElement* rootElement = this->rootElement();
320     if (!rootElement)
321         return;
322     rootElement->unpauseAnimations();
323     rootElement->setCurrentTime(0);
324 }
325
326 void SVGImage::stopAnimation()
327 {
328     SVGSVGElement* rootElement = this->rootElement();
329     if (!rootElement)
330         return;
331     rootElement->pauseAnimations();
332 }
333
334 void SVGImage::resetAnimation()
335 {
336     stopAnimation();
337 }
338
339 bool SVGImage::dataChanged(bool allDataReceived)
340 {
341     // Don't do anything if is an empty image.
342     if (!data()->size())
343         return true;
344
345     if (allDataReceived) {
346         PageConfiguration pageConfiguration;
347         fillWithEmptyClients(pageConfiguration);
348         m_chromeClient = std::make_unique<SVGImageChromeClient>(this);
349         pageConfiguration.chromeClient = m_chromeClient.get();
350         m_loaderClient = std::make_unique<SVGFrameLoaderClient>(m_dataProtocolLoader);
351         pageConfiguration.loaderClientForMainFrame = m_loaderClient.get();
352
353         bool canHaveScrollbars = false; // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
354         bool transparent = true; // SVG Images are transparent.
355
356         // FIXME: If this SVG ends up loading itself, we might leak the world.
357         // The Cache code does not know about CachedImages holding Frames and
358         // won't know to break the cycle.
359         // This will become an issue when SVGImage will be able to load other
360         // SVGImage objects, but we're safe now, because SVGImage can only be
361         // loaded by a top-level document.
362         m_page = Page::createPageFromBuffer(pageConfiguration, data(), "image/svg+xml", canHaveScrollbars, transparent);
363
364         // Set the intrinsic size before a container size is available.
365         m_intrinsicSize = containerSize();
366     }
367
368     return m_page != nullptr;
369 }
370
371 String SVGImage::filenameExtension() const
372 {
373     return "svg";
374 }
375
376 bool isInSVGImage(const Element* element)
377 {
378     ASSERT(element);
379
380     Page* page = element->document().page();
381     if (!page)
382         return false;
383
384     return page->chrome().client().isSVGImageChromeClient();
385 }
386
387 }