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