WebCore:
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Nov 2007 02:02:11 +0000 (02:02 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Nov 2007 02:02:11 +0000 (02:02 +0000)
        Reviewed by Darin.

        Test: media/video-controls.html

        <rdar://problem/5605668>
        Implement controls attribute for video element

        - Add RenderMedia and make RenderVideo its subclass. Controls code goes to RenderMedia while
          video specific (painting etc) code stays in RenderVideo. For example audio controls can be implemented
          later by just instantiating RenderMedia as renderer.
        - Make media renderer hierarchy inherit from RenderBlock instead of RenderReplaced so it can
          have child nodes (for controls).
        - Controls are implemented as a shadow DOM.
        - Current look is a placeholder. It is defined purely in CSS.
        - Some things like volume controls are not yet implemented.
        - Fade-in/out is done manually, CSS animations don't work well in shadow trees.

        * WebCore.xcodeproj/project.pbxproj:
        * css/html4.css:
        * html/HTMLMediaElement.cpp:
        (WebCore::HTMLMediaElement::attributeChanged):
        (WebCore::HTMLMediaElement::updateMovie):
        (WebCore::HTMLMediaElement::defaultEventHandler):
        * html/HTMLMediaElement.h:
        * rendering/RenderMedia.cpp: Added.
        (WebCore::MediaControlShadowRootElement::MediaControlShadowRootElement):
        (WebCore::MediaControlShadowRootElement::isShadowNode):
        (WebCore::MediaControlShadowRootElement::shadowParentNode):
        (WebCore::MediaControlInputElement::MediaControlInputElement):
        (WebCore::MediaControlInputElement::attachToParent):
        (WebCore::MediaControlPlayButtonElement::MediaControlPlayButtonElement):
        (WebCore::MediaControlPlayButtonElement::inPausedState):
        (WebCore::MediaControlPlayButtonElement::defaultEventHandler):
        (WebCore::MediaControlPlayButtonElement::update):
        (WebCore::MediaControlTimelineElement::MediaControlTimelineElement):
        (WebCore::MediaControlTimelineElement::defaultEventHandler):
        (WebCore::MediaControlTimelineElement::update):
        (WebCore::RenderMedia::RenderMedia):
        (WebCore::RenderMedia::~RenderMedia):
        (WebCore::RenderMedia::mediaElement):
        (WebCore::RenderMedia::movie):
        (WebCore::RenderMedia::setStyle):
        (WebCore::RenderMedia::createControlsShadowRoot):
        (WebCore::RenderMedia::createPanel):
        (WebCore::RenderMedia::createPlayButton):
        (WebCore::RenderMedia::createTimeline):
        (WebCore::RenderMedia::createTimeDisplay):
        (WebCore::RenderMedia::updateFromElement):
        (WebCore::RenderMedia::updateControls):
        (WebCore::RenderMedia::timeUpdateTimerFired):
        (WebCore::RenderMedia::updateTimeDisplay):
        (WebCore::RenderMedia::updateControlVisibility):
        (WebCore::RenderMedia::changeOpacity):
        (WebCore::RenderMedia::opacityAnimationTimerFired):
        (WebCore::RenderMedia::forwardEvent):
        * rendering/RenderMedia.h: Added.
        (WebCore::RenderMedia::renderName):
        (WebCore::RenderMedia::isMedia):
        (WebCore::RenderMedia::intrinsicSize):
        * rendering/RenderObject.h:
        (WebCore::RenderObject::isMedia):
        * rendering/RenderVideo.cpp:
        (WebCore::RenderVideo::RenderVideo):
        (WebCore::RenderVideo::videoSizeChanged):
        (WebCore::RenderVideo::paintObject):
        (WebCore::RenderVideo::layout):
        (WebCore::RenderVideo::updateFromElement):
        (WebCore::RenderVideo::calcAspectRatioWidth):
        (WebCore::RenderVideo::calcAspectRatioHeight):
        * rendering/RenderVideo.h:

LayoutTests:

        Reviewed by Darin.

        <rdar://problem/5605668>
        Implement controls attribute for video element

        - A new test for the controls attribute.
        - Add the controls attibute to all existing video tests. This will give the code
          some additional exercise without affecting test results.

        * media/progress-event.html:
        * media/video-append-source.html:
        * media/video-autoplay.html:
        * media/video-buffered.html:
        * media/video-controls-expected.txt: Added.
        * media/video-controls.html: Added.
        * media/video-currentTime-set.html:
        * media/video-currentTime-set2.html:
        * media/video-currentTime.html:
        * media/video-dom-autoplay.html:
        * media/video-dom-end.html:
        * media/video-dom-loopcount.html:
        * media/video-dom-loopend.html:
        * media/video-dom-loopstart.html:
        * media/video-dom-src.html:
        * media/video-dom-start.html:
        * media/video-end.html:
        * media/video-error-abort.html:
        * media/video-error-does-not-exist.html:
        * media/video-load-networkState.html:
        * media/video-load-readyState.html:
        * media/video-loopcount.html:
        * media/video-loopend.html:
        * media/video-loopstart.html:
        * media/video-muted.html:
        * media/video-no-autoplay.html:
        * media/video-pause-empty-events.html:
        * media/video-play-empty-events.html:
        * media/video-play-pause-events.html:
        * media/video-play-pause-exception.html:
        * media/video-poster.html:
        * media/video-seek-past-end-paused.html:
        * media/video-seek-past-end-playing.html:
        * media/video-seekable.html:
        * media/video-seeking.html:
        * media/video-size.html:
        * media/video-source-media.html:
        * media/video-source-type.html:
        * media/video-source.html:
        * media/video-src-change.html:
        * media/video-src-remove.html:
        * media/video-src-set.html:
        * media/video-src-source.html:
        * media/video-src.html:
        * media/video-start.html:
        * media/video-volume.html:
        * media/video-width-height.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@28127 268f45cc-cd09-0410-ab3c-d52691b4dbfc

58 files changed:
LayoutTests/ChangeLog
LayoutTests/media/progress-event.html
LayoutTests/media/video-append-source.html
LayoutTests/media/video-autoplay.html
LayoutTests/media/video-buffered.html
LayoutTests/media/video-controls-expected.txt [new file with mode: 0644]
LayoutTests/media/video-controls.html [new file with mode: 0644]
LayoutTests/media/video-currentTime-set.html
LayoutTests/media/video-currentTime-set2.html
LayoutTests/media/video-currentTime.html
LayoutTests/media/video-dom-autoplay.html
LayoutTests/media/video-dom-end.html
LayoutTests/media/video-dom-loopcount.html
LayoutTests/media/video-dom-loopend.html
LayoutTests/media/video-dom-loopstart.html
LayoutTests/media/video-dom-src.html
LayoutTests/media/video-dom-start.html
LayoutTests/media/video-end.html
LayoutTests/media/video-error-abort.html
LayoutTests/media/video-error-does-not-exist.html
LayoutTests/media/video-load-networkState.html
LayoutTests/media/video-load-readyState.html
LayoutTests/media/video-loopcount.html
LayoutTests/media/video-loopend.html
LayoutTests/media/video-loopstart.html
LayoutTests/media/video-muted.html
LayoutTests/media/video-no-autoplay.html
LayoutTests/media/video-pause-empty-events.html
LayoutTests/media/video-play-empty-events.html
LayoutTests/media/video-play-pause-events.html
LayoutTests/media/video-play-pause-exception.html
LayoutTests/media/video-poster.html
LayoutTests/media/video-seek-past-end-paused.html
LayoutTests/media/video-seek-past-end-playing.html
LayoutTests/media/video-seekable.html
LayoutTests/media/video-seeking.html
LayoutTests/media/video-size.html
LayoutTests/media/video-source-media.html
LayoutTests/media/video-source-type.html
LayoutTests/media/video-source.html
LayoutTests/media/video-src-change.html
LayoutTests/media/video-src-remove.html
LayoutTests/media/video-src-set.html
LayoutTests/media/video-src-source.html
LayoutTests/media/video-src.html
LayoutTests/media/video-start.html
LayoutTests/media/video-volume.html
LayoutTests/media/video-width-height.html
WebCore/ChangeLog
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/css/html4.css
WebCore/html/HTMLMediaElement.cpp
WebCore/html/HTMLMediaElement.h
WebCore/rendering/RenderMedia.cpp [new file with mode: 0644]
WebCore/rendering/RenderMedia.h [new file with mode: 0644]
WebCore/rendering/RenderObject.h
WebCore/rendering/RenderVideo.cpp
WebCore/rendering/RenderVideo.h

index 45fe41c02441762aac51e358c8ec01ee6a939a33..e71b89c48e5c8d7ba365e79ded0e8bcf6c28c443 100644 (file)
@@ -1,3 +1,62 @@
+2007-11-28  Antti Koivisto  <antti@apple.com>
+
+        Reviewed by Darin.
+        
+        <rdar://problem/5605668>
+        Implement controls attribute for video element
+        
+        - A new test for the controls attribute.
+        - Add the controls attibute to all existing video tests. This will give the code
+          some additional exercise without affecting test results.
+
+        * media/progress-event.html:
+        * media/video-append-source.html:
+        * media/video-autoplay.html:
+        * media/video-buffered.html:
+        * media/video-controls-expected.txt: Added.
+        * media/video-controls.html: Added.
+        * media/video-currentTime-set.html:
+        * media/video-currentTime-set2.html:
+        * media/video-currentTime.html:
+        * media/video-dom-autoplay.html:
+        * media/video-dom-end.html:
+        * media/video-dom-loopcount.html:
+        * media/video-dom-loopend.html:
+        * media/video-dom-loopstart.html:
+        * media/video-dom-src.html:
+        * media/video-dom-start.html:
+        * media/video-end.html:
+        * media/video-error-abort.html:
+        * media/video-error-does-not-exist.html:
+        * media/video-load-networkState.html:
+        * media/video-load-readyState.html:
+        * media/video-loopcount.html:
+        * media/video-loopend.html:
+        * media/video-loopstart.html:
+        * media/video-muted.html:
+        * media/video-no-autoplay.html:
+        * media/video-pause-empty-events.html:
+        * media/video-play-empty-events.html:
+        * media/video-play-pause-events.html:
+        * media/video-play-pause-exception.html:
+        * media/video-poster.html:
+        * media/video-seek-past-end-paused.html:
+        * media/video-seek-past-end-playing.html:
+        * media/video-seekable.html:
+        * media/video-seeking.html:
+        * media/video-size.html:
+        * media/video-source-media.html:
+        * media/video-source-type.html:
+        * media/video-source.html:
+        * media/video-src-change.html:
+        * media/video-src-remove.html:
+        * media/video-src-set.html:
+        * media/video-src-source.html:
+        * media/video-src.html:
+        * media/video-start.html:
+        * media/video-volume.html:
+        * media/video-width-height.html:
+
 2007-11-28  Justin Garcia  <justin.garcia@apple.com>
 
         Reviewed by Darin Adler.
index 67a39b7b51c76c6e8648bd31e375dd7545a5f95e..cee1d794ad9c33e6d19923856722c57295bfd0d2 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 waitForEventAndTest("begin", "!event.lengthComputable && event.loaded==0 && event.total==0");
index 579f383cd5386bfd42694917ab8f7804fad77095..56eedfe510dfb3beb6bb312301658089618c30de 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.currentSrc==''");
index d0f6eb74b8144850023cd7fdfb3b2515de446312..fd63b45513e614057b7bd5ab9ab92c487cbe26eb 100644 (file)
@@ -1,4 +1,4 @@
-<video autoplay></video>
+<video autoplay controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.autoplay");
index 41206d886397626232fbeb4fd1741ed00d73ca25..c00f519c19a9d7fe15d80eae55c1bf29706a7926 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.buffered.length == 0");
diff --git a/LayoutTests/media/video-controls-expected.txt b/LayoutTests/media/video-controls-expected.txt
new file mode 100644 (file)
index 0000000..bcc37ea
--- /dev/null
@@ -0,0 +1,15 @@
+Test 'controls' attribute
+
+TEST(video.controls) OK
+EVENT(load)
+TEST(video.controls) OK
+RUN(video.removeAttribute('controls'))
+TEST(!video.controls) OK
+RUN(video.controls = true)
+TEST(video.getAttribute('controls') == 'controls') OK
+RUN(video.controls = false)
+TEST(!video.getAttribute('controls')) OK
+RUN(video.setAttribute('controls','controls'))
+TEST(video.controls) OK
+END OF TEST
+
diff --git a/LayoutTests/media/video-controls.html b/LayoutTests/media/video-controls.html
new file mode 100644 (file)
index 0000000..216932f
--- /dev/null
@@ -0,0 +1,24 @@
+<video controls></video>
+<p>Test 'controls' attribute<p>
+<script src=video-test.js></script>
+<script>
+test("video.controls");
+waitForEvent('load', function () {
+    document.body.offsetTop;
+    test("video.controls");
+    run("video.removeAttribute('controls')");
+    document.body.offsetTop;
+    test("!video.controls");
+    run("video.controls = true");
+    document.body.offsetTop;
+    test("video.getAttribute('controls') == 'controls'");
+    run("video.controls = false");
+    document.body.offsetTop;
+    test("!video.getAttribute('controls')");
+    run("video.setAttribute('controls','controls')");
+    document.body.offsetTop;
+    test("video.controls");
+    endTest();
+} );
+video.src = 'content/test.mp4';
+</script>
index 03ad4450cb6535ef19e60a8d7325d212524df866..a70e05a71a351e8bd15b97d32a1e073342a3aec4 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4></video>
+<video src=content/test.mp4 controls></video>
 <script src=video-test.js></script>
 <script>
 waitForEvent('load',  
index 0154aa5947aaacb4157474cace2cc9053d9a8eed..e093e22283c1e342365aaed47f2d0ea7a608d94e 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 waitForEvent('load',  
index b47f63c2aa3fbe047a341a07103f5f02d937d1e6..10ed0c9d10279976aaa47c2ce25d5aff5e3998e8 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.currentTime == 0")
index ee7ec1884c05662c0630cf31caaa4a8f1344739f..5dbd1ad61b0cb0deb38e549c6931c29058d5eece 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("!video.autoplay");
index d78d7a7f31222cb5a67488c581de90c6a7e77463..d659bff19928b89e1fb3e35ae0d3a8666ec379b3 100644 (file)
@@ -1,4 +1,4 @@
-<video autoplay src=content/test.mp4></video>
+<video autoplay src=content/test.mp4 controls></video>
 <script src=video-test.js></script>
 <script>
 video.end = 0.5;
index 397aab210a2501520b841aae1736f9b964bd699c..87e8fd33f68a14de9f902a89619de37b675170bb 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4 loopend=0.2s end=0.2s></video>
+<video src=content/test.mp4 loopend=0.2s end=0.2s controls></video>
 <p>Test that playcount DOM attribute causes playback to end after right number of repeats.</p>
 <script src=video-test.js></script>
 <script>
index d64a01a9f5ba2b8d7de64296ac72516f2d990ad4..81ffbdbc6e31bfc7a8f2f6ca37eb8c6c08181da8 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4 playcount=2></video>
+<video src=content/test.mp4 playcount=2 controls></video>
 <p>Test that video loops when time reaches the value specified by the loopEnd DOM attribute.</p>
 <script src=video-test.js></script>
 <script>
index 44265cc05211bf5bc13696fe1a5dab1cbf1fb546..f57cab45dcfc299da8f39f921fc9c6cd07f7d3c1 100644 (file)
@@ -1,4 +1,4 @@
-<video playcount=2></video>
+<video playcount=2 controls></video>
 <p>Test that playing video rewinds to time specified by the loopStart DOM attribute when it loops.</p>
 <script src=video-test.js></script>
 <script>
index e5d2d4fad2f6d68060490031cba9d460747fa9f6..bb355219f8db66fb21d5b5b1cede2c8c8d31a477 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.currentSrc==''");
index ffa390bd8acf5ba3bc2d987d2badc69d8666bb5e..23e58f3959721e3ecd3864e91149e6f47689084e 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test that when start DOM attribute is set, playback starts from that time.</p>
 <script src=video-test.js></script>
 <script>
index 1f8d4e6da539f66971367801c3c86b935a3d8a7e..b97763d8fbb40cdfa370100c0d6524d8bc0a84b9 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4 end=0.5s></video>
+<video src=content/test.mp4 end=0.5s controls></video>
 <script src=video-test.js></script>
 <script>
 test("!video.ended");
index 3814a2ca007c6525504740ba045aa7af5e74930f..fbfa3e4db4878f4ce0bd3dc3eca107e24adebdcf 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("!video.error");
index a3a0d185a5d728ab5f821ab2c61e1723b8cf1fba..0a212054afe197f9b37bdb678f2c4075277fc1e7 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("!video.error");
index 20e1cc6034a66f6b3c59ba3a7e2296b5211320ce..439b4827ba318dbfe91c7d56a600109a0a37fe11 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.networkState == HTMLMediaElement.EMPTY");
index 092dee0b9d4745adf8aed2180c2093d9329cf179..7cef661fecb00e1107f05d6500677e8f8da21b34 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.readyState == HTMLMediaElement.DATA_UNAVAILABLE");
index c184851c777a0abfe11e4b8432c5803cac160f68..2688f725a4cb695909475021f6bc28754986de06 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4 playcount=3 loopend=0.2s end=0.2s></video>
+<video src=content/test.mp4 playcount=3 loopend=0.2s end=0.2s controls></video>
 <p>Test that playcount attribute causes playback to end after right number of repeats.</p>
 <script src=video-test.js></script>
 <script>
index 845372ff91abb1abb5157890f7cc4b8d91c38aa5..85cdd41e4087edb0c8336e425cb973ad5479b97b 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4 playcount=2 loopend=0.5s></video>
+<video src=content/test.mp4 playcount=2 loopend=0.5s controls></video>
 <p>Test that video loops when time reaches the value specified by the loopEnd attribute.</p>
 <script src=video-test.js></script>
 <script>
index c789b8018db2ea1e267a1cdfdc68dc61f79ef15c..f750aa181a3136ba9a858616b10e7f652adbcd51 100644 (file)
@@ -1,4 +1,4 @@
-<video playcount=2 loopstart=1s></video>
+<video playcount=2 loopstart=1s controls></video>
 <p>Test that playing video rewinds to time specified by the loopstart attribute when it loops.</p>
 <script src=video-test.js></script>
 <script>
index 41ffd452a4dbff0934796c3948453de33af1057a..1a0573c0813aa9501c802515dae95fb86fa17eaa 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test 'muted' attribute<p>
 <script src=video-test.js></script>
 <script>
index 36cff2f7b848a483b42e7b88ddf45334523f9e69..05d83dbc1af494c58207a101622a6474a4b3b724 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.paused");
index da76c6eb1b5a9b6895817ade8b093bdb8c04f4c2..e4e6aaaf71bc935c1d32e2aa10fa8107ef01d7ef 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test that pause() from EMPTY network state triggers load()</p>
 <script src=video-test.js></script>
 <script>
index e1905ad6c75d2e230abc4209abcb2b979a2fc636..c7e27d66779cc4085ce24162c6bd21eadf22f52a 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test that play() from EMPTY network state triggers load() and async play event.</p>
 <script src=video-test.js></script>
 <script>
index 75255522098d60fe0178ce864190fb48299f23eb..e0051cb34e251000533503cd6f060b15f0d54ad1 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test that calling play() and pause() triggers async play, timeupdate and pause events.</p>
 <script src=video-test.js></script>
 <script>
index c83a5fc7bcdbc637d47aa2beb275faa76624ba3e..2c04b5cab4efa826b4af8eac2aca5e0fb322422f 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Video has no src. Test that play() and pause() rethrow the exception from load(). Test that the play and pause events are not dispatched.</p>
 <script src=video-test.js></script>
 <script>
index 8b46861c3e78fb7507910de65997115557377bef..7b9cb290fbd289e1db2f64f783d5763126ece9b7 100644 (file)
@@ -1,4 +1,4 @@
-<video poster="content/greenbox.png"></video>
+<video poster="content/greenbox.png" controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.getAttribute('poster') == 'content/greenbox.png'");
index 5825ae58289a5d72851702256e75eb5f5a9c6db0..4aea2ecbe39e7c315fa4f16f1fb8a888e9e50857 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test that seeking paused video past it's duration time sets currentTime to duration and leaves video paused.</p>
 <script src=video-test.js></script>
 <script>
index a3cde9620391e408246e1cf3d20ea494e49a8689..13ecd619e68c9b758b0bb04fc5e05b5642b152c6 100644 (file)
@@ -1,4 +1,4 @@
-<video playcount=2></video>
+<video playcount=2 controls></video>
 <p>Test that seeking video with remaining loops past it's end rewinds to the beginning and continues playback.</p>
 <script src=video-test.js></script>
 <script>
index 7520c6023c5f24a39b087760e1aa98a90e267aa2..10f961ab39e85dfdd241cc950604e6691590f1de 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.seekable.length == 0");
index 1d1c44cf4e679a239eeb29f43eeabaa8ab0095a3..b3a2334251ec22600303a0c4654ee3b1a469c5ff 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test that seeking attribute is true in timeupdate event after seek and goes back to false when seeking completes </p>
 <script src=video-test.js></script>
 <script>
index 67938063d600aa721cd50b3443a1b3c0bd293709..59bfca4734b4f78df5b1425b5e0475d28a1ece18 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.videoWidth == 0 && video.videoHeight == 0");
index ffa505e454ea3139e8fc822856fde697990c6dcf..c40cd62353680c99c0a2630fe30009d0d2eeebba 100644 (file)
@@ -1,4 +1,4 @@
-<video>
+<video controls>
     <source src=content/error.mpeg media="print">
     <source src=content/error2.mpeg media="screen and (min-device-width: 80000px)">
     <source src=content/test.mp4 media="screen and (min-device-width: 100px)">
index 37dd64de16eaf0b6b241de11aa3b4c3b1dbf7878..94b2d6ee09b1b2230c79749536fb1affec3835f0 100644 (file)
@@ -1,4 +1,4 @@
-<video>
+<video controls>
     <source src=content/error.mpeg type=video/blahblah>
     <source src=content/test.mp4 type=video/mpeg>
     <source src=content/error2.mpeg type=video/mpeg>
index dcbce53090459256c0cdabcb5277e9442b247282..588733d7901fd88069de2c752cbe75dccbada130 100644 (file)
@@ -1,4 +1,4 @@
-<video>
+<video controls>
     <source src=content/test.mp4>
 </video>
 <script src=video-test.js></script>
index 479a8c0ea3c59b887f8f0c7a95d54301a7af45ae..7d51129f0090632ae7094643a2f8ace4673450d3 100644 (file)
@@ -1,4 +1,4 @@
-<video src=bogus></video>
+<video src=bogus controls></video>
 <div>
 Test that changing src attribute triggers load when network state is empty.<br>
 Test that it does not trigger load when network state is non-empty.
index a9cd55de1b812524860825aaefbad19e7a385bf8..4a1905507a82724600705d96cb8bfc2c084a5614 100644 (file)
@@ -1,4 +1,4 @@
-<video src=bogus>
+<video src=bogus controls>
     <source src=content/test.mp4></source>
 </video>
 <div>
index 4ce58284ba2c9561736bb92e3e914083a19e26b5..b2197ecf867ea09ed70f227f4c31eb157babd4b2 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <div>
 Test that setting src attribute triggers load
 </div>
index d1eab7d12b0a80b8332263196c1567a506dae40f..1fc0bb43ce44d723abb9ec827420ae4b196e0932 100644 (file)
@@ -1,4 +1,4 @@
-<video src=content/test.mp4>
+<video src=content/test.mp4 controls>
     <source src=content/error.mpeg>
 </video>
 <script src=video-test.js></script>
index 12df747eec1845919051ce97674ba94a2940665b..7916bbfaa66b6a04f3aab8c5f6c2b5bb5352a747 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <script src=video-test.js></script>
 <script>
 waitForEventTestAndEnd('begin', "relativeURL(video.currentSrc) == 'content/test.mp4'");
index bbbeb31a19168931a2c9771416cd472ceb4bcda6..0197bf58f28ae9c83f4272edfbdfd4c61f992824 100644 (file)
@@ -1,4 +1,4 @@
-<video start=1s></video>
+<video start=1s controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.start == 1.0");
index ee9f18df44fab899a6e0229e648cb7b1fa336547..eaf90a6e988a43968384f7dfc72330adab8e715f 100644 (file)
@@ -1,4 +1,4 @@
-<video></video>
+<video controls></video>
 <p>Test 'volume' attribute<p>
 <script src=video-test.js></script>
 <script>
index 177b2fd0031205af12d19f6be3057ccb727a50c0..7e8626b6d11e9480ccb61da2db5a29aa15a7eb6f 100644 (file)
@@ -1,4 +1,4 @@
-<video width=100 height=50></video>
+<video width=100 height=50 controls></video>
 <script src=video-test.js></script>
 <script>
 test("video.width == 100");
index 2ad1ce139c7750e9b7d6a876ae1488ea7be13969..1e76f01b9afb8160d70f03d03dd470f89f561b45 100644 (file)
@@ -1,3 +1,76 @@
+2007-11-28  Antti Koivisto  <antti@apple.com>
+
+        Reviewed by Darin.
+
+        Test: media/video-controls.html
+        
+        <rdar://problem/5605668>
+        Implement controls attribute for video element
+        
+        - Add RenderMedia and make RenderVideo its subclass. Controls code goes to RenderMedia while
+          video specific (painting etc) code stays in RenderVideo. For example audio controls can be implemented
+          later by just instantiating RenderMedia as renderer.
+        - Make media renderer hierarchy inherit from RenderBlock instead of RenderReplaced so it can 
+          have child nodes (for controls).
+        - Controls are implemented as a shadow DOM.
+        - Current look is a placeholder. It is defined purely in CSS.
+        - Some things like volume controls are not yet implemented.
+        - Fade-in/out is done manually, CSS animations don't work well in shadow trees.
+          
+        * WebCore.xcodeproj/project.pbxproj:
+        * css/html4.css:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::attributeChanged):
+        (WebCore::HTMLMediaElement::updateMovie):
+        (WebCore::HTMLMediaElement::defaultEventHandler):
+        * html/HTMLMediaElement.h:
+        * rendering/RenderMedia.cpp: Added.
+        (WebCore::MediaControlShadowRootElement::MediaControlShadowRootElement):
+        (WebCore::MediaControlShadowRootElement::isShadowNode):
+        (WebCore::MediaControlShadowRootElement::shadowParentNode):
+        (WebCore::MediaControlInputElement::MediaControlInputElement):
+        (WebCore::MediaControlInputElement::attachToParent):
+        (WebCore::MediaControlPlayButtonElement::MediaControlPlayButtonElement):
+        (WebCore::MediaControlPlayButtonElement::inPausedState):
+        (WebCore::MediaControlPlayButtonElement::defaultEventHandler):
+        (WebCore::MediaControlPlayButtonElement::update):
+        (WebCore::MediaControlTimelineElement::MediaControlTimelineElement):
+        (WebCore::MediaControlTimelineElement::defaultEventHandler):
+        (WebCore::MediaControlTimelineElement::update):
+        (WebCore::RenderMedia::RenderMedia):
+        (WebCore::RenderMedia::~RenderMedia):
+        (WebCore::RenderMedia::mediaElement):
+        (WebCore::RenderMedia::movie):
+        (WebCore::RenderMedia::setStyle):
+        (WebCore::RenderMedia::createControlsShadowRoot):
+        (WebCore::RenderMedia::createPanel):
+        (WebCore::RenderMedia::createPlayButton):
+        (WebCore::RenderMedia::createTimeline):
+        (WebCore::RenderMedia::createTimeDisplay):
+        (WebCore::RenderMedia::updateFromElement):
+        (WebCore::RenderMedia::updateControls):
+        (WebCore::RenderMedia::timeUpdateTimerFired):
+        (WebCore::RenderMedia::updateTimeDisplay):
+        (WebCore::RenderMedia::updateControlVisibility):
+        (WebCore::RenderMedia::changeOpacity):
+        (WebCore::RenderMedia::opacityAnimationTimerFired):
+        (WebCore::RenderMedia::forwardEvent):
+        * rendering/RenderMedia.h: Added.
+        (WebCore::RenderMedia::renderName):
+        (WebCore::RenderMedia::isMedia):
+        (WebCore::RenderMedia::intrinsicSize):
+        * rendering/RenderObject.h:
+        (WebCore::RenderObject::isMedia):
+        * rendering/RenderVideo.cpp:
+        (WebCore::RenderVideo::RenderVideo):
+        (WebCore::RenderVideo::videoSizeChanged):
+        (WebCore::RenderVideo::paintObject):
+        (WebCore::RenderVideo::layout):
+        (WebCore::RenderVideo::updateFromElement):
+        (WebCore::RenderVideo::calcAspectRatioWidth):
+        (WebCore::RenderVideo::calcAspectRatioHeight):
+        * rendering/RenderVideo.h:
+
 2007-11-28  Justin Garcia  <justin.garcia@apple.com>
 
         Reviewed by Darin Adler.
index 27419bfba4c2737cd815513f4a3966b22fa92279..0dcca7df43141a871563dbd433ac311366f3b035 100644 (file)
                E44614510CD68A3500FADA75 /* RenderVideo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4B41E330CBFB60900AF2ECE /* RenderVideo.cpp */; };
                E44614520CD68A3500FADA75 /* RenderVideo.h in Headers */ = {isa = PBXBuildFile; fileRef = E4B41E340CBFB60900AF2ECE /* RenderVideo.h */; };
                E4B4232F0CBFB66400AF2ECE /* QTKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4B4232E0CBFB66400AF2ECE /* QTKit.framework */; };
