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 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 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& element, PassRef<RenderStyle> style) 52 : RenderMedia(element, WTF::move(style)) 53{ 54 setIntrinsicSize(calculateIntrinsicSize()); 55} 56 57RenderVideo::~RenderVideo() 58{ 59 if (MediaPlayer* player = videoElement().player()) { 60 player->setVisible(false); 61 player->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() && document().isMediaDocument()) 88 return; 89 90 if (size == intrinsicSize()) 91 return; 92 93 setIntrinsicSize(size); 94 setPreferredLogicalWidthsDirty(true); 95 setNeedsLayout(); 96} 97 98LayoutSize RenderVideo::calculateIntrinsicSize() 99{ 100 // Spec text from 4.8.6 101 // 102 // The intrinsic width of a video element's playback area is the intrinsic width 103 // of the video resource, if that is available; otherwise it is the intrinsic 104 // width of the poster frame, if that is available; otherwise it is 300 CSS pixels. 105 // 106 // The intrinsic height of a video element's playback area is the intrinsic height 107 // of the video resource, if that is available; otherwise it is the intrinsic 108 // height of the poster frame, if that is available; otherwise it is 150 CSS pixels. 109 MediaPlayer* player = videoElement().player(); 110 if (player && videoElement().readyState() >= HTMLVideoElement::HAVE_METADATA) { 111 LayoutSize size = player->naturalSize(); 112 if (!size.isEmpty()) 113 return size; 114 } 115 116 if (videoElement().shouldDisplayPosterImage() && !m_cachedImageSize.isEmpty() && !imageResource().errorOccurred()) 117 return m_cachedImageSize; 118 119 // <video> in standalone media documents should not use the default 300x150 120 // size since they also have audio-only files. By setting the intrinsic 121 // size to 300x1 the video will resize itself in these cases, and audio will 122 // have the correct height (it needs to be > 0 for controls to render properly). 123 if (videoElement().document().isMediaDocument()) 124 return LayoutSize(defaultSize().width(), 1); 125 126 return defaultSize(); 127} 128 129void RenderVideo::imageChanged(WrappedImagePtr newImage, const IntRect* rect) 130{ 131 RenderMedia::imageChanged(newImage, rect); 132 133 // Cache the image intrinsic size so we can continue to use it to draw the image correctly 134 // even if we know the video intrinsic size but aren't able to draw video frames yet 135 // (we don't want to scale the poster to the video size without keeping aspect ratio). 136 if (videoElement().shouldDisplayPosterImage()) 137 m_cachedImageSize = intrinsicSize(); 138 139 // The intrinsic size is now that of the image, but in case we already had the 140 // intrinsic size of the video we call this here to restore the video size. 141 updateIntrinsicSize(); 142} 143 144IntRect RenderVideo::videoBox() const 145{ 146 LayoutSize intrinsicSize = this->intrinsicSize(); 147 148 if (videoElement().shouldDisplayPosterImage()) 149 intrinsicSize = m_cachedImageSize; 150 151 return pixelSnappedIntRect(replacedContentRect(intrinsicSize)); 152} 153 154bool RenderVideo::shouldDisplayVideo() const 155{ 156 return !videoElement().shouldDisplayPosterImage(); 157} 158 159void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 160{ 161 MediaPlayer* mediaPlayer = videoElement().player(); 162 bool displayingPoster = videoElement().shouldDisplayPosterImage(); 163 164 Page* page = frame().page(); 165 166 if (!displayingPoster && !mediaPlayer) { 167 if (page && paintInfo.phase == PaintPhaseForeground) 168 page->addRelevantUnpaintedObject(this, visualOverflowRect()); 169 return; 170 } 171 172 LayoutRect rect = videoBox(); 173 if (rect.isEmpty()) { 174 if (page && paintInfo.phase == PaintPhaseForeground) 175 page->addRelevantUnpaintedObject(this, visualOverflowRect()); 176 return; 177 } 178 rect.moveBy(paintOffset); 179 180 if (page && paintInfo.phase == PaintPhaseForeground) 181 page->addRelevantRepaintedObject(this, rect); 182 183 LayoutRect contentRect = contentBoxRect(); 184 contentRect.moveBy(paintOffset); 185 GraphicsContext* context = paintInfo.context; 186 bool clip = !contentRect.contains(rect); 187 GraphicsContextStateSaver stateSaver(*context, clip); 188 if (clip) 189 context->clip(contentRect); 190 191 if (displayingPoster) 192 paintIntoRect(context, rect); 193 else if (view().frameView().paintBehavior() & PaintBehaviorFlattenCompositingLayers) 194 mediaPlayer->paintCurrentFrameInContext(context, pixelSnappedIntRect(rect)); 195 else 196 mediaPlayer->paint(context, pixelSnappedIntRect(rect)); 197} 198 199void RenderVideo::layout() 200{ 201 StackStats::LayoutCheckPoint layoutCheckPoint; 202 RenderMedia::layout(); 203 updatePlayer(); 204} 205 206HTMLVideoElement& RenderVideo::videoElement() const 207{ 208 return toHTMLVideoElement(RenderMedia::mediaElement()); 209} 210 211void RenderVideo::updateFromElement() 212{ 213 RenderMedia::updateFromElement(); 214 updatePlayer(); 215} 216 217void RenderVideo::updatePlayer() 218{ 219 if (documentBeingDestroyed()) 220 return; 221 222 updateIntrinsicSize(); 223 224 MediaPlayer* mediaPlayer = videoElement().player(); 225 if (!mediaPlayer) 226 return; 227 228 if (!videoElement().inActiveDocument()) { 229 mediaPlayer->setVisible(false); 230 return; 231 } 232 233 contentChanged(VideoChanged); 234 235 IntRect videoBounds = videoBox(); 236 mediaPlayer->setFrameView(&view().frameView()); 237 mediaPlayer->setSize(IntSize(videoBounds.width(), videoBounds.height())); 238 mediaPlayer->setVisible(true); 239 mediaPlayer->setShouldMaintainAspectRatio(style().objectFit() != ObjectFitFill); 240} 241 242LayoutUnit RenderVideo::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const 243{ 244 return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred); 245} 246 247LayoutUnit RenderVideo::computeReplacedLogicalHeight() const 248{ 249 return RenderReplaced::computeReplacedLogicalHeight(); 250} 251 252LayoutUnit RenderVideo::minimumReplacedHeight() const 253{ 254 return RenderReplaced::minimumReplacedHeight(); 255} 256 257bool RenderVideo::supportsAcceleratedRendering() const 258{ 259 if (MediaPlayer* player = videoElement().player()) 260 return player->supportsAcceleratedRendering(); 261 return false; 262} 263 264void RenderVideo::acceleratedRenderingStateChanged() 265{ 266 if (MediaPlayer* player = videoElement().player()) 267 player->acceleratedRenderingStateChanged(); 268} 269 270bool RenderVideo::requiresImmediateCompositing() const 271{ 272 MediaPlayer* player = videoElement().player(); 273 return player && player->requiresImmediateCompositing(); 274} 275 276#if ENABLE(FULLSCREEN_API) 277static const RenderBlock* rendererPlaceholder(const RenderObject* renderer) 278{ 279 RenderObject* parent = renderer->parent(); 280 if (!parent) 281 return 0; 282 283 RenderFullScreen* fullScreen = parent->isRenderFullScreen() ? toRenderFullScreen(parent) : 0; 284 if (!fullScreen) 285 return 0; 286 287 return fullScreen->placeholder(); 288} 289 290LayoutUnit RenderVideo::offsetLeft() const 291{ 292 if (const RenderBlock* block = rendererPlaceholder(this)) 293 return block->offsetLeft(); 294 return RenderMedia::offsetLeft(); 295} 296 297LayoutUnit RenderVideo::offsetTop() const 298{ 299 if (const RenderBlock* block = rendererPlaceholder(this)) 300 return block->offsetTop(); 301 return RenderMedia::offsetTop(); 302} 303 304LayoutUnit RenderVideo::offsetWidth() const 305{ 306 if (const RenderBlock* block = rendererPlaceholder(this)) 307 return block->offsetWidth(); 308 return RenderMedia::offsetWidth(); 309} 310 311LayoutUnit RenderVideo::offsetHeight() const 312{ 313 if (const RenderBlock* block = rendererPlaceholder(this)) 314 return block->offsetHeight(); 315 return RenderMedia::offsetHeight(); 316} 317#endif 318 319bool RenderVideo::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const 320{ 321 if (videoElement().shouldDisplayPosterImage()) 322 return RenderImage::foregroundIsKnownToBeOpaqueInRect(localRect, maxDepthToTest); 323 324 if (!videoBox().contains(enclosingIntRect(localRect))) 325 return false; 326 327 if (MediaPlayer* player = videoElement().player()) 328 return player->hasAvailableVideoFrame(); 329 330 return false; 331} 332 333} // namespace WebCore 334 335#endif 336