2009-07-02 Pierre d'Herbemont <pdherbemont@apple.com>
[WebKit-https.git] / WebCore / rendering / RenderMedia.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009 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 COMPUTER, 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 COMPUTER, 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
28 #if ENABLE(VIDEO)
29 #include "RenderMedia.h"
30
31 #include "EventNames.h"
32 #include "FloatConversion.h"
33 #include "HTMLNames.h"
34 #include "MediaControlElements.h"
35 #include "MouseEvent.h"
36 #include <wtf/CurrentTime.h>
37 #include <wtf/MathExtras.h>
38
39 using namespace std;
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 static const double cTimeUpdateRepeatDelay = 0.2;
46 static const double cOpacityAnimationRepeatDelay = 0.05;
47 // FIXME get this from style
48 static const double cOpacityAnimationDuration = 0.1;
49
50 RenderMedia::RenderMedia(HTMLMediaElement* video)
51     : RenderReplaced(video)
52     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
53     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
54     , m_mouseOver(false)
55     , m_opacityAnimationStartTime(0)
56     , m_opacityAnimationFrom(0)
57     , m_opacityAnimationTo(1.0f)
58     , m_previousVisible(VISIBLE)
59 {
60 }
61
62 RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
63     : RenderReplaced(video, intrinsicSize)
64     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
65     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
66     , m_mouseOver(false)
67     , m_opacityAnimationStartTime(0)
68     , m_opacityAnimationFrom(0)
69     , m_opacityAnimationTo(1.0f)
70 {
71 }
72
73 RenderMedia::~RenderMedia()
74 {
75 }
76
77 void RenderMedia::destroy()
78 {
79     if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
80
81         // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach() 
82         //  when display: style changes
83         m_panel->detach();
84
85         removeChild(m_controlsShadowRoot->renderer());
86         m_controlsShadowRoot->detach();
87         m_controlsShadowRoot = 0;
88     }
89     RenderReplaced::destroy();
90 }
91
92 HTMLMediaElement* RenderMedia::mediaElement() const
93
94     return static_cast<HTMLMediaElement*>(node()); 
95 }
96
97 MediaPlayer* RenderMedia::player() const
98 {
99     return mediaElement()->player();
100 }
101
102 void RenderMedia::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
103 {
104     RenderReplaced::styleDidChange(diff, oldStyle);
105
106     if (m_controlsShadowRoot) {
107         m_panel->updateStyle();
108         m_muteButton->updateStyle();
109         m_playButton->updateStyle();
110         m_seekBackButton->updateStyle();
111         m_seekForwardButton->updateStyle();
112         m_rewindButton->updateStyle();
113         m_returnToRealtimeButton->updateStyle();
114         m_statusDisplay->updateStyle();
115         m_timelineContainer->updateStyle();
116         m_timeline->updateStyle();
117         m_fullscreenButton->updateStyle();
118         m_currentTimeDisplay->updateStyle();
119         m_timeRemainingDisplay->updateStyle();
120     }
121 }
122
123 void RenderMedia::layout()
124 {
125     IntSize oldSize = contentBoxRect().size();
126
127     RenderReplaced::layout();
128
129     RenderBox* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0;
130     if (!controlsRenderer)
131         return;
132     IntSize newSize = contentBoxRect().size();
133     if (newSize != oldSize || controlsRenderer->needsLayout()) {
134         controlsRenderer->setLocation(borderLeft() + paddingLeft(), borderTop() + paddingTop());
135         controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed));
136         controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed));
137         controlsRenderer->setNeedsLayout(true, false);
138         controlsRenderer->layout();
139         setChildNeedsLayout(false);
140     }
141 }
142
143 void RenderMedia::createControlsShadowRoot()
144 {
145     ASSERT(!m_controlsShadowRoot);
146     m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
147     addChild(m_controlsShadowRoot->renderer());
148 }
149
150 void RenderMedia::createPanel()
151 {
152     ASSERT(!m_panel);
153     m_panel = new MediaControlElement(document(), MEDIA_CONTROLS_PANEL, mediaElement());
154     m_panel->attachToParent(m_controlsShadowRoot.get());
155 }
156
157 void RenderMedia::createMuteButton()
158 {
159     ASSERT(!m_muteButton);
160     m_muteButton = new MediaControlMuteButtonElement(document(), mediaElement());
161     m_muteButton->attachToParent(m_panel.get());
162 }
163
164 void RenderMedia::createPlayButton()
165 {
166     ASSERT(!m_playButton);
167     m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
168     m_playButton->attachToParent(m_panel.get());
169 }
170
171 void RenderMedia::createSeekBackButton()
172 {
173     ASSERT(!m_seekBackButton);
174     m_seekBackButton = new MediaControlSeekButtonElement(document(), mediaElement(), false);
175     m_seekBackButton->attachToParent(m_panel.get());
176 }
177
178 void RenderMedia::createSeekForwardButton()
179 {
180     ASSERT(!m_seekForwardButton);
181     m_seekForwardButton = new MediaControlSeekButtonElement(document(), mediaElement(), true);
182     m_seekForwardButton->attachToParent(m_panel.get());
183 }
184
185 void RenderMedia::createRewindButton()
186 {
187     ASSERT(!m_rewindButton);
188     m_rewindButton = new MediaControlRewindButtonElement(document(), mediaElement());
189     m_rewindButton->attachToParent(m_panel.get());
190 }
191
192 void RenderMedia::createReturnToRealtimeButton()
193 {
194     ASSERT(!m_returnToRealtimeButton);
195     m_returnToRealtimeButton = new MediaControlReturnToRealtimeButtonElement(document(), mediaElement());
196     m_returnToRealtimeButton->attachToParent(m_panel.get());
197 }
198
199 void RenderMedia::createStatusDisplay()
200 {
201     ASSERT(!m_statusDisplay);
202     m_statusDisplay = new MediaControlStatusDisplayElement(document(), mediaElement());
203     m_statusDisplay->attachToParent(m_panel.get());
204 }
205
206 void RenderMedia::createTimelineContainer()
207 {
208     ASSERT(!m_timelineContainer);
209     m_timelineContainer = new MediaControlTimelineContainerElement(document(), mediaElement());
210     m_timelineContainer->attachToParent(m_panel.get());
211 }
212
213 void RenderMedia::createTimeline()
214 {
215     ASSERT(!m_timeline);
216     m_timeline = new MediaControlTimelineElement(document(), mediaElement());
217     m_timeline->setAttribute(precisionAttr, "float");
218     m_timeline->attachToParent(m_timelineContainer.get());
219 }
220
221 void RenderMedia::createCurrentTimeDisplay()
222 {
223     ASSERT(!m_currentTimeDisplay);
224     m_currentTimeDisplay = new MediaControlElement(document(), MEDIA_CONTROLS_CURRENT_TIME_DISPLAY, mediaElement());
225     m_currentTimeDisplay->attachToParent(m_timelineContainer.get());
226 }
227
228 void RenderMedia::createTimeRemainingDisplay()
229 {
230     ASSERT(!m_timeRemainingDisplay);
231     m_timeRemainingDisplay = new MediaControlElement(document(), MEDIA_CONTROLS_TIME_REMAINING_DISPLAY, mediaElement());
232     m_timeRemainingDisplay->attachToParent(m_timelineContainer.get());
233 }
234
235 void RenderMedia::createFullscreenButton()
236 {
237     ASSERT(!m_fullscreenButton);
238     m_fullscreenButton = new MediaControlFullscreenButtonElement(document(), mediaElement());
239     m_fullscreenButton->attachToParent(m_panel.get());
240 }
241     
242 void RenderMedia::updateFromElement()
243 {
244     updateControls();
245 }
246             
247 void RenderMedia::updateControls()
248 {
249     HTMLMediaElement* media = mediaElement();
250     if (!media->controls() || !media->inActiveDocument()) {
251         if (m_controlsShadowRoot) {
252             m_controlsShadowRoot->detach();
253             m_panel = 0;
254             m_muteButton = 0;
255             m_playButton = 0;
256             m_statusDisplay = 0;
257             m_timelineContainer = 0;
258             m_timeline = 0;
259             m_seekBackButton = 0;
260             m_seekForwardButton = 0;
261             m_rewindButton = 0;
262             m_returnToRealtimeButton = 0;
263             m_currentTimeDisplay = 0;
264             m_timeRemainingDisplay = 0;
265             m_fullscreenButton = 0;
266             m_controlsShadowRoot = 0;
267         }
268         m_opacityAnimationTo = 1.0f;
269         m_opacityAnimationTimer.stop();
270         m_timeUpdateTimer.stop();
271         return;
272     }
273     
274     if (!m_controlsShadowRoot) {
275         createControlsShadowRoot();
276         createPanel();
277         if (m_panel) {
278             createRewindButton();
279             createMuteButton();
280             createPlayButton();
281             createReturnToRealtimeButton();
282             createStatusDisplay();
283             createTimelineContainer();
284             createSeekBackButton();
285             createSeekForwardButton();
286             createFullscreenButton();
287         }
288         if (m_timelineContainer) {
289             createCurrentTimeDisplay();
290             createTimeline();
291             createTimeRemainingDisplay();
292         }
293     }
294
295     if (media->canPlay()) {
296         if (m_timeUpdateTimer.isActive())
297             m_timeUpdateTimer.stop();
298     } else if (style()->visibility() == VISIBLE && m_timeline && m_timeline->renderer() && m_timeline->renderer()->style()->display() != NONE ) {
299         m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
300     }
301
302     m_previousVisible = style()->visibility();
303     
304     if (m_panel)
305         m_panel->update();
306     if (m_muteButton)
307         m_muteButton->update();
308     if (m_playButton)
309         m_playButton->update();
310     if (m_timelineContainer)
311         m_timelineContainer->update();
312     if (m_timeline)
313         m_timeline->update();
314     if (m_currentTimeDisplay)
315         m_currentTimeDisplay->update();
316     if (m_timeRemainingDisplay)
317         m_timeRemainingDisplay->update();
318     if (m_seekBackButton)
319         m_seekBackButton->update();
320     if (m_seekForwardButton)
321         m_seekForwardButton->update();
322     if (m_rewindButton)
323         m_rewindButton->update();
324     if (m_returnToRealtimeButton)
325         m_returnToRealtimeButton->update();
326     if (m_statusDisplay)
327         m_statusDisplay->update();
328     if (m_fullscreenButton)
329         m_fullscreenButton->update();
330     updateTimeDisplay();
331     updateControlVisibility();
332 }
333
334 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
335 {
336     if (m_timeline)
337         m_timeline->update(false);
338     updateTimeDisplay();
339 }
340     
341 String RenderMedia::formatTime(float time)
342 {
343     if (!isfinite(time))
344         time = 0;
345     int seconds = (int)fabsf(time); 
346     int hours = seconds / (60 * 60);
347     int minutes = (seconds / 60) % 60;
348     seconds %= 60;
349     if (hours) {
350         if (hours > 9)
351             return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
352         else
353             return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
354     }
355     else
356         return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
357 }
358
359 void RenderMedia::updateTimeDisplay()
360 {
361     if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || style()->visibility() != VISIBLE)
362         return;
363     float now = mediaElement()->currentTime();
364     float duration = mediaElement()->duration();
365
366     String timeString = formatTime(now);
367     ExceptionCode ec;
368     m_currentTimeDisplay->setInnerText(timeString, ec);
369     
370     timeString = formatTime(now - duration);
371     m_timeRemainingDisplay->setInnerText(timeString, ec);
372 }
373
374 void RenderMedia::updateControlVisibility() 
375 {
376     if (!m_panel || !m_panel->renderer())
377         return;
378
379     // Don't fade for audio controls.
380     HTMLMediaElement* media = mediaElement();
381     if (!media->hasVideo())
382         return;
383
384     // do fading manually, css animations don't work well with shadow trees
385     bool visible = style()->visibility() == VISIBLE && (m_mouseOver || media->canPlay());
386     if (visible == (m_opacityAnimationTo > 0))
387         return;
388
389     if (style()->visibility() != m_previousVisible) {
390         // don't fade gradually if it the element has just changed visibility
391         m_previousVisible = style()->visibility();
392         m_opacityAnimationTo = m_previousVisible == VISIBLE ? 1.0f : 0;
393         changeOpacity(m_panel.get(), m_opacityAnimationTo);
394         return;
395     }
396
397     if (visible) {
398         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
399         m_opacityAnimationTo = 1.0f;
400     } else {
401         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
402         m_opacityAnimationTo = 0;
403     }
404     m_opacityAnimationStartTime = currentTime();
405     m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
406 }
407     
408 void RenderMedia::changeOpacity(HTMLElement* e, float opacity) 
409 {
410     if (!e || !e->renderer() || !e->renderer()->style())
411         return;
412     RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style());
413     s->setOpacity(opacity);
414     // z-index can't be auto if opacity is used
415     s->setZIndex(0);
416     e->renderer()->setStyle(s.release());
417 }
418     
419 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
420 {
421     double time = currentTime() - m_opacityAnimationStartTime;
422     if (time >= cOpacityAnimationDuration) {
423         time = cOpacityAnimationDuration;
424         m_opacityAnimationTimer.stop();
425     }
426     float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
427     changeOpacity(m_panel.get(), opacity);
428 }
429
430 void RenderMedia::forwardEvent(Event* event)
431 {
432     if (event->isMouseEvent() && m_controlsShadowRoot) {
433         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
434         IntPoint point(mouseEvent->absoluteLocation());
435         if (m_muteButton && m_muteButton->hitTest(point))
436             m_muteButton->defaultEventHandler(event);
437
438         if (m_playButton && m_playButton->hitTest(point))
439             m_playButton->defaultEventHandler(event);
440
441         if (m_seekBackButton && m_seekBackButton->hitTest(point))
442             m_seekBackButton->defaultEventHandler(event);
443
444         if (m_seekForwardButton && m_seekForwardButton->hitTest(point))
445             m_seekForwardButton->defaultEventHandler(event);
446
447         if (m_rewindButton && m_rewindButton->hitTest(point))
448             m_rewindButton->defaultEventHandler(event);
449
450         if (m_returnToRealtimeButton && m_returnToRealtimeButton->hitTest(point))
451             m_returnToRealtimeButton->defaultEventHandler(event);
452
453         if (m_timeline && m_timeline->hitTest(point))
454             m_timeline->defaultEventHandler(event);
455
456         if (m_fullscreenButton && m_fullscreenButton->hitTest(point))
457             m_fullscreenButton->defaultEventHandler(event);
458         
459         if (event->type() == eventNames().mouseoverEvent) {
460             m_mouseOver = true;
461             updateControlVisibility();
462         }
463         if (event->type() == eventNames().mouseoutEvent) {
464             // When the scrollbar thumb captures mouse events, we should treat the mouse as still being over our renderer if the new target is a descendant
465             Node* mouseOverNode = mouseEvent->relatedTarget() ? mouseEvent->relatedTarget()->toNode() : 0;
466             RenderObject* mouseOverRenderer = mouseOverNode ? mouseOverNode->renderer() : 0;
467             m_mouseOver = mouseOverRenderer && mouseOverRenderer->isDescendantOf(this);
468             updateControlVisibility();
469         }
470     }
471 }
472
473 int RenderMedia::lowestPosition(bool includeOverflowInterior, bool includeSelf) const
474 {
475     int bottom = RenderReplaced::lowestPosition(includeOverflowInterior, includeSelf);
476     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
477         return bottom;
478     
479     return max(bottom,  m_controlsShadowRoot->renderBox()->y() + m_controlsShadowRoot->renderBox()->lowestPosition(includeOverflowInterior, includeSelf));
480 }
481
482 int RenderMedia::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const
483 {
484     int right = RenderReplaced::rightmostPosition(includeOverflowInterior, includeSelf);
485     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
486         return right;
487     
488     return max(right, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderBox()->rightmostPosition(includeOverflowInterior, includeSelf));
489 }
490
491 int RenderMedia::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const
492 {
493     int left = RenderReplaced::leftmostPosition(includeOverflowInterior, includeSelf);
494     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
495         return left;
496     
497     return min(left, m_controlsShadowRoot->renderBox()->x() +  m_controlsShadowRoot->renderBox()->leftmostPosition(includeOverflowInterior, includeSelf));
498 }
499
500 } // namespace WebCore
501
502 #endif