+               E4C279580CF9741900E97B98 /* RenderMedia.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4C279560CF9741900E97B98 /* RenderMedia.cpp */; };
+               E4C279590CF9741900E97B98 /* RenderMedia.h in Headers */ = {isa = PBXBuildFile; fileRef = E4C279570CF9741900E97B98 /* RenderMedia.h */; };
                ED048ABC0833F132006E1E67 /* textAreaResizeCorner.tiff in Resources */ = {isa = PBXBuildFile; fileRef = ED048ABB0833F132006E1E67 /* textAreaResizeCorner.tiff */; };
                ED2BA83C09A24B91006C0AC4 /* DocumentMarker.h in Headers */ = {isa = PBXBuildFile; fileRef = ED2BA83B09A24B91006C0AC4 /* DocumentMarker.h */; settings = {ATTRIBUTES = (Private, ); }; };
                ED501DC60B249F2900AE18D9 /* EditorMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED501DC50B249F2900AE18D9 /* EditorMac.mm */; };
                E4B423800CBFB73C00AF2ECE /* JSHTMLMediaElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSHTMLMediaElement.h; sourceTree = "<group>"; };
                E4B423850CBFB73C00AF2ECE /* JSProgressEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSProgressEvent.cpp; sourceTree = "<group>"; };
                E4B423860CBFB73C00AF2ECE /* JSProgressEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSProgressEvent.h; sourceTree = "<group>"; };
+               E4C279560CF9741900E97B98 /* RenderMedia.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderMedia.cpp; sourceTree = "<group>"; };
+               E4C279570CF9741900E97B98 /* RenderMedia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderMedia.h; sourceTree = "<group>"; };
                ED048ABB0833F132006E1E67 /* textAreaResizeCorner.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = textAreaResizeCorner.tiff; sourceTree = "<group>"; };
                ED2BA83B09A24B91006C0AC4 /* DocumentMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocumentMarker.h; sourceTree = "<group>"; };
                ED501DC50B249F2900AE18D9 /* EditorMac.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = EditorMac.mm; path = mac/EditorMac.mm; sourceTree = "<group>"; };
                                A8EA7A4C0A191A5200A8EF5F /* RenderListItem.h */,
                                A8EA7A4B0A191A5200A8EF5F /* RenderListMarker.cpp */,
                                A8EA7A4A0A191A5200A8EF5F /* RenderListMarker.h */,
