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