Adapt inline SVG sizing behavior to Firefox and Blink
[WebKit-https.git] / Source / WebCore / svg / graphics / SVGImage.cpp
index 6a76fa3..4c059d9 100644 (file)
  */
 
 #include "config.h"
-
-#if ENABLE(SVG)
 #include "SVGImage.h"
 
+#include "Chrome.h"
 #include "DocumentLoader.h"
-#include "EmptyClients.h"
+#include "ElementIterator.h"
 #include "FrameView.h"
 #include "ImageBuffer.h"
 #include "ImageObserver.h"
+#include "IntRect.h"
+#include "MainFrame.h"
 #include "RenderSVGRoot.h"
+#include "RenderStyle.h"
 #include "SVGDocument.h"
+#include "SVGForeignObjectElement.h"
+#include "SVGImageChromeClient.h"
 #include "SVGSVGElement.h"
 #include "Settings.h"
 
 namespace WebCore {
 
-class SVGImageChromeClient : public EmptyChromeClient {
-    WTF_MAKE_NONCOPYABLE(SVGImageChromeClient); WTF_MAKE_FAST_ALLOCATED;
-public:
-    SVGImageChromeClient(SVGImage* image)
-        : m_image(image)
-    {
-    }
-
-    virtual bool isSVGImageChromeClient() const { return true; }
-    SVGImage* image() const { return m_image; }
-
-private:
-    virtual void chromeDestroyed()
-    {
-        m_image = 0;
-    }
-
-    virtual void invalidateContentsAndRootView(const IntRect& r, bool)
-    {
-        // If m_image->m_page is null, we're being destructed, don't fire changedInRect() in that case.
-        if (m_image && m_image->imageObserver() && m_image->m_page)
-            m_image->imageObserver()->changedInRect(m_image, r);
-    }
-
-    SVGImage* m_image;
-};
-
 SVGImage::SVGImage(ImageObserver* observer)
     : Image(observer)
 {
@@ -78,26 +55,55 @@ SVGImage::~SVGImage()
 {
     if (m_page) {
         // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
-        OwnPtr<Page> currentPage = m_page.release();
-        currentPage->mainFrame()->loader()->frameDetached(); // Break both the loader and view references to the frame
+        std::unique_ptr<Page> currentPage = std::move(m_page);
+        currentPage->mainFrame().loader().frameDetached(); // Break both the loader and view references to the frame
     }
 
     // Verify that page teardown destroyed the Chrome
     ASSERT(!m_chromeClient || !m_chromeClient->image());
 }
 
-void SVGImage::setContainerSize(const IntSize&)
+bool SVGImage::hasSingleSecurityOrigin() const
+{
+    if (!m_page)
+        return true;
+
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
+    if (!rootElement)
+        return true;
+
+    // Don't allow foreignObject elements since they can leak information with arbitrary HTML (like spellcheck or control theme).
+    if (descendantsOfType<SVGForeignObjectElement>(*rootElement).first())
+        return false;
+
+    // Because SVG image rendering disallows external resources and links,
+    // these images effectively are restricted to a single security origin.
+    return true;
+}
+
+void SVGImage::setContainerSize(const FloatSize& size)
 {
-    // SVGImageCache already intercepted this call, as it stores & caches the desired container sizes & zoom levels.
-    ASSERT_NOT_REACHED();
+    if (!m_page || !usesContainerSize())
+        return;
+
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
+    if (!rootElement)
+        return;
+    RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
+    if (!renderer)
+        return;
+
+    FrameView* view = frameView();
+    view->resize(this->containerSize());
+
+    renderer->setContainerSize(IntSize(size));
 }
 
-IntSize SVGImage::size() const
+IntSize SVGImage::containerSize() const
 {
     if (!m_page)
         return IntSize();
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
     if (!rootElement)
         return IntSize();
 
@@ -111,10 +117,10 @@ IntSize SVGImage::size() const
         return containerSize;
 
     // Assure that a container size is always given for a non-identity zoom level.
-    ASSERT(renderer->style()->effectiveZoom() == 1);
+    ASSERT(renderer->style().effectiveZoom() == 1);
 
     FloatSize currentSize;
-    if (rootElement->intrinsicWidth().isFixed() && rootElement->intrinsicHeight().isFixed())
+    if (rootElement->hasIntrinsicWidth() && rootElement->hasIntrinsicHeight())
         currentSize = rootElement->currentViewportSize();
     else
         currentSize = rootElement->currentViewBoxRect().size();
@@ -126,82 +132,104 @@ IntSize SVGImage::size() const
     return IntSize(300, 150);
 }
 
-void SVGImage::drawSVGToImageBuffer(ImageBuffer* buffer, const IntSize& size, float zoom, float scale, ShouldClearBuffer shouldClear)
+void SVGImage::drawForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& dstRect,
+    const FloatRect& srcRect, ColorSpace colorSpace, CompositeOperator compositeOp, BlendMode blendMode)
 {
-    // FIXME: This doesn't work correctly with animations. If an image contains animations, that say run for 2 seconds,
-    // and we currently have one <img> that displays us. If we open another document referencing the same SVGImage it
-    // will display the document at a time where animations already ran - even though it has its own ImageBuffer.
-    // We currently don't implement SVGSVGElement::setCurrentTime, and can NOT go back in time, once animations started.
-    // There's no way to fix this besides avoiding style/attribute mutations from SVGAnimationElement.
-    ASSERT(buffer);
-    ASSERT(!size.isEmpty());
-
     if (!m_page)
         return;
 
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
-    if (!rootElement)
-        return;
-    RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
-    if (!renderer)
-        return;
-
-    // Draw image at requested size.
     ImageObserver* observer = imageObserver();
     ASSERT(observer);
 
     // Temporarily reset image observer, we don't want to receive any changeInRect() calls due to this relayout.
     setImageObserver(0);
 
-    // Disable repainting; we don't want deferred repaints to schedule any timers due to this relayout.
-    frame->view()->beginDisableRepaints();
+    IntSize roundedContainerSize = roundedIntSize(containerSize);
+    setContainerSize(roundedContainerSize);
 
-    renderer->setContainerSize(size);
-    frame->view()->resize(this->size());
+    FloatRect scaledSrc = srcRect;
+    scaledSrc.scale(1 / zoom);
 
-    if (zoom != 1)
-        frame->setPageZoomFactor(zoom);
+    // Compensate for the container size rounding by adjusting the source rect.
+    FloatSize adjustedSrcSize = scaledSrc.size();
+    adjustedSrcSize.scale(roundedContainerSize.width() / containerSize.width(), roundedContainerSize.height() / containerSize.height());
+    scaledSrc.setSize(adjustedSrcSize);
 
-    // Eventually clear image buffer.
-    IntRect rect(IntPoint(), size);
+    draw(context, dstRect, scaledSrc, colorSpace, compositeOp, blendMode, ImageOrientationDescription());
 
-    FloatRect scaledRect(rect);
-    scaledRect.scale(scale);
+    setImageObserver(observer);
+}
+
+#if USE(CAIRO)
+// Passes ownership of the native image to the caller so PassNativeImagePtr needs
+// to be a smart pointer type.
+PassNativeImagePtr SVGImage::nativeImageForCurrentFrame()
+{
+    if (!m_page)
+        return 0;
 
-    if (shouldClear == ClearImageBuffer)
-        buffer->context()->clearRect(enclosingIntRect(scaledRect));
+    std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(size(), 1);
+    if (!buffer) // failed to allocate image
+        return 0;
 
-    // Draw SVG on top of ImageBuffer.
-    draw(buffer->context(), enclosingIntRect(scaledRect), rect, ColorSpaceDeviceRGB, CompositeSourceOver);
+    draw(buffer->context(), rect(), rect(), ColorSpaceDeviceRGB, CompositeSourceOver, BlendModeNormal, ImageOrientationDescription());
 
-    // Reset container size & zoom to initial state. Otherwhise the size() of this
-    // image would return whatever last size was set by drawSVGToImageBuffer().
-    if (zoom != 1)
-        frame->setPageZoomFactor(1);
+    // FIXME: WK(Bug 113657): We should use DontCopyBackingStore here.
+    return buffer->copyImage(CopyBackingStore)->nativeImageForCurrentFrame();
+}
+#endif
 
-    renderer->setContainerSize(IntSize());
-    frame->view()->resize(this->size());
-    if (frame->view()->needsLayout())
-        frame->view()->layout();
+void SVGImage::drawPatternForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& srcRect,
+    const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace colorSpace, CompositeOperator compositeOp, const FloatRect& dstRect, BlendMode blendMode)
+{
+    FloatRect zoomedContainerRect = FloatRect(FloatPoint(), containerSize);
+    zoomedContainerRect.scale(zoom);
 
-    setImageObserver(observer);
+    // The ImageBuffer size needs to be scaled to match the final resolution.
+    AffineTransform transform = context->getCTM();
+    FloatSize imageBufferScale = FloatSize(transform.xScale(), transform.yScale());
+    ASSERT(imageBufferScale.width());
+    ASSERT(imageBufferScale.height());
+
+    FloatRect imageBufferSize = zoomedContainerRect;
+    imageBufferSize.scale(imageBufferScale.width(), imageBufferScale.height());
 
-    frame->view()->endDisableRepaints();
+    std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(expandedIntSize(imageBufferSize.size()), 1);
+    if (!buffer) // Failed to allocate buffer.
+        return;
+    drawForContainer(buffer->context(), containerSize, zoom, imageBufferSize, zoomedContainerRect, ColorSpaceDeviceRGB, CompositeSourceOver, BlendModeNormal);
+    if (context->drawLuminanceMask())
+        buffer->convertToLuminanceMask();
+
+    RefPtr<Image> image = buffer->copyImage(DontCopyBackingStore, Unscaled);
+    image->setSpaceSize(spaceSize());
+
+    // Adjust the source rect and transform due to the image buffer's scaling.
+    FloatRect scaledSrcRect = srcRect;
+    scaledSrcRect.scale(imageBufferScale.width(), imageBufferScale.height());
+    AffineTransform unscaledPatternTransform(patternTransform);
+    unscaledPatternTransform.scale(1 / imageBufferScale.width(), 1 / imageBufferScale.height());
+
+    context->setDrawLuminanceMask(false);
+    image->drawPattern(context, scaledSrcRect, unscaledPatternTransform, phase, colorSpace, compositeOp, dstRect, blendMode);
 }
 
