1/* 2 * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27 28#if ENABLE(VIDEO) 29#include "RenderVideo.h" 30 31#include "Document.h" 32#include "Frame.h" 33#include "FrameView.h" 34#include "GraphicsContext.h" 35#include "HTMLNames.h" 36#include "HTMLVideoElement.h" 37#include "MediaPlayer.h" 38#include "Page.h" 39#include "PaintInfo.h" 40#include "RenderView.h" 41#include <wtf/StackStats.h> 42 43#if ENABLE(FULLSCREEN_API) 44#include "RenderFullScreen.h" 45#endif 46 47namespace WebCore { 48 49using namespace HTMLNames; 50 51RenderVideo::RenderVideo(HTMLVideoElement* video) 52 : RenderMedia(video) 53{ 54 setIntrinsicSize(calculateIntrinsicSize()); 55} 56 57RenderVideo::~RenderVideo() 58{ 59 if (MediaPlayer* p = mediaElement()->player()) { 60 p->setVisible(false); 61 p->setFrameView(0); 62 } 63} 64 65IntSize RenderVideo::defaultSize() 66{ 67 // These values are specified in the spec. 68 static const int cDefaultWidth = 300; 69 static const int cDefaultHeight = 150; 70 71 return IntSize(cDefaultWidth, cDefaultHeight); 72} 73 74void RenderVideo::intrinsicSizeChanged() 75{ 76 if (videoElement()->shouldDisplayPosterImage()) 77 RenderMedia::intrinsicSizeChanged(); 78 updateIntrinsicSize(); 79} 80 81void RenderVideo::updateIntrinsicSize() 82{ 83 LayoutSize size = calculateIntrinsicSize(); 84 size.scale(style()->effectiveZoom()); 85 86 // Never set the element size to zero when in a media document. 87 if (size.isEmpty() && node()->ownerDocument() && node()->ownerDocument()->isMediaDocument()) 88 return; 89 90 if (size == intrinsicSize()) 91 return; 92 93 setIntrinsicSize(size); 94 setPreferredLogicalWidthsDirty(true); 95 setNeedsLayout(true); 96} 97 98LayoutSize RenderVideo::calculateIntrinsicSize() 99{ 100 HTMLVideoElement* video = videoElement(); 101 102 // Spec text from 4.8.6 103 // 104 // The intrinsic width of a video element's playback area is the intrinsic width 105 // of the video resource, if that is available; otherwise it is the intrinsic 106 // width of the poster frame, if that is available; otherwise it is 300 CSS pixels. 107 // 108 // The intrinsic height of a video element's playback area is the intrinsic height 109 // of the video resource, if that is available; otherwise it is the intrinsic 110 // height of the poster frame, if that is available; otherwise it is 150 CSS pixels. 111 MediaPlayer* player = mediaElement()->player(); 112 if (player && video->readyState() >= HTMLVideoElement::HAVE_METADATA) { 113 LayoutSize size = player->naturalSize(); 114 if (!size.isEmpty()) 115 return size; 116 } 117 118 if (video->shouldDisplayPosterImage() && !m_cachedImageSize.isEmpty() && !imageResource()->errorOccurred()) 119 return m_cachedImageSize; 120 121 // When the natural size of the video is unavailable, we use the provided 122 // width and height attributes of the video element as the intrinsic size until 123 // better values become available. 124 if (video->hasAttribute(widthAttr) && video->hasAttribute(heightAttr)) 125 return LayoutSize(video->width(), video->height()); 126 127 // <video> in standalone media documents should not use the default 300x150 128 // size since they also have audio-only files. By setting the intrinsic 129 // size to 300x1 the video will resize itself in these cases, and audio will 130 // have the correct height (it needs to be > 0 for controls to render properly). 131 if (video->ownerDocument() && video->ownerDocument()->isMediaDocument()) 132 return LayoutSize(defaultSize().width(), 1); 133 134 return defaultSize(); 135} 136 137void RenderVideo::imageChanged(WrappedImagePtr newImage, const IntRect* rect) 138{ 139 RenderMedia::imageChanged(newImage, rect); 140 141 // Cache the image intrinsic size so we can continue to use it to draw the image correctly 142 // even if we know the video intrinsic size but aren't able to draw video frames yet 143 // (we don't want to scale the poster to the video size without keeping aspect ratio). 144 if (videoElement()->shouldDisplayPosterImage()) 145 m_cachedImageSize = intrinsicSize(); 146 147 // The intrinsic size is now that of the image, but in case we already had the 148 // intrinsic size of the video we call this here to restore the video size. 149 updateIntrinsicSize(); 150} 151 152IntRect RenderVideo::videoBox() const 153{ 154 if (m_cachedImageSize.isEmpty() && videoElement()->shouldDisplayPosterImage()) 155 return IntRect(); 156 157 LayoutSize elementSize; 158 if (videoElement()->shouldDisplayPosterImage()) 159 elementSize = m_cachedImageSize; 160 else 161 elementSize = intrinsicSize(); 162 163 IntRect contentRect = pixelSnappedIntRect(contentBoxRect()); 164 if (elementSize.isEmpty() || contentRect.isEmpty()) 165 return IntRect(); 166 167 LayoutRect renderBox = contentRect; 168 LayoutUnit ratio = renderBox.width() * elementSize.height() - renderBox.height() * elementSize.width(); 169 if (ratio > 0) { 170 LayoutUnit newWidth = renderBox.height() * elementSize.width() / elementSize.height(); 171 // Just fill the whole area if the difference is one pixel or less (in both sides) 172 if (renderBox.width() - newWidth > 2) 173 renderBox.setWidth(newWidth); 174 renderBox.move((contentRect.width() - renderBox.width()) / 2, 0); 175 } else if (ratio < 0) { 176 LayoutUnit newHeight = renderBox.width() * elementSize.height() / elementSize.width(); 177 if (renderBox.height() - newHeight > 2) 178 renderBox.setHeight(newHeight); 179 renderBox.move(0, (contentRect.height() - renderBox.height()) / 2); 180 } 181 182 return pixelSnappedIntRect(renderBox); 183} 184 185bool RenderVideo::shouldDisplayVideo() const 186{ 187 return !videoElement()->shouldDisplayPosterImage(); 188} 189 190void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 191{ 192 MediaPlayer* mediaPlayer = mediaElement()->player(); 193 bool displayingPoster = videoElement()->shouldDisplayPosterImage(); 194 195 Page* page = 0; 196 if (Frame* frame = this->frame()) 197 page = frame->page(); 198 199 if (!displayingPoster && !mediaPlayer) { 200 if (page && paintInfo.phase == PaintPhaseForeground) 201 page->addRelevantUnpaintedObject(this, visualOverflowRect()); 202 return; 203 } 204 205 LayoutRect rect = videoBox(); 206 if (rect.isEmpty()) { 207 if (page && paintInfo.phase == PaintPhaseForeground) 208 page->addRelevantUnpaintedObject(this, visualOverflowRect()); 209 return; 210 } 211 rect.moveBy(paintOffset); 212 213 if (page && paintInfo.phase == PaintPhaseForeground) 214 page->addRelevantRepaintedObject(this, rect); 215 216 if (displayingPoster) 217 paintIntoRect(paintInfo.context, rect); 218 else if (document()->view() && document()->view()->paintBehavior() & PaintBehaviorFlattenCompositingLayers) 219 mediaPlayer->paintCurrentFrameInContext(paintInfo.context, pixelSnappedIntRect(rect)); 220 else 221 mediaPlayer->paint(paintInfo.context, pixelSnappedIntRect(rect)); 222} 223 224void RenderVideo::layout() 225{ 226 StackStats::LayoutCheckPoint layoutCheckPoint; 227 RenderMedia::layout(); 228 updatePlayer(); 229} 230 231HTMLVideoElement* RenderVideo::videoElement() const 232{ 233 ASSERT(node()->hasTagName(videoTag)); 234 return static_cast<HTMLVideoElement*>(node()); 235} 236 237void RenderVideo::updateFromElement() 238{ 239 RenderMedia::updateFromElement(); 240 updatePlayer(); 241} 242 243void RenderVideo::updatePlayer() 244{ 245 updateIntrinsicSize(); 246 247 MediaPlayer* mediaPlayer = mediaElement()->player(); 248 if (!mediaPlayer) 249 return; 250 251 if (!videoElement()->inActiveDocument()) { 252 mediaPlayer->setVisible(false); 253 return; 254 } 255 256#if USE(ACCELERATED_COMPOSITING) 257 contentChanged(VideoChanged); 258#endif 259 260 IntRect videoBounds = videoBox(); 261 mediaPlayer->setFrameView(document()->view()); 262 mediaPlayer->setSize(IntSize(videoBounds.width(), videoBounds.height())); 263 mediaPlayer->setVisible(true); 264} 265 266LayoutUnit RenderVideo::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const 267{ 268 return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred); 269} 270 271LayoutUnit RenderVideo::computeReplacedLogicalHeight() const 272{ 273 return RenderReplaced::computeReplacedLogicalHeight(); 274} 275 276LayoutUnit RenderVideo::minimumReplacedHeight() const 277{ 278 return RenderReplaced::minimumReplacedHeight(); 279} 280 281#if USE(ACCELERATED_COMPOSITING) 282bool RenderVideo::supportsAcceleratedRendering() const 283{ 284 MediaPlayer* p = mediaElement()->player(); 285 if (p) 286 return p->supportsAcceleratedRendering(); 287 288 return false; 289} 290 291void RenderVideo::acceleratedRenderingStateChanged() 292{ 293 MediaPlayer* p = mediaElement()->player(); 294 if (p) 295 p->acceleratedRenderingStateChanged(); 296} 297#endif // USE(ACCELERATED_COMPOSITING) 298 299bool RenderVideo::requiresImmediateCompositing() const 300{ 301 MediaPlayer* player = mediaElement()->player(); 302 return player && player->requiresImmediateCompositing(); 303} 304 305#if ENABLE(FULLSCREEN_API) 306static const RenderBlock* rendererPlaceholder(const RenderObject* renderer) 307{ 308 RenderObject* parent = renderer->parent(); 309 if (!parent) 310 return 0; 311 312 RenderFullScreen* fullScreen = parent->isRenderFullScreen() ? toRenderFullScreen(parent) : 0; 313 if (!fullScreen) 314 return 0; 315 316 return fullScreen->placeholder(); 317} 318 319LayoutUnit RenderVideo::offsetLeft() const 320{ 321 if (const RenderBlock* block = rendererPlaceholder(this)) 322 return block->offsetLeft(); 323 return RenderMedia::offsetLeft(); 324} 325 326LayoutUnit RenderVideo::offsetTop() const 327{ 328 if (const RenderBlock* block = rendererPlaceholder(this)) 329 return block->offsetTop(); 330 return RenderMedia::offsetTop(); 331} 332 333LayoutUnit RenderVideo::offsetWidth() const 334{ 335 if (const RenderBlock* block = rendererPlaceholder(this)) 336 return block->offsetWidth(); 337 return RenderMedia::offsetWidth(); 338} 339 340LayoutUnit RenderVideo::offsetHeight() const 341{ 342 if (const RenderBlock* block = rendererPlaceholder(this)) 343 return block->offsetHeight(); 344 return RenderMedia::offsetHeight(); 345} 346#endif 347 348bool RenderVideo::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const 349{ 350 if (videoElement()->shouldDisplayPosterImage()) 351 return RenderImage::foregroundIsKnownToBeOpaqueInRect(localRect, maxDepthToTest); 352 353 return videoBox().contains(enclosingIntRect(localRect)); 354} 355 356} // namespace WebCore 357 358#endif 359