Adapt inline SVG sizing behavior to Firefox and Blink
[WebKit-https.git] / Source / WebCore / svg / graphics / SVGImage.cpp
index 9466a66..4c059d9 100644 (file)
  */
 
 #include "config.h"
-
-#if ENABLE(SVG)
 #include "SVGImage.h"
 
-#include "CachedPage.h"
+#include "Chrome.h"
 #include "DocumentLoader.h"
-#include "EmptyClients.h"
-#include "FileChooser.h"
-#include "FileIconLoader.h"
-#include "FloatRect.h"
-#include "Frame.h"
-#include "FrameLoader.h"
+#include "ElementIterator.h"
 #include "FrameView.h"
-#include "GraphicsContext.h"
-#include "HTMLFormElement.h"
 #include "ImageBuffer.h"
 #include "ImageObserver.h"
-#include "Length.h"
-#include "Page.h"
+#include "IntRect.h"
+#include "MainFrame.h"
 #include "RenderSVGRoot.h"
-#include "RenderView.h"
-#include "ResourceError.h"
+#include "RenderStyle.h"
 #include "SVGDocument.h"
-#include "SVGLength.h"
-#include "SVGRenderSupport.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 invalidateContentsAndWindow(const IntRect& r, bool)
-    {
-        if (m_image && m_image->imageObserver())
-            m_image->imageObserver()->changedInRect(m_image, r);
-    }
-
-    SVGImage* m_image;
-};
-
 SVGImage::SVGImage(ImageObserver* observer)
     : Image(observer)
 {
@@ -90,42 +54,56 @@ SVGImage::SVGImage(ImageObserver* observer)
 SVGImage::~SVGImage()
 {
     if (m_page) {
-        m_page->mainFrame()->loader()->frameDetached(); // Break both the loader and view references to the frame
-
-        // Clear explicitly because we want to delete the page before the ChromeClient.
-        // FIXME: I believe that's already guaranteed by C++ object destruction rules,
-        // so this may matter only for the assertion below.
-        m_page.clear();
+        // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
+        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
 {
-    ASSERT_NOT_REACHED();
+    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;
 }
 
-bool SVGImage::usesContainerSize() const
+void SVGImage::setContainerSize(const FloatSize& size)
 {
-    if (!m_page)
-        return false;
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
+    if (!m_page || !usesContainerSize())
+        return;
+
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
     if (!rootElement)
-        return false;
-    if (RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer()))
-        return !renderer->containerSize().isEmpty();
-    return false;
+        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();
 
@@ -139,77 +117,119 @@ 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);
-    IntSize size = enclosingIntRect(rootElement->currentViewBoxRect(SVGSVGElement::CalculateViewBoxInHostDocument)).size();
-    if (!size.isEmpty())
-        return size;
+    ASSERT(renderer->style().effectiveZoom() == 1);
+
+    FloatSize currentSize;
+    if (rootElement->hasIntrinsicWidth() && rootElement->hasIntrinsicHeight())
+        currentSize = rootElement->currentViewportSize();
+    else
+        currentSize = rootElement->currentViewBoxRect().size();
+
+    if (!currentSize.isEmpty())
+        return IntSize(static_cast<int>(ceilf(currentSize.width())), static_cast<int>(ceilf(currentSize.height())));
 
     // As last resort, use CSS default intrinsic size.
     return IntSize(300, 150);
 }
 
-void SVGImage::drawSVGToImageBuffer(ImageBuffer* buffer, const IntSize& size, float zoom, 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());
-
-    Frame* frame = m_page->mainFrame();
-    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
-    if (!rootElement)
-        return;
-    RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
-    if (!renderer)
+    if (!m_page)
         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 this relayout.
+    // Temporarily reset image observer, we don't want to receive any changeInRect() calls due to this relayout.
     setImageObserver(0);
-    renderer->setContainerSize(size);
-    frame->view()->resize(this->size());
-    if (zoom != 1)
-        frame->setPageZoomFactor(zoom);
-
-    // Eventually clear image buffer.
-    IntRect rect(IntPoint(), size);
-    if (shouldClear == ClearImageBuffer)
-        buffer->context()->clearRect(rect);
-
-    // Draw SVG on top of ImageBuffer.
-    draw(buffer->context(), rect, rect, ColorSpaceDeviceRGB, CompositeSourceOver);
-
-    // 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);
-
-    renderer->setContainerSize(IntSize());
-    frame->view()->resize(this->size());
-    if (frame->view()->needsLayout())
-        frame->view()->layout();
-
-    setImageObserver(observer); 
+
+    IntSize roundedContainerSize = roundedIntSize(containerSize);
+    setContainerSize(roundedContainerSize);
+
+    FloatRect scaledSrc = srcRect;
+    scaledSrc.scale(1 / 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);
+
+    draw(context, dstRect, scaledSrc, colorSpace, compositeOp, blendMode, ImageOrientationDescription());
+
+    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;
+
+    std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(size(), 1);
+    if (!buffer) // failed to allocate image
+        return 0;
+
+    draw(buffer->context(), rect(), rect(), ColorSpaceDeviceRGB, CompositeSourceOver, BlendModeNormal, ImageOrientationDescription());
+
+    // FIXME: WK(Bug 113657): We should use DontCopyBackingStore here.
+    return buffer->copyImage(CopyBackingStore)->nativeImageForCurrentFrame();
+}
+#endif
+
+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);
+
+    // 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());
+
+    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 = m_page->mainFrame()->view();
+    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());
     