-void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator compositeOp)
+void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator compositeOp, BlendMode blendMode, ImageOrientationDescription)
 {
     if (!m_page)
         return;
 
     FrameView* view = frameView();
+    ASSERT(view);
 
     GraphicsContextStateSaver stateSaver(*context);
-    context->setCompositeOperation(compositeOp);
+    context->setCompositeOperation(compositeOp, blendMode);
     context->clip(enclosingIntRect(dstRect));
-    if (compositeOp != CompositeSourceOver)
+    bool compositingRequiresTransparencyLayer = compositeOp != CompositeSourceOver || blendMode != BlendModeNormal;
+    if (compositingRequiresTransparencyLayer) {
         context->beginTransparencyLayer(1);
+        context->setCompositeOperation(CompositeSourceOver, BlendModeNormal);
+    }
 
     FloatSize scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
     
@@ -213,18 +241,21 @@ void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const Fl
     context->translate(destOffset.x(), destOffset.y());
     context->scale(scale);
 
-    view->resize(size());
+    view->resize(containerSize());
 
     if (view->needsLayout())
         view->layout();
 
-    view->paint(context, IntRect(0, 0, view->width(), view->height()));
+    view->paint(context, enclosingIntRect(srcRect));
 
-    if (compositeOp != CompositeSourceOver)
+    if (compositingRequiresTransparencyLayer)
         context->endTransparencyLayer();
 
     stateSaver.restore();
 
+    if (!m_url.isEmpty())
+        view->scrollToFragment(m_url);
+
     if (imageObserver())
         imageObserver()->didDraw(this);
 }