+                               E4C279560CF9741900E97B98 /* RenderMedia.cpp */,
+                               E4C279570CF9741900E97B98 /* RenderMedia.h */,
                                ABDDFE730A5C6E6F00A3E11D /* RenderMenuList.cpp */,
                                ABDDFE740A5C6E7000A3E11D /* RenderMenuList.h */,
                                BCEA4840097D93020094C9E4 /* RenderObject.cpp */,
                                BCB16C2D0979C3BD00467741 /* loader.h in Headers */,
                                93309DF8099E64920056E581 /* markup.h in Headers */,
                                93309E1E099E64920056E581 /* visible_units.h in Headers */,
+                               E4C279590CF9741900E97B98 /* RenderMedia.h in Headers */,
                                E1BE512E0CF6C512002EA959 /* XSLTUnicodeSort.h in Headers */,
                                D06C0D8F0CFD11460065F43F /* RemoveFormatCommand.h in Headers */,
                        );
                                BCB16C2C0979C3BD00467741 /* loader.cpp in Sources */,
                                93309DF7099E64920056E581 /* markup.cpp in Sources */,
                                93309E1D099E64920056E581 /* visible_units.cpp in Sources */,
+                               E4C279580CF9741900E97B98 /* RenderMedia.cpp in Sources */,
                                E1BE512D0CF6C512002EA959 /* XSLTUnicodeSort.cpp in Sources */,
                                D06C0D900CFD11460065F43F /* RemoveFormatCommand.cpp in Sources */,
                        );
