d2d18c380ac5a68bd4a27b6e2de992f0c6e8494e
[WebKit-https.git] / Source / WebCore / html / HTMLVideoElement.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 #if ENABLE(VIDEO)
28 #include "HTMLVideoElement.h"
29
30 #include "Attribute.h"
31 #include "CSSPropertyNames.h"
32 #include "Chrome.h"
33 #include "ChromeClient.h"
34 #include "Document.h"
35 #include "Frame.h"
36 #include "HTMLImageLoader.h"
37 #include "HTMLNames.h"
38 #include "HTMLParserIdioms.h"
39 #include "Logging.h"
40 #include "Page.h"
41 #include "RenderImage.h"
42 #include "RenderVideo.h"
43 #include "ScriptController.h"
44 #include "Settings.h"
45 #include <wtf/NeverDestroyed.h>
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 inline HTMLVideoElement::HTMLVideoElement(const QualifiedName& tagName, Document& document, bool createdByParser)
52     : HTMLMediaElement(tagName, document, createdByParser)
53 {
54     ASSERT(hasTagName(videoTag));
55     setHasCustomStyleResolveCallbacks();
56     if (document.settings())
57         m_defaultPosterURL = document.settings()->defaultVideoPosterURL();
58 }
59
60 Ref<HTMLVideoElement> HTMLVideoElement::create(const QualifiedName& tagName, Document& document, bool createdByParser)
61 {
62     Ref<HTMLVideoElement> videoElement = adoptRef(*new HTMLVideoElement(tagName, document, createdByParser));
63     videoElement->suspendIfNeeded();
64     return videoElement;
65 }
66
67 bool HTMLVideoElement::rendererIsNeeded(const RenderStyle& style) 
68 {
69     return HTMLElement::rendererIsNeeded(style); 
70 }
71
72 RenderPtr<RenderElement> HTMLVideoElement::createElementRenderer(Ref<RenderStyle>&& style)
73 {
74     return createRenderer<RenderVideo>(*this, WTF::move(style));
75 }
76
77 void HTMLVideoElement::didAttachRenderers()
78 {
79     HTMLMediaElement::didAttachRenderers();
80
81     updateDisplayState();
82     if (shouldDisplayPosterImage()) {
83         if (!m_imageLoader)
84             m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
85         m_imageLoader->updateFromElement();
86         if (renderer())
87             downcast<RenderImage>(*renderer()).imageResource().setCachedImage(m_imageLoader->image());
88     }
89 }
90
91 void HTMLVideoElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
92 {
93     if (name == widthAttr)
94         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
95     else if (name == heightAttr)
96         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
97     else
98         HTMLMediaElement::collectStyleForPresentationAttribute(name, value, style);
99 }
100
101 bool HTMLVideoElement::isPresentationAttribute(const QualifiedName& name) const
102 {
103     if (name == widthAttr || name == heightAttr)
104         return true;
105     return HTMLMediaElement::isPresentationAttribute(name);
106 }
107
108 void HTMLVideoElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
109 {
110     if (name == posterAttr) {
111         // Force a poster recalc by setting m_displayMode to Unknown directly before calling updateDisplayState.
112         HTMLMediaElement::setDisplayMode(Unknown);
113         updateDisplayState();
114
115         if (shouldDisplayPosterImage()) {
116             if (!m_imageLoader)
117                 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
118             m_imageLoader->updateFromElementIgnoringPreviousError();
119         } else {
120             if (renderer())
121                 downcast<RenderImage>(*renderer()).imageResource().setCachedImage(nullptr);
122         }
123     }
124 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
125     else if (name == webkitwirelessvideoplaybackdisabledAttr)
126         mediaSession().setWirelessVideoPlaybackDisabled(*this, webkitWirelessVideoPlaybackDisabled());
127 #endif
128 #if ENABLE(VIDEO_PRESENTATION_MODE)
129     else if (name == onwebkitpresentationmodechangedAttr)
130         setAttributeEventListener(eventNames().webkitpresentationmodechangedEvent, name, value);
131 #endif
132     else {
133         HTMLMediaElement::parseAttribute(name, value);    
134
135 #if PLATFORM(IOS) && ENABLE(WIRELESS_PLAYBACK_TARGET)
136         if (name == webkitairplayAttr)
137             mediaSession().setWirelessVideoPlaybackDisabled(*this, webkitWirelessVideoPlaybackDisabled());
138 #endif
139     }
140
141 }
142
143 bool HTMLVideoElement::supportsFullscreen() const
144 {
145     Page* page = document().page();
146     if (!page) 
147         return false;
148
149     if (!player() || !player()->supportsFullscreen())
150         return false;
151
152 #if PLATFORM(IOS)
153     // Fullscreen implemented by player.
154     return true;
155 #else
156 #if ENABLE(FULLSCREEN_API)
157     // If the full screen API is enabled and is supported for the current element
158     // do not require that the player has a video track to enter full screen.
159     if (page->chrome().client().supportsFullScreenForElement(this, false))
160         return true;
161 #endif
162
163     if (!player()->hasVideo())
164         return false;
165
166     return page->chrome().client().supportsVideoFullscreen();
167 #endif // PLATFORM(IOS)
168 }
169
170 unsigned HTMLVideoElement::videoWidth() const
171 {
172     if (!player())
173         return 0;
174     return clampToUnsigned(player()->naturalSize().width());
175 }
176
177 unsigned HTMLVideoElement::videoHeight() const
178 {
179     if (!player())
180         return 0;
181     return clampToUnsigned(player()->naturalSize().height());
182 }
183
184 bool HTMLVideoElement::isURLAttribute(const Attribute& attribute) const
185 {
186     return attribute.name() == posterAttr || HTMLMediaElement::isURLAttribute(attribute);
187 }
188
189 const AtomicString& HTMLVideoElement::imageSourceURL() const
190 {
191     const AtomicString& url = getAttribute(posterAttr);
192     if (!stripLeadingAndTrailingHTMLSpaces(url).isEmpty())
193         return url;
194     return m_defaultPosterURL;
195 }
196
197 void HTMLVideoElement::setDisplayMode(DisplayMode mode)
198 {
199     DisplayMode oldMode = displayMode();
200     URL poster = posterImageURL();
201
202     if (!poster.isEmpty()) {
203         // We have a poster path, but only show it until the user triggers display by playing or seeking and the
204         // media engine has something to display.
205         if (mode == Video) {
206             if (oldMode != Video && player())
207                 player()->prepareForRendering();
208             if (!hasAvailableVideoFrame())
209                 mode = PosterWaitingForVideo;
210         }
211     } else if (oldMode != Video && player())
212         player()->prepareForRendering();
213
214     HTMLMediaElement::setDisplayMode(mode);
215
216     if (player() && player()->canLoadPoster()) {
217         bool canLoad = true;
218         if (!poster.isEmpty()) {
219             if (Frame* frame = document().frame())
220                 canLoad = frame->loader().willLoadMediaElementURL(poster);
221         }
222         if (canLoad)
223             player()->setPoster(poster);
224     }
225
226     if (renderer() && displayMode() != oldMode)
227         renderer()->updateFromElement();
228 }
229
230 void HTMLVideoElement::updateDisplayState()
231 {
232     if (posterImageURL().isEmpty())
233         setDisplayMode(Video);
234     else if (displayMode() < Poster)
235         setDisplayMode(Poster);
236 }
237
238 void HTMLVideoElement::paintCurrentFrameInContext(GraphicsContext* context, const FloatRect& destRect)
239 {
240     MediaPlayer* player = HTMLMediaElement::player();
241     if (!player)
242         return;
243     
244     player->setVisible(true); // Make player visible or it won't draw.
245     player->paintCurrentFrameInContext(context, destRect);
246 }
247
248 bool HTMLVideoElement::copyVideoTextureToPlatformTexture(GraphicsContext3D* context, Platform3DObject texture, GC3Dint level, GC3Denum type, GC3Denum internalFormat, bool premultiplyAlpha, bool flipY)
249 {
250     if (!player())
251         return false;
252     return player()->copyVideoTextureToPlatformTexture(context, texture, level, type, internalFormat, premultiplyAlpha, flipY);
253 }
254
255 bool HTMLVideoElement::hasAvailableVideoFrame() const
256 {
257     if (!player())
258         return false;
259     
260     return player()->hasVideo() && player()->hasAvailableVideoFrame();
261 }
262
263 PassNativeImagePtr HTMLVideoElement::nativeImageForCurrentTime()
264 {
265     if (!player())
266         return nullptr;
267
268     return player()->nativeImageForCurrentTime();
269 }
270
271 void HTMLVideoElement::webkitEnterFullscreen(ExceptionCode& ec)
272 {
273     if (isFullscreen())
274         return;
275
276     // Generate an exception if this isn't called in response to a user gesture, or if the 
277     // element does not support fullscreen.
278     if (!mediaSession().fullscreenPermitted(*this) || !supportsFullscreen()) {
279         ec = INVALID_STATE_ERR;
280         return;
281     }
282
283     enterFullscreen();
284 }
285
286 void HTMLVideoElement::webkitExitFullscreen()
287 {
288     if (isFullscreen())
289         exitFullscreen();
290 }
291
292 bool HTMLVideoElement::webkitSupportsFullscreen()
293 {
294     return supportsFullscreen();
295 }
296
297 bool HTMLVideoElement::webkitDisplayingFullscreen()
298 {
299     return isFullscreen();
300 }
301
302 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
303 bool HTMLVideoElement::webkitWirelessVideoPlaybackDisabled() const
304 {
305     return mediaSession().wirelessVideoPlaybackDisabled(*this);
306 }
307
308 void HTMLVideoElement::setWebkitWirelessVideoPlaybackDisabled(bool disabled)
309 {
310     setBooleanAttribute(webkitwirelessvideoplaybackdisabledAttr, disabled);
311 }
312 #endif
313
314 void HTMLVideoElement::didMoveToNewDocument(Document* oldDocument)
315 {
316     if (m_imageLoader)
317         m_imageLoader->elementDidMoveToNewDocument();
318     HTMLMediaElement::didMoveToNewDocument(oldDocument);
319 }
320
321 #if ENABLE(MEDIA_STATISTICS)
322 unsigned HTMLVideoElement::webkitDecodedFrameCount() const
323 {
324     if (!player())
325         return 0;
326
327     return player()->decodedFrameCount();
328 }
329
330 unsigned HTMLVideoElement::webkitDroppedFrameCount() const
331 {
332     if (!player())
333         return 0;
334
335     return player()->droppedFrameCount();
336 }
337 #endif
338
339 URL HTMLVideoElement::posterImageURL() const
340 {
341     String url = stripLeadingAndTrailingHTMLSpaces(imageSourceURL());
342     if (url.isEmpty())
343         return URL();
344     return document().completeURL(url);
345 }
346
347 #if ENABLE(VIDEO_PRESENTATION_MODE)
348
349 static const AtomicString& presentationModeFullscreen()
350 {
351     static NeverDestroyed<AtomicString> fullscreen("fullscreen", AtomicString::ConstructFromLiteral);
352     return fullscreen;
353 }
354
355 static const AtomicString& presentationModeOptimized()
356 {
357     static NeverDestroyed<AtomicString> optimized("optimized", AtomicString::ConstructFromLiteral);
358     return optimized;
359 }
360
361 static const AtomicString& presentationModeInline()
362 {
363     static NeverDestroyed<AtomicString> inlineMode("inline", AtomicString::ConstructFromLiteral);
364     return inlineMode;
365 }
366
367 bool HTMLVideoElement::webkitSupportsPresentationMode(const String& mode) const
368 {
369     if (mode == presentationModeFullscreen())
370         return mediaSession().fullscreenPermitted(*this) && supportsFullscreen();
371
372     if (mode == presentationModeOptimized())
373         return wkIsOptimizedFullscreenSupported() && mediaSession().allowsAlternateFullscreen(*this) && supportsFullscreen();
374
375     if (mode == presentationModeInline())
376         return !mediaSession().requiresFullscreenForVideoPlayback(*this);
377
378     return false;
379 }
380
381 void HTMLVideoElement::webkitSetPresentationMode(const String& mode)
382 {
383     if (mode == presentationModeInline() && isFullscreen()) {
384         exitFullscreen();
385         return;
386     }
387
388     if (!mediaSession().fullscreenPermitted(*this) || !supportsFullscreen())
389         return;
390
391     LOG(Media, "HTMLVideoElement::webkitSetPresentationMode(%p) - setting to \"%s\"", this, mode.utf8().data());
392
393     if (mode == presentationModeFullscreen())
394         enterFullscreen(VideoFullscreenModeStandard);
395     else if (mode == presentationModeOptimized())
396         enterFullscreen(VideoFullscreenModeOptimized);
397 }
398
399 String HTMLVideoElement::webkitPresentationMode() const
400 {
401     HTMLMediaElement::VideoFullscreenMode mode = fullscreenMode();
402
403     if (mode == VideoFullscreenModeStandard)
404         return presentationModeFullscreen();
405
406     if (mode & VideoFullscreenModeOptimized)
407         return presentationModeOptimized();
408
409     if (mode == VideoFullscreenModeNone)
410         return presentationModeInline();
411
412     ASSERT_NOT_REACHED();
413     return presentationModeInline();
414 }
415
416 void HTMLVideoElement::fullscreenModeChanged(VideoFullscreenMode mode)
417 {
418     if (mode != fullscreenMode()) {
419         LOG(Media, "HTMLVideoElement::fullscreenModeChanged(%p) - mode changed from %i to %i", this, fullscreenMode(), mode);
420         scheduleEvent(eventNames().webkitpresentationmodechangedEvent);
421     }
422
423     HTMLMediaElement::fullscreenModeChanged(mode);
424 }
425
426 #endif
427
428 }
429
430 #endif