[Win][Direct2D] Implement basic SVG support
[WebKit-https.git] / Source / WebCore / svg / graphics / SVGImage.cpp
1 /*
2  * Copyright (C) 2006 Eric Seidel <eric@webkit.org>
3  * Copyright (C) 2008, 2009, 2015 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 "DOMWindow.h"
33 #include "DocumentLoader.h"
34 #include "ElementIterator.h"
35 #include "FrameLoader.h"
36 #include "FrameView.h"
37 #include "ImageBuffer.h"
38 #include "ImageObserver.h"
39 #include "IntRect.h"
40 #include "JSDOMWindowBase.h"
41 #include "MainFrame.h"
42 #include "Page.h"
43 #include "PageConfiguration.h"
44 #include "RenderSVGRoot.h"
45 #include "RenderStyle.h"
46 #include "SVGDocument.h"
47 #include "SVGFEImageElement.h"
48 #include "SVGForeignObjectElement.h"
49 #include "SVGImageClients.h"
50 #include "SVGImageElement.h"
51 #include "SVGSVGElement.h"
52 #include "Settings.h"
53 #include "TextStream.h"
54 #include <runtime/JSCInlines.h>
55 #include <runtime/JSLock.h>
56
57 #if USE(DIRECT2D)
58 #include "COMPtr.h"
59 #include <d2d1.h>
60 #endif
61
62 namespace WebCore {
63
64 SVGImage::SVGImage(ImageObserver& observer, const URL& url)
65     : Image(&observer)
66     , m_url(url)
67 {
68 }
69
70 SVGImage::~SVGImage()
71 {
72     if (m_page) {
73         // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
74         std::unique_ptr<Page> currentPage = WTFMove(m_page);
75         currentPage->mainFrame().loader().frameDetached(); // Break both the loader and view references to the frame
76     }
77
78     // Verify that page teardown destroyed the Chrome
79     ASSERT(!m_chromeClient || !m_chromeClient->image());
80 }
81
82 inline SVGSVGElement* SVGImage::rootElement() const
83 {
84     if (!m_page)
85         return nullptr;
86     return SVGDocument::rootElement(*m_page->mainFrame().document());
87 }
88
89 bool SVGImage::hasSingleSecurityOrigin() const
90 {
91     SVGSVGElement* rootElement = this->rootElement();
92     if (!rootElement)
93         return true;
94
95     // FIXME: Once foreignObject elements within SVG images are updated to not leak cross-origin data
96     // (e.g., visited links, spellcheck) we can remove the SVGForeignObjectElement check here and
97     // research if we can remove the Image::hasSingleSecurityOrigin mechanism entirely.
98     for (auto& element : descendantsOfType<SVGElement>(*rootElement)) {
99         if (is<SVGForeignObjectElement>(element))
100             return false;
101         if (is<SVGImageElement>(element)) {
102             if (!downcast<SVGImageElement>(element).hasSingleSecurityOrigin())
103                 return false;
104         } else if (is<SVGFEImageElement>(element)) {
105             if (!downcast<SVGFEImageElement>(element).hasSingleSecurityOrigin())
106                 return false;
107         }
108     }
109
110     // Because SVG image rendering disallows external resources and links,
111     // these images effectively are restricted to a single security origin.
112     return true;
113 }
114
115 void SVGImage::setContainerSize(const FloatSize& size)
116 {
117     if (!usesContainerSize())
118         return;
119
120     SVGSVGElement* rootElement = this->rootElement();
121     if (!rootElement)
122         return;
123     auto* renderer = downcast<RenderSVGRoot>(rootElement->renderer());
124     if (!renderer)
125         return;
126
127     FrameView* view = frameView();
128     view->resize(this->containerSize());
129
130     renderer->setContainerSize(IntSize(size));
131 }
132
133 IntSize SVGImage::containerSize() const
134 {
135     SVGSVGElement* rootElement = this->rootElement();
136     if (!rootElement)
137         return IntSize();
138
139     auto* renderer = downcast<RenderSVGRoot>(rootElement->renderer());
140     if (!renderer)
141         return IntSize();
142
143     // If a container size is available it has precedence.
144     IntSize containerSize = renderer->containerSize();
145     if (!containerSize.isEmpty())
146         return containerSize;
147
148     // Assure that a container size is always given for a non-identity zoom level.
149     ASSERT(renderer->style().effectiveZoom() == 1);
150
151     FloatSize currentSize;
152     if (rootElement->hasIntrinsicWidth() && rootElement->hasIntrinsicHeight())
153         currentSize = rootElement->currentViewportSize();
154     else
155         currentSize = rootElement->currentViewBoxRect().size();
156
157     if (!currentSize.isEmpty())
158         return IntSize(static_cast<int>(ceilf(currentSize.width())), static_cast<int>(ceilf(currentSize.height())));
159
160     // As last resort, use CSS default intrinsic size.
161     return IntSize(300, 150);
162 }
163
164 void SVGImage::drawForContainer(GraphicsContext& context, const FloatSize containerSize, float zoom, const FloatRect& dstRect,
165     const FloatRect& srcRect, CompositeOperator compositeOp, BlendMode blendMode)
166 {
167     if (!m_page)
168         return;
169
170     ImageObserver* observer = imageObserver();
171     ASSERT(observer);
172
173     // Temporarily reset image observer, we don't want to receive any changeInRect() calls due to this relayout.
174     setImageObserver(nullptr);
175
176     IntSize roundedContainerSize = roundedIntSize(containerSize);
177     setContainerSize(roundedContainerSize);
178
179     FloatRect scaledSrc = srcRect;
180     scaledSrc.scale(1 / zoom);
181
182     // Compensate for the container size rounding by adjusting the source rect.
183     FloatSize adjustedSrcSize = scaledSrc.size();
184     adjustedSrcSize.scale(roundedContainerSize.width() / containerSize.width(), roundedContainerSize.height() / containerSize.height());
185     scaledSrc.setSize(adjustedSrcSize);
186
187     draw(context, dstRect, scaledSrc, compositeOp, blendMode, ImageOrientationDescription());
188
189     setImageObserver(observer);
190 }
191
192 #if USE(CAIRO)
193 // Passes ownership of the native image to the caller so NativeImagePtr needs
194 // to be a smart pointer type.
195 NativeImagePtr SVGImage::nativeImageForCurrentFrame(const GraphicsContext*)
196 {
197     if (!m_page)
198         return nullptr;
199
200     // Cairo does not use the accelerated drawing flag, so it's OK to make an unconditionally unaccelerated buffer.
201     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(size(), Unaccelerated);
202     if (!buffer) // failed to allocate image
203         return nullptr;
204
205     draw(buffer->context(), rect(), rect(), CompositeSourceOver, BlendModeNormal, ImageOrientationDescription());
206
207     // FIXME: WK(Bug 113657): We should use DontCopyBackingStore here.
208     return buffer->copyImage(CopyBackingStore)->nativeImageForCurrentFrame();
209 }
210 #endif
211
212 #if USE(DIRECT2D)
213 NativeImagePtr SVGImage::nativeImage(const GraphicsContext* targetContext)
214 {
215     ASSERT(targetContext);
216     if (!m_page || !targetContext)
217         return nullptr;
218
219     auto platformContext = targetContext->platformContext();
220     ASSERT(platformContext);
221
222     // Draw the SVG into a bitmap.
223     COMPtr<ID2D1BitmapRenderTarget> nativeImageTarget;
224     HRESULT hr = platformContext->CreateCompatibleRenderTarget(IntSize(rect().size()), &nativeImageTarget);
225     ASSERT(SUCCEEDED(hr));
226
227     nativeImageTarget->BeginDraw();
228     GraphicsContext localContext(nativeImageTarget.get());
229     localContext.setDidBeginDraw(true);
230
231     draw(localContext, rect(), rect(), CompositeSourceOver, BlendModeNormal, ImageOrientationDescription());
232
233     hr = nativeImageTarget->Flush();
234     ASSERT(SUCCEEDED(hr));
235
236     COMPtr<ID2D1Bitmap> nativeImage;
237     hr = nativeImageTarget->GetBitmap(&nativeImage);
238     ASSERT(SUCCEEDED(hr));
239
240     return nativeImage;
241 }
242 #endif
243
244 void SVGImage::drawPatternForContainer(GraphicsContext& context, const FloatSize& containerSize, float zoom, const FloatRect& srcRect,
245     const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator compositeOp, const FloatRect& dstRect, BlendMode blendMode)
246 {
247     FloatRect zoomedContainerRect = FloatRect(FloatPoint(), containerSize);
248     zoomedContainerRect.scale(zoom);
249
250     // The ImageBuffer size needs to be scaled to match the final resolution.
251     AffineTransform transform = context.getCTM();
252     FloatSize imageBufferScale = FloatSize(transform.xScale(), transform.yScale());
253     ASSERT(imageBufferScale.width());
254     ASSERT(imageBufferScale.height());
255
256     FloatRect imageBufferSize = zoomedContainerRect;
257     imageBufferSize.scale(imageBufferScale.width(), imageBufferScale.height());
258
259     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(imageBufferSize.size()), 1, ColorSpaceSRGB, context);
260     if (!buffer) // Failed to allocate buffer.
261         return;
262     drawForContainer(buffer->context(), containerSize, zoom, imageBufferSize, zoomedContainerRect, CompositeSourceOver, BlendModeNormal);
263     if (context.drawLuminanceMask())
264         buffer->convertToLuminanceMask();
265
266     RefPtr<Image> image = ImageBuffer::sinkIntoImage(WTFMove(buffer), Unscaled);
267     if (!image)
268         return;
269
270     // Adjust the source rect and transform due to the image buffer's scaling.
271     FloatRect scaledSrcRect = srcRect;
272     scaledSrcRect.scale(imageBufferScale.width(), imageBufferScale.height());
273     AffineTransform unscaledPatternTransform(patternTransform);
274     unscaledPatternTransform.scale(1 / imageBufferScale.width(), 1 / imageBufferScale.height());
275
276     context.setDrawLuminanceMask(false);
277     image->drawPattern(context, dstRect, scaledSrcRect, unscaledPatternTransform, phase, spacing, compositeOp, blendMode);
278 }
279
280 void SVGImage::draw(GraphicsContext& context, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp, BlendMode blendMode, ImageOrientationDescription)
281 {
282     if (!m_page)
283         return;
284
285     FrameView* view = frameView();
286     ASSERT(view);
287
288     GraphicsContextStateSaver stateSaver(context);
289     context.setCompositeOperation(compositeOp, blendMode);
290     context.clip(enclosingIntRect(dstRect));
291
292     float alpha = context.alpha();
293     bool compositingRequiresTransparencyLayer = compositeOp != CompositeSourceOver || blendMode != BlendModeNormal || alpha < 1;
294     if (compositingRequiresTransparencyLayer) {
295         context.beginTransparencyLayer(alpha);
296         context.setCompositeOperation(CompositeSourceOver, BlendModeNormal);
297     }
298
299     FloatSize scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
300     
301     // We can only draw the entire frame, clipped to the rect we want. So compute where the top left
302     // of the image would be if we were drawing without clipping, and translate accordingly.
303     FloatSize topLeftOffset(srcRect.location().x() * scale.width(), srcRect.location().y() * scale.height());
304     FloatPoint destOffset = dstRect.location() - topLeftOffset;
305
306     context.translate(destOffset.x(), destOffset.y());
307     context.scale(scale);
308
309     view->resize(containerSize());
310
311     if (!m_url.isEmpty())
312         view->scrollToFragment(m_url);
313     
314     if (view->needsLayout())
315         view->layout();
316
317     view->paint(context, intersection(context.clipBounds(), enclosingIntRect(srcRect)));
318
319     if (compositingRequiresTransparencyLayer)
320         context.endTransparencyLayer();
321
322     stateSaver.restore();
323
324     if (imageObserver())
325         imageObserver()->didDraw(this);
326 }
327
328 RenderBox* SVGImage::embeddedContentBox() const
329 {
330     SVGSVGElement* rootElement = this->rootElement();
331     if (!rootElement)
332         return nullptr;
333     return downcast<RenderBox>(rootElement->renderer());
334 }
335
336 FrameView* SVGImage::frameView() const
337 {
338     if (!m_page)
339         return nullptr;
340     return m_page->mainFrame().view();
341 }
342
343 bool SVGImage::hasRelativeWidth() const
344 {
345     SVGSVGElement* rootElement = this->rootElement();
346     if (!rootElement)
347         return false;
348     return rootElement->intrinsicWidth().isPercentOrCalculated();
349 }
350
351 bool SVGImage::hasRelativeHeight() const
352 {
353     SVGSVGElement* rootElement = this->rootElement();
354     if (!rootElement)
355         return false;
356     return rootElement->intrinsicHeight().isPercentOrCalculated();
357 }
358
359 void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
360 {
361     SVGSVGElement* rootElement = this->rootElement();
362     if (!rootElement)
363         return;
364
365     intrinsicWidth = rootElement->intrinsicWidth();
366     intrinsicHeight = rootElement->intrinsicHeight();
367     if (rootElement->preserveAspectRatio().align() == SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
368         return;
369
370     intrinsicRatio = rootElement->viewBox().size();
371     if (intrinsicRatio.isEmpty() && intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
372         intrinsicRatio = FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
373 }
374
375 // FIXME: support catchUpIfNecessary.
376 void SVGImage::startAnimation(CatchUpAnimation)
377 {
378     SVGSVGElement* rootElement = this->rootElement();
379     if (!rootElement)
380         return;
381     rootElement->unpauseAnimations();
382     rootElement->setCurrentTime(0);
383 }
384
385 void SVGImage::stopAnimation()
386 {
387     SVGSVGElement* rootElement = this->rootElement();
388     if (!rootElement)
389         return;
390     rootElement->pauseAnimations();
391 }
392
393 void SVGImage::resetAnimation()
394 {
395     stopAnimation();
396 }
397
398 void SVGImage::reportApproximateMemoryCost() const
399 {
400     Document* document = m_page->mainFrame().document();
401     size_t decodedImageMemoryCost = 0;
402
403     for (Node* node = document; node; node = NodeTraversal::next(*node))
404         decodedImageMemoryCost += node->approximateMemoryCost();
405
406     JSC::VM& vm = JSDOMWindowBase::commonVM();
407     JSC::JSLockHolder lock(vm);
408     // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated.
409     // https://bugs.webkit.org/show_bug.cgi?id=142595
410     vm.heap.deprecatedReportExtraMemory(decodedImageMemoryCost + data()->size());
411 }
412
413 bool SVGImage::dataChanged(bool allDataReceived)
414 {
415     // Don't do anything if is an empty image.
416     if (!data()->size())
417         return true;
418
419     if (allDataReceived) {
420         PageConfiguration pageConfiguration(makeUniqueRef<EmptyEditorClient>(), SocketProvider::create());
421         fillWithEmptyClients(pageConfiguration);
422         m_chromeClient = std::make_unique<SVGImageChromeClient>(this);
423         pageConfiguration.chromeClient = m_chromeClient.get();
424
425         // FIXME: If this SVG ends up loading itself, we might leak the world.
426         // The Cache code does not know about CachedImages holding Frames and
427         // won't know to break the cycle.
428         // This will become an issue when SVGImage will be able to load other
429         // SVGImage objects, but we're safe now, because SVGImage can only be
430         // loaded by a top-level document.
431         m_page = std::make_unique<Page>(WTFMove(pageConfiguration));
432         m_page->settings().setMediaEnabled(false);
433         m_page->settings().setScriptEnabled(false);
434         m_page->settings().setPluginsEnabled(false);
435         m_page->settings().setAcceleratedCompositingEnabled(false);
436
437         Frame& frame = m_page->mainFrame();
438         frame.setView(FrameView::create(frame));
439         frame.init();
440         FrameLoader& loader = frame.loader();
441         loader.forceSandboxFlags(SandboxAll);
442
443         frame.view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
444         frame.view()->setTransparent(true); // SVG Images are transparent.
445
446         ASSERT(loader.activeDocumentLoader()); // DocumentLoader should have been created by frame->init().
447         loader.activeDocumentLoader()->writer().setMIMEType("image/svg+xml");
448         loader.activeDocumentLoader()->writer().begin(URL()); // create the empty document
449         loader.activeDocumentLoader()->writer().addData(data()->data(), data()->size());
450         loader.activeDocumentLoader()->writer().end();
451
452         // Set the intrinsic size before a container size is available.
453         m_intrinsicSize = containerSize();
454         reportApproximateMemoryCost();
455     }
456
457     return m_page != nullptr;
458 }
459
460 String SVGImage::filenameExtension() const
461 {
462     return ASCIILiteral("svg");
463 }
464
465 bool isInSVGImage(const Element* element)
466 {
467     ASSERT(element);
468
469     Page* page = element->document().page();
470     if (!page)
471         return false;
472
473     return page->chrome().client().isSVGImageChromeClient();
474 }
475
476 void SVGImage::dump(TextStream& ts) const
477 {
478     Image::dump(ts);
479     ts.dumpProperty("url", m_url.string());
480 }
481
482
483 }