Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 #include "core/svg/graphics/SVGImage.h"
31
32 #include "core/animation/AnimationTimeline.h"
33 #include "core/dom/NodeTraversal.h"
34 #include "core/dom/shadow/ComposedTreeWalker.h"
35 #include "core/frame/FrameView.h"
36 #include "core/frame/LocalFrame.h"
37 #include "core/frame/Settings.h"
38 #include "core/loader/FrameLoadRequest.h"
39 #include "core/page/Chrome.h"
40 #include "core/rendering/style/RenderStyle.h"
41 #include "core/rendering/svg/RenderSVGRoot.h"
42 #include "core/svg/SVGDocumentExtensions.h"
43 #include "core/svg/SVGFEImageElement.h"
44 #include "core/svg/SVGImageElement.h"
45 #include "core/svg/SVGSVGElement.h"
46 #include "core/svg/animation/SMILTimeContainer.h"
47 #include "core/svg/graphics/SVGImageChromeClient.h"
48 #include "platform/EventDispatchForbiddenScope.h"
49 #include "platform/LengthFunctions.h"
50 #include "platform/TraceEvent.h"
51 #include "platform/geometry/IntRect.h"
52 #include "platform/graphics/DisplayList.h"
53 #include "platform/graphics/GraphicsContextStateSaver.h"
54 #include "platform/graphics/ImageBuffer.h"
55 #include "platform/graphics/ImageObserver.h"
56 #include "wtf/PassRefPtr.h"
57
58 namespace blink {
59
60 SVGImage::SVGImage(ImageObserver* observer)
61     : Image(observer)
62 {
63 }
64
65 SVGImage::~SVGImage()
66 {
67     if (m_page) {
68         // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
69         OwnPtrWillBeRawPtr<Page> currentPage = m_page.release();
70         // Break both the loader and view references to the frame
71         currentPage->willBeDestroyed();
72     }
73
74     // Verify that page teardown destroyed the Chrome
75     ASSERT(!m_chromeClient || !m_chromeClient->image());
76 }
77
78 bool SVGImage::isInSVGImage(const Node* node)
79 {
80     ASSERT(node);
81
82     Page* page = node->document().page();
83     if (!page)
84         return false;
85
86     return page->chrome().client().isSVGImageChromeClient();
87 }
88
89 bool SVGImage::currentFrameHasSingleSecurityOrigin() const
90 {
91     if (!m_page)
92         return true;
93
94     LocalFrame* frame = toLocalFrame(m_page->mainFrame());
95
96     RELEASE_ASSERT(frame->document()->loadEventFinished());
97
98     SVGSVGElement* rootElement = frame->document()->accessSVGExtensions().rootElement();
99     if (!rootElement)
100         return true;
101
102     // Don't allow foreignObject elements or images that are not known to be
103     // single-origin since these can leak cross-origin information.
104     ComposedTreeWalker walker(rootElement);
105     while (Node* node = walker.get()) {
106         if (isSVGForeignObjectElement(*node))
107             return false;
108         if (isSVGImageElement(*node)) {
109             if (!toSVGImageElement(*node).currentFrameHasSingleSecurityOrigin())
110                 return false;
111         } else if (isSVGFEImageElement(*node)) {
112             if (!toSVGFEImageElement(*node).currentFrameHasSingleSecurityOrigin())
113                 return false;
114         }
115         walker.next();
116     }
117
118     // Because SVG image rendering disallows external resources and links, these
119     // images effectively are restricted to a single security origin.
120     return true;
121 }
122
123 static SVGSVGElement* svgRootElement(Page* page)
124 {
125     if (!page)
126         return 0;
127     LocalFrame* frame = toLocalFrame(page->mainFrame());
128     return frame->document()->accessSVGExtensions().rootElement();
129 }
130
131 void SVGImage::setContainerSize(const IntSize& size)
132 {
133     if (!usesContainerSize())
134         return;
135
136     SVGSVGElement* rootElement = svgRootElement(m_page.get());
137     if (!rootElement)
138         return;
139
140     FrameView* view = frameView();
141     view->resize(this->containerSize());
142
143     RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
144     if (!renderer)
145         return;
146     renderer->setContainerSize(size);
147 }
148
149 IntSize SVGImage::containerSize() const
150 {
151     SVGSVGElement* rootElement = svgRootElement(m_page.get());
152     if (!rootElement)
153         return IntSize();
154
155     RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
156     if (!renderer)
157         return IntSize();
158
159     // If a container size is available it has precedence.
160     IntSize containerSize = renderer->containerSize();
161     if (!containerSize.isEmpty())
162         return containerSize;
163
164     // Assure that a container size is always given for a non-identity zoom level.
165     ASSERT(renderer->style()->effectiveZoom() == 1);
166
167     FloatSize intrinsicSize;
168     double intrinsicRatio = 0;
169     renderer->computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio);
170
171     if (intrinsicSize.isEmpty() && intrinsicRatio) {
172         if (!intrinsicSize.width() && intrinsicSize.height())
173             intrinsicSize.setWidth(intrinsicSize.height() * intrinsicRatio);
174         else if (intrinsicSize.width() && !intrinsicSize.height())
175             intrinsicSize.setHeight(intrinsicSize.width() / intrinsicRatio);
176     }
177
178     if (!intrinsicSize.isEmpty())
179         return expandedIntSize(intrinsicSize);
180
181     // As last resort, use CSS replaced element fallback size.
182     return IntSize(300, 150);
183 }
184
185 void SVGImage::drawForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& dstRect,
186     const FloatRect& srcRect, CompositeOperator compositeOp, blink::WebBlendMode blendMode)
187 {
188     if (!m_page)
189         return;
190
191     // Temporarily disable the image observer to prevent changeInRect() calls due re-laying out the image.
192     ImageObserverDisabler imageObserverDisabler(this);
193
194     IntSize roundedContainerSize = roundedIntSize(containerSize);
195     setContainerSize(roundedContainerSize);
196
197     FloatRect scaledSrc = srcRect;
198     scaledSrc.scale(1 / zoom);
199
200     // Compensate for the container size rounding by adjusting the source rect.
201     FloatSize adjustedSrcSize = scaledSrc.size();
202     adjustedSrcSize.scale(roundedContainerSize.width() / containerSize.width(), roundedContainerSize.height() / containerSize.height());
203     scaledSrc.setSize(adjustedSrcSize);
204
205     draw(context, dstRect, scaledSrc, compositeOp, blendMode);
206 }
207
208 PassRefPtr<NativeImageSkia> SVGImage::nativeImageForCurrentFrame()
209 {
210     if (!m_page)
211         return nullptr;
212
213     OwnPtr<ImageBuffer> buffer = ImageBuffer::create(size());
214     if (!buffer)
215         return nullptr;
216
217     drawForContainer(buffer->context(), size(), 1, rect(), rect(), CompositeSourceOver, blink::WebBlendModeNormal);
218
219     // FIXME: WK(Bug 113657): We should use DontCopyBackingStore here.
220     return buffer->copyImage(CopyBackingStore)->nativeImageForCurrentFrame();
221 }
222
223 void SVGImage::drawPatternForContainer(GraphicsContext* context, const FloatSize containerSize,
224     float zoom, const FloatRect& srcRect, const FloatSize& tileScale, const FloatPoint& phase,
225     CompositeOperator compositeOp, const FloatRect& dstRect, blink::WebBlendMode blendMode,
226     const IntSize& repeatSpacing)
227 {
228     // Tile adjusted for scaling/stretch.
229     FloatRect tile(srcRect);
230     tile.scale(tileScale.width(), tileScale.height());
231
232     // Expand the tile to account for repeat spacing.
233     FloatRect spacedTile(tile);
234     spacedTile.expand(repeatSpacing);
235
236     // Record using a dedicated GC, to avoid inheriting unwanted state (pending color filters
237     // for example must be applied atomically during the final fill/composite phase).
238     GraphicsContext recordingContext(0);
239     recordingContext.beginRecording(spacedTile);
240     // When generating an expanded tile, make sure we don't draw into the spacing area.
241     if (tile != spacedTile)
242         recordingContext.clipRect(tile);
243     drawForContainer(&recordingContext, containerSize, zoom, tile, srcRect, CompositeSourceOver,
244         blink::WebBlendModeNormal);
245     RefPtr<DisplayList> tileDisplayList = recordingContext.endRecording();
246
247     SkMatrix patternTransform;
248     patternTransform.setTranslate(phase.x() + tile.x(), phase.y() + tile.y());
249     SkRect tileRect = SkRect::MakeWH(spacedTile.width(), spacedTile.height());
250     RefPtr<SkShader> patternShader = adoptRef(SkShader::CreatePictureShader(
251         tileDisplayList->picture().get(), SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
252         &patternTransform, &tileRect));
253
254     SkPaint paint;
255     paint.setShader(patternShader.get());
256     paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode));
257     paint.setColorFilter(context->colorFilter());
258     context->drawRect(dstRect, paint);
259 }
260
261 void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp, blink::WebBlendMode blendMode)
262 {
263     if (!m_page)
264         return;
265
266     GraphicsContextStateSaver stateSaver(*context);
267     context->setCompositeOperation(compositeOp, blendMode);
268     context->clip(enclosingIntRect(dstRect));
269
270     bool compositingRequiresTransparencyLayer = compositeOp != CompositeSourceOver || blendMode != blink::WebBlendModeNormal;
271     float opacity = context->getNormalizedAlpha() / 255.f;
272     bool requiresTransparencyLayer = compositingRequiresTransparencyLayer || opacity < 1;
273     if (requiresTransparencyLayer) {
274         context->beginTransparencyLayer(opacity);
275         if (compositingRequiresTransparencyLayer)
276             context->setCompositeOperation(CompositeSourceOver, blink::WebBlendModeNormal);
277     }
278
279     FloatSize scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
280
281     // We can only draw the entire frame, clipped to the rect we want. So compute where the top left
282     // of the image would be if we were drawing without clipping, and translate accordingly.
283     FloatSize topLeftOffset(srcRect.location().x() * scale.width(), srcRect.location().y() * scale.height());
284     FloatPoint destOffset = dstRect.location() - topLeftOffset;
285
286     context->translate(destOffset.x(), destOffset.y());
287     context->scale(scale.width(), scale.height());
288
289     FrameView* view = frameView();
290     view->resize(containerSize());
291
292     if (!m_url.isEmpty())
293         view->scrollToFragment(m_url);
294
295     view->updateLayoutAndStyleForPainting();
296     view->paint(context, enclosingIntRect(srcRect));
297     ASSERT(!view->needsLayout());
298
299     if (requiresTransparencyLayer)
300         context->endLayer();
301
302     stateSaver.restore();
303
304     if (imageObserver())
305         imageObserver()->didDraw(this);
306
307     // Start any (SMIL) animations if needed. This will restart or continue
308     // animations if preceded by calls to resetAnimation or stopAnimation
309     // respectively.
310     startAnimation();
311 }
312
313 RenderBox* SVGImage::embeddedContentBox() const
314 {
315     SVGSVGElement* rootElement = svgRootElement(m_page.get());
316     if (!rootElement)
317         return 0;
318     return toRenderBox(rootElement->renderer());
319 }
320
321 FrameView* SVGImage::frameView() const
322 {
323     if (!m_page)
324         return 0;
325
326     return toLocalFrame(m_page->mainFrame())->view();
327 }
328
329 void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
330 {
331     SVGSVGElement* rootElement = svgRootElement(m_page.get());
332     if (!rootElement)
333         return;
334
335     intrinsicWidth = rootElement->intrinsicWidth();
336     intrinsicHeight = rootElement->intrinsicHeight();
337     if (rootElement->preserveAspectRatio()->currentValue()->align() == SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
338         return;
339
340     intrinsicRatio = rootElement->viewBox()->currentValue()->value().size();
341     if (intrinsicRatio.isEmpty() && intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
342         intrinsicRatio = FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
343 }
344
345 // FIXME: support CatchUpAnimation = CatchUp.
346 void SVGImage::startAnimation(CatchUpAnimation)
347 {
348     SVGSVGElement* rootElement = svgRootElement(m_page.get());
349     if (!rootElement || !rootElement->animationsPaused())
350         return;
351     rootElement->unpauseAnimations();
352 }
353
354 void SVGImage::stopAnimation()
355 {
356     SVGSVGElement* rootElement = svgRootElement(m_page.get());
357     if (!rootElement)
358         return;
359     rootElement->pauseAnimations();
360 }
361
362 void SVGImage::resetAnimation()
363 {
364     SVGSVGElement* rootElement = svgRootElement(m_page.get());
365     if (!rootElement)
366         return;
367     rootElement->pauseAnimations();
368     rootElement->setCurrentTime(0);
369 }
370
371 bool SVGImage::hasAnimations() const
372 {
373     SVGSVGElement* rootElement = svgRootElement(m_page.get());
374     if (!rootElement)
375         return false;
376     return rootElement->timeContainer()->hasAnimations() || toLocalFrame(m_page->mainFrame())->document()->timeline().hasPendingUpdates();
377 }
378
379 bool SVGImage::dataChanged(bool allDataReceived)
380 {
381     TRACE_EVENT0("blink", "SVGImage::dataChanged");
382
383     // Don't do anything if is an empty image.
384     if (!data()->size())
385         return true;
386
387     if (allDataReceived) {
388         // SVGImage will fire events (and the default C++ handlers run) but doesn't
389         // actually allow script to run so it's fine to call into it. We allow this
390         // since it means an SVG data url can synchronously load like other image
391         // types.
392         EventDispatchForbiddenScope::AllowUserAgentEvents allowUserAgentEvents;
393
394         static FrameLoaderClient* dummyFrameLoaderClient = new EmptyFrameLoaderClient;
395
396         Page::PageClients pageClients;
397         fillWithEmptyClients(pageClients);
398         m_chromeClient = adoptPtr(new SVGImageChromeClient(this));
399         pageClients.chromeClient = m_chromeClient.get();
400
401         // FIXME: If this SVG ends up loading itself, we might leak the world.
402         // The Cache code does not know about ImageResources holding Frames and
403         // won't know to break the cycle.
404         // This will become an issue when SVGImage will be able to load other
405         // SVGImage objects, but we're safe now, because SVGImage can only be
406         // loaded by a top-level document.
407         OwnPtrWillBeRawPtr<Page> page;
408         {
409             TRACE_EVENT0("blink", "SVGImage::dataChanged::createPage");
410             page = adoptPtrWillBeNoop(new Page(pageClients));
411             page->settings().setScriptEnabled(false);
412             page->settings().setPluginsEnabled(false);
413             page->settings().setAcceleratedCompositingEnabled(false);
414         }
415
416         RefPtrWillBeRawPtr<LocalFrame> frame = nullptr;
417         {
418             TRACE_EVENT0("blink", "SVGImage::dataChanged::createFrame");
419             frame = LocalFrame::create(dummyFrameLoaderClient, &page->frameHost(), 0);
420             frame->setView(FrameView::create(frame.get()));
421             frame->init();
422         }
423
424         FrameLoader& loader = frame->loader();
425         loader.forceSandboxFlags(SandboxAll);
426
427         frame->view()->setScrollbarsSuppressed(true);
428         frame->view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
429         frame->view()->setTransparent(true); // SVG Images are transparent.
430
431         m_page = page.release();
432
433         TRACE_EVENT0("blink", "SVGImage::dataChanged::load");
434         loader.load(FrameLoadRequest(0, blankURL(), SubstituteData(data(), AtomicString("image/svg+xml", AtomicString::ConstructFromLiteral),
435             AtomicString("UTF-8", AtomicString::ConstructFromLiteral), KURL(), ForceSynchronousLoad)));
436         // Set the intrinsic size before a container size is available.
437         m_intrinsicSize = containerSize();
438     }
439
440     return m_page;
441 }
442
443 String SVGImage::filenameExtension() const
444 {
445     return "svg";
446 }
447
448 }