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