<https://webkit.org/b/120166> Add Traversal<ElementType> template
[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
30 #if ENABLE(SVG)
31 #include "SVGImage.h"
32
33 #include "Chrome.h"
34 #include "DocumentLoader.h"
35 #include "ElementTraversal.h"
36 #include "FrameView.h"
37 #include "ImageBuffer.h"
38 #include "ImageObserver.h"
39 #include "IntRect.h"
40 #include "RenderSVGRoot.h"
41 #include "RenderStyle.h"
42 #include "SVGDocument.h"
43 #include "SVGForeignObjectElement.h"
44 #include "SVGImageChromeClient.h"
45 #include "SVGSVGElement.h"
46 #include "Settings.h"
47
48 namespace WebCore {
49
50 SVGImage::SVGImage(ImageObserver* observer)
51     : Image(observer)
52 {
53 }
54
55 SVGImage::~SVGImage()
56 {
57     if (m_page) {
58         // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
59         OwnPtr<Page> currentPage = m_page.release();
60         currentPage->mainFrame()->loader().frameDetached(); // Break both the loader and view references to the frame
61     }
62
63     // Verify that page teardown destroyed the Chrome
64     ASSERT(!m_chromeClient || !m_chromeClient->image());
65 }
66
67 bool SVGImage::hasSingleSecurityOrigin() const
68 {
69     if (!m_page)
70         return true;
71
72     Frame* frame = m_page->mainFrame();
73     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
74     if (!rootElement)
75         return true;
76
77     // Don't allow foreignObject elements since they can leak information with arbitrary HTML (like spellcheck or control theme).
78     if (Traversal<SVGForeignObjectElement>::firstWithin(rootElement))
79         return false;
80
81     // Because SVG image rendering disallows external resources and links,
82     // these images effectively are restricted to a single security origin.
83     return true;
84 }
85
86 void SVGImage::setContainerSize(const IntSize& size)
87 {
88     if (!m_page || !usesContainerSize())
89         return;
90
91     Frame* frame = m_page->mainFrame();
92     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
93     if (!rootElement)
94         return;
95     RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
96     if (!renderer)
97         return;
98
99     FrameView* view = frameView();
100     view->resize(this->containerSize());
101
102     renderer->setContainerSize(size);
103 }
104
105 IntSize SVGImage::containerSize() const
106 {
107     if (!m_page)
108         return IntSize();
109     Frame* frame = m_page->mainFrame();
110     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
111     if (!rootElement)
112         return IntSize();
113
114     RenderSVGRoot* renderer = toRenderSVGRoot(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->intrinsicWidth().isFixed() && rootElement->intrinsicHeight().isFixed())
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);
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     OwnPtr<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);
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)
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     OwnPtr<ImageBuffer> buffer = ImageBuffer::create(expandedIntSize(imageBufferSize.size()), 1);
202     if (!buffer) // Failed to allocate buffer.
203         return;
204     drawForContainer(buffer->context(), containerSize, zoom, imageBufferSize, zoomedContainerRect, ColorSpaceDeviceRGB, CompositeSourceOver, BlendModeNormal);
205     RefPtr<Image> image = buffer->copyImage(DontCopyBackingStore, Unscaled);
206
207     // Adjust the source rect and transform due to the image buffer's scaling.
208     FloatRect scaledSrcRect = srcRect;
209     scaledSrcRect.scale(imageBufferScale.width(), imageBufferScale.height());
210     AffineTransform unscaledPatternTransform(patternTransform);
211     unscaledPatternTransform.scale(1 / imageBufferScale.width(), 1 / imageBufferScale.height());
212
213     image->drawPattern(context, scaledSrcRect, unscaledPatternTransform, phase, colorSpace, compositeOp, dstRect);
214 }
215
216 void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator compositeOp, BlendMode blendMode)
217 {
218     if (!m_page)
219         return;
220
221     FrameView* view = frameView();
222
223     GraphicsContextStateSaver stateSaver(*context);
224     context->setCompositeOperation(compositeOp, blendMode);
225     context->clip(enclosingIntRect(dstRect));
226     if (compositeOp != CompositeSourceOver)
227         context->beginTransparencyLayer(1);
228
229     FloatSize scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
230     
231     // We can only draw the entire frame, clipped to the rect we want. So compute where the top left
232     // of the image would be if we were drawing without clipping, and translate accordingly.
233     FloatSize topLeftOffset(srcRect.location().x() * scale.width(), srcRect.location().y() * scale.height());
234     FloatPoint destOffset = dstRect.location() - topLeftOffset;
235
236     context->translate(destOffset.x(), destOffset.y());
237     context->scale(scale);
238
239     view->resize(containerSize());
240
241     if (view->needsLayout())
242         view->layout();
243
244     view->paint(context, enclosingIntRect(srcRect));
245
246     if (compositeOp != CompositeSourceOver)
247         context->endTransparencyLayer();
248
249     stateSaver.restore();
250
251     if (imageObserver())
252         imageObserver()->didDraw(this);
253 }
254
255 RenderBox* SVGImage::embeddedContentBox() const
256 {
257     if (!m_page)
258         return 0;
259     Frame* frame = m_page->mainFrame();
260     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
261     if (!rootElement)
262         return 0;
263     return toRenderBox(rootElement->renderer());
264 }
265
266 FrameView* SVGImage::frameView() const
267 {
268     if (!m_page)
269         return 0;
270
271     return m_page->mainFrame()->view();
272 }
273
274 bool SVGImage::hasRelativeWidth() const
275 {
276     if (!m_page)
277         return false;
278     Frame* frame = m_page->mainFrame();
279     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
280     if (!rootElement)
281         return false;
282     return rootElement->intrinsicWidth().isPercent();
283 }
284
285 bool SVGImage::hasRelativeHeight() const
286 {
287     if (!m_page)
288         return false;
289     Frame* frame = m_page->mainFrame();
290     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
291     if (!rootElement)
292         return false;
293     return rootElement->intrinsicHeight().isPercent();
294 }
295
296 void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
297 {
298     if (!m_page)
299         return;
300     Frame* frame = m_page->mainFrame();
301     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
302     if (!rootElement)
303         return;
304
305     intrinsicWidth = rootElement->intrinsicWidth();
306     intrinsicHeight = rootElement->intrinsicHeight();
307     if (rootElement->preserveAspectRatio().align() == SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
308         return;
309
310     intrinsicRatio = rootElement->viewBox().size();
311     if (intrinsicRatio.isEmpty() && intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
312         intrinsicRatio = FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
313 }
314
315 // FIXME: support catchUpIfNecessary.
316 void SVGImage::startAnimation(bool /* catchUpIfNecessary */)
317 {
318     if (!m_page)
319         return;
320     Frame* frame = m_page->mainFrame();
321     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
322     if (!rootElement)
323         return;
324     rootElement->unpauseAnimations();
325     rootElement->setCurrentTime(0);
326 }
327
328 void SVGImage::stopAnimation()
329 {
330     if (!m_page)
331         return;
332     Frame* frame = m_page->mainFrame();
333     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
334     if (!rootElement)
335         return;
336     rootElement->pauseAnimations();
337 }
338
339 void SVGImage::resetAnimation()
340 {
341     stopAnimation();
342 }
343
344 bool SVGImage::dataChanged(bool allDataReceived)
345 {
346     // Don't do anything if is an empty image.
347     if (!data()->size())
348         return true;
349
350     if (allDataReceived) {
351         static FrameLoaderClient* dummyFrameLoaderClient =  new EmptyFrameLoaderClient;
352
353         Page::PageClients pageClients;
354         fillWithEmptyClients(pageClients);
355         m_chromeClient = adoptPtr(new SVGImageChromeClient(this));
356         pageClients.chromeClient = m_chromeClient.get();
357
358         // FIXME: If this SVG ends up loading itself, we might leak the world.
359         // The Cache code does not know about CachedImages holding Frames and
360         // won't know to break the cycle.
361         // This will become an issue when SVGImage will be able to load other
362         // SVGImage objects, but we're safe now, because SVGImage can only be
363         // loaded by a top-level document.
364         m_page = adoptPtr(new Page(pageClients));
365         m_page->settings().setMediaEnabled(false);
366         m_page->settings().setScriptEnabled(false);
367         m_page->settings().setPluginsEnabled(false);
368
369         RefPtr<Frame> frame = Frame::create(m_page.get(), 0, dummyFrameLoaderClient);
370         frame->setView(FrameView::create(frame.get()));
371         frame->init();
372         FrameLoader& loader = frame->loader();
373         loader.forceSandboxFlags(SandboxAll);
374
375         frame->view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
376         frame->view()->setTransparent(true); // SVG Images are transparent.
377
378         ASSERT(loader.activeDocumentLoader()); // DocumentLoader should have been created by frame->init().
379         loader.activeDocumentLoader()->writer()->setMIMEType("image/svg+xml");
380         loader.activeDocumentLoader()->writer()->begin(KURL()); // create the empty document
381         loader.activeDocumentLoader()->writer()->addData(data()->data(), data()->size());
382         loader.activeDocumentLoader()->writer()->end();
383
384         // Set the intrinsic size before a container size is available.
385         m_intrinsicSize = containerSize();
386     }
387
388     return m_page;
389 }
390
391 String SVGImage::filenameExtension() const
392 {
393     return "svg";
394 }
395
396 bool isInSVGImage(const Element* element)
397 {
398     ASSERT(element);
399
400     Page* page = element->document()->page();
401     if (!page)
402         return false;
403
404     return page->chrome().client().isSVGImageChromeClient();
405 }
406
407 }
408
409 #endif // ENABLE(SVG)