index ff1f60677d8e4bb26ad7e19d99d1a2e7662d4223..1f5db5fd660ccee6456f5b3ac60f1b2ee261853b 100644 (file)
@@ -589,4 +589,62 @@ iframe {
     border: 2px inset
 }
 
+/* media controls */
+/* FIXME this is not the final styling */
+
+video::-webkit-media-controls-panel {
+    display: inline-block;
+    position: absolute;
+    left: 0; right: 0; top: 0; bottom: 0;
+    margin: auto auto 10% auto;
+    width: 230px;
+    height: 65px;
+    border: 2px solid #eee;
+    background: rgba(0,0,0,0.6);
+    -webkit-border-radius: 10px;
+    -webkit-user-select: none;
+}
+
+video::-webkit-media-controls-play-button {
+    -webkit-appearance: none;
+    display: inline-block;
+    position: absolute;
+    margin: 5px auto auto auto;
+    left: 0; right: 0; top: 0; bottom: 0;
+    border: 0;
+    color: white;
+    background: transparent;
+    cursor: pointer;
+    text-align: center; 
+    font-size: 25pt;
+    width: 2em;
+    height: 1.2em;
+}
+
+video::-webkit-media-controls-timeline {
+    -webkit-appearance: slider-horizontal;
+    position: absolute;
+    margin: auto auto 5px 10px;
+    left: 0; right: 0; top: 0; bottom: 1px;
+    width: 60%;
+    height: 12px;
+    border: 2px solid white;
+    -webkit-border-radius: 6px;
+    z-index: 1;
+}
+
+video::-webkit-media-controls-time-display {
+    display: inline-block;
+    position: absolute;
+    margin: auto 5px 5px 5px;
+    left: 0; right: 0; top: 0; bottom: 0px;
+    height: 1.2em;
+    text-align: right;
+    padding-right: 5px;
+    -webkit-border-radius: 7px;
+    background-color: #eee;
+    color: black;
+    font-size: 10pt;
+}
+
 /* noscript is handled internally, as it depends on settings */
