[SCM] WebKit Debian packaging branch, debian/experimental, updated. upstream/1.3.3-9427-gc2be6fc

eric.carlson at apple.com eric.carlson at apple.com
Wed Dec 22 14:45:42 UTC 2010


The following commit has been merged in the debian/experimental branch:
commit d041ae7b16b334ac2c52dff292ca0e2180167092
Author: eric.carlson at apple.com <eric.carlson at apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date:   Tue Oct 19 16:43:37 2010 +0000

    2010-10-19  Eric Carlson  <eric.carlson at apple.com>
    
            Reviewed by Darin Adler.
    
            https://bugs.webkit.org/show_bug.cgi?id=46763
            CRASH in WebCore::ThreadTimers::sharedTimerFiredInternal
    
            Fix crashes caused by moving and deleting <source> element(s) of active media element.
    
            Tests: media/video-source-moved.html
                   media/video-source-removed.html
    
            * html/HTMLMediaElement.cpp:
            (WebCore::HTMLMediaElement::HTMLMediaElement): Add logging. Initialize selectNextSourceChild.
            (WebCore::HTMLMediaElement::~HTMLMediaElement): Ditto.
            (WebCore::HTMLMediaElement::insertedIntoDocument): Ditto.
            (WebCore::HTMLMediaElement::removedFromDocument): Ditto.
            (WebCore::HTMLMediaElement::scheduleLoad): Ditto.
            (WebCore::HTMLMediaElement::setNetworkState): Deal with m_currentSourceNode being null when
            the media engine signals a failure by skipping the error message and continuing as usual.
            (WebCore::HTMLMediaElement::setVolume): Fix logging typo.
            (WebCore::HTMLMediaElement::havePotentialSourceChild): Save and restore m_nextChildNodeToConsider
            around call to selectNextSourceChild because they are both significant.
            (WebCore::HTMLMediaElement::selectNextSourceChild): Use m_nextChildNodeToConsider to pick
            the first node to consider. Bail immediately if it signals that we have already processed
            every <source> node. Stach the node following the current source element in m_nextChildNodeToConsider
            so we can resume the search even if m_currentSourceNode is removed while it is being processed.
            (WebCore::HTMLMediaElement::sourceWasAdded): New, move logic from HTMLSourceElement::insertedIntoTree
            here and correct it to deal with a <source> node being inserted immediately after the
            current <source> node and a new <source> node being inserted at the end of the list after
            all candidates have failed.
            (WebCore::HTMLMediaElement::sourceWillBeRemoved): New, deal with current source node and next
            potential node being removed.
            * html/HTMLMediaElement.h:
            (WebCore::HTMLMediaElement::sourceChildEndOfListValue): New, define sentinal value used to indicate
            that all nodes have been processed.
    
            * html/HTMLSourceElement.cpp:
            (WebCore::HTMLSourceElement::HTMLSourceElement): Add logging.
            (WebCore::HTMLSourceElement::insertedIntoTree): Call mediaElement->sourceWasAdded instead
            of having logic here.
            (WebCore::HTMLSourceElement::willRemove): New, call mediaElement->sourceWillBeRemoved
            (WebCore::HTMLSourceElement::scheduleErrorEvent): Add logging.
            (WebCore::HTMLSourceElement::cancelPendingErrorEvent): Add logging.
            * html/HTMLSourceElement.h:
    
    2010-10-19  Eric Carlson  <eric.carlson at apple.com>
    
            Reviewed by Darin Adler.
    
            https://bugs.webkit.org/show_bug.cgi?id=46763
            CRASH in WebCore::ThreadTimers::sharedTimerFiredInternal
    
            Test moving and deleting <source> element(s) of active <video>.
    
            * media/media-file.js:
            (mimeTypeForExtension): New, added to make it possible to set 'type' attribute on <source>.
    
            * media/video-source-moved-expected.txt: Added.
            * media/video-source-moved.html: Added.
            * media/video-source-removed-expected.txt: Added.
            * media/video-source-removed.html: Added.
    
    
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@70063 268f45cc-cd09-0410-ab3c-d52691b4dbfc

diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index e4f7f0e..fbaefae 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,20 @@
+2010-10-19  Eric Carlson  <eric.carlson at apple.com>
+
+        Reviewed by Darin Adler.
+
+        https://bugs.webkit.org/show_bug.cgi?id=46763
+        CRASH in WebCore::ThreadTimers::sharedTimerFiredInternal
+
+        Test moving and deleting <source> element(s) of active <video>.
+
+        * media/media-file.js:
+        (mimeTypeForExtension): New, added to make it possible to set 'type' attribute on <source>.
+
+        * media/video-source-moved-expected.txt: Added.
+        * media/video-source-moved.html: Added.
+        * media/video-source-removed-expected.txt: Added.
+        * media/video-source-removed.html: Added.
+
 2010-10-18  Antonio Gomes  <agomes at rim.com>
 
         Reviewed by Kenneth Rohde Christiansen.
