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 #include "core/rendering/RenderVideo.h" 29 30 #include "HTMLNames.h" 31 #include "core/dom/Document.h" 32 #include "core/html/HTMLVideoElement.h" 33 #include "core/page/Frame.h" 34 #include "core/page/FrameView.h" 35 #include "core/page/Page.h" 36 #include "core/platform/graphics/MediaPlayer.h" 37 #include "core/rendering/PaintInfo.h" 38 #include "core/rendering/RenderFullScreen.h" 39 40 namespace WebCore { 41 42 using namespace HTMLNames; 43 44 RenderVideo::RenderVideo(HTMLVideoElement* video) 45 : RenderMedia(video) 46 { 47 setIntrinsicSize(calculateIntrinsicSize()); 48 } 49 50 RenderVideo::~RenderVideo() 51 { 52 } 53 54 IntSize RenderVideo::defaultSize() 55 { 56 // These values are specified in the spec. 57 static const int cDefaultWidth = 300; 58 static const int cDefaultHeight = 150; 59 60 return IntSize(cDefaultWidth, cDefaultHeight); 61 } 62 63 void RenderVideo::intrinsicSizeChanged() 64 { 65 if (videoElement()->shouldDisplayPosterImage()) 66 RenderMedia::intrinsicSizeChanged(); 67 updateIntrinsicSize(); 68 } 69 70 void RenderVideo::updateIntrinsicSize() 71 { 72 LayoutSize size = calculateIntrinsicSize(); 73 size.scale(style()->effectiveZoom()); 74 75 // Never set the element size to zero when in a media document. 76 if (size.isEmpty() && node()->ownerDocument() && node()->ownerDocument()->isMediaDocument()) 77 return; 78 79 if (size == intrinsicSize()) 80 return; 81 82 setIntrinsicSize(size); 83 setPreferredLogicalWidthsDirty(true); 84 setNeedsLayout(); 85 } 86 87 LayoutSize RenderVideo::calculateIntrinsicSize() 88 { 89 HTMLVideoElement* video = videoElement(); 90 91 // Spec text from 4.8.6 92 // 93 // The intrinsic width of a video element's playback area is the intrinsic width 94 // of the video resource, if that is available; otherwise it is the intrinsic 95 // width of the poster frame, if that is available; otherwise it is 300 CSS pixels. 96 // 97 // The intrinsic height of a video element's playback area is the intrinsic height 98 // of the video resource, if that is available; otherwise it is the intrinsic 99 // height of the poster frame, if that is available; otherwise it is 150 CSS pixels. 100 MediaPlayer* player = mediaElement()->player(); 101 if (player && video->readyState() >= HTMLVideoElement::HAVE_METADATA) { 102 LayoutSize size = player->naturalSize(); 103 if (!size.isEmpty()) 104 return size; 105 } 106 107 if (video->shouldDisplayPosterImage() && !m_cachedImageSize.isEmpty() && !imageResource()->errorOccurred()) 108 return m_cachedImageSize; 109 110 // When the natural size of the video is unavailable, we use the provided 111 // width and height attributes of the video element as the intrinsic size until 112 // better values become available. 113 if (video->hasAttribute(widthAttr) && video->hasAttribute(heightAttr)) 114 return LayoutSize(video->width(), video->height()); 115 116 // <video> in standalone media documents should not use the default 300x150 117 // size since they also have audio-only files. By setting the intrinsic 118 // size to 300x1 the video will resize itself in these cases, and audio will 119 // have the correct height (it needs to be > 0 for controls to render properly). 120 if (video->ownerDocument() && video->ownerDocument()->isMediaDocument()) 121 return LayoutSize(defaultSize().width(), 1); 122 123 return defaultSize(); 124 } 125 126 void RenderVideo::imageChanged(WrappedImagePtr newImage, const IntRect* rect) 127 { 128 RenderMedia::imageChanged(newImage, rect); 129 130 // Cache the image intrinsic size so we can continue to use it to draw the image correctly 131 // even if we know the video intrinsic size but aren't able to draw video frames yet 132 // (we don't want to scale the poster to the video size without keeping aspect ratio). 133 if (videoElement()->shouldDisplayPosterImage()) 134 m_cachedImageSize = intrinsicSize(); 135 136 // The intrinsic size is now that of the image, but in case we already had the 137 // intrinsic size of the video we call this here to restore the video size. 138 updateIntrinsicSize(); 139 } 140 141 IntRect RenderVideo::videoBox() const 142 { 143 if (m_cachedImageSize.isEmpty() && videoElement()->shouldDisplayPosterImage()) 144 return IntRect(); 145 146 LayoutSize elementSize; 147 if (videoElement()->shouldDisplayPosterImage()) 148 elementSize = m_cachedImageSize; 149 else 150 elementSize = intrinsicSize(); 151 152 IntRect contentRect = pixelSnappedIntRect(contentBoxRect()); 153 if (elementSize.isEmpty() || contentRect.isEmpty()) 154 return IntRect(); 155 156 LayoutRect renderBox = contentRect; 157 LayoutUnit ratio = renderBox.width() * elementSize.height() - renderBox.height() * elementSize.width(); 158 if (ratio > 0) { 159 LayoutUnit newWidth = renderBox.height() * elementSize.width() / elementSize.height(); 160 // Just fill the whole area if the difference is one pixel or less (in both sides) 161 if (renderBox.width() - newWidth > 2) 162 renderBox.setWidth(newWidth); 163 renderBox.move((contentRect.width() - renderBox.width()) / 2, 0); 164 } else if (ratio < 0) { 165 LayoutUnit newHeight = renderBox.width() * elementSize.height() / elementSize.width(); 166 if (renderBox.height() - newHeight > 2) 167 renderBox.setHeight(newHeight); 168 renderBox.move(0, (contentRect.height() - renderBox.height()) / 2); 169 } 170 171 return pixelSnappedIntRect(renderBox); 172 } 173 174 bool RenderVideo::shouldDisplayVideo() const 175 { 176 return !videoElement()->shouldDisplayPosterImage(); 177 } 178 179 void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 180 { 181 MediaPlayer* mediaPlayer = mediaElement()->player(); 182 bool displayingPoster = videoElement()->shouldDisplayPosterImage(); 183 184 Page* page = 0; 185 if (Frame* frame = this->frame()) 186 page = frame->page(); 187 188 if (!displayingPoster && !mediaPlayer) { 189 if (page && paintInfo.phase == PaintPhaseForeground) 190 page->addRelevantUnpaintedObject(this, visualOverflowRect()); 191 return; 192 } 193 194 LayoutRect rect = videoBox(); 195 if (rect.isEmpty()) { 196 if (page && paintInfo.phase == PaintPhaseForeground) 197 page->addRelevantUnpaintedObject(this, visualOverflowRect()); 198 return; 199 } 200 rect.moveBy(paintOffset); 201 202 if (page && paintInfo.phase == PaintPhaseForeground) 203 page->addRelevantRepaintedObject(this, rect); 204 205 if (displayingPoster) 206 paintIntoRect(paintInfo.context, rect); 207 else if (document()->view() && document()->view()->paintBehavior() & PaintBehaviorFlattenCompositingLayers) 208 mediaPlayer->paintCurrentFrameInContext(paintInfo.context, pixelSnappedIntRect(rect)); 209 else 210 mediaPlayer->paint(paintInfo.context, pixelSnappedIntRect(rect)); 211 } 212 213 void RenderVideo::layout() 214 { 215 StackStats::LayoutCheckPoint layoutCheckPoint; 216 RenderMedia::layout(); 217 updatePlayer(); 218 } 219 220 HTMLVideoElement* RenderVideo::videoElement() const 221 { 222 ASSERT(isHTMLVideoElement(node())); 223 return toHTMLVideoElement(node()); 224 } 225 226 void RenderVideo::updateFromElement() 227 { 228 RenderMedia::updateFromElement(); 229 updatePlayer(); 230 } 231 232 void RenderVideo::updatePlayer() 233 { 234 updateIntrinsicSize(); 235 236 MediaPlayer* mediaPlayer = mediaElement()->player(); 237 if (!mediaPlayer) 238 return; 239 240 if (!videoElement()->inActiveDocument()) 241 return; 242 243 contentChanged(VideoChanged); 244 } 245 246 LayoutUnit RenderVideo::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const 247 { 248 return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred); 249 } 250 251 LayoutUnit RenderVideo::computeReplacedLogicalHeight() const 252 { 253 return RenderReplaced::computeReplacedLogicalHeight(); 254 } 255 256 LayoutUnit RenderVideo::minimumReplacedHeight() const 257 { 258 return RenderReplaced::minimumReplacedHeight(); 259 } 260 261 bool RenderVideo::supportsAcceleratedRendering() const 262 { 263 MediaPlayer* p = mediaElement()->player(); 264 if (p) 265 return p->supportsAcceleratedRendering(); 266 267 return false; 268 } 269 270 static const RenderBlock* rendererPlaceholder(const RenderObject* renderer) 271 { 272 RenderObject* parent = renderer->parent(); 273 if (!parent) 274 return 0; 275 276 RenderFullScreen* fullScreen = parent->isRenderFullScreen() ? toRenderFullScreen(parent) : 0; 277 if (!fullScreen) 278 return 0; 279 280 return fullScreen->placeholder(); 281 } 282 283 LayoutUnit RenderVideo::offsetLeft() const 284 { 285 if (const RenderBlock* block = rendererPlaceholder(this)) 286 return block->offsetLeft(); 287 return RenderMedia::offsetLeft(); 288 } 289 290 LayoutUnit RenderVideo::offsetTop() const 291 { 292 if (const RenderBlock* block = rendererPlaceholder(this)) 293 return block->offsetTop(); 294 return RenderMedia::offsetTop(); 295 } 296 297 LayoutUnit RenderVideo::offsetWidth() const 298 { 299 if (const RenderBlock* block = rendererPlaceholder(this)) 300 return block->offsetWidth(); 301 return RenderMedia::offsetWidth(); 302 } 303 304 LayoutUnit RenderVideo::offsetHeight() const 305 { 306 if (const RenderBlock* block = rendererPlaceholder(this)) 307 return block->offsetHeight(); 308 return RenderMedia::offsetHeight(); 309 } 310 311 } // namespace WebCore 312