Upstream version 10.38.208.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 static bool deviceSupportsMouse(const Document& document)
49 {
50     return !document.settings() || document.settings()->deviceSupportsMouse();
51 }
52
53 MediaControls::MediaControls(HTMLMediaElement& mediaElement)
54     : HTMLDivElement(mediaElement.document())
55     , m_mediaElement(&mediaElement)
56     , m_panel(nullptr)
57     , m_textDisplayContainer(nullptr)
58     , m_overlayPlayButton(nullptr)
59     , m_overlayEnclosure(nullptr)
60     , m_playButton(nullptr)
61     , m_currentTimeDisplay(nullptr)
62     , m_timeline(nullptr)
63     , m_muteButton(nullptr)
64     , m_volumeSlider(nullptr)
65     , m_toggleClosedCaptionsButton(nullptr)
66     , m_fullScreenButton(nullptr)
67     , m_durationDisplay(nullptr)
68     , m_enclosure(nullptr)
69     , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
70     , m_isMouseOverControls(false)
71     , m_isPausedForScrubbing(false)
72 {
73 }
74
75 PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
76 {
77     RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
78
79     if (controls->initializeControls())
80         return controls.release();
81
82     return nullptr;
83 }
84
85 bool MediaControls::initializeControls()
86 {
87     TrackExceptionState exceptionState;
88
89     if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
90         RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
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         m_overlayEnclosure = overlayEnclosure.get();
98         appendChild(overlayEnclosure.release(), exceptionState);
99         if (exceptionState.hadException())
100             return false;
101     }
102
103     // Create an enclosing element for the panel so we can visually offset the controls correctly.
104     RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
105
106     RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
107
108     RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
109     m_playButton = playButton.get();
110     panel->appendChild(playButton.release(), exceptionState);
111     if (exceptionState.hadException())
112         return false;
113
114     RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
115     m_timeline = timeline.get();
116     panel->appendChild(timeline.release(), exceptionState);
117     if (exceptionState.hadException())
118         return false;
119
120     RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
121     m_currentTimeDisplay = currentTimeDisplay.get();
122     m_currentTimeDisplay->hide();
123     panel->appendChild(currentTimeDisplay.release(), exceptionState);
124     if (exceptionState.hadException())
125         return false;
126
127     RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
128     m_durationDisplay = durationDisplay.get();
129     panel->appendChild(durationDisplay.release(), exceptionState);
130     if (exceptionState.hadException())
131         return false;
132
133     RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
134     m_muteButton = muteButton.get();
135     panel->appendChild(muteButton.release(), exceptionState);
136     if (exceptionState.hadException())
137         return false;
138
139     RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
140     m_volumeSlider = slider.get();
141     panel->appendChild(slider.release(), exceptionState);
142     if (exceptionState.hadException())
143         return false;
144
145     RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
146     m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
147     panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
148     if (exceptionState.hadException())
149         return false;
150
151     RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
152     m_fullScreenButton = fullscreenButton.get();
153     panel->appendChild(fullscreenButton.release(), exceptionState);
154     if (exceptionState.hadException())
155         return false;
156
157     m_panel = panel.get();
158     enclosure->appendChild(panel.release(), exceptionState);
159     if (exceptionState.hadException())
160         return false;
161
162     m_enclosure = enclosure.get();
163     appendChild(enclosure.release(), exceptionState);
164     if (exceptionState.hadException())
165         return false;
166
167     return true;
168 }
169
170 void MediaControls::reset()
171 {
172     double duration = mediaElement().duration();
173     m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
174     m_durationDisplay->setCurrentValue(duration);
175
176     updatePlayState();
177
178     updateCurrentTimeDisplay();
179
180     m_timeline->setDuration(duration);
181     m_timeline->setPosition(mediaElement().currentTime());
182
183     if (!mediaElement().hasAudio())
184         m_volumeSlider->hide();
185     else
186         m_volumeSlider->show();
187     updateVolume();
188
189     refreshClosedCaptionsButtonVisibility();
190
191     if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
192         m_fullScreenButton->show();
193     else
194         m_fullScreenButton->hide();
195
196     makeOpaque();
197 }
198
199 void MediaControls::show()
200 {
201     makeOpaque();
202     m_panel->setIsDisplayed(true);
203     m_panel->show();
204     if (m_overlayPlayButton)
205         m_overlayPlayButton->updateDisplayType();
206 }
207
208 void MediaControls::mediaElementFocused()
209 {
210     show();
211     stopHideMediaControlsTimer();
212 }
213
214 void MediaControls::hide()
215 {
216     m_panel->setIsDisplayed(false);
217     m_panel->hide();
218     if (m_overlayPlayButton)
219         m_overlayPlayButton->hide();
220 }
221
222 void MediaControls::makeOpaque()
223 {
224     m_panel->makeOpaque();
225 }
226
227 void MediaControls::makeTransparent()
228 {
229     m_panel->makeTransparent();
230 }
231
232 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
233 {
234     // Never hide for a media element without visual representation.
235     if (!mediaElement().hasVideo())
236         return false;
237     // Don't hide if the mouse is over the controls.
238     const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover;
239     if (!ignoreControlsHover && m_panel->hovered())
240         return false;
241     // Don't hide if the mouse is over the video area.
242     const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
243     if (!ignoreVideoHover && m_isMouseOverControls)
244         return false;
245     // Don't hide if focus is on the HTMLMediaElement or within the
246     // controls/shadow tree. (Perform the checks separately to avoid going
247     // through all the potential ancestor hosts for the focused element.)
248     const bool ignoreFocus = behaviorFlags & IgnoreFocus;
249     if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
250         return false;
251     return true;
252 }
253
254 void MediaControls::playbackStarted()
255 {
256     m_currentTimeDisplay->show();
257     m_durationDisplay->hide();
258
259     updatePlayState();
260     m_timeline->setPosition(mediaElement().currentTime());
261     updateCurrentTimeDisplay();
262
263     startHideMediaControlsTimer();
264 }
265
266 void MediaControls::playbackProgressed()
267 {
268     m_timeline->setPosition(mediaElement().currentTime());
269     updateCurrentTimeDisplay();
270
271     if (shouldHideMediaControls())
272         makeTransparent();
273 }
274
275 void MediaControls::playbackStopped()
276 {
277     updatePlayState();
278     m_timeline->setPosition(mediaElement().currentTime());
279     updateCurrentTimeDisplay();
280     makeOpaque();
281
282     stopHideMediaControlsTimer();
283 }
284
285 void MediaControls::updatePlayState()
286 {
287     if (m_isPausedForScrubbing)
288         return;
289
290     if (m_overlayPlayButton)
291         m_overlayPlayButton->updateDisplayType();
292     m_playButton->updateDisplayType();
293 }
294
295 void MediaControls::beginScrubbing()
296 {
297     if (!mediaElement().togglePlayStateWillPlay()) {
298         m_isPausedForScrubbing = true;
299         mediaElement().togglePlayState();
300     }
301 }
302
303 void MediaControls::endScrubbing()
304 {
305     if (m_isPausedForScrubbing) {
306         m_isPausedForScrubbing = false;
307         if (mediaElement().togglePlayStateWillPlay())
308             mediaElement().togglePlayState();
309     }
310 }
311
312 void MediaControls::updateCurrentTimeDisplay()
313 {
314     double now = mediaElement().currentTime();
315     double duration = mediaElement().duration();
316
317     // After seek, hide duration display and show current time.
318     if (now > 0) {
319         m_currentTimeDisplay->show();
320         m_durationDisplay->hide();
321     }
322
323     // Allow the theme to format the time.
324     m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
325     m_currentTimeDisplay->setCurrentValue(now);
326 }
327
328 void MediaControls::updateVolume()
329 {
330     m_muteButton->updateDisplayType();
331     if (m_muteButton->renderer())
332         m_muteButton->renderer()->paintInvalidationForWholeRenderer();
333
334     if (mediaElement().muted())
335         m_volumeSlider->setVolume(0);
336     else
337         m_volumeSlider->setVolume(mediaElement().volume());
338 }
339
340 void MediaControls::changedClosedCaptionsVisibility()
341 {
342     m_toggleClosedCaptionsButton->updateDisplayType();
343 }
344
345 void MediaControls::refreshClosedCaptionsButtonVisibility()
346 {
347     if (mediaElement().hasClosedCaptions())
348         m_toggleClosedCaptionsButton->show();
349     else
350         m_toggleClosedCaptionsButton->hide();
351 }
352
353 void MediaControls::closedCaptionTracksChanged()
354 {
355     refreshClosedCaptionsButtonVisibility();
356 }
357
358 void MediaControls::enteredFullscreen()
359 {
360     m_fullScreenButton->setIsFullscreen(true);
361     stopHideMediaControlsTimer();
362     startHideMediaControlsTimer();
363 }
364
365 void MediaControls::exitedFullscreen()
366 {
367     m_fullScreenButton->setIsFullscreen(false);
368     stopHideMediaControlsTimer();
369     startHideMediaControlsTimer();
370 }
371
372 void MediaControls::defaultEventHandler(Event* event)
373 {
374     HTMLDivElement::defaultEventHandler(event);
375
376     if (event->type() == EventTypeNames::mouseover) {
377         if (!containsRelatedTarget(event)) {
378             m_isMouseOverControls = true;
379             if (!mediaElement().togglePlayStateWillPlay()) {
380                 makeOpaque();
381                 if (shouldHideMediaControls())
382                     startHideMediaControlsTimer();
383             }
384         }
385         return;
386     }
387
388     if (event->type() == EventTypeNames::mouseout) {
389         if (!containsRelatedTarget(event)) {
390             m_isMouseOverControls = false;
391             stopHideMediaControlsTimer();
392         }
393         return;
394     }
395
396     if (event->type() == EventTypeNames::mousemove) {
397         // When we get a mouse move, show the media controls, and start a timer
398         // that will hide the media controls after a 3 seconds without a mouse move.
399         makeOpaque();
400         if (shouldHideMediaControls(IgnoreVideoHover))
401             startHideMediaControlsTimer();
402         return;
403     }
404 }
405
406 void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
407 {
408     if (mediaElement().togglePlayStateWillPlay())
409         return;
410
411     unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover;
412     // FIXME: improve this check, see http://www.crbug.com/401177.
413     if (!deviceSupportsMouse(document())) {
414         behaviorFlags |= IgnoreControlsHover;
415     }
416     if (!shouldHideMediaControls(behaviorFlags))
417         return;
418
419     makeTransparent();
420 }
421
422 void MediaControls::startHideMediaControlsTimer()
423 {
424     m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
425 }
426
427 void MediaControls::stopHideMediaControlsTimer()
428 {
429     m_hideMediaControlsTimer.stop();
430 }
431
432 const AtomicString& MediaControls::shadowPseudoId() const
433 {
434     DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls"));
435     return id;
436 }
437
438 bool MediaControls::containsRelatedTarget(Event* event)
439 {
440     if (!event->isMouseEvent())
441         return false;
442     EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
443     if (!relatedTarget)
444         return false;
445     return contains(relatedTarget->toNode());
446 }
447
448 void MediaControls::createTextTrackDisplay()
449 {
450     if (m_textDisplayContainer)
451         return;
452
453     RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
454     m_textDisplayContainer = textDisplayContainer.get();
455
456     // Insert it before (behind) all other control elements.
457     if (m_overlayEnclosure && m_overlayPlayButton)
458         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
459     else
460         insertBefore(textDisplayContainer.release(), m_enclosure);
461 }
462
463 void MediaControls::showTextTrackDisplay()
464 {
465     if (!m_textDisplayContainer)
466         createTextTrackDisplay();
467     m_textDisplayContainer->show();
468 }
469
470 void MediaControls::hideTextTrackDisplay()
471 {
472     if (!m_textDisplayContainer)
473         createTextTrackDisplay();
474     m_textDisplayContainer->hide();
475 }
476
477 void MediaControls::updateTextTrackDisplay()
478 {
479     if (!m_textDisplayContainer)
480         createTextTrackDisplay();
481
482     m_textDisplayContainer->updateDisplay();
483 }
484
485 void MediaControls::trace(Visitor* visitor)
486 {
487     visitor->trace(m_mediaElement);
488     visitor->trace(m_panel);
489     visitor->trace(m_textDisplayContainer);
490     visitor->trace(m_overlayPlayButton);
491     visitor->trace(m_overlayEnclosure);
492     visitor->trace(m_playButton);
493     visitor->trace(m_currentTimeDisplay);
494     visitor->trace(m_timeline);
495     visitor->trace(m_muteButton);
496     visitor->trace(m_volumeSlider);
497     visitor->trace(m_toggleClosedCaptionsButton);
498     visitor->trace(m_fullScreenButton);
499     visitor->trace(m_durationDisplay);
500     visitor->trace(m_enclosure);
501     HTMLDivElement::trace(visitor);
502 }
503
504 }