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