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