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#include "HTMLVideoElement.h"
28
29#if ENABLE(VIDEO)
30
31#include "CSSPropertyNames.h"
32#include "Chrome.h"
33#include "ChromeClient.h"
34#include "Document.h"
35#include "EventNames.h"
36#include "Frame.h"
37#include "HTMLImageLoader.h"
38#include "HTMLNames.h"
39#include "HTMLParserIdioms.h"
40#include "Logging.h"
41#include "Page.h"
42#include "RenderImage.h"
43#include "RenderVideo.h"
44#include "ScriptController.h"
45#include "Settings.h"
46#include <wtf/IsoMallocInlines.h>
47#include <wtf/text/TextStream.h>
48
49#if ENABLE(VIDEO_PRESENTATION_MODE)
50#include "VideoFullscreenModel.h"
51#endif
52
53namespace WebCore {
54
55WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLVideoElement);
56
57using namespace HTMLNames;
58
59inline HTMLVideoElement::HTMLVideoElement(const QualifiedName& tagName, Document& document, bool createdByParser)
60 : HTMLMediaElement(tagName, document, createdByParser)
61{
62 ASSERT(hasTagName(videoTag));
63 setHasCustomStyleResolveCallbacks();
64 m_defaultPosterURL = document.settings().defaultVideoPosterURL();
65}
66
67Ref<HTMLVideoElement> HTMLVideoElement::create(const QualifiedName& tagName, Document& document, bool createdByParser)
68{
69 auto videoElement = adoptRef(*new HTMLVideoElement(tagName, document, createdByParser));
70 videoElement->finishInitialization();
71 videoElement->suspendIfNeeded();
72 return videoElement;
73}
74
75Ref<HTMLVideoElement> HTMLVideoElement::create(Document& document)
76{
77 return create(videoTag, document, false);
78}
79
80bool HTMLVideoElement::rendererIsNeeded(const RenderStyle& style)
81{
82 return HTMLElement::rendererIsNeeded(style);
83}
84
85RenderPtr<RenderElement> HTMLVideoElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
86{
87 return createRenderer<RenderVideo>(*this, WTFMove(style));
88}
89
90void HTMLVideoElement::didAttachRenderers()
91{
92 HTMLMediaElement::didAttachRenderers();
93
94 updateDisplayState();
95 if (shouldDisplayPosterImage()) {
96 if (!m_imageLoader)
97 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
98 m_imageLoader->updateFromElement();
99 if (auto* renderer = this->renderer())
100 renderer->imageResource().setCachedImage(m_imageLoader->image());
101 }
102}
103
104void HTMLVideoElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
105{
106 if (name == widthAttr)
107 addHTMLLengthToStyle(style, CSSPropertyWidth, value);
108 else if (name == heightAttr)
109 addHTMLLengthToStyle(style, CSSPropertyHeight, value);
110 else
111 HTMLMediaElement::collectStyleForPresentationAttribute(name, value, style);
112}
113
114bool HTMLVideoElement::isPresentationAttribute(const QualifiedName& name) const
115{
116 if (name == widthAttr || name == heightAttr)
117 return true;
118 return HTMLMediaElement::isPresentationAttribute(name);
119}
120
121void HTMLVideoElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
122{
123 if (name == posterAttr) {
124 // Force a poster recalc by setting m_displayMode to Unknown directly before calling updateDisplayState.
125 HTMLMediaElement::setDisplayMode(Unknown);
126 updateDisplayState();
127
128 if (shouldDisplayPosterImage()) {
129 if (!m_imageLoader)
130 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
131 m_imageLoader->updateFromElementIgnoringPreviousError();
132 } else {
133 if (auto* renderer = this->renderer())
134 renderer->imageResource().setCachedImage(nullptr);
135 }
136 }
137#if ENABLE(WIRELESS_PLAYBACK_TARGET)
138 else if (name == webkitwirelessvideoplaybackdisabledAttr)
139 mediaSession().setWirelessVideoPlaybackDisabled(true);
140#endif
141 else {
142 HTMLMediaElement::parseAttribute(name, value);
143
144#if PLATFORM(IOS_FAMILY) && ENABLE(WIRELESS_PLAYBACK_TARGET)
145 if (name == webkitairplayAttr) {
146 bool disabled = false;
147 if (equalLettersIgnoringASCIICase(attributeWithoutSynchronization(HTMLNames::webkitairplayAttr), "deny"))
148 disabled = true;
149 mediaSession().setWirelessVideoPlaybackDisabled(disabled);
150 }
151#endif
152 }
153
154}
155
156bool HTMLVideoElement::supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenMode videoFullscreenMode) const
157{
158 if (!player())
159 return false;
160
161 if (videoFullscreenMode == HTMLMediaElementEnums::VideoFullscreenModePictureInPicture) {
162 if (!mediaSession().allowsPictureInPicture())
163 return false;
164 if (!player()->supportsPictureInPicture())
165 return false;
166 }
167
168 Page* page = document().page();
169 if (!page)
170 return false;
171
172 if (!player()->supportsFullscreen())
173 return false;
174
175#if PLATFORM(IOS_FAMILY)
176 UNUSED_PARAM(videoFullscreenMode);
177 // Fullscreen implemented by player.
178 return true;
179#else
180#if ENABLE(FULLSCREEN_API)
181 // If the full screen API is enabled and is supported for the current element
182 // do not require that the player has a video track to enter full screen.
183 if (videoFullscreenMode == HTMLMediaElementEnums::VideoFullscreenModeStandard && page->chrome().client().supportsFullScreenForElement(*this, false))
184 return true;
185#endif
186
187 if (!player()->hasVideo())
188 return false;
189
190 return page->chrome().client().supportsVideoFullscreen(videoFullscreenMode);
191#endif // PLATFORM(IOS_FAMILY)
192}
193
194
195#if ENABLE(FULLSCREEN_API) && PLATFORM(IOS_FAMILY)
196void HTMLVideoElement::webkitRequestFullscreen()
197{
198 webkitSetPresentationMode(HTMLVideoElement::VideoPresentationMode::Fullscreen);
199}
200#endif
201
202unsigned HTMLVideoElement::videoWidth() const
203{
204 if (!player())
205 return 0;
206 return clampToUnsigned(player()->naturalSize().width());
207}
208
209unsigned HTMLVideoElement::videoHeight() const
210{
211 if (!player())
212 return 0;
213 return clampToUnsigned(player()->naturalSize().height());
214}
215
216void HTMLVideoElement::scheduleResizeEvent()
217{
218 m_lastReportedVideoWidth = videoWidth();
219 m_lastReportedVideoHeight = videoHeight();
220 scheduleEvent(eventNames().resizeEvent);
221}
222
223void HTMLVideoElement::scheduleResizeEventIfSizeChanged()
224{
225 if (m_lastReportedVideoWidth == videoWidth() && m_lastReportedVideoHeight == videoHeight())
226 return;
227 scheduleResizeEvent();
228}
229
230bool HTMLVideoElement::isURLAttribute(const Attribute& attribute) const
231{
232 return attribute.name() == posterAttr || HTMLMediaElement::isURLAttribute(attribute);
233}
234
235const AtomicString& HTMLVideoElement::imageSourceURL() const
236{
237 const AtomicString& url = attributeWithoutSynchronization(posterAttr);
238 if (!stripLeadingAndTrailingHTMLSpaces(url).isEmpty())
239 return url;
240 return m_defaultPosterURL;
241}
242
243void HTMLVideoElement::setDisplayMode(DisplayMode mode)
244{
245 DisplayMode oldMode = displayMode();
246 URL poster = posterImageURL();
247
248 if (!poster.isEmpty()) {
249 // We have a poster path, but only show it until the user triggers display by playing or seeking and the
250 // media engine has something to display.
251 if (mode == Video) {
252 if (oldMode != Video && player())
253 player()->prepareForRendering();
254 if (!hasAvailableVideoFrame())
255 mode = PosterWaitingForVideo;
256 }
257 } else if (oldMode != Video && player())
258 player()->prepareForRendering();
259
260 HTMLMediaElement::setDisplayMode(mode);
261
262 if (player() && player()->canLoadPoster()) {
263 bool canLoad = true;
264 if (!poster.isEmpty()) {
265 if (RefPtr<Frame> frame = document().frame())
266 canLoad = frame->loader().willLoadMediaElementURL(poster, *this);
267 }
268 if (canLoad)
269 player()->setPoster(poster);
270 }
271
272 if (auto* renderer = this->renderer()) {
273 if (displayMode() != oldMode)
274 renderer->updateFromElement();
275 }
276}
277
278void HTMLVideoElement::updateDisplayState()
279{
280 if (posterImageURL().isEmpty())
281 setDisplayMode(Video);
282 else if (displayMode() < Poster)
283 setDisplayMode(Poster);
284}
285
286void HTMLVideoElement::paintCurrentFrameInContext(GraphicsContext& context, const FloatRect& destRect)
287{
288 RefPtr<MediaPlayer> player = HTMLMediaElement::player();
289 if (!player)
290 return;
291
292 player->setVisible(true); // Make player visible or it won't draw.
293 player->paintCurrentFrameInContext(context, destRect);
294}
295
296bool HTMLVideoElement::copyVideoTextureToPlatformTexture(GraphicsContext3D* context, Platform3DObject texture, GC3Denum target, GC3Dint level, GC3Denum internalFormat, GC3Denum format, GC3Denum type, bool premultiplyAlpha, bool flipY)
297{
298 if (!player())
299 return false;
300 return player()->copyVideoTextureToPlatformTexture(context, texture, target, level, internalFormat, format, type, premultiplyAlpha, flipY);
301}
302
303bool HTMLVideoElement::hasAvailableVideoFrame() const
304{
305 if (!player())
306 return false;
307
308 return player()->hasVideo() && player()->hasAvailableVideoFrame();
309}
310
311NativeImagePtr HTMLVideoElement::nativeImageForCurrentTime()
312{
313 if (!player())
314 return nullptr;
315
316 return player()->nativeImageForCurrentTime();
317}
318
319ExceptionOr<void> HTMLVideoElement::webkitEnterFullscreen()
320{
321 ALWAYS_LOG(LOGIDENTIFIER);
322 if (isFullscreen())
323 return { };
324
325 // Generate an exception if this isn't called in response to a user gesture, or if the
326 // element does not support fullscreen.
327 if (!mediaSession().fullscreenPermitted() || !supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard))
328 return Exception { InvalidStateError };
329
330 enterFullscreen();
331 return { };
332}
333
334void HTMLVideoElement::webkitExitFullscreen()
335{
336 ALWAYS_LOG(LOGIDENTIFIER);
337 if (isFullscreen())
338 exitFullscreen();
339}
340
341bool HTMLVideoElement::webkitSupportsFullscreen()
342{
343 return supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard);
344}
345
346bool HTMLVideoElement::webkitDisplayingFullscreen()
347{
348 return isFullscreen();
349}
350
351void HTMLVideoElement::ancestorWillEnterFullscreen()
352{
353#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
354 if (fullscreenMode() == VideoFullscreenModeNone)
355 return;
356
357 // If this video element's presentation mode is not inline, but its ancestor
358 // is entering fullscreen, exit its current fullscreen mode.
359 exitToFullscreenModeWithoutAnimationIfPossible(fullscreenMode(), VideoFullscreenModeNone);
360#endif
361}
362
363#if ENABLE(WIRELESS_PLAYBACK_TARGET)
364bool HTMLVideoElement::webkitWirelessVideoPlaybackDisabled() const
365{
366 return mediaSession().wirelessVideoPlaybackDisabled();
367}
368
369void HTMLVideoElement::setWebkitWirelessVideoPlaybackDisabled(bool disabled)
370{
371 setBooleanAttribute(webkitwirelessvideoplaybackdisabledAttr, disabled);
372}
373#endif
374
375void HTMLVideoElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
376{
377 if (m_imageLoader)
378 m_imageLoader->elementDidMoveToNewDocument();
379 HTMLMediaElement::didMoveToNewDocument(oldDocument, newDocument);
380}
381
382#if ENABLE(MEDIA_STATISTICS)
383unsigned HTMLVideoElement::webkitDecodedFrameCount() const
384{
385 if (!player())
386 return 0;
387
388 return player()->decodedFrameCount();
389}
390
391unsigned HTMLVideoElement::webkitDroppedFrameCount() const
392{
393 if (!player())
394 return 0;
395
396 return player()->droppedFrameCount();
397}
398#endif
399
400URL HTMLVideoElement::posterImageURL() const
401{
402 String url = stripLeadingAndTrailingHTMLSpaces(imageSourceURL());
403 if (url.isEmpty())
404 return URL();
405 return document().completeURL(url);
406}
407
408#if ENABLE(VIDEO_PRESENTATION_MODE)
409
410bool HTMLVideoElement::webkitSupportsPresentationMode(VideoPresentationMode mode) const
411{
412 if (mode == VideoPresentationMode::Fullscreen)
413 return supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard);
414
415 if (mode == VideoPresentationMode::PictureInPicture) {
416#if PLATFORM(COCOA)
417 if (!supportsPictureInPicture())
418 return false;
419#endif
420
421 return supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture);
422 }
423
424 if (mode == VideoPresentationMode::Inline)
425 return !mediaSession().requiresFullscreenForVideoPlayback();
426
427 return false;
428}
429
430static inline HTMLMediaElementEnums::VideoFullscreenMode toFullscreenMode(HTMLVideoElement::VideoPresentationMode mode)
431{
432 switch (mode) {
433 case HTMLVideoElement::VideoPresentationMode::Fullscreen:
434 return HTMLMediaElementEnums::VideoFullscreenModeStandard;
435 case HTMLVideoElement::VideoPresentationMode::PictureInPicture:
436 return HTMLMediaElementEnums::VideoFullscreenModePictureInPicture;
437 case HTMLVideoElement::VideoPresentationMode::Inline:
438 return HTMLMediaElementEnums::VideoFullscreenModeNone;
439 }
440 ASSERT_NOT_REACHED();
441 return HTMLMediaElementEnums::VideoFullscreenModeNone;
442}
443
444void HTMLVideoElement::webkitSetPresentationMode(VideoPresentationMode mode)
445{
446 ALWAYS_LOG(LOGIDENTIFIER, mode);
447 setFullscreenMode(toFullscreenMode(mode));
448}
449
450void HTMLVideoElement::setFullscreenMode(HTMLMediaElementEnums::VideoFullscreenMode mode)
451{
452 if (mode == VideoFullscreenModeNone && isFullscreen()) {
453 exitFullscreen();
454 return;
455 }
456
457 if (!mediaSession().fullscreenPermitted() || !supportsFullscreen(mode))
458 return;
459
460 enterFullscreen(mode);
461}
462
463static HTMLVideoElement::VideoPresentationMode toPresentationMode(HTMLMediaElementEnums::VideoFullscreenMode mode)
464{
465 if (mode == HTMLMediaElementEnums::VideoFullscreenModeStandard)
466 return HTMLVideoElement::VideoPresentationMode::Fullscreen;
467
468 if (mode & HTMLMediaElementEnums::VideoFullscreenModePictureInPicture)
469 return HTMLVideoElement::VideoPresentationMode::PictureInPicture;
470
471 if (mode == HTMLMediaElementEnums::VideoFullscreenModeNone)
472 return HTMLVideoElement::VideoPresentationMode::Inline;
473
474 ASSERT_NOT_REACHED();
475 return HTMLVideoElement::VideoPresentationMode::Inline;
476}
477
478auto HTMLVideoElement::webkitPresentationMode() const -> VideoPresentationMode
479{
480 return toPresentationMode(fullscreenMode());
481}
482
483void HTMLVideoElement::fullscreenModeChanged(VideoFullscreenMode mode)
484{
485 if (mode != fullscreenMode()) {
486 ALWAYS_LOG(LOGIDENTIFIER, "changed from ", fullscreenMode(), ", to ", mode);
487 scheduleEvent(eventNames().webkitpresentationmodechangedEvent);
488 }
489
490 if (player())
491 player()->setVideoFullscreenMode(mode);
492
493 HTMLMediaElement::fullscreenModeChanged(mode);
494}
495
496#endif
497
498#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
499void HTMLVideoElement::exitToFullscreenModeWithoutAnimationIfPossible(HTMLMediaElementEnums::VideoFullscreenMode fromMode, HTMLMediaElementEnums::VideoFullscreenMode toMode)
500{
501 if (document().page()->chrome().client().supportsVideoFullscreen(fromMode))
502 document().page()->chrome().client().exitVideoFullscreenToModeWithoutAnimation(*this, toMode);
503}
504#endif
505
506}
507
508#endif
509