index 3dac44beb6dc90cab1a0ab609934783a3746575c..14290face522f27aa03356ae5b84831bbb8ae251 100644 (file)
@@ -31,6 +31,7 @@
 #include "CSSHelper.h"
 #include "CSSPropertyNames.h"
 #include "CSSValueKeywords.h"
+#include "Event.h"
 #include "EventNames.h"
 #include "ExceptionCode.h"
 #include "HTMLDocument.h"
@@ -108,6 +109,9 @@ void HTMLMediaElement::attributeChanged(Attribute* attr, bool preserveDecls)
         // change to src attribute triggers load()
         if (inDocument() && m_networkState == EMPTY)
             scheduleLoad();
+    } if (attrName == controlsAttr) {
+        if (renderer())
+            renderer()->updateFromElement();
     }
 }
     
@@ -984,6 +988,9 @@ void HTMLMediaElement::updateMovie()
         m_movie->play();
     else if (!shouldBePlaying && !m_movie->paused())
         m_movie->pause();
+    
+    if (renderer())
+        renderer()->updateFromElement();
 }
     
 void HTMLMediaElement::willSaveToCache()
@@ -1018,6 +1025,13 @@ void HTMLMediaElement::didRestoreFromCache()
     if (renderer())
         renderer()->updateFromElement();
 }
+    
+void HTMLMediaElement::defaultEventHandler(Event* event)
+{
+    if (renderer() && renderer()->isMedia())
+        static_cast<RenderMedia*>(renderer())->forwardEvent(event);
+    HTMLElement::defaultEventHandler(event);
+}
 
 }
 
