RenderVideo should always update the intrinsic size before layout.
[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 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
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 RenderVideo::RenderVideo(HTMLVideoElement& element, Ref<RenderStyle>&& style)
52     : RenderMedia(element, WTFMove(style))
53 {
54     setIntrinsicSize(calculateIntrinsicSize());
55 }
56
57 RenderVideo::~RenderVideo()
58 {
59     if (MediaPlayer* player = videoElement().player())
60         player->setVisible(false);
61 }
62
63 IntSize RenderVideo::defaultSize()
64 {
65     // These values are specified in the spec.
66     static const int cDefaultWidth = 300;
67     static const int cDefaultHeight = 150;
68
69     return IntSize(cDefaultWidth, cDefaultHeight);
70 }
71
72 void RenderVideo::intrinsicSizeChanged()
73 {
74     if (videoElement().shouldDisplayPosterImage())
75         RenderMedia::intrinsicSizeChanged();
76     updateIntrinsicSize(); 
77 }
78
79 bool RenderVideo::updateIntrinsicSize()
80 {
81     LayoutSize size = calculateIntrinsicSize();
82     size.scale(style().effectiveZoom());
83
84     // Never set the element size to zero when in a media document.
85     if (size.isEmpty() && document().isMediaDocument())
86         return false;
87
88     if (size == intrinsicSize())
89         return false;
90
91     setIntrinsicSize(size);
92     setPreferredLogicalWidthsDirty(true);
93     setNeedsLayout();
94     return true;
95 }
96     
97 LayoutSize RenderVideo::calculateIntrinsicSize()
98 {
99     // Spec text from 4.8.6
100     //
101     // The intrinsic width of a video element's playback area is the intrinsic width 
102     // of the video resource, if that is available; otherwise it is the intrinsic 
103     // width of the poster frame, if that is available; otherwise it is 300 CSS pixels.
104     //
105     // The intrinsic height of a video element's playback area is the intrinsic height 
106     // of the video resource, if that is available; otherwise it is the intrinsic 
107     // height of the poster frame, if that is available; otherwise it is 150 CSS pixels.
108     MediaPlayer* player = videoElement().player();
109     if (player && videoElement().readyState() >= HTMLVideoElement::HAVE_METADATA) {
110         LayoutSize size(player->naturalSize());
111         if (!size.isEmpty())
112             return size;
113     }
114
115     if (videoElement().shouldDisplayPosterImage() && !m_cachedImageSize.isEmpty() && !imageResource().errorOccurred())
116         return m_cachedImageSize;
117
118     // <video> in standalone media documents should not use the default 300x150
119     // size since they also have audio-only files. By setting the intrinsic
120     // size to 300x1 the video will resize itself in these cases, and audio will
121     // have the correct height (it needs to be > 0 for controls to render properly).
122     if (videoElement().document().isMediaDocument())
123         return LayoutSize(defaultSize().width(), 1);
124
125     return defaultSize();
126 }
127
128 void RenderVideo::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
129 {
130     RenderMedia::imageChanged(newImage, rect);
131
132     // Cache the image intrinsic size so we can continue to use it to draw the image correctly
133     // even if we know the video intrinsic size but aren't able to draw video frames yet
134     // (we don't want to scale the poster to the video size without keeping aspect ratio).
135     if (videoElement().shouldDisplayPosterImage())
136         m_cachedImageSize = intrinsicSize();
137
138     // The intrinsic size is now that of the image, but in case we already had the
139     // intrinsic size of the video we call this here to restore the video size.
140     updateIntrinsicSize();
141 }
142
143 IntRect RenderVideo::videoBox() const
144 {
145     LayoutSize intrinsicSize = this->intrinsicSize();
146
147     if (videoElement().shouldDisplayPosterImage())
148         intrinsicSize = m_cachedImageSize;
149
150     return snappedIntRect(replacedContentRect(intrinsicSize));
151 }
152
153 bool RenderVideo::shouldDisplayVideo() const
154 {
155     return !videoElement().shouldDisplayPosterImage();
156 }
157
158 void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
159 {
160     MediaPlayer* mediaPlayer = videoElement().player();
161     bool displayingPoster = videoElement().shouldDisplayPosterImage();
162
163     Page* page = frame().page();
164
165     if (!displayingPoster && !mediaPlayer) {
166         if (page && paintInfo.phase == PaintPhaseForeground)
167             page->addRelevantUnpaintedObject(this, visualOverflowRect());
168         return;
169     }
170
171     LayoutRect rect = videoBox();
172     if (rect.isEmpty()) {
173         if (page && paintInfo.phase == PaintPhaseForeground)
174             page->addRelevantUnpaintedObject(this, visualOverflowRect());
175         return;
176     }
177     rect.moveBy(paintOffset);
178
179     if (page && paintInfo.phase == PaintPhaseForeground)
180         page->addRelevantRepaintedObject(this, rect);
181
182     LayoutRect contentRect = contentBoxRect();
183     contentRect.moveBy(paintOffset);
184     GraphicsContext& context = paintInfo.context();
185     bool clip = !contentRect.contains(rect);
186     GraphicsContextStateSaver stateSaver(context, clip);
187     if (clip)
188         context.clip(contentRect);
189
190     if (displayingPoster)
191         paintIntoRect(context, rect);
192     else if (!videoElement().isFullscreen() || !mediaPlayer->supportsAcceleratedRendering()) {
193         if (view().frameView().paintBehavior() & PaintBehaviorFlattenCompositingLayers)
194             mediaPlayer->paintCurrentFrameInContext(context, rect);
195         else
196             mediaPlayer->paint(context, rect);
197     }
198 }
199
200 void RenderVideo::layout()
201 {
202     StackStats::LayoutCheckPoint layoutCheckPoint;
203     updateIntrinsicSize();
204     RenderMedia::layout();
205     updatePlayer();
206 }
207     
208 HTMLVideoElement& RenderVideo::videoElement() const
209 {
210     return downcast<HTMLVideoElement>(RenderMedia::mediaElement());
211 }
212
213 void RenderVideo::updateFromElement()
214 {
215     RenderMedia::updateFromElement();
216     updatePlayer();
217 }
218
219 void RenderVideo::updatePlayer()
220 {
221     if (documentBeingDestroyed())
222         return;
223
224     bool intrinsicSizeChanged;
225     intrinsicSizeChanged = updateIntrinsicSize();
226     ASSERT_UNUSED(intrinsicSizeChanged, !intrinsicSizeChanged || !view().frameView().isInRenderTreeLayout());
227
228     MediaPlayer* mediaPlayer = videoElement().player();
229     if (!mediaPlayer)
230         return;
231
232     if (!videoElement().inActiveDocument()) {
233         mediaPlayer->setVisible(false);
234         return;
235     }
236
237     contentChanged(VideoChanged);
238     
239     IntRect videoBounds = videoBox(); 
240     mediaPlayer->setSize(IntSize(videoBounds.width(), videoBounds.height()));
241     mediaPlayer->setVisible(true);
242     mediaPlayer->setShouldMaintainAspectRatio(style().objectFit() != ObjectFitFill);
243 }
244
245 LayoutUnit RenderVideo::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
246 {
247     return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred);
248 }
249
250 LayoutUnit RenderVideo::computeReplacedLogicalHeight() const
251 {
252     return RenderReplaced::computeReplacedLogicalHeight();
253 }
254
255 LayoutUnit RenderVideo::minimumReplacedHeight() const 
256 {
257     return RenderReplaced::minimumReplacedHeight(); 
258 }
259
260 bool RenderVideo::supportsAcceleratedRendering() const
261 {
262     if (MediaPlayer* player = videoElement().player())
263         return player->supportsAcceleratedRendering();
264     return false;
265 }
266
267 void RenderVideo::acceleratedRenderingStateChanged()
268 {
269     if (MediaPlayer* player = videoElement().player())
270         player->acceleratedRenderingStateChanged();
271 }
272
273 bool RenderVideo::requiresImmediateCompositing() const
274 {
275     MediaPlayer* player = videoElement().player();
276     return player && player->requiresImmediateCompositing();
277 }
278
279 #if ENABLE(FULLSCREEN_API)
280 static const RenderBlock* rendererPlaceholder(const RenderObject* renderer)
281 {
282     RenderObject* parent = renderer->parent();
283     return is<RenderFullScreen>(parent) ? downcast<RenderFullScreen>(*parent).placeholder() : nullptr;
284 }
285
286 LayoutUnit RenderVideo::offsetLeft() const
287 {
288     if (const RenderBlock* block = rendererPlaceholder(this))
289         return block->offsetLeft();
290     return RenderMedia::offsetLeft();
291 }
292
293 LayoutUnit RenderVideo::offsetTop() const
294 {
295     if (const RenderBlock* block = rendererPlaceholder(this))
296         return block->offsetTop();
297     return RenderMedia::offsetTop();
298 }
299
300 LayoutUnit RenderVideo::offsetWidth() const
301 {
302     if (const RenderBlock* block = rendererPlaceholder(this))
303         return block->offsetWidth();
304     return RenderMedia::offsetWidth();
305 }
306
307 LayoutUnit RenderVideo::offsetHeight() const
308 {
309     if (const RenderBlock* block = rendererPlaceholder(this))
310         return block->offsetHeight();
311     return RenderMedia::offsetHeight();
312 }
313 #endif
314
315 bool RenderVideo::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const
316 {
317     if (videoElement().shouldDisplayPosterImage())
318         return RenderImage::foregroundIsKnownToBeOpaqueInRect(localRect, maxDepthToTest);
319
320     if (!videoBox().contains(enclosingIntRect(localRect)))
321         return false;
322
323     if (MediaPlayer* player = videoElement().player())
324         return player->hasAvailableVideoFrame();
325
326     return false;
327 }
328
329 } // namespace WebCore
330
331 #endif