Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2006 Eric Seidel <eric (at) webkit.org>
      3  * Copyright (C) 2008, 2009 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 
     30 #include "core/svg/graphics/SVGImage.h"
     31 
     32 #include "core/dom/NodeTraversal.h"
     33 #include "core/dom/shadow/ComposedTreeWalker.h"
     34 #include "core/loader/DocumentLoader.h"
     35 #include "core/page/Chrome.h"
     36 #include "core/frame/Frame.h"
     37 #include "core/frame/FrameView.h"
     38 #include "core/frame/Settings.h"
     39 #include "core/rendering/style/RenderStyle.h"
     40 #include "core/rendering/svg/RenderSVGRoot.h"
     41 #include "core/svg/SVGDocument.h"
     42 #include "core/svg/SVGFEImageElement.h"
     43 #include "core/svg/SVGImageElement.h"
     44 #include "core/svg/SVGSVGElement.h"
     45 #include "core/svg/graphics/SVGImageChromeClient.h"
     46 #include "platform/LengthFunctions.h"
     47 #include "platform/geometry/IntRect.h"
     48 #include "platform/graphics/GraphicsContextStateSaver.h"
     49 #include "platform/graphics/ImageBuffer.h"
     50 #include "platform/graphics/ImageObserver.h"
     51 #include "wtf/PassRefPtr.h"
     52 
     53 namespace WebCore {
     54 
     55 SVGImage::SVGImage(ImageObserver* observer)
     56     : Image(observer)
     57 {
     58 }
     59 
     60 SVGImage::~SVGImage()
     61 {
     62     if (m_page) {
     63         // Store m_page in a local variable, clearing m_page, so that SVGImageChromeClient knows we're destructed.
     64         OwnPtr<Page> currentPage = m_page.release();
     65         currentPage->mainFrame()->loader().frameDetached(); // Break both the loader and view references to the frame
     66     }
     67 
     68     // Verify that page teardown destroyed the Chrome
     69     ASSERT(!m_chromeClient || !m_chromeClient->image());
     70 }
     71 
     72 bool SVGImage::isInSVGImage(const Element* element)
     73 {
     74     ASSERT(element);
     75 
     76     Page* page = element->document().page();
     77     if (!page)
     78         return false;
     79 
     80     return page->chrome().client().isSVGImageChromeClient();
     81 }
     82 
     83 bool SVGImage::currentFrameHasSingleSecurityOrigin() const
     84 {
     85     if (!m_page)
     86         return true;
     87 
     88     Frame* frame = m_page->mainFrame();
     89 
     90     RELEASE_ASSERT(frame->document()->loadEventFinished());
     91 
     92     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
     93     if (!rootElement)
     94         return true;
     95 
     96     // Don't allow foreignObject elements or images that are not known to be
     97     // single-origin since these can leak cross-origin information.
     98     ComposedTreeWalker walker(rootElement);
     99     while (Node* node = walker.get()) {
    100         if (node->hasTagName(SVGNames::foreignObjectTag))
    101             return false;
    102         if (node->hasTagName(SVGNames::imageTag))
    103             return toSVGImageElement(node)->currentFrameHasSingleSecurityOrigin();
    104         if (node->hasTagName(SVGNames::feImageTag))
    105             return toSVGFEImageElement(node)->currentFrameHasSingleSecurityOrigin();
    106         walker.next();
    107     }
    108 
    109     // Because SVG image rendering disallows external resources and links, these
    110     // images effectively are restricted to a single security origin.
    111     return true;
    112 }
    113 
    114 void SVGImage::setContainerSize(const IntSize& size)
    115 {
    116     if (!m_page || !usesContainerSize())
    117         return;
    118 
    119     Frame* frame = m_page->mainFrame();
    120     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    121     if (!rootElement)
    122         return;
    123 
    124     FrameView* view = frameView();
    125     view->resize(this->containerSize());
    126 
    127     RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
    128     if (!renderer)
    129         return;
    130     renderer->setContainerSize(size);
    131 }
    132 
    133 IntSize SVGImage::containerSize() const
    134 {
    135     if (!m_page)
    136         return IntSize();
    137     Frame* frame = m_page->mainFrame();
    138     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    139     if (!rootElement)
    140         return IntSize();
    141 
    142     RenderSVGRoot* renderer = toRenderSVGRoot(rootElement->renderer());
    143     if (!renderer)
    144         return IntSize();
    145 
    146     // If a container size is available it has precedence.
    147     IntSize containerSize = renderer->containerSize();
    148     if (!containerSize.isEmpty())
    149         return containerSize;
    150 
    151     // Assure that a container size is always given for a non-identity zoom level.
    152     ASSERT(renderer->style()->effectiveZoom() == 1);
    153 
    154     FloatSize currentSize;
    155     if (rootElement->intrinsicWidth().isFixed() && rootElement->intrinsicHeight().isFixed())
    156         currentSize = rootElement->currentViewportSize();
    157     else
    158         currentSize = rootElement->currentViewBoxRect().size();
    159 
    160     if (!currentSize.isEmpty())
    161         return IntSize(static_cast<int>(ceilf(currentSize.width())), static_cast<int>(ceilf(currentSize.height())));
    162 
    163     // As last resort, use CSS default intrinsic size.
    164     return IntSize(300, 150);
    165 }
    166 
    167 void SVGImage::drawForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& dstRect,
    168     const FloatRect& srcRect, CompositeOperator compositeOp, blink::WebBlendMode blendMode)
    169 {
    170     if (!m_page)
    171         return;
    172 
    173     // Temporarily disable the image observer to prevent changeInRect() calls due re-laying out the image.
    174     ImageObserverDisabler imageObserverDisabler(this);
    175 
    176     IntSize roundedContainerSize = roundedIntSize(containerSize);
    177     setContainerSize(roundedContainerSize);
    178 
    179     FloatRect scaledSrc = srcRect;
    180     scaledSrc.scale(1 / zoom);
    181 
    182     // Compensate for the container size rounding by adjusting the source rect.
    183     FloatSize adjustedSrcSize = scaledSrc.size();
    184     adjustedSrcSize.scale(roundedContainerSize.width() / containerSize.width(), roundedContainerSize.height() / containerSize.height());
    185     scaledSrc.setSize(adjustedSrcSize);
    186 
    187     draw(context, dstRect, scaledSrc, compositeOp, blendMode);
    188 }
    189 
    190 PassRefPtr<NativeImageSkia> SVGImage::nativeImageForCurrentFrame()
    191 {
    192     if (!m_page)
    193         return 0;
    194 
    195     OwnPtr<ImageBuffer> buffer = ImageBuffer::create(size());
    196     if (!buffer)
    197         return 0;
    198 
    199     drawForContainer(buffer->context(), size(), 1, rect(), rect(), CompositeSourceOver, blink::WebBlendModeNormal);
    200 
    201     // FIXME: WK(Bug 113657): We should use DontCopyBackingStore here.
    202     return buffer->copyImage(CopyBackingStore)->nativeImageForCurrentFrame();
    203 }
    204 
    205 void SVGImage::drawPatternForContainer(GraphicsContext* context, const FloatSize containerSize, float zoom, const FloatRect& srcRect,
    206     const FloatSize& scale, const FloatPoint& phase, CompositeOperator compositeOp, const FloatRect& dstRect, blink::WebBlendMode blendMode, const IntSize& repeatSpacing)
    207 {
    208     FloatRect zoomedContainerRect = FloatRect(FloatPoint(), containerSize);
    209     zoomedContainerRect.scale(zoom);
    210 
    211     // The ImageBuffer size needs to be scaled to match the final resolution.
    212     // FIXME: No need to get the full CTM here, we just need the scale.
    213     AffineTransform transform = context->getCTM();
    214     FloatSize imageBufferScale = FloatSize(transform.xScale(), transform.yScale());
    215     ASSERT(imageBufferScale.width());
    216     ASSERT(imageBufferScale.height());
    217 
    218     FloatSize scaleWithoutCTM(scale.width() / imageBufferScale.width(), scale.height() / imageBufferScale.height());
    219 
    220     FloatRect imageBufferSize = zoomedContainerRect;
    221     imageBufferSize.scale(imageBufferScale.width(), imageBufferScale.height());
    222 
    223     OwnPtr<ImageBuffer> buffer = ImageBuffer::create(expandedIntSize(imageBufferSize.size()));
    224     if (!buffer) // Failed to allocate buffer.
    225         return;
    226 
    227     drawForContainer(buffer->context(), containerSize, zoom, imageBufferSize, zoomedContainerRect, CompositeSourceOver, blink::WebBlendModeNormal);
    228     RefPtr<Image> image = buffer->copyImage(DontCopyBackingStore, Unscaled);
    229 
    230     // Adjust the source rect and transform due to the image buffer's scaling.
    231     FloatRect scaledSrcRect = srcRect;
    232     scaledSrcRect.scale(imageBufferScale.width(), imageBufferScale.height());
    233 
    234     image->drawPattern(context, scaledSrcRect, scaleWithoutCTM, phase, compositeOp, dstRect, blendMode, repeatSpacing);
    235 }
    236 
    237 void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp, blink::WebBlendMode blendMode)
    238 {
    239     if (!m_page)
    240         return;
    241 
    242     GraphicsContextStateSaver stateSaver(*context);
    243     context->setCompositeOperation(compositeOp, blendMode);
    244     context->clip(enclosingIntRect(dstRect));
    245     if (compositeOp != CompositeSourceOver)
    246         context->beginTransparencyLayer(1);
    247 
    248     FloatSize scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
    249 
    250     // We can only draw the entire frame, clipped to the rect we want. So compute where the top left
    251     // of the image would be if we were drawing without clipping, and translate accordingly.
    252     FloatSize topLeftOffset(srcRect.location().x() * scale.width(), srcRect.location().y() * scale.height());
    253     FloatPoint destOffset = dstRect.location() - topLeftOffset;
    254 
    255     context->translate(destOffset.x(), destOffset.y());
    256     context->scale(scale);
    257 
    258     FrameView* view = frameView();
    259     view->resize(containerSize());
    260 
    261     if (view->needsLayout())
    262         view->layout();
    263 
    264     view->paint(context, enclosingIntRect(srcRect));
    265 
    266     if (compositeOp != CompositeSourceOver)
    267         context->endLayer();
    268 
    269     stateSaver.restore();
    270 
    271     if (imageObserver())
    272         imageObserver()->didDraw(this);
    273 }
    274 
    275 RenderBox* SVGImage::embeddedContentBox() const
    276 {
    277     if (!m_page)
    278         return 0;
    279     Frame* frame = m_page->mainFrame();
    280     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    281     if (!rootElement)
    282         return 0;
    283     return toRenderBox(rootElement->renderer());
    284 }
    285 
    286 FrameView* SVGImage::frameView() const
    287 {
    288     if (!m_page)
    289         return 0;
    290 
    291     return m_page->mainFrame()->view();
    292 }
    293 
    294 bool SVGImage::hasRelativeWidth() const
    295 {
    296     if (!m_page)
    297         return false;
    298     Frame* frame = m_page->mainFrame();
    299     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    300     if (!rootElement)
    301         return false;
    302     return rootElement->intrinsicWidth().isPercent();
    303 }
    304 
    305 bool SVGImage::hasRelativeHeight() const
    306 {
    307     if (!m_page)
    308         return false;
    309     Frame* frame = m_page->mainFrame();
    310     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    311     if (!rootElement)
    312         return false;
    313     return rootElement->intrinsicHeight().isPercent();
    314 }
    315 
    316 void SVGImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
    317 {
    318     if (!m_page)
    319         return;
    320     Frame* frame = m_page->mainFrame();
    321     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    322     if (!rootElement)
    323         return;
    324 
    325     intrinsicWidth = rootElement->intrinsicWidth();
    326     intrinsicHeight = rootElement->intrinsicHeight();
    327     if (rootElement->preserveAspectRatioCurrentValue().align() == SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE)
    328         return;
    329 
    330     intrinsicRatio = rootElement->viewBoxCurrentValue().size();
    331     if (intrinsicRatio.isEmpty() && intrinsicWidth.isFixed() && intrinsicHeight.isFixed())
    332         intrinsicRatio = FloatSize(floatValueForLength(intrinsicWidth, 0), floatValueForLength(intrinsicHeight, 0));
    333 }
    334 
    335 // FIXME: support catchUpIfNecessary.
    336 void SVGImage::startAnimation(bool /* catchUpIfNecessary */)
    337 {
    338     if (!m_page)
    339         return;
    340     Frame* frame = m_page->mainFrame();
    341     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    342     if (!rootElement)
    343         return;
    344     rootElement->unpauseAnimations();
    345     rootElement->setCurrentTime(0);
    346 }
    347 
    348 void SVGImage::stopAnimation()
    349 {
    350     if (!m_page)
    351         return;
    352     Frame* frame = m_page->mainFrame();
    353     SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
    354     if (!rootElement)
    355         return;
    356     rootElement->pauseAnimations();
    357 }
    358 
    359 void SVGImage::resetAnimation()
    360 {
    361     stopAnimation();
    362 }
    363 
    364 bool SVGImage::dataChanged(bool allDataReceived)
    365 {
    366     TRACE_EVENT0("webkit", "SVGImage::dataChanged");
    367 
    368     // Don't do anything if is an empty image.
    369     if (!data()->size())
    370         return true;
    371 
    372     if (allDataReceived) {
    373         static FrameLoaderClient* dummyFrameLoaderClient =  new EmptyFrameLoaderClient;
    374 
    375         Page::PageClients pageClients;
    376         fillWithEmptyClients(pageClients);
    377         m_chromeClient = adoptPtr(new SVGImageChromeClient(this));
    378         pageClients.chromeClient = m_chromeClient.get();
    379 
    380         // FIXME: If this SVG ends up loading itself, we might leak the world.
    381         // The Cache code does not know about ImageResources holding Frames and
    382         // won't know to break the cycle.
    383         // This will become an issue when SVGImage will be able to load other
    384         // SVGImage objects, but we're safe now, because SVGImage can only be
    385         // loaded by a top-level document.
    386         m_page = adoptPtr(new Page(pageClients));
    387         m_page->settings().setMediaEnabled(false);
    388         m_page->settings().setScriptEnabled(false);
    389         m_page->settings().setPluginsEnabled(false);
    390         m_page->settings().setAcceleratedCompositingEnabled(false);
    391 
    392         RefPtr<Frame> frame = Frame::create(FrameInit::create(0, m_page.get(), dummyFrameLoaderClient));
    393         frame->setView(FrameView::create(frame.get()));
    394         frame->init();
    395         FrameLoader& loader = frame->loader();
    396         loader.forceSandboxFlags(SandboxAll);
    397 
    398         frame->view()->setScrollbarsSuppressed(true);
    399         frame->view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
    400         frame->view()->setTransparent(true); // SVG Images are transparent.
    401 
    402         ASSERT(loader.activeDocumentLoader()); // DocumentLoader should have been created by frame->init().
    403         DocumentWriter* writer = loader.activeDocumentLoader()->beginWriting("image/svg+xml", "UTF-8");
    404         writer->addData(data()->data(), data()->size());
    405         loader.activeDocumentLoader()->endWriting(writer);
    406         // Set the intrinsic size before a container size is available.
    407         m_intrinsicSize = containerSize();
    408     }
    409 
    410     return m_page;
    411 }
    412 
    413 String SVGImage::filenameExtension() const
    414 {
    415     return "svg";
    416 }
    417 
    418 }
    419