@@ -233,8 +264,7 @@ RenderBox* SVGImage::embeddedContentBox() const
 {
     if (!m_page)
         return 0;
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
     if (!rootElement)
         return 0;
     return toRenderBox(rootElement->renderer());
@@ -244,16 +274,14 @@ FrameView* SVGImage::frameView() const
 {
     if (!m_page)
         return 0;
-
-    return m_page->mainFrame()->view();
+    return m_page->mainFrame().view();
 }
 
 bool SVGImage::hasRelativeWidth() const
 {
     if (!m_page)
         return false;
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
     if (!rootElement)
         return false;
     return rootElement->intrinsicWidth().isPercent();
@@ -263,8 +291,7 @@ bool SVGImage::hasRelativeHeight() const
 {
     if (!m_page)
         return false;
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
     if (!rootElement)
         return false;
     return rootElement->intrinsicHeight().isPercent();
@@ -274,8 +301,7 @@ void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrin
 {
     if (!m_page)
         return;
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
     if (!rootElement)
         return;
 
@@ -289,21 +315,31 @@ void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrin
         intrinsicRatio = FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
 }
 
-NativeImagePtr SVGImage::nativeImageForCurrentFrame()
+// FIXME: support catchUpIfNecessary.
+void SVGImage::startAnimation(CatchUpAnimation)
 {
-    // FIXME: In order to support dynamic SVGs we need to have a way to invalidate this
-    // frame cache, or better yet, not use a cache for tiled drawing at all, instead
-    // having a tiled drawing callback (hopefully non-virtual).
-    if (!m_frameCache) {
-        if (!m_page)
-            return 0;
-        OwnPtr<ImageBuffer> buffer = ImageBuffer::create(size(), 1);
-        if (!buffer) // failed to allocate image
-            return 0;
-        draw(buffer->context(), rect(), rect(), ColorSpaceDeviceRGB, CompositeSourceOver);
-        m_frameCache = buffer->copyImage(CopyBackingStore);
-    }
-    return m_frameCache->nativeImageForCurrentFrame();
+    if (!m_page)
+        return;
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
+    if (!rootElement)
+        return;
+    rootElement->unpauseAnimations();
+    rootElement->setCurrentTime(0);
+}
+
+void SVGImage::stopAnimation()
+{
+    if (!m_page)
+        return;
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
+    if (!rootElement)
+        return;
+    rootElement->pauseAnimations();
+}
+
+void SVGImage::resetAnimation()
+{
+    stopAnimation();
 }
 
 bool SVGImage::dataChanged(bool allDataReceived)
@@ -313,11 +349,9 @@ bool SVGImage::dataChanged(bool allDataReceived)
         return true;
 
     if (allDataReceived) {
-        static FrameLoaderClient* dummyFrameLoaderClient =  new EmptyFrameLoaderClient;
-
         Page::PageClients pageClients;
         fillWithEmptyClients(pageClients);
-        m_chromeClient = adoptPtr(new SVGImageChromeClient(this));
+        m_chromeClient = std::make_unique<SVGImageChromeClient>(this);
         pageClients.chromeClient = m_chromeClient.get();
 
         // FIXME: If this SVG ends up loading itself, we might leak the world.
@@ -326,28 +360,31 @@ bool SVGImage::dataChanged(bool allDataReceived)
         // This will become an issue when SVGImage will be able to load other
         // SVGImage objects, but we're safe now, because SVGImage can only be
         // loaded by a top-level document.
-        m_page = adoptPtr(new Page(pageClients));
-        m_page->settings()->setMediaEnabled(false);
-        m_page->settings()->setScriptEnabled(false);
-        m_page->settings()->setPluginsEnabled(false);
-
-        RefPtr<Frame> frame = Frame::create(m_page.get(), 0, dummyFrameLoaderClient);
-        frame->setView(FrameView::create(frame.get()));
-        frame->init();
-        FrameLoader* loader = frame->loader();
-        loader->forceSandboxFlags(SandboxAll);
-
-        frame->view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
-        frame->view()->setTransparent(true); // SVG Images are transparent.
-
-        ASSERT(loader->activeDocumentLoader()); // DocumentLoader should have been created by frame->init().
-        loader->activeDocumentLoader()->writer()->setMIMEType("image/svg+xml");
-        loader->activeDocumentLoader()->writer()->begin(KURL()); // create the empty document
-        loader->activeDocumentLoader()->writer()->addData(data()->data(), data()->size());
-        loader->activeDocumentLoader()->writer()->end();
+        m_page = std::make_unique<Page>(pageClients);
+        m_page->settings().setMediaEnabled(false);
+        m_page->settings().setScriptEnabled(false);
+        m_page->settings().setPluginsEnabled(false);
+
+        Frame& frame = m_page->mainFrame();
+        frame.setView(FrameView::create(frame));
+        frame.init();
+        FrameLoader& loader = frame.loader();
+        loader.forceSandboxFlags(SandboxAll);
+
+        frame.view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
+        frame.view()->setTransparent(true); // SVG Images are transparent.
+
+        ASSERT(loader.activeDocumentLoader()); // DocumentLoader should have been created by frame->init().
+        loader.activeDocumentLoader()->writer().setMIMEType("image/svg+xml");
+        loader.activeDocumentLoader()->writer().begin(URL()); // create the empty document
+        loader.activeDocumentLoader()->writer().addData(data()->data(), data()->size());
+        loader.activeDocumentLoader()->writer().end();
+
+        // Set the intrinsic size before a container size is available.
+        m_intrinsicSize = containerSize();
     }
 
-    return m_page;
+    return m_page != nullptr;
 }
 
 String SVGImage::filenameExtension() const
@@ -355,15 +392,15 @@ String SVGImage::filenameExtension() const
     return "svg";
 }
 
-void SVGImage::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
+bool isInSVGImage(const Element* element)
 {
-    MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CachedResourceImage);
-    Image::reportMemoryUsage(memoryObjectInfo);
-    info.addMember(m_chromeClient);
-    info.addMember(m_page);
-    info.addMember(m_frameCache);
-}
+    ASSERT(element);
+
+    Page* page = element->document().page();
+    if (!page)
+        return false;
 
+    return page->chrome().client().isSVGImageChromeClient();
 }
 
-#endif // ENABLE(SVG)
+}