diff --git a/LayoutTests/media/media-file.js b/LayoutTests/media/media-file.js
index 20e8235..c95595a 100644
--- a/LayoutTests/media/media-file.js
+++ b/LayoutTests/media/media-file.js
@@ -31,6 +31,19 @@ function findMediaFile(tagName, name) {
     return "";
 }
 
+function mimeTypeForExtension(extension) {
+    for (var i = 0; i < videoCodecs.length; ++i) {
+        if (extension == videoCodecs[i][1])
+            return videoCodecs[i][0];
+    }
+    for (var i = 0; i < audioCodecs.length; ++i) {
+        if (extension == audioCodecs[i][1])
+            return audioCodecs[i][0];
+    }
+
+    return "";
+}
+
 function setSrcByTagName(tagName, src) {
     var elements = document.getElementsByTagName(tagName);
     if (elements) {
diff --git a/LayoutTests/media/video-source-moved-expected.txt b/LayoutTests/media/video-source-moved-expected.txt
new file mode 100644
index 0000000..62f77cf
--- /dev/null
+++ b/LayoutTests/media/video-source-moved-expected.txt
@@ -0,0 +1,34 @@
+Test to make sure a <source> moved after the media element begins processing is handled correctly.
+
+Moving previous <source> element to end of list, it should be processed again.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was processed a second time. OK
+
+Moving current <source> element, it should be processed again.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was processed a second time. OK
+
+Moving next <source> element, it should be processed again.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was processed a second time. OK
+
+Moving current <source> element to beginning of list, it should not be processed again.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was not processed OK
+
+Moving next <source> element to beginning of list, it should never processed.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was not processed OK
+
+<span> inserted after current <source> element before it is removed, processing should proceed normally.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was not processed OK
+
+<span> inserted after next <source> element before it is removed, processing should proceed normally.
+EXPECTED ([object HTMLSourceElement] != 'null') OK
+<source> moved was not processed OK
+
+PASS
+
+END OF TEST
+
diff --git a/LayoutTests/media/video-source-moved.html b/LayoutTests/media/video-source-moved.html
new file mode 100644
index 0000000..ba5a96c
--- /dev/null
+++ b/LayoutTests/media/video-source-moved.html
@@ -0,0 +1,205 @@
+<!doctype HTML>
+<html>
+    <head>
+        <title>moving &lt;source&gt; element test</title>
+        <script src=video-test.js></script>
+        <script src=media-file.js></script>
+        <script>
+
+            var testInfo = 
+            {
+                current : -1,
+                tests : 
+                [
+                    { fcn : moveToEnd, errorCount : 0, moved : null, done : false, iteration : 1},
+                    { fcn : moveToEnd, errorCount : 0, moved : null, done : false, iteration : 2},
+                    { fcn : moveToEnd, errorCount : 0, moved : null, done : false, iteration : 3},
+                    { fcn : moveEarlier, errorCount : 0, moved : null, iteration : 1 },
+                    { fcn : moveEarlier, errorCount : 0, moved : null, iteration : 2 },
+                    { fcn : moveEarlier, errorCount : 0, moved : null, iteration : 3 },
+                    { fcn : moveEarlier, errorCount : 0, moved : null, iteration : 4 }
+                ]
+            };
+
+            function findCurrentSourceElement()
+            {
+                var sources = video.getElementsByTagName('source');
+                var currentSrc = video.currentSrc;
+                var ndx;
+                for (ndx = 0; ndx < sources.length; ++ndx) {
+                    if (sources[ndx].src == currentSrc)
+                        break;
+                }
+                if (ndx >= sources.length) {
+                    failTest(currentSrc + " not found in &lt;source&gt; list");
+                    return null;
+                }
+                return sources[ndx];
+            }
+
+            function moveEarlier(test, event)
+            {
+                if (test.done)
+                    return;
+
+                switch (++test.errorCount)
+                {
+                    case 1:
+                        // Do nothing on the first error event
+                        break;
+
+                    case 2:
+                        var current = findCurrentSourceElement();
+                        switch (test.iteration)
+                        {
+                            case 1:
+                                consoleWrite("Moving <b>current<" + "/b> &lt;source&gt; element to beginning of list, it should not be processed again.");
+                                test.moved = video.removeChild(current);
+                                break;
+                            case 2:
+                                consoleWrite("Moving <b>next<" + "/b> &lt;source&gt; element to beginning of list, it should never processed.");
+                                test.moved = video.removeChild(current.nextSibling);
+                                break;
+                            case 3:
+                                consoleWrite("&lt;span&gt; inserted after <b>current<" + "/b> &lt;source&gt; element before it is removed, processing should proceed normally.");
+                                var span = document.createElement("span")
+                                span.appendChild(document.createTextNode("Your browser doesn't support HTML5 video!"));
+                                video.insertBefore(span, current.nextSibling);
+                                test.moved = video.removeChild(current);
+                                break;
+                            case 4:
+                                consoleWrite("&lt;span&gt; inserted after <b>next<" + "/b> &lt;source&gt; element before it is removed, processing should proceed normally.");
+                                var span = document.createElement("span")
+                                span.appendChild(document.createTextNode("Your browser doesn't support HTML5 video!"));
+                                video.insertBefore(span, current.nextSibling.nextSibling);
+                                test.moved = video.removeChild(current.nextSibling);
+                                break;
+                            default:
+                                failTest("Malformed test data!");
+                                break;
+                        }
+
+                        testExpected(test.moved, null, '!=');
+                        video.insertBefore(test.moved, video.firstChild);
+                        break;
+
+                    default:
+                        // We should never get an error for the element we moved.
+                        if (event.target == test.moved) {
+                            failTest("Error fired for &lt;source&gt; moved to beginning of list.");
+                            test.done = true;
+                            return;
+                        } else if (!event.target.nextSibling) {
+                            logResult(true, "&lt;source&gt; moved was not processed"); 
+                            setTimeout(configureNextTest, 100);
+                        }
+                        break;
+                }
+            }
+
+            function moveToEnd(test, event)
+            {
+                switch (++test.errorCount)
+                {
+                    case 1:
+                        // Do nothing on the first error event
+                        break;
+
+                    case 2:
+                        var current = findCurrentSourceElement();
+                        switch (test.iteration)
+                        {
+                            case 1:
+                                consoleWrite("Moving <b>previous<" + "/b> &lt;source&gt; element to end of list, it should be processed again.");
+                                test.moved = video.removeChild(current.previousSibling);
+                                break;
+                            case 2:
+                                consoleWrite("Moving <b>current<" + "/b> &lt;source&gt; element, it should be processed again.");
+                                test.moved = video.removeChild(current);
+                                break;
+                            case 3:
+                                consoleWrite("Moving <b>next<" + "/b> &lt;source&gt; element, it should be processed again.");
+                                test.moved = video.removeChild(current.nextSibling);
+                                break;
+                            default:
+                                failTest("Malformed test data!");
+                                break;
+                        }
+
+                        testExpected(test.moved, null, '!=');
+                        video.appendChild(test.moved);
+                        break;
+
+                    default:
+                        if (event.target == test.moved) {
+                            logResult(true, "&lt;source&gt; moved was processed a second time."); 
+                            setTimeout(configureNextTest, 100);
+                        }  else if (!event.target.nextSibling) {
+                            // We should never reach the end of the source list since the tests stops
+                            // when the error fires for the moved element.
+                            failTest("Error never fired for &lt;source&gt; moved!");
+                        }
+                        break;
+                }
+            }
+
+            function runOneTest(evt)
+            {
+                var test = testInfo.tests[testInfo.current];
+                test.fcn(test, evt);
+            }
+
+            function addSource(index)
+            {
+                var source = document.createElement('source');
+                source.src = findMediaFile("video", index + "-" + Date.now());
+                source.type = mimeTypeForExtension(source.src.split('.')[1]);
+                video.appendChild(source);
+            }
+            
+            function runNextTest()
+            {
+                consoleWrite("");
+                if (++testInfo.current >= testInfo.tests.length) {
+                    consoleWrite("PASS<br>");
+                    endTest();
+                    return;
+                }
+
+                testInfo.errorCount = 0;
+                video = mediaElement = document.createElement('video');
+                document.body.appendChild(video);
+
+                // Add a bunch of source elements with bogus urls because we want to rearrange elements 
+                // after the media engine begins processing sources, and we can't predict the delay 
+                // between when the media element fires an 'error' event and our handler is called,
+                // but we need to guarantee that there are <source> elements that haven't been processed
+                // when we run the test.
+                for (var ndx = 1; ndx <= 10; ndx++)
+                    addSource(ndx);
+            }
+
+            function configureNextTest()
+            {
+                var videos = document.querySelectorAll('video');
+                for (var ndx = 0; ndx < videos.length; ++ndx)
+                    videos[ndx].parentNode.removeChild(videos[ndx]);
+                video = mediaElement = null;
+                setTimeout(runNextTest, 100);
+            }
+
+            function setup()
+            {
+                document.addEventListener("error", runOneTest, true);
+                configureNextTest();
+            }
+
+        </script>
+    </head>
+
+    <body>
+        <div>Test to make sure a &lt;source&gt; moved after the media element begins processing 
+        is handled correctly.</div>
+        <script>setup()</script>
+    </body>
+</html>
diff --git a/LayoutTests/media/video-source-removed-expected.txt b/LayoutTests/media/video-source-removed-expected.txt
new file mode 100644
index 0000000..029d5f8
--- /dev/null
+++ b/LayoutTests/media/video-source-removed-expected.txt
@@ -0,0 +1,33 @@
+Test to make sure removing a media element's <source>(s) does not cause a crash.
+
+Removing all <source> elements with removeChild()
+-> removeChild(0)
+-> removeChild(1)
+-> removeChild(2)
+-> removeChild(3)
+-> removeChild(4)
+-> removeChild(5)
+-> removeChild(6)
+-> removeChild(7)
+-> removeChild(8)
+-> removeChild(9)
+
+Removing all <source> by setting .innerHTML
+-> video.innerHTML = ''
+
+Removing all <source> elements with replaceChild()
+-> replaceChild(0)
+-> replaceChild(1)
+-> replaceChild(2)
+-> replaceChild(3)
+-> replaceChild(4)
+-> replaceChild(5)
+-> replaceChild(6)
+-> replaceChild(7)
+-> replaceChild(8)
+-> replaceChild(9)
+
+PASS: A crash did not occur when removing <source> elements.
+
+END OF TEST
+
diff --git a/LayoutTests/media/video-source-removed.html b/LayoutTests/media/video-source-removed.html
new file mode 100644
index 0000000..316b0ee
--- /dev/null
+++ b/LayoutTests/media/video-source-removed.html
@@ -0,0 +1,92 @@
+<!doctype HTML>
+<html>
+    <head>
+        <title>crash after removing &lt;source&gt; test</title>
+        <script src=video-test.js></script>
+        <script src=media-file.js></script>
+        <script>
+
+            var testInfo = 
+            {
+                current : -1,
+                tests : [removeChild, innerHTML, replaceChild]
+            };
+
+            function removeChild(sources)
+            {
+                consoleWrite("Removing all &lt;source&gt; elements with <i>removeChild()<" + "/i>");
+                for (var ndx = 0; ndx < sources.length; ++ndx) {
+                    consoleWrite(" -> removeChild(" + ndx + ")"); 
+                    video.removeChild(sources[ndx]);
+               }
+            }
+
+            function innerHTML()
+            {
+                consoleWrite("Removing all &lt;source&gt; by setting <i>.innerHTML<" + "/i>");
+                consoleWrite(" -> video.innerHTML = ''"); 
+            }
+
+            function replaceChild(sources)
+            {
+                consoleWrite("Removing all &lt;source&gt; elements with <i>replaceChild()<" + "/i>");
+                var span = document.createElement("span")
+                span.appendChild(document.createTextNode("Yo"));
+                for (var ndx = 0; ndx < sources.length; ++ndx) {
+                    consoleWrite(" -> replaceChild(" + ndx + ")"); 
+                   video.replaceChild(span, sources[ndx]);
+                }
+            }
+
+            function runOneTest()
+            {
+                testInfo.tests[testInfo.current](document.querySelectorAll('source'));
+                setTimeout(configureNextTest, 100);
+            }
+
+            function addSource(index)
+            {
+                source = document.createElement('source');
+                source.src = findMediaFile("video", index + "-" + Date.now());
+                source.type = mimeTypeForExtension(source.src.split('.')[1]);
+                video.appendChild(source);
+            }
+            
+            function runNextTest()
+            {
+                consoleWrite("");
+                if (++testInfo.current >= testInfo.tests.length) {
+                    consoleWrite("PASS: A crash did not occur when removing &lt;source&gt; elements.<br>");
+                    endTest();
+                    return;
+                }
+
+                video = mediaElement = document.createElement('video');
+                document.body.appendChild(video);
+                video.addEventListener("loadstart", runOneTest);
+
+                // Add a bunch of source elements with bogus urls because we want to remove elements 
+                // after the media engine begins processing sources, and we can't predict the delay 
+                // between when the media element fires an 'error' event and our handler is called,
+                // but we need to guarantee that there are <source> elements that haven't been processed
+                // when we run the test.
+                for (var ndx = 1; ndx <= 10; ndx++)
+                    addSource(ndx);
+            }
+
+            function configureNextTest()
+            {
+                var videos = document.querySelectorAll('video');
+                for (var ndx = 0; ndx < videos.length; ++ndx)
+                    videos[ndx].parentNode.removeChild(videos[ndx]);
+                video = mediaElement = null;
+                setTimeout(runNextTest, 100);
+            }
+        </script>
+    </head>
+
+    <body>
+        Test to make sure removing a media element's &lt;source&gt;(s) does not cause a crash.
+        <script>configureNextTest()</script>
+    </body>
+</html>
diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog
index fc89f23..5550d79 100644
--- a/WebCore/ChangeLog
+++ b/WebCore/ChangeLog
@@ -1,3 +1,49 @@
+2010-10-19  Eric Carlson  <eric.carlson at apple.com>
+
+        Reviewed by Darin Adler.
+
+        https://bugs.webkit.org/show_bug.cgi?id=46763
+        CRASH in WebCore::ThreadTimers::sharedTimerFiredInternal
+
+        Fix crashes caused by moving and deleting <source> element(s) of active media element.
+
+        Tests: media/video-source-moved.html
+               media/video-source-removed.html
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::HTMLMediaElement): Add logging. Initialize selectNextSourceChild.
+        (WebCore::HTMLMediaElement::~HTMLMediaElement): Ditto.
+        (WebCore::HTMLMediaElement::insertedIntoDocument): Ditto.
+        (WebCore::HTMLMediaElement::removedFromDocument): Ditto.
+        (WebCore::HTMLMediaElement::scheduleLoad): Ditto.
+        (WebCore::HTMLMediaElement::setNetworkState): Deal with m_currentSourceNode being null when
+        the media engine signals a failure by skipping the error message and continuing as usual.
+        (WebCore::HTMLMediaElement::setVolume): Fix logging typo.
+        (WebCore::HTMLMediaElement::havePotentialSourceChild): Save and restore m_nextChildNodeToConsider
+        around call to selectNextSourceChild because they are both significant.
+        (WebCore::HTMLMediaElement::selectNextSourceChild): Use m_nextChildNodeToConsider to pick
+        the first node to consider. Bail immediately if it signals that we have already processed 
+        every <source> node. Stach the node following the current source element in m_nextChildNodeToConsider
+        so we can resume the search even if m_currentSourceNode is removed while it is being processed.
+        (WebCore::HTMLMediaElement::sourceWasAdded): New, move logic from HTMLSourceElement::insertedIntoTree
+        here and correct it to deal with a <source> node being inserted immediately after the
+        current <source> node and a new <source> node being inserted at the end of the list after
+        all candidates have failed.
+        (WebCore::HTMLMediaElement::sourceWillBeRemoved): New, deal with current source node and next
+        potential node being removed.
+        * html/HTMLMediaElement.h:
+        (WebCore::HTMLMediaElement::sourceChildEndOfListValue): New, define sentinal value used to indicate
+        that all nodes have been processed.
+
+        * html/HTMLSourceElement.cpp:
+        (WebCore::HTMLSourceElement::HTMLSourceElement): Add logging.
+        (WebCore::HTMLSourceElement::insertedIntoTree): Call mediaElement->sourceWasAdded instead
+        of having logic here.
+        (WebCore::HTMLSourceElement::willRemove): New, call mediaElement->sourceWillBeRemoved
+        (WebCore::HTMLSourceElement::scheduleErrorEvent): Add logging.
+        (WebCore::HTMLSourceElement::cancelPendingErrorEvent): Add logging.
+        * html/HTMLSourceElement.h:
+
 2010-10-19  Luiz Agostini  <luiz.agostini at openbossa.org>
 
         Reviewed by Antti Koivisto.
diff --git a/WebCore/html/HTMLMediaElement.cpp b/WebCore/html/HTMLMediaElement.cpp
index 7c2138f..362c771 100644
--- a/WebCore/html/HTMLMediaElement.cpp
+++ b/WebCore/html/HTMLMediaElement.cpp
@@ -125,6 +125,7 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* docum
     , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max())
     , m_loadState(WaitingForSource)
     , m_currentSourceNode(0)
