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