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
40namespace WebCore {
41
42using namespace HTMLNames;
43
44RenderVideo::RenderVideo(HTMLVideoElement* video)
45    : RenderMedia(video)
46{
47    setIntrinsicSize(calculateIntrinsicSize());
48}
49
50RenderVideo::~RenderVideo()
51{
52}
53
54IntSize 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
63void RenderVideo::intrinsicSizeChanged()
64{
65    if (videoElement()->shouldDisplayPosterImage())
66        RenderMedia::intrinsicSizeChanged();
67    updateIntrinsicSize();
68}
69
70void 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
87LayoutSize 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
126void 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
141IntRect 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
174bool RenderVideo::shouldDisplayVideo() const
175{
176    return !videoElement()->shouldDisplayPosterImage();
177}
178
179void 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
213void RenderVideo::layout()
214{
215    StackStats::LayoutCheckPoint layoutCheckPoint;
216    RenderMedia::layout();
217    updatePlayer();
218}
219
220HTMLVideoElement* RenderVideo::videoElement() const
221{
222    ASSERT(isHTMLVideoElement(node()));
223    return toHTMLVideoElement(node());
224}
225
226void RenderVideo::updateFromElement()
227{
228    RenderMedia::updateFromElement();
229    updatePlayer();
230}
231
232void 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
246LayoutUnit RenderVideo::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
247{
248    return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred);
249}
250
251LayoutUnit RenderVideo::computeReplacedLogicalHeight() const
252{
253    return RenderReplaced::computeReplacedLogicalHeight();
254}
255
256LayoutUnit RenderVideo::minimumReplacedHeight() const
257{
258    return RenderReplaced::minimumReplacedHeight();
259}
260
261bool RenderVideo::supportsAcceleratedRendering() const
262{
263    MediaPlayer* p = mediaElement()->player();
264    if (p)
265        return p->supportsAcceleratedRendering();
266
267    return false;
268}
269
270static 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
283LayoutUnit RenderVideo::offsetLeft() const
284{
285    if (const RenderBlock* block = rendererPlaceholder(this))
286        return block->offsetLeft();
287    return RenderMedia::offsetLeft();
288}
289
290LayoutUnit RenderVideo::offsetTop() const
291{
292    if (const RenderBlock* block = rendererPlaceholder(this))
293        return block->offsetTop();
294    return RenderMedia::offsetTop();
295}
296
297LayoutUnit RenderVideo::offsetWidth() const
298{
299    if (const RenderBlock* block = rendererPlaceholder(this))
300        return block->offsetWidth();
301    return RenderMedia::offsetWidth();
302}
303
304LayoutUnit 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