+    , m_nextChildNodeToConsider(0)
     , m_player(0)
 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
     , m_proxyWidget(0)
@@ -155,12 +156,14 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* docum
     , m_loadInitiatedByUserGesture(false)
     , m_completelyLoaded(false)
 {
+    LOG(Media, "HTMLMediaElement::HTMLMediaElement");
     document->registerForDocumentActivationCallbacks(this);
     document->registerForMediaVolumeCallbacks(this);
 }
 
 HTMLMediaElement::~HTMLMediaElement()
 {
+    LOG(Media, "HTMLMediaElement::~HTMLMediaElement");
     if (m_isWaitingUntilMediaCanStart)
         document()->removeMediaCanStartListener(this);
     setShouldDelayLoadEvent(false);
@@ -324,6 +327,7 @@ RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*)
  
 void HTMLMediaElement::insertedIntoDocument()
 {
+    LOG(Media, "HTMLMediaElement::removedFromDocument");
     HTMLElement::insertedIntoDocument();
     if (!getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
         scheduleLoad();
@@ -331,6 +335,7 @@ void HTMLMediaElement::insertedIntoDocument()
 
 void HTMLMediaElement::removedFromDocument()
 {
+    LOG(Media, "HTMLMediaElement::removedFromDocument");
     if (m_networkState > NETWORK_EMPTY)
         pause(processingUserGesture());
     if (m_isFullscreen)
@@ -370,6 +375,7 @@ void HTMLMediaElement::recalcStyle(StyleChange change)
 
 void HTMLMediaElement::scheduleLoad()
 {
+    LOG(Media, "HTMLMediaElement::scheduleLoad");
 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
     createMediaPlayerProxy();
 #endif
@@ -863,7 +869,7 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
     LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState));
 
     if (state == MediaPlayer::Empty) {
-        // just update the cached state and leave, we can't do anything 
+        // Just update the cached state and leave, we can't do anything.
         m_networkState = NETWORK_EMPTY;
         return;
     }
@@ -874,16 +880,17 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
         // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
         // <source> children, schedule the next one
         if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
-            ASSERT(m_currentSourceNode);
-            if (!m_currentSourceNode)
-                return;
 
-            m_currentSourceNode->scheduleErrorEvent();
+            if (m_currentSourceNode)
+                m_currentSourceNode->scheduleErrorEvent();
+            else
+                LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
+
             if (havePotentialSourceChild()) {
-                LOG(Media, "HTMLMediaElement::setNetworkState scheduling next <source>");
+                LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
                 scheduleNextSourceChild();
             } else {
-                LOG(Media, "HTMLMediaElement::setNetworkState no more <source> elements, waiting");
+                LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting");
                 waitForSourceChange();
             }
 
@@ -1419,7 +1426,7 @@ float HTMLMediaElement::volume() const
 
 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec)
 {
-    LOG(Media, "HTMLMediaElement::setControls(%f)", vol);
+    LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
 
     if (vol < 0.0f || vol > 1.0f) {
         ec = INDEX_SIZE_ERR;
@@ -1567,11 +1574,15 @@ float HTMLMediaElement::percentLoaded() const
 
 bool HTMLMediaElement::havePotentialSourceChild()
 {
-    // Stash the current <source> node so we can restore it after checking
-    // to see there is another potential
+    // Stash the current <source> node and next nodes so we can restore them after checking
+    // to see there is another potential.
     HTMLSourceElement* currentSourceNode = m_currentSourceNode;
+    Node* nextNode = m_nextChildNodeToConsider;
+
     KURL nextURL = selectNextSourceChild(0, DoNothing);
+
     m_currentSourceNode = currentSourceNode;
+    m_nextChildNodeToConsider = nextNode;
 
     return nextURL.isValid();
 }
@@ -1585,22 +1596,29 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType *contentType, InvalidSo
         LOG(Media, "HTMLMediaElement::selectNextSourceChild(contentType : \"%s\")", contentType ? contentType->raw().utf8().data() : "");
 #endif
 
+    if (m_nextChildNodeToConsider == sourceChildEndOfListValue()) {
+#if !LOG_DISABLED
+        if (shouldLog)
+            LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\"");
+#endif
+        return KURL();
+    }
+
     KURL mediaURL;
     Node* node;
-    bool lookingForPreviousNode = m_currentSourceNode;
+    HTMLSourceElement* source;
+    bool lookingForStartNode = m_nextChildNodeToConsider;
     bool canUse = false;
 
     for (node = firstChild(); !canUse && node; node = node->nextSibling()) {
-        if (!node->hasTagName(sourceTag))
+        if (lookingForStartNode && m_nextChildNodeToConsider != node)
             continue;
-
-        if (lookingForPreviousNode) {
-            if (m_currentSourceNode == static_cast<HTMLSourceElement*>(node))
-                lookingForPreviousNode = false;
+        lookingForStartNode = false;
+        
+        if (!node->hasTagName(sourceTag))
             continue;
-        }
 
-        HTMLSourceElement* source = static_cast<HTMLSourceElement*>(node);
+        source = static_cast<HTMLSourceElement*>(node);
 
         // If candidate does not have a src attribute, or if its src attribute's value is the empty string ... jump down to the failed step below
         mediaURL = source->getNonEmptyURLAttribute(srcAttr);
@@ -1635,26 +1653,108 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType *contentType, InvalidSo
         if (!isSafeToLoadURL(mediaURL, actionIfInvalid) || !dispatchBeforeLoadEvent(mediaURL.string()))
             goto check_again;
 
-        // Making it this far means the <source> looks reasonable
+        // Making it this far means the <source> looks reasonable.
         canUse = true;
-        if (contentType)
-            *contentType = ContentType(source->type());
 
 check_again:
         if (!canUse && actionIfInvalid == Complain)
             source->scheduleErrorEvent();
-        m_currentSourceNode = static_cast<HTMLSourceElement*>(node);
     }
 
-    if (!canUse)
+    if (canUse) {
+        if (contentType)
+            *contentType = ContentType(source->type());
+        m_currentSourceNode = source;
+        m_nextChildNodeToConsider = source->nextSibling();
+        if (!m_nextChildNodeToConsider)
+            m_nextChildNodeToConsider = sourceChildEndOfListValue();
+    } else {
         m_currentSourceNode = 0;
+        m_nextChildNodeToConsider = sourceChildEndOfListValue();
+    }
+
 #if !LOG_DISABLED
     if (shouldLog)
-        LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %s", canUse ? urlForLogging(mediaURL.string()).utf8().data() : "");
+        LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode, canUse ? urlForLogging(mediaURL.string()).utf8().data() : "");
 #endif
     return canUse ? mediaURL : KURL();
 }
 
