Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / shadow / MediaControls.cpp
1 /*
2  * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
3  * Copyright (C) 2011, 2012 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "core/html/shadow/MediaControls.h"
29
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "core/events/MouseEvent.h"
32 #include "core/frame/Settings.h"
33 #include "core/html/HTMLMediaElement.h"
34 #include "core/html/MediaController.h"
35 #include "core/rendering/RenderTheme.h"
36
37 namespace blink {
38
39 // If you change this value, then also update the corresponding value in
40 // LayoutTests/media/media-controls.js.
41 static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3;
42
43 static bool fullscreenIsSupported(const Document& document)
44 {
45     return !document.settings() || document.settings()->fullscreenSupported();
46 }
47
48 MediaControls::MediaControls(HTMLMediaElement& mediaElement)
49     : HTMLDivElement(mediaElement.document())
50     , m_mediaElement(&mediaElement)
51     , m_panel(nullptr)
52     , m_textDisplayContainer(nullptr)
53     , m_overlayPlayButton(nullptr)
54     , m_overlayEnclosure(nullptr)
55     , m_playButton(nullptr)
56     , m_currentTimeDisplay(nullptr)
57     , m_timeline(nullptr)
58     , m_muteButton(nullptr)
59     , m_volumeSlider(nullptr)
60     , m_toggleClosedCaptionsButton(nullptr)
61     , m_fullScreenButton(nullptr)
62     , m_castButton(nullptr)
63     , m_overlayCastButton(nullptr)
64     , m_durationDisplay(nullptr)
65     , m_enclosure(nullptr)
66     , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
67     , m_isMouseOverControls(false)
68     , m_isPausedForScrubbing(false)
69     , m_wasLastEventTouch(false)
70 {
71 }
72
73 PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
74 {
75     RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
76
77     if (controls->initializeControls())
78         return controls.release();
79
80     return nullptr;
81 }
82
83 bool MediaControls::initializeControls()
84 {
85     TrackExceptionState exceptionState;
86
87     RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
88
89     if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
90         RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
91         m_overlayPlayButton = overlayPlayButton.get();
92         overlayEnclosure->appendChild(overlayPlayButton.release(), exceptionState);
93         if (exceptionState.hadException())
94             return false;
95     }
96
97     RefPtrWillBeRawPtr<MediaControlCastButtonElement> overlayCastButton = MediaControlCastButtonElement::create(*this, true);
98     m_overlayCastButton = overlayCastButton.get();
99     overlayEnclosure->appendChild(overlayCastButton.release(), exceptionState);
100     if (exceptionState.hadException())
101         return false;
102
103     m_overlayEnclosure = overlayEnclosure.get();
104     appendChild(overlayEnclosure.release(), exceptionState);
105     if (exceptionState.hadException())
106         return false;
107
108     // Create an enclosing element for the panel so we can visually offset the controls correctly.
109     RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
110
111     RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
112
113     RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
114     m_playButton = playButton.get();
115     panel->appendChild(playButton.release(), exceptionState);
116     if (exceptionState.hadException())
117         return false;
118
119     RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
120     m_timeline = timeline.get();
121     panel->appendChild(timeline.release(), exceptionState);
122     if (exceptionState.hadException())
123         return false;
124
125     RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
126     m_currentTimeDisplay = currentTimeDisplay.get();
127     m_currentTimeDisplay->hide();
128     panel->appendChild(currentTimeDisplay.release(), exceptionState);
129     if (exceptionState.hadException())
130         return false;
131
132     RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
133     m_durationDisplay = durationDisplay.get();
134     panel->appendChild(durationDisplay.release(), exceptionState);
135     if (exceptionState.hadException())
136         return false;
137
138     RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
139     m_muteButton = muteButton.get();
140     panel->appendChild(muteButton.release(), exceptionState);
141     if (exceptionState.hadException())
142         return false;
143
144     RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
145     m_volumeSlider = slider.get();
146     panel->appendChild(slider.release(), exceptionState);
147     if (exceptionState.hadException())
148         return false;
149
150     RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
151     m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
152     panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
153     if (exceptionState.hadException())
154         return false;
155
156     RefPtrWillBeRawPtr<MediaControlCastButtonElement> castButton = MediaControlCastButtonElement::create(*this, false);
157     m_castButton = castButton.get();
158     panel->appendChild(castButton.release(), exceptionState);
159     if (exceptionState.hadException())
160         return false;
161
162     RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
163     m_fullScreenButton = fullscreenButton.get();
164     panel->appendChild(fullscreenButton.release(), exceptionState);
165     if (exceptionState.hadException())
166         return false;
167
168     m_panel = panel.get();
169     enclosure->appendChild(panel.release(), exceptionState);
170     if (exceptionState.hadException())
171         return false;
172
173     m_enclosure = enclosure.get();
174     appendChild(enclosure.release(), exceptionState);
175     if (exceptionState.hadException())
176         return false;
177
178     return true;
179 }
180
181 void MediaControls::reset()
182 {
183     double duration = mediaElement().duration();
184     m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
185     m_durationDisplay->setCurrentValue(duration);
186
187     updatePlayState();
188
189     updateCurrentTimeDisplay();
190
191     m_timeline->setDuration(duration);
192     m_timeline->setPosition(mediaElement().currentTime());
193
194     if (!mediaElement().hasAudio())
195         m_volumeSlider->hide();
196     else
197         m_volumeSlider->show();
198     updateVolume();
199
200     refreshClosedCaptionsButtonVisibility();
201
202     if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
203         m_fullScreenButton->show();
204     else
205         m_fullScreenButton->hide();
206
207     refreshCastButtonVisibility();
208     makeOpaque();
209 }
210
211 void MediaControls::show()
212 {
213     makeOpaque();
214     m_panel->setIsDisplayed(true);
215     m_panel->show();
216     if (m_overlayPlayButton)
217         m_overlayPlayButton->updateDisplayType();
218 }
219
220 void MediaControls::mediaElementFocused()
221 {
222     show();
223     resetHideMediaControlsTimer();
224 }
225
226 void MediaControls::hide()
227 {
228     m_panel->setIsDisplayed(false);
229     m_panel->hide();
230     if (m_overlayPlayButton)
231         m_overlayPlayButton->hide();
232 }
233
234 void MediaControls::makeOpaque()
235 {
236     m_panel->makeOpaque();
237 }
238
239 void MediaControls::makeTransparent()
240 {
241     m_panel->makeTransparent();
242 }
243
244 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
245 {
246     // Never hide for a media element without visual representation.
247     if (!mediaElement().hasVideo())
248         return false;
249     // Don't hide if the mouse is over the controls.
250     const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover;
251     if (!ignoreControlsHover && m_panel->hovered())
252         return false;
253     // Don't hide if the mouse is over the video area.
254     const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
255     if (!ignoreVideoHover && m_isMouseOverControls)
256         return false;
257     // Don't hide if focus is on the HTMLMediaElement or within the
258     // controls/shadow tree. (Perform the checks separately to avoid going
259     // through all the potential ancestor hosts for the focused element.)
260     const bool ignoreFocus = behaviorFlags & IgnoreFocus;
261     if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
262         return false;
263     return true;
264 }
265
266 void MediaControls::playbackStarted()
267 {
268     m_currentTimeDisplay->show();
269     m_durationDisplay->hide();
270
271     updatePlayState();
272     m_timeline->setPosition(mediaElement().currentTime());
273     updateCurrentTimeDisplay();
274
275     startHideMediaControlsTimer();
276 }
277
278 void MediaControls::playbackProgressed()
279 {
280     m_timeline->setPosition(mediaElement().currentTime());
281     updateCurrentTimeDisplay();
282
283     if (shouldHideMediaControls())
284         makeTransparent();
285 }
286
287 void MediaControls::playbackStopped()
288 {
289     updatePlayState();
290     m_timeline->setPosition(mediaElement().currentTime());
291     updateCurrentTimeDisplay();
292     makeOpaque();
293
294     stopHideMediaControlsTimer();
295 }
296
297 void MediaControls::updatePlayState()
298 {
299     if (m_isPausedForScrubbing)
300         return;
301
302     if (m_overlayPlayButton)
303         m_overlayPlayButton->updateDisplayType();
304     m_playButton->updateDisplayType();
305 }
306
307 void MediaControls::beginScrubbing()
308 {
309     if (!mediaElement().togglePlayStateWillPlay()) {
310         m_isPausedForScrubbing = true;
311         mediaElement().togglePlayState();
312     }
313 }
314
315 void MediaControls::endScrubbing()
316 {
317     if (m_isPausedForScrubbing) {
318         m_isPausedForScrubbing = false;
319         if (mediaElement().togglePlayStateWillPlay())
320             mediaElement().togglePlayState();
321     }
322 }
323
324 void MediaControls::updateCurrentTimeDisplay()
325 {
326     double now = mediaElement().currentTime();
327     double duration = mediaElement().duration();
328
329     // After seek, hide duration display and show current time.
330     if (now > 0) {
331         m_currentTimeDisplay->show();
332         m_durationDisplay->hide();
333     }
334
335     // Allow the theme to format the time.
336     m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
337     m_currentTimeDisplay->setCurrentValue(now);
338 }
339
340 void MediaControls::updateVolume()
341 {
342     m_muteButton->updateDisplayType();
343     if (m_muteButton->renderer())
344         m_muteButton->renderer()->setShouldDoFullPaintInvalidation(true);
345
346     if (mediaElement().muted())
347         m_volumeSlider->setVolume(0);
348     else
349         m_volumeSlider->setVolume(mediaElement().volume());
350 }
351
352 void MediaControls::changedClosedCaptionsVisibility()
353 {
354     m_toggleClosedCaptionsButton->updateDisplayType();
355 }
356
357 void MediaControls::refreshClosedCaptionsButtonVisibility()
358 {
359     if (mediaElement().hasClosedCaptions())
360         m_toggleClosedCaptionsButton->show();
361     else
362         m_toggleClosedCaptionsButton->hide();
363 }
364
365 void MediaControls::textTracksChanged()
366 {
367     refreshClosedCaptionsButtonVisibility();
368 }
369
370 void MediaControls::refreshCastButtonVisibility()
371 {
372     if (mediaElement().hasRemoteRoutes()) {
373         // The reason for the autoplay test is that some pages (e.g. vimeo.com) have an autoplay background video, which
374         // doesn't autoplay on Chrome for Android (we prevent it) so starts paused. In such cases we don't want to automatically
375         // show the cast button, since it looks strange and is unlikely to correspond with anything the user wants to do.
376         // If a user does want to cast a paused autoplay video then they can still do so by touching or clicking on the
377         // video, which will cause the cast button to appear.
378         if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) {
379             showOverlayCastButton();
380         } else if (mediaElement().shouldShowControls()) {
381             m_overlayCastButton->hide();
382             m_castButton->show();
383         }
384     } else {
385         m_castButton->hide();
386         m_overlayCastButton->hide();
387     }
388 }
389
390 void MediaControls::showOverlayCastButton()
391 {
392     m_overlayCastButton->show();
393     resetHideMediaControlsTimer();
394 }
395
396 void MediaControls::enteredFullscreen()
397 {
398     m_fullScreenButton->setIsFullscreen(true);
399     stopHideMediaControlsTimer();
400     startHideMediaControlsTimer();
401 }
402
403 void MediaControls::exitedFullscreen()
404 {
405     m_fullScreenButton->setIsFullscreen(false);
406     stopHideMediaControlsTimer();
407     startHideMediaControlsTimer();
408 }
409
410 void MediaControls::startedCasting()
411 {
412     m_castButton->setIsPlayingRemotely(true);
413     m_overlayCastButton->setIsPlayingRemotely(true);
414 }
415
416 void MediaControls::stoppedCasting()
417 {
418     m_castButton->setIsPlayingRemotely(false);
419     m_overlayCastButton->setIsPlayingRemotely(false);
420 }
421
422 void MediaControls::defaultEventHandler(Event* event)
423 {
424     HTMLDivElement::defaultEventHandler(event);
425     m_wasLastEventTouch = event->isTouchEvent() || event->isGestureEvent()
426         || (event->isMouseEvent() && toMouseEvent(event)->fromTouch());
427
428     if (event->type() == EventTypeNames::mouseover) {
429         if (!containsRelatedTarget(event)) {
430             m_isMouseOverControls = true;
431             if (!mediaElement().togglePlayStateWillPlay()) {
432                 makeOpaque();
433                 if (shouldHideMediaControls())
434                     startHideMediaControlsTimer();
435             }
436         }
437         return;
438     }
439
440     if (event->type() == EventTypeNames::mouseout) {
441         if (!containsRelatedTarget(event)) {
442             m_isMouseOverControls = false;
443             stopHideMediaControlsTimer();
444         }
445         return;
446     }
447
448     if (event->type() == EventTypeNames::mousemove) {
449         // When we get a mouse move, show the media controls, and start a timer
450         // that will hide the media controls after a 3 seconds without a mouse move.
451         makeOpaque();
452         if (shouldHideMediaControls(IgnoreVideoHover))
453             startHideMediaControlsTimer();
454         return;
455     }
456 }
457
458 void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
459 {
460     if (mediaElement().togglePlayStateWillPlay())
461         return;
462
463     unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover;
464     if (m_wasLastEventTouch) {
465         behaviorFlags |= IgnoreControlsHover;
466     }
467     if (!shouldHideMediaControls(behaviorFlags))
468         return;
469
470     makeTransparent();
471     m_overlayCastButton->hide();
472 }
473
474 void MediaControls::startHideMediaControlsTimer()
475 {
476     m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
477 }
478
479 void MediaControls::stopHideMediaControlsTimer()
480 {
481     m_hideMediaControlsTimer.stop();
482 }
483
484 void MediaControls::resetHideMediaControlsTimer()
485 {
486     stopHideMediaControlsTimer();
487     if (!mediaElement().paused())
488         startHideMediaControlsTimer();
489 }
490
491
492 const AtomicString& MediaControls::shadowPseudoId() const
493 {
494     DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls", AtomicString::ConstructFromLiteral));
495     return id;
496 }
497
498 bool MediaControls::containsRelatedTarget(Event* event)
499 {
500     if (!event->isMouseEvent())
501         return false;
502     EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
503     if (!relatedTarget)
504         return false;
505     return contains(relatedTarget->toNode());
506 }
507
508 void MediaControls::createTextTrackDisplay()
509 {
510     if (m_textDisplayContainer)
511         return;
512
513     RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
514     m_textDisplayContainer = textDisplayContainer.get();
515
516     // Insert it before (behind) all other control elements.
517     if (m_overlayPlayButton)
518         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
519     else
520         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayCastButton);
521 }
522
523 void MediaControls::showTextTrackDisplay()
524 {
525     if (!m_textDisplayContainer)
526         createTextTrackDisplay();
527     m_textDisplayContainer->show();
528 }
529
530 void MediaControls::hideTextTrackDisplay()
531 {
532     if (!m_textDisplayContainer)
533         createTextTrackDisplay();
534     m_textDisplayContainer->hide();
535 }
536
537 void MediaControls::updateTextTrackDisplay()
538 {
539     if (!m_textDisplayContainer)
540         createTextTrackDisplay();
541
542     m_textDisplayContainer->updateDisplay();
543 }
544
545 void MediaControls::trace(Visitor* visitor)
546 {
547     visitor->trace(m_mediaElement);
548     visitor->trace(m_panel);
549     visitor->trace(m_textDisplayContainer);
550     visitor->trace(m_overlayPlayButton);
551     visitor->trace(m_overlayEnclosure);
552     visitor->trace(m_playButton);
553     visitor->trace(m_currentTimeDisplay);
554     visitor->trace(m_timeline);
555     visitor->trace(m_muteButton);
556     visitor->trace(m_volumeSlider);
557     visitor->trace(m_toggleClosedCaptionsButton);
558     visitor->trace(m_fullScreenButton);
559     visitor->trace(m_durationDisplay);
560     visitor->trace(m_enclosure);
561     visitor->trace(m_castButton);
562     visitor->trace(m_overlayCastButton);
563     HTMLDivElement::trace(visitor);
564 }
565
566 }