index 4da50d6c0374cd7807fb3059abc68397c6e8f71f..c44286648d2f575ac1c960b492afd8459e781a82 100644 (file)
@@ -62,6 +62,8 @@ public:
     
     void scheduleLoad();
     
+    virtual void defaultEventHandler(Event*);
+    
 // DOM API
 // error state
     PassRefPtr<MediaError> error() const;
diff --git a/WebCore/rendering/RenderMedia.cpp b/WebCore/rendering/RenderMedia.cpp
new file mode 100644 (file)
index 0000000..48a6633
--- /dev/null
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+
+#if ENABLE(VIDEO)
+#include "RenderMedia.h"
+
+#include "CSSStyleSelector.h"
+#include "Document.h"
+#include "Event.h"
+#include "EventNames.h"
+#include "FloatConversion.h"
+#include "FrameView.h"
+#include "GraphicsContext.h"
+#include "HTMLDivElement.h"
+#include "HTMLInputElement.h"
+#include "HTMLMediaElement.h"
+#include "HTMLNames.h"
+#include "MouseEvent.h"
+#include "Movie.h"
+#include "RenderSlider.h"
+#include "SystemTime.h"
+
+using namespace std;
+
+namespace WebCore {
+
+using namespace EventNames;
+using namespace HTMLNames;
+    
+static const double cTimeUpdateRepeatDelay = 0.2;
+static const double cOpacityAnimationRepeatDelay = 0.05;
+// FIXME get this from style
+static const double cOpacityAnimationDuration = 0.5;
+    
+class MediaControlShadowRootElement : public HTMLDivElement {
+public:
+    MediaControlShadowRootElement(Document* doc, HTMLMediaElement* mediaElement) 
+        : HTMLDivElement(doc)
+        , m_mediaElement(mediaElement) { }
+    
+    virtual bool isShadowNode() const { return true; }
+    virtual Node* shadowParentNode() { return m_mediaElement; }
+    
+private:
+    HTMLMediaElement* m_mediaElement;    
+};
+    
+// ----------------------------
+
+class MediaControlInputElement : public HTMLInputElement {
+public:
+    MediaControlInputElement(Document*, RenderStyle::PseudoId, String type, HTMLMediaElement*);
+    void attachToParent(PassRefPtr<Element>);
+protected:
+    HTMLMediaElement* m_mediaElement;   
+};
+    
+MediaControlInputElement::MediaControlInputElement(Document* doc, RenderStyle::PseudoId pseudo, String type, HTMLMediaElement* mediaElement) 
+: HTMLInputElement(doc)
+, m_mediaElement(mediaElement)
+{
+    setInputType(type);
+    RenderStyle* style = m_mediaElement->renderer()->getPseudoStyle(pseudo);
+    RenderObject* renderer = createRenderer(m_mediaElement->renderer()->renderArena(), style);
+    setRenderer(renderer);
+    renderer->setStyle(style);
+    renderer->updateFromElement();
+    setAttached();
+    setInDocument(true);
+}
+
+void MediaControlInputElement::attachToParent(PassRefPtr<Element> parent)
+{
+    parent->addChild(this);
+    parent->renderer()->addChild(renderer());
+}
+    
+// ----------------------------
+
+class MediaControlPlayButtonElement : public MediaControlInputElement {
+public:
+    MediaControlPlayButtonElement(Document* doc, HTMLMediaElement* element)
+        : MediaControlInputElement(doc, RenderStyle::MEDIA_CONTROLS_PLAY_BUTTON, "button", element) { }
+    bool inPausedState() const;
+    virtual void defaultEventHandler(Event*);
+    void update();
+};
+
+bool MediaControlPlayButtonElement::inPausedState() const
+{
+    return m_mediaElement->paused() || m_mediaElement->ended() || m_mediaElement->networkState() < HTMLMediaElement::LOADED_METADATA;
+}
+
+void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
+{
+    if (event->type() == clickEvent) {
+        ExceptionCode ec;
+        if (inPausedState())
+            m_mediaElement->play(ec);
+        else 
+            m_mediaElement->pause(ec);
+        event->defaultHandled();
+    }
+    HTMLInputElement::defaultEventHandler(event);
+}
+
+void MediaControlPlayButtonElement::update()
+{
+    // FIXME: these are here just for temporary look
+    static const UChar blackRightPointingTriangle = 0x25b6;
+    const UChar twoBlackVerticalRectangles[] = { 0x25AE, 0x25AE };
+    setValue(inPausedState() ? String(&blackRightPointingTriangle, 1) : String(&twoBlackVerticalRectangles[0], 2));
+    renderer()->updateFromElement();
+}
+
+// ----------------------------
+
+class MediaControlTimelineElement : public MediaControlInputElement {
+public:
+    MediaControlTimelineElement(Document* doc, HTMLMediaElement* element)
+        : MediaControlInputElement(doc, RenderStyle::MEDIA_CONTROLS_TIMELINE, "range", element) { 
+            setAttribute(precisionAttr, "float");
+        }
+    virtual void defaultEventHandler(Event*);
+    void update(bool updateDuration = true);
+};
+
+void MediaControlTimelineElement::defaultEventHandler(Event* event)
+{
+    float oldTime = (float)value().toDouble();
+    HTMLInputElement::defaultEventHandler(event);
+    float time = (float)value().toDouble();
+    if (oldTime != time) {
+        ExceptionCode ec;
+        m_mediaElement->setCurrentTime(time, ec);
+    }
+}
+
+void MediaControlTimelineElement::update(bool updateDuration) 
+{
+    if (updateDuration) {
+        float dur = m_mediaElement->duration();
+        setAttribute(maxAttr, String::number(isfinite(dur) ? dur : 0));
+    }
+    setValue(String::number(m_mediaElement->currentTime()));
+}
+
+// ----------------------------
+
+RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
+    : RenderBlock(video)
+    , m_controlsShadowRoot(0)
+    , m_panel(0)
+    , m_playButton(0)
+    , m_timeline(0)
+    , m_timeDisplay(0)
+    , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
+    , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
+    , m_mouseOver(false)
+    , m_opacityAnimationStartTime(0)
+    , m_opacityAnimationFrom(0)
+    , m_opacityAnimationTo(1.0f)
+    , m_intrinsicSize(intrinsicSize)
+{
+    setReplaced();
+}
+
+RenderMedia::~RenderMedia()
+{
+}
+HTMLMediaElement* RenderMedia::mediaElement() const
+{ 
+    return static_cast<HTMLMediaElement*>(node()); 
+}
+
+Movie* RenderMedia::movie() const
+{
+    return mediaElement()->movie();
+}
+
+void RenderMedia::setStyle(RenderStyle* newStyle)
+{
+    RenderBlock::setStyle(newStyle);
+    setReplaced();
+}
+
+void RenderMedia::createControlsShadowRoot()
+{
+    ASSERT(!m_controlsShadowRoot);
+    m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
+    RenderStyle* rootStyle = new (renderArena()) RenderStyle();
+    rootStyle->setDisplay(BLOCK);
+    rootStyle->setPosition(RelativePosition);
+    rootStyle->setHeight(Length(100.0, Percent));
+    RenderObject* renderer = m_controlsShadowRoot->createRenderer(renderArena(), rootStyle);
+    m_controlsShadowRoot->setRenderer(renderer);
+    renderer->setStyle(rootStyle);
+    renderer->updateFromElement();
+    m_controlsShadowRoot->setAttached();
+    m_controlsShadowRoot->setInDocument(true);
+    addChild(renderer);
+}
+
+void RenderMedia::createPanel()
+{
+    ASSERT(!m_panel);
+    RenderStyle* style = getPseudoStyle(RenderStyle::MEDIA_CONTROLS_PANEL);
+    m_panel = new HTMLDivElement(document());
+    RenderObject* renderer = m_panel->createRenderer(renderArena(), style);
+    m_panel->setRenderer(renderer);
+    renderer->setStyle(style);
+    m_panel->setAttached();
+    m_panel->setInDocument(true);
+    m_controlsShadowRoot->addChild(m_panel);
+    m_controlsShadowRoot->renderer()->addChild(renderer);
+}
+
+void RenderMedia::createPlayButton()
+{
+    ASSERT(!m_playButton);
+    m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
+    m_playButton->attachToParent(m_panel);
+}
+
+void RenderMedia::createTimeline()
+{
+    ASSERT(!m_timeline);
+    m_timeline = new MediaControlTimelineElement(document(), mediaElement());
+    m_timeline->attachToParent(m_panel);
+}
+  
+void RenderMedia::createTimeDisplay()
+{
+    ASSERT(!m_timeDisplay);
+    RenderStyle* style = getPseudoStyle(RenderStyle::MEDIA_CONTROLS_TIME_DISPLAY);
+    m_timeDisplay = new HTMLDivElement(document());
+    RenderObject* renderer = m_timeDisplay->createRenderer(renderArena(), style);
+    m_timeDisplay->setRenderer(renderer);
+    renderer->setStyle(style);
+    m_timeDisplay->setAttached();
+    m_timeDisplay->setInDocument(true);
+    m_panel->addChild(m_timeDisplay);
+    m_panel->renderer()->addChild(renderer);
+}
+    
+void RenderMedia::updateFromElement()
+{
+    updateControls();
+}
+            
+void RenderMedia::updateControls()
+{
+    HTMLMediaElement* media = mediaElement();
+    if (!media->controls()) {
+        if (m_controlsShadowRoot) {
+            m_controlsShadowRoot->detach();
+            m_panel = 0;
+            m_playButton = 0;
+            m_timeline = 0;
+            m_timeDisplay = 0;
+            m_controlsShadowRoot = 0;
+        }
+        return;
+    }
+    
+    if (!m_controlsShadowRoot) {
+        createControlsShadowRoot();
+        createPanel();
+        createPlayButton();
+        createTimeline();
+        createTimeDisplay();
+    }
+    
+    if (media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA)
+        m_timeUpdateTimer.stop();
+    else
+        m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
+    
+    if (m_playButton)
+        m_playButton->update();
+    if (m_timeline)
+        m_timeline->update();
+    updateTimeDisplay();
+    updateControlVisibility();
+}
+
+void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
+{
+    if (m_timeline)
+        m_timeline->update(false);
+    updateTimeDisplay();
+}
+    
+String RenderMedia::formatTime(float time)
+{
+    if (!isfinite(time))
+        time = 0;
+    int seconds = (int)time; 
+    int hours = seconds / (60 * 60);
+    int minutes = (seconds / 60) % 60;
+    seconds %= 60;
+    return String::format("%02d:%02d:%02d", hours, minutes, seconds);
+}
+
+void RenderMedia::updateTimeDisplay()
+{
+    if (!m_timeDisplay)
+        return;
+    String timeString = formatTime(mediaElement()->currentTime());
+    ExceptionCode ec;
+    m_timeDisplay->setInnerText(timeString, ec);
+}
+    
+void RenderMedia::updateControlVisibility() 
+{
+    if (!m_panel)
+        return;
+    // do fading manually, css animations don't work well with shadow trees
+    HTMLMediaElement* media = mediaElement();
+    bool visible = m_mouseOver || media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA;
+    if (visible == (m_opacityAnimationTo > 0))
+        return;
+    if (visible) {
+        m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
+        m_opacityAnimationTo = 1.0f;
+    } else {
+        m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
+        m_opacityAnimationTo = 0;
+    }
+    m_opacityAnimationStartTime = currentTime();
+    m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
+}
+    
+void RenderMedia::changeOpacity(HTMLElement* e, float opacity) 
+{
+    if (!e || !e->renderer() || !e->renderer()->style())
+        return;
+    RenderStyle* s = new (renderArena()) RenderStyle(*e->renderer()->style());
+    s->setOpacity(opacity);
+    // z-index can't be auto if opacity is used
+    s->setZIndex(0);
+    e->renderer()->setStyle(s);
+}
+    
+void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
+{
+    double time = currentTime() - m_opacityAnimationStartTime;
+    if (time >= cOpacityAnimationDuration) {
+        time = cOpacityAnimationDuration;
+        m_opacityAnimationTimer.stop();
+    }
+    float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
+    changeOpacity(m_panel.get(), opacity);
+}
+    
+void RenderMedia::forwardEvent(Event* event)
+{
+    if (event->isMouseEvent() && m_controlsShadowRoot) {
+        MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
+        IntPoint point(mouseEvent->pageX(), mouseEvent->pageY());
+        if (m_playButton && m_playButton->renderer()->absoluteBoundingBoxRect().contains(point))
+            m_playButton->defaultEventHandler(event);
+        if (m_timeline && m_timeline->renderer()->absoluteBoundingBoxRect().contains(point))
+            m_timeline->defaultEventHandler(event);
+        
+        if (event->type() == mouseoverEvent) {
+            m_mouseOver = true;
+            updateControlVisibility();
+        }
+        if (event->type() == mouseoutEvent) {
+            // FIXME: moving over scrollbar thumb generates mouseout for the ancestor media element for some reason
+            m_mouseOver = absoluteBoundingBoxRect().contains(point);
+            updateControlVisibility();
+        }
+    }
+}
+
+} // namespace WebCore
+
+#endif
diff --git a/WebCore/rendering/RenderMedia.h b/WebCore/rendering/RenderMedia.h
new file mode 100644 (file)
index 0000000..319b73c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef RenderMedia_h
+#define RenderMedia_h
+
+#if ENABLE(VIDEO)
+
+#include "RenderBlock.h"
+#include "Timer.h"
+
+namespace WebCore {
+    
+class HTMLInputElement;
+class HTMLMediaElement;
+class MediaControlPlayButtonElement;
+class MediaControlTimelineElement;
+class Movie;
+
+class RenderMedia : public RenderBlock {
+public:
+    RenderMedia(HTMLMediaElement*, const IntSize& intrinsicSize);
+    virtual ~RenderMedia();
+    
+    virtual void setStyle(RenderStyle* newStyle);
+
+    virtual const char* renderName() const { return "RenderMedia"; }
+    virtual bool isMedia() const { return true; }
+
+    virtual IntSize intrinsicSize() const { return m_intrinsicSize; }
+    
+    HTMLMediaElement* mediaElement() const;
+    Movie* movie() const;
+
+    static String formatTime(float time);
+
+    void updateFromElement();
+    void updateMovie();
+    void updateControls();
+    
+    void forwardEvent(Event*);
+    
+    void setIntrinsicSize(IntSize size) { m_intrinsicSize = size; }
+    
+private:
+    void createControlsShadowRoot();
+    void createPanel();
+    void createPlayButton();
+    void createTimeline();
+    void createTimeDisplay();
+    
+    void timeUpdateTimerFired(Timer<RenderMedia>*);
+    void updateTimeDisplay();
+    
+    void updateControlVisibility();
+    void changeOpacity(HTMLElement*, float opacity);
+    void opacityAnimationTimerFired(Timer<RenderMedia>*);
+
+    RefPtr<HTMLElement> m_controlsShadowRoot;
+    RefPtr<HTMLElement> m_panel;
+    RefPtr<MediaControlPlayButtonElement> m_playButton;
+    RefPtr<MediaControlTimelineElement> m_timeline;
+    RefPtr<HTMLElement> m_timeDisplay;
+    
+    Timer<RenderMedia> m_timeUpdateTimer;
+    Timer<RenderMedia> m_opacityAnimationTimer;
+    bool m_mouseOver;
+    double m_opacityAnimationStartTime;
+    float m_opacityAnimationFrom;
+    float m_opacityAnimationTo;
+    IntSize m_intrinsicSize;
+};
+
+} // namespace WebCore
+
+#endif
+#endif // RenderMedia_h
index 3203bb64ec6d3136d295c48d8fc746ea0d8dc898..ef61f54087935b14c4a800b5f81f32a5432aca53 100644 (file)
@@ -274,6 +274,7 @@ public:
     virtual bool isMenuList() const { return false; }
     virtual bool isListBox() const { return false; }
     virtual bool isSlider() const { return false; }