+void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
+{
+    LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source);
+
+#if !LOG_DISABLED
+    if (source->hasTagName(sourceTag)) {
+        KURL url = source->getNonEmptyURLAttribute(srcAttr);
+        LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLogging(url).utf8().data());
+    }
+#endif
+    
+    // We should only consider a <source> element when there is not src attribute at all.
+    if (hasAttribute(srcAttr))
+        return;
+
+    // 4.8.8 - If a source element is inserted as a child of a media element that has no src 
+    // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke 
+    // the media element's resource selection algorithm.
+    if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
+        scheduleLoad();
+        return;
+    }
+
+    if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
+        LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source");
+        m_nextChildNodeToConsider = source;
+        return;
+    }
+
+    if (m_nextChildNodeToConsider != sourceChildEndOfListValue())
+        return;
+    
+    // 4.8.9.5, resource selection algorithm, source elements section:
+    // 20 - Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
+    // 21 - Asynchronously await a stable state...
+    // 22 - Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case 
+    // it hasn't been fired yet).
+    setShouldDelayLoadEvent(true);
+
+    // 23 - Set the networkState back to NETWORK_LOADING.
+    m_networkState = NETWORK_LOADING;
+    
+    // 24 - Jump back to the find next candidate step above.
+    m_nextChildNodeToConsider = source;
+    scheduleNextSourceChild();
+}
+
+void HTMLMediaElement::sourceWillBeRemoved(HTMLSourceElement* source)
+{
+    LOG(Media, "HTMLMediaElement::sourceWillBeRemoved(%p)", source);
+
+#if !LOG_DISABLED
+    if (source->hasTagName(sourceTag)) {
+        KURL url = source->getNonEmptyURLAttribute(srcAttr);
+        LOG(Media, "HTMLMediaElement::sourceWillBeRemoved - 'src' is %s", urlForLogging(url).utf8().data());
+    }
+#endif
+
+    if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
+        return;
+
+    if (source == m_nextChildNodeToConsider) {
+        m_nextChildNodeToConsider = m_nextChildNodeToConsider->nextSibling();
+        if (!m_nextChildNodeToConsider)
+            m_nextChildNodeToConsider = sourceChildEndOfListValue();
+        LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider);
+    } else if (source == m_currentSourceNode) {
+        // Clear the current source node pointer, but don't change the movie as the spec says:
+        // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already 
+        // inserted in a video or audio element will have no effect.
+        m_currentSourceNode = 0;
+        LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0");
+    }
+}
+
 void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
 {
     LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged");
diff --git a/WebCore/html/HTMLMediaElement.h b/WebCore/html/HTMLMediaElement.h
index 99232e0..e5e7ae0 100644
--- a/WebCore/html/HTMLMediaElement.h
+++ b/WebCore/html/HTMLMediaElement.h
@@ -169,6 +169,9 @@ public:
 
     bool processingUserGesture() const;
 
+    void sourceWillBeRemoved(HTMLSourceElement*);
+    void sourceWasAdded(HTMLSourceElement*);
+
 protected:
     HTMLMediaElement(const QualifiedName&, Document*);
     virtual ~HTMLMediaElement();
@@ -331,7 +334,9 @@ private:
     enum LoadState { WaitingForSource, LoadingFromSrcAttr, LoadingFromSourceElement };
     LoadState m_loadState;
     HTMLSourceElement* m_currentSourceNode;
-    
+    Node* m_nextChildNodeToConsider;
+    Node* sourceChildEndOfListValue() { return static_cast<Node*>(this); }
+
     OwnPtr<MediaPlayer> m_player;
 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
     RefPtr<Widget> m_proxyWidget;
diff --git a/WebCore/html/HTMLSourceElement.cpp b/WebCore/html/HTMLSourceElement.cpp
index 96c9829..59b3882 100644
--- a/WebCore/html/HTMLSourceElement.cpp
+++ b/WebCore/html/HTMLSourceElement.cpp
@@ -33,6 +33,7 @@
 #include "HTMLDocument.h"
 #include "HTMLMediaElement.h"
 #include "HTMLNames.h"
+#include "Logging.h"
 
 using namespace std;
 
@@ -44,6 +45,7 @@ inline HTMLSourceElement::HTMLSourceElement(const QualifiedName& tagName, Docume
     : HTMLElement(tagName, document)
     , m_errorEventTimer(this, &HTMLSourceElement::errorEventTimerFired)
 {
+    LOG(Media, "HTMLSourceElement::HTMLSourceElement - %p", this);
     ASSERT(hasTagName(sourceTag));
 }
 
@@ -55,13 +57,17 @@ PassRefPtr<HTMLSourceElement> HTMLSourceElement::create(const QualifiedName& tag
 void HTMLSourceElement::insertedIntoTree(bool deep)
 {
     HTMLElement::insertedIntoTree(deep);
-    if (parentNode() && (parentNode()->hasTagName(audioTag) ||  parentNode()->hasTagName(videoTag))) {
-        HTMLMediaElement* media = static_cast<HTMLMediaElement*>(parentNode());
-        if (media->networkState() == HTMLMediaElement::NETWORK_EMPTY)
-            media->scheduleLoad();
-    }
+    if (parentNode() && (parentNode()->hasTagName(audioTag) || parentNode()->hasTagName(videoTag)))
+        static_cast<HTMLMediaElement*>(parentNode())->sourceWasAdded(this);
 }
 
+void HTMLSourceElement::willRemove()
+{
+    if (parentNode() && (parentNode()->hasTagName(audioTag) || parentNode()->hasTagName(videoTag)))
+        static_cast<HTMLMediaElement*>(parentNode())->sourceWillBeRemoved(this);
+    HTMLElement::willRemove();
+}
+    
 void HTMLSourceElement::setSrc(const String& url)
 {
     setAttribute(srcAttr, url);
@@ -89,6 +95,7 @@ void HTMLSourceElement::setType(const String& type)
 
 void HTMLSourceElement::scheduleErrorEvent()
 {
+    LOG(Media, "HTMLSourceElement::scheduleErrorEvent - %p", this);
     if (m_errorEventTimer.isActive())
         return;
 
@@ -97,11 +104,13 @@ void HTMLSourceElement::scheduleErrorEvent()
 
 void HTMLSourceElement::cancelPendingErrorEvent()
 {
+    LOG(Media, "HTMLSourceElement::cancelPendingErrorEvent - %p", this);
     m_errorEventTimer.stop();
 }
 
 void HTMLSourceElement::errorEventTimerFired(Timer<HTMLSourceElement>*)
 {
+    LOG(Media, "HTMLSourceElement::errorEventTimerFired - %p", this);
     dispatchEvent(Event::create(eventNames().errorEvent, false, true));
 }
 
diff --git a/WebCore/html/HTMLSourceElement.h b/WebCore/html/HTMLSourceElement.h
index 8aa1d06..cc1e5d7 100644
--- a/WebCore/html/HTMLSourceElement.h
+++ b/WebCore/html/HTMLSourceElement.h
@@ -50,6 +50,7 @@ private:
     HTMLSourceElement(const QualifiedName&, Document*);
     
     virtual void insertedIntoTree(bool);
+    virtual void willRemove();
     virtual bool isURLAttribute(Attribute*) const;
 
     void errorEventTimerFired(Timer<HTMLSourceElement>*);

-- 
WebKit Debian packaging



More information about the Pkg-webkit-commits mailing list