Implement object-fit CSS property
[WebKit-https.git] / Source / WebCore / rendering / RenderVideo.cpp
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
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 RenderVideo::RenderVideo(HTMLVideoElement* video)
52     : RenderMedia(video)
53 {
54     setIntrinsicSize(calculateIntrinsicSize());
55 }
56
57 RenderVideo::~RenderVideo()
58 {
59     if (MediaPlayer* p = mediaElement()->player()) {
60         p->setVisible(false);
61         p->setFrameView(0);
62     }
63 }
64
65 IntSize 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
74 void RenderVideo::intrinsicSizeChanged()
75 {
76     if (videoElement()->shouldDisplayPosterImage())
77         RenderMedia::intrinsicSizeChanged();
78     updateIntrinsicSize(); 
79 }
80
81 void 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     
98 LayoutSize 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->document() && video->document()->isMediaDocument())
132         return LayoutSize(defaultSize().width(), 1);
133
134     return defaultSize();
135 }
136
137 void 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
152 IntRect RenderVideo::videoBox() const
153 {
154     LayoutSize intrinsicSize = this->intrinsicSize();
155
156     if (videoElement()->shouldDisplayPosterImage())
157         intrinsicSize = m_cachedImageSize;
158
159     return pixelSnappedIntRect(replacedContentRect(intrinsicSize));
160 }
161
162 bool RenderVideo::shouldDisplayVideo() const
163 {
164     return !videoElement()->shouldDisplayPosterImage();
165 }
166
167 void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
168 {
169     MediaPlayer* mediaPlayer = mediaElement()->player();
170     bool displayingPoster = videoElement()->shouldDisplayPosterImage();
171
172     Page* page = frame().page();
173
174     if (!displayingPoster && !mediaPlayer) {
175         if (page && paintInfo.phase == PaintPhaseForeground)
176             page->addRelevantUnpaintedObject(this, visualOverflowRect());
177         return;
178     }
179
180     LayoutRect rect = videoBox();
181     if (rect.isEmpty()) {
182         if (page && paintInfo.phase == PaintPhaseForeground)
183             page->addRelevantUnpaintedObject(this, visualOverflowRect());
184         return;
185     }
186     rect.moveBy(paintOffset);
187
188     if (page && paintInfo.phase == PaintPhaseForeground)
189         page->addRelevantRepaintedObject(this, rect);
190
191     LayoutRect contentRect = contentBoxRect();
192     contentRect.moveBy(paintOffset);
193     GraphicsContext* context = paintInfo.context;
194     bool clip = !contentRect.contains(rect);
195     GraphicsContextStateSaver stateSaver(*context, clip);
196     if (clip)
197         context->clip(contentRect);
198
199     if (displayingPoster)
200         paintIntoRect(context, rect);
201     else if (view().frameView().paintBehavior() & PaintBehaviorFlattenCompositingLayers)
202         mediaPlayer->paintCurrentFrameInContext(context, pixelSnappedIntRect(rect));
203     else
204         mediaPlayer->paint(context, pixelSnappedIntRect(rect));
205 }
206
207 void RenderVideo::layout()
208 {
209     StackStats::LayoutCheckPoint layoutCheckPoint;
210     RenderMedia::layout();
211     updatePlayer();
212 }
213     
214 HTMLVideoElement* RenderVideo::videoElement() const
215 {
216     return toHTMLVideoElement(node()); 
217 }
218
219 void RenderVideo::updateFromElement()
220 {
221     RenderMedia::updateFromElement();
222     updatePlayer();
223 }
224
225 void RenderVideo::updatePlayer()
226 {
227     if (documentBeingDestroyed())
228         return;
229
230     updateIntrinsicSize();
231
232     MediaPlayer* mediaPlayer = mediaElement()->player();
233     if (!mediaPlayer)
234         return;
235
236     if (!videoElement()->inActiveDocument()) {
237         mediaPlayer->setVisible(false);
238         return;
239     }
240
241 #if USE(ACCELERATED_COMPOSITING)
242     contentChanged(VideoChanged);
243 #endif
244     
245     IntRect videoBounds = videoBox(); 
246     mediaPlayer->setFrameView(&view().frameView());
247     mediaPlayer->setSize(IntSize(videoBounds.width(), videoBounds.height()));
248     mediaPlayer->setVisible(true);
249 }
250
251 LayoutUnit RenderVideo::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
252 {
253     return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred);
254 }
255
256 LayoutUnit RenderVideo::computeReplacedLogicalHeight() const
257 {
258     return RenderReplaced::computeReplacedLogicalHeight();
259 }
260
261 LayoutUnit RenderVideo::minimumReplacedHeight() const 
262 {
263     return RenderReplaced::minimumReplacedHeight(); 
264 }
265
266 #if USE(ACCELERATED_COMPOSITING)
267 bool RenderVideo::supportsAcceleratedRendering() const
268 {
269     MediaPlayer* p = mediaElement()->player();
270     if (p)
271         return p->supportsAcceleratedRendering();
272
273     return false;
274 }
275
276 void RenderVideo::acceleratedRenderingStateChanged()
277 {
278     MediaPlayer* p = mediaElement()->player();
279     if (p)
280         p->acceleratedRenderingStateChanged();
281 }
282 #endif  // USE(ACCELERATED_COMPOSITING)
283
284 #if ENABLE(FULLSCREEN_API)
285 static const RenderBlock* rendererPlaceholder(const RenderObject* renderer)
286 {
287     RenderObject* parent = renderer->parent();
288     if (!parent)
289         return 0;
290     
291     RenderFullScreen* fullScreen = parent->isRenderFullScreen() ? toRenderFullScreen(parent) : 0;
292     if (!fullScreen)
293         return 0;
294     
295     return fullScreen->placeholder();
296 }
297
298 LayoutUnit RenderVideo::offsetLeft() const
299 {
300     if (const RenderBlock* block = rendererPlaceholder(this))
301         return block->offsetLeft();
302     return RenderMedia::offsetLeft();
303 }
304
305 LayoutUnit RenderVideo::offsetTop() const
306 {
307     if (const RenderBlock* block = rendererPlaceholder(this))
308         return block->offsetTop();
309     return RenderMedia::offsetTop();
310 }
311
312 LayoutUnit RenderVideo::offsetWidth() const
313 {
314     if (const RenderBlock* block = rendererPlaceholder(this))
315         return block->offsetWidth();
316     return RenderMedia::offsetWidth();
317 }
318
319 LayoutUnit RenderVideo::offsetHeight() const
320 {
321     if (const RenderBlock* block = rendererPlaceholder(this))
322         return block->offsetHeight();
323     return RenderMedia::offsetHeight();
324 }
325 #endif
326
327 bool RenderVideo::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const
328 {
329     if (videoElement()->shouldDisplayPosterImage())
330         return RenderImage::foregroundIsKnownToBeOpaqueInRect(localRect, maxDepthToTest);
331
332     if (!videoBox().contains(enclosingIntRect(localRect)))
333         return false;
334
335     if (MediaPlayer* player = mediaElement()->player())
336         return player->hasAvailableVideoFrame();
337
338     return false;
339 }
340
341 } // namespace WebCore
342
343 #endif