+    virtual bool isMedia() const { return false; }
 
     bool isRoot() const { return document()->documentElement() == node(); }
     bool isBody() const;
index 056646c483a0330025c4e2860398fba23d106fc2..5275b2a5d686287e04c9fefa5033abf867cc7a63 100644 (file)
@@ -31,8 +31,8 @@
 #include "Document.h"
 #include "FrameView.h"
 #include "GraphicsContext.h"
-#include "HTMLMediaElement.h"
 #include "HTMLNames.h"
+#include "HTMLVideoElement.h"
 #include "Movie.h"
 
 using namespace std;
@@ -42,9 +42,8 @@ namespace WebCore {
 using namespace HTMLNames;
 
 RenderVideo::RenderVideo(HTMLMediaElement* video)
-    : RenderReplaced(video)
+    : RenderMedia(video, video->movie() ? video->movie()->naturalSize() : IntSize(300, 150))
 {
-    setIntrinsicSize(movie() ? movie()->naturalSize() : IntSize(0, 0));
 }
 
 RenderVideo::~RenderVideo()
@@ -54,12 +53,7 @@ RenderVideo::~RenderVideo()
         m->setParentWidget(0);
     }
 }
-
-Movie* RenderVideo::movie() const
-{
-    return static_cast<HTMLMediaElement*>(element())->movie();
-}
-
+    
 void RenderVideo::videoSizeChanged()
 {
     if (!movie())
@@ -81,74 +75,32 @@ void RenderVideo::videoSizeChanged()
     }
 }
 
-void RenderVideo::paint(PaintInfo& paintInfo, int tx, int ty)
+void RenderVideo::paintObject(PaintInfo& paintInfo, int tx, int ty)
 {
-    if (!shouldPaint(paintInfo, tx, ty))
+    if (style()->visibility() != VISIBLE)
         return;
 
-    tx += m_x;
-    ty += m_y;
-        
-    if (hasBoxDecorations() && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline) 
-        paintBoxDecorations(paintInfo, tx, ty);
-
-    GraphicsContext* context = paintInfo.context;
-
-    if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && style()->visibility() == VISIBLE)
-        paintOutline(context, tx, ty, width(), height(), style());
-
-    if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)
-        return;
-
-    if (!shouldPaintWithinRoot(paintInfo))
-        return;
-        
-    bool isPrinting = document()->printing();
-    if (isPrinting)
-        return;
+    if (paintInfo.phase == PaintPhaseForeground && movie() && !document()->printing()) {
+        updateMovie();
+            
+        IntRect rect = contentBox();
+        rect.move(tx, ty);
+        movie()->paint(paintInfo.context, rect);
+    }
     
-    if (!movie())
-        return;
-           
-    updateMovie();
-        
-    int cWidth = contentWidth();
-    int cHeight = contentHeight();
-    int leftBorder = borderLeft();
-    int topBorder = borderTop();
-    int leftPad = paddingLeft();
-    int topPad = paddingTop();
-
-    IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), IntSize(cWidth, cHeight));
-
-    movie()->paint(context, rect);
+    // Paint the children.
+    RenderBlock::paintObject(paintInfo, tx, ty);
 }
 
 void RenderVideo::layout()
 {
-    ASSERT(needsLayout());
-
-    IntRect oldBounds;
-    IntRect oldOutlineBox;
-    bool checkForRepaint = checkForRepaintDuringLayout();
-    if (checkForRepaint) {
-        oldBounds = absoluteClippedOverflowRect();
-        oldOutlineBox = absoluteOutlineBox();
-    }
-
-    calcWidth();
-    calcHeight();
-    
+    RenderBlock::layout();
     updateMovie();
-        
-    if (checkForRepaint)
-        repaintAfterLayoutIfNeeded(oldBounds, oldOutlineBox);
-    
-    setNeedsLayout(false);
 }
-
+    
 void RenderVideo::updateFromElement()
 {
+    RenderMedia::updateFromElement();
     updateMovie();
 }
 
@@ -232,9 +184,7 @@ int RenderVideo::calcAspectRatioWidth() const
     int intrinsicHeight = intrinsicSize().height();
     if (!intrinsicHeight)
         return 0;
-    //if (!m_cachedImage || m_cachedImage->errorOccurred())
-   //     return intrinsicWidth(); // Don't bother scaling.
-    return RenderReplaced::calcReplacedHeight() * intrinsicWidth / intrinsicHeight;
+    return RenderBox::calcReplacedHeight() * intrinsicWidth / intrinsicHeight;
 }
 
 int RenderVideo::calcAspectRatioHeight() const
@@ -243,9 +193,7 @@ int RenderVideo::calcAspectRatioHeight() const
     int intrinsicHeight = intrinsicSize().height();
     if (!intrinsicWidth)
         return 0;
-    //if (!m_cachedImage || m_cachedImage->errorOccurred())
-    //    return intrinsicHeight(); // Don't bother scaling.
-    return RenderReplaced::calcReplacedWidth() * intrinsicHeight / intrinsicWidth;
+    return RenderBox::calcReplacedWidth() * intrinsicHeight / intrinsicWidth;
 }
 
 void RenderVideo::calcPrefWidths()
index df057423a24e7d2ef21fb35dbbc7f164adaa370e..e227b839304811ea3b4e2d48efbead06fcffcb55 100644 (file)
 
 #if ENABLE(VIDEO)
 
-#include "RenderReplaced.h"
+#include "RenderMedia.h"
 
 namespace WebCore {
     
 class HTMLMediaElement;
-class Movie;
 
-class RenderVideo : public RenderReplaced {
+class RenderVideo : public RenderMedia {
 public:
     RenderVideo(HTMLMediaElement*);
     virtual ~RenderVideo();
 
     virtual const char* renderName() const { return "RenderVideo"; }
-    
-    virtual void paint(PaintInfo&, int tx, int ty);
+
+    virtual void paintObject(PaintInfo&, int tx, int ty);
 
     virtual void layout();
 
@@ -53,10 +52,7 @@ public:
     
     void videoSizeChanged();
     
-    Movie* movie() const;
-    
     void updateFromElement();
-    void updateMovie();
 
 private:
     int calcAspectRatioWidth() const;
@@ -64,6 +60,8 @@ private:
 
     bool isWidthSpecified() const;
     bool isHeightSpecified() const;
+
+    void updateMovie();
 };
 
 } // namespace WebCore