@@ -221,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);
 }
@@ -241,46 +264,82 @@ 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());
 }
 
+FrameView* SVGImage::frameView() const
+{
+    if (!m_page)
+        return 0;
+    return m_page->mainFrame().view();
+}
+
+bool SVGImage::hasRelativeWidth() const
+{
+    if (!m_page)
+        return false;
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
+    if (!rootElement)
+        return false;
+    return rootElement->intrinsicWidth().isPercent();
+}
+
+bool SVGImage::hasRelativeHeight() const
+{
+    if (!m_page)
+        return false;
+    SVGSVGElement* rootElement = toSVGDocument(m_page->mainFrame().document())->rootElement();
+    if (!rootElement)
+        return false;
+    return rootElement->intrinsicHeight().isPercent();
+}
+
 void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
 {
     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;
-    RenderBox* renderer = toRenderBox(rootElement->renderer());
-    if (!renderer)
+
+    intrinsicWidth = rootElement->intrinsicWidth();
+    intrinsicHeight = rootElement->intrinsicHeight();
+    if (rootElement->preserveAspectRatio().align() == SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
         return;
 
-    intrinsicWidth = renderer->style()->width();
-    intrinsicHeight = renderer->style()->height();
-    if (rootElement->preserveAspectRatio().align() != SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
-        intrinsicRatio = rootElement->currentViewBoxRect().size();
+    intrinsicRatio = rootElement->viewBox().size();
+    if (intrinsicRatio.isEmpty() && intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
+        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());
-        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)
@@ -290,29 +349,10 @@ bool SVGImage::dataChanged(bool allDataReceived)
         return true;
 
     if (allDataReceived) {
-        static FrameLoaderClient* dummyFrameLoaderClient =  new EmptyFrameLoaderClient;
-
         Page::PageClients pageClients;
-        m_chromeClient = adoptPtr(new SVGImageChromeClient(this));
+        fillWithEmptyClients(pageClients);
+        m_chromeClient = std::make_unique<SVGImageChromeClient>(this);
         pageClients.chromeClient = m_chromeClient.get();
-#if ENABLE(CONTEXT_MENUS)
-        static ContextMenuClient* dummyContextMenuClient = new EmptyContextMenuClient;
-        pageClients.contextMenuClient = dummyContextMenuClient;
-#endif
-        static EditorClient* dummyEditorClient = new EmptyEditorClient;
-        pageClients.editorClient = dummyEditorClient;
-#if ENABLE(DRAG_SUPPORT)
-        static DragClient* dummyDragClient = new EmptyDragClient;
-        pageClients.dragClient = dummyDragClient;
-#endif
-        static InspectorClient* dummyInspectorClient = new EmptyInspectorClient;
-        pageClients.inspectorClient = dummyInspectorClient;
-#if ENABLE(DEVICE_ORIENTATION)
-        static DeviceMotionClient* dummyDeviceMotionClient = new EmptyDeviceMotionClient;
-        pageClients.deviceMotionClient = dummyDeviceMotionClient;
-        static DeviceOrientationClient* dummyDeviceOrientationClient = new EmptyDeviceOrientationClient;
-        pageClients.deviceOrientationClient = dummyDeviceOrientationClient;
-#endif
 
         // FIXME: If this SVG ends up loading itself, we might leak the world.
         // The Cache code does not know about CachedImages holding Frames and
@@ -320,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
@@ -349,6 +392,15 @@ String SVGImage::filenameExtension() const
     return "svg";
 }
 
+bool isInSVGImage(const Element* element)
+{
+    ASSERT(element);
+
+    Page* page = element->document().page();
+    if (!page)
+        return false;
+
+    return page->chrome().client().isSVGImageChromeClient();
 }
 
-#endif // ENABLE(SVG)
+}