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