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