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