Some event handler fixes
[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     else {
129         HTMLMediaElement::parseAttribute(name, value);    
130
131 #if PLATFORM(IOS) && ENABLE(WIRELESS_PLAYBACK_TARGET)
132         if (name == webkitairplayAttr)
133             mediaSession().setWirelessVideoPlaybackDisabled(*this, webkitWirelessVideoPlaybackDisabled());
134 #endif
135     }
136
137 }
138
139 bool HTMLVideoElement::supportsFullscreen() const
140 {
141     Page* page = document().page();
142     if (!page) 
143         return false;
144
145     if (!player() || !player()->supportsFullscreen())
146         return false;
147
148 #if PLATFORM(IOS)
149     // Fullscreen implemented by player.
150     return true;
151 #else
152 #if ENABLE(FULLSCREEN_API)
153     // If the full screen API is enabled and is supported for the current element
154     // do not require that the player has a video track to enter full screen.
155     if (page->chrome().client().supportsFullScreenForElement(this, false))
156         return true;
157 #endif
158
159     if (!player()->hasVideo())
160         return false;
161
162     return page->chrome().client().supportsVideoFullscreen();
163 #endif // PLATFORM(IOS)
164 }
165
166 unsigned HTMLVideoElement::videoWidth() const
167 {
168     if (!player())
169         return 0;
170     return clampToUnsigned(player()->naturalSize().width());
171 }
172
173 unsigned HTMLVideoElement::videoHeight() const
174 {
175     if (!player())
176         return 0;
177     return clampToUnsigned(player()->naturalSize().height());
178 }
179
180 bool HTMLVideoElement::isURLAttribute(const Attribute& attribute) const
181 {
182     return attribute.name() == posterAttr || HTMLMediaElement::isURLAttribute(attribute);
183 }
184
185 const AtomicString& HTMLVideoElement::imageSourceURL() const
186 {
187     const AtomicString& url = getAttribute(posterAttr);
188     if (!stripLeadingAndTrailingHTMLSpaces(url).isEmpty())
189         return url;
190     return m_defaultPosterURL;
191 }
192
193 void HTMLVideoElement::setDisplayMode(DisplayMode mode)
194 {
195     DisplayMode oldMode = displayMode();
196     URL poster = posterImageURL();
197
198     if (!poster.isEmpty()) {
199         // We have a poster path, but only show it until the user triggers display by playing or seeking and the
200         // media engine has something to display.
201         if (mode == Video) {
202             if (oldMode != Video && player())
203                 player()->prepareForRendering();
204             if (!hasAvailableVideoFrame())
205                 mode = PosterWaitingForVideo;
206         }
207     } else if (oldMode != Video && player())
208         player()->prepareForRendering();
209
210     HTMLMediaElement::setDisplayMode(mode);
211
212     if (player() && player()->canLoadPoster()) {
213         bool canLoad = true;
214         if (!poster.isEmpty()) {
215             if (Frame* frame = document().frame())
216                 canLoad = frame->loader().willLoadMediaElementURL(poster);
217         }
218         if (canLoad)
219             player()->setPoster(poster);
220     }
221
222     if (renderer() && displayMode() != oldMode)
223         renderer()->updateFromElement();
224 }
225
226 void HTMLVideoElement::updateDisplayState()
227 {
228     if (posterImageURL().isEmpty())
229         setDisplayMode(Video);
230     else if (displayMode() < Poster)
231         setDisplayMode(Poster);
232 }
233
234 void HTMLVideoElement::paintCurrentFrameInContext(GraphicsContext* context, const FloatRect& destRect)
235 {
236     MediaPlayer* player = HTMLMediaElement::player();
237     if (!player)
238         return;
239     
240     player->setVisible(true); // Make player visible or it won't draw.
241     player->paintCurrentFrameInContext(context, destRect);
242 }
243
244 bool HTMLVideoElement::copyVideoTextureToPlatformTexture(GraphicsContext3D* context, Platform3DObject texture, GC3Dint level, GC3Denum type, GC3Denum internalFormat, bool premultiplyAlpha, bool flipY)
245 {
246     if (!player())
247         return false;
248     return player()->copyVideoTextureToPlatformTexture(context, texture, level, type, internalFormat, premultiplyAlpha, flipY);
249 }
250
251 bool HTMLVideoElement::hasAvailableVideoFrame() const
252 {
253     if (!player())
254         return false;
255     
256     return player()->hasVideo() && player()->hasAvailableVideoFrame();
257 }
258
259 PassNativeImagePtr HTMLVideoElement::nativeImageForCurrentTime()
260 {
261     if (!player())
262         return nullptr;
263
264     return player()->nativeImageForCurrentTime();
265 }
266
267 void HTMLVideoElement::webkitEnterFullscreen(ExceptionCode& ec)
268 {
269     if (isFullscreen())
270         return;
271
272     // Generate an exception if this isn't called in response to a user gesture, or if the 
273     // element does not support fullscreen.
274     if (!mediaSession().fullscreenPermitted(*this) || !supportsFullscreen()) {
275         ec = INVALID_STATE_ERR;
276         return;
277     }
278
279     enterFullscreen();
280 }
281
282 void HTMLVideoElement::webkitExitFullscreen()
283 {
284     if (isFullscreen())
285         exitFullscreen();
286 }
287
288 bool HTMLVideoElement::webkitSupportsFullscreen()
289 {
290     return supportsFullscreen();
291 }
292
293 bool HTMLVideoElement::webkitDisplayingFullscreen()
294 {
295     return isFullscreen();
296 }
297
298 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
299 bool HTMLVideoElement::webkitWirelessVideoPlaybackDisabled() const
300 {
301     return mediaSession().wirelessVideoPlaybackDisabled(*this);
302 }
303
304 void HTMLVideoElement::setWebkitWirelessVideoPlaybackDisabled(bool disabled)
305 {
306     setBooleanAttribute(webkitwirelessvideoplaybackdisabledAttr, disabled);
307 }
308 #endif
309
310 void HTMLVideoElement::didMoveToNewDocument(Document* oldDocument)
311 {
312     if (m_imageLoader)
313         m_imageLoader->elementDidMoveToNewDocument();
314     HTMLMediaElement::didMoveToNewDocument(oldDocument);
315 }
316
317 #if ENABLE(MEDIA_STATISTICS)
318 unsigned HTMLVideoElement::webkitDecodedFrameCount() const
319 {
320     if (!player())
321         return 0;
322
323     return player()->decodedFrameCount();
324 }
325
326 unsigned HTMLVideoElement::webkitDroppedFrameCount() const
327 {
328     if (!player())
329         return 0;
330
331     return player()->droppedFrameCount();
332 }
333 #endif
334
335 URL HTMLVideoElement::posterImageURL() const
336 {
337     String url = stripLeadingAndTrailingHTMLSpaces(imageSourceURL());
338     if (url.isEmpty())
339         return URL();
340     return document().completeURL(url);
341 }
342
343 #if ENABLE(VIDEO_PRESENTATION_MODE)
344
345 static const AtomicString& presentationModeFullscreen()
346 {
347     static NeverDestroyed<AtomicString> fullscreen("fullscreen", AtomicString::ConstructFromLiteral);
348     return fullscreen;
349 }
350
351 static const AtomicString& presentationModeOptimized()
352 {
353     static NeverDestroyed<AtomicString> optimized("optimized", AtomicString::ConstructFromLiteral);
354     return optimized;
355 }
356
357 static const AtomicString& presentationModeInline()
358 {
359     static NeverDestroyed<AtomicString> inlineMode("inline", AtomicString::ConstructFromLiteral);
360     return inlineMode;
361 }
362
363 bool HTMLVideoElement::webkitSupportsPresentationMode(const String& mode) const
364 {
365     if (mode == presentationModeFullscreen())
366         return mediaSession().fullscreenPermitted(*this) && supportsFullscreen();
367
368     if (mode == presentationModeOptimized())
369         return wkIsOptimizedFullscreenSupported() && mediaSession().allowsAlternateFullscreen(*this) && supportsFullscreen();
370
371     if (mode == presentationModeInline())
372         return !mediaSession().requiresFullscreenForVideoPlayback(*this);
373
374     return false;
375 }
376
377 void HTMLVideoElement::webkitSetPresentationMode(const String& mode)
378 {
379     if (mode == presentationModeInline() && isFullscreen()) {
380         exitFullscreen();
381         return;
382     }
383
384     if (!mediaSession().fullscreenPermitted(*this) || !supportsFullscreen())
385         return;
386
387     LOG(Media, "HTMLVideoElement::webkitSetPresentationMode(%p) - setting to \"%s\"", this, mode.utf8().data());
388
389     if (mode == presentationModeFullscreen())
390         enterFullscreen(VideoFullscreenModeStandard);
391     else if (mode == presentationModeOptimized())
392         enterFullscreen(VideoFullscreenModeOptimized);
393 }
394
395 String HTMLVideoElement::webkitPresentationMode() const
396 {
397     HTMLMediaElement::VideoFullscreenMode mode = fullscreenMode();
398
399     if (mode == VideoFullscreenModeStandard)
400         return presentationModeFullscreen();
401
402     if (mode & VideoFullscreenModeOptimized)
403         return presentationModeOptimized();
404
405     if (mode == VideoFullscreenModeNone)
406         return presentationModeInline();
407
408     ASSERT_NOT_REACHED();
409     return presentationModeInline();
410 }
411
412 void HTMLVideoElement::fullscreenModeChanged(VideoFullscreenMode mode)
413 {
414     if (mode != fullscreenMode()) {
415         LOG(Media, "HTMLVideoElement::fullscreenModeChanged(%p) - mode changed from %i to %i", this, fullscreenMode(), mode);
416         scheduleEvent(eventNames().webkitpresentationmodechangedEvent);
417     }
418
419     HTMLMediaElement::fullscreenModeChanged(mode);
420 }
421
422 #endif
423
424 }
425
426 #endif