Nullptr crash accessing Document in GenericEventQueue::dispatchOneEvent()
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Jul 2018 03:31:29 +0000 (03:31 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Jul 2018 03:31:29 +0000 (03:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=187284

Reviewed by Eric Carlson.

Source/WebCore:

The null pointer crash was caused by some GenericEventQueue dispatching an event in a stopped document,
which does not have a valid script execution context because some uses of GenericEventQueue in media code
was not closing the queue upon stopping of all active DOM objects.

Fixed all uses of GenericEventQueue which did not suspend or stop the queue with active DOM objects.
Made SourceBufferList and TrackListBase (along with AudioTrackList, TextTrackList, and VideoTrackList)
inherit from ActiveDOMObject instead of ContextDestructionObserver to do this.

Also fixed a bug that media elements inside a template element (and other cases where the document doesn't
have a browsing context) were scheduling events since this would hit the newly added debug assertion in
GenericEventQueue::dispatchOneEvent.

Test: media/track/video-track-addition-and-frame-removal.html

* Modules/encryptedmedia/legacy/WebKitMediaKeySession.cpp:
(WebCore::WebKitMediaKeySession::suspend): Assert that we never try to suspend when the document has
this object alive since canSuspendForDocumentSuspension always returns false.
(WebCore::WebKitMediaKeySession::resume): Ditto.
(WebCore::WebKitMediaKeySession::stop): Stop the event queue to avoid the crash.
* Modules/encryptedmedia/legacy/WebKitMediaKeySession.h:
* Modules/mediasource/MediaSource.cpp:
(WebCore::MediaSource::removeSourceBuffer): Don't do any work to update tracks when the active DOM
objects are stopped since this MediaSource and the related media objects are about to be destructed.
(WebCore::MediaSource::suspend): Assert that m_asyncEventQueue is empty as canSuspendForDocumentSuspension
returns false whenever the queue is not empty.
(WebCore::MediaSource::resume): Ditto.
* Modules/mediasource/MediaSource.h:
* Modules/mediasource/SourceBuffer.cpp:
(WebCore::SourceBuffer::suspend): Ditto.
(WebCore::SourceBuffer::resume): Ditto.
(WebCore::SourceBuffer::stop): Stop the event queue to avoid the crash.
* Modules/mediasource/SourceBuffer.h:
* Modules/mediasource/SourceBufferList.cpp:
(WebCore::SourceBufferList): Made this an active DOM object.
(WebCore::SourceBufferList::SourceBufferList):
(WebCore::SourceBufferList::canSuspendForDocumentSuspension const): Added. Return false when there are
pending events to match other media code.
(WebCore::SourceBufferList::suspend): Added. Assert that the event queue is empty here.
(WebCore::SourceBufferList::resume): Ditto.
(WebCore::SourceBufferList::stop): Added. Stop the event queue to avoid the crash.
(WebCore::SourceBufferList::activeDOMObjectName const): Added.
* Modules/mediasource/SourceBufferList.h:
(WebCore::SourceBufferList): Made this an active DOM object.
* Modules/mediasource/SourceBufferList.idl:
* dom/Document.h:
(WebCore::Document::hasBrowsingContext const): Added.
* dom/GenericEventQueue.cpp:
(WebCore::GenericEventQueue::dispatchOneEvent): Added an assertion to catch when an event is dispatched
inside a stopped document, which is never correct and causes this crash down the line.
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::playInternal): Exit early when the document doesn't have a browsing context;
e.g. when the media element is inside a template element.
(WebCore::HTMLMediaElement::pauseInternal): Ditto.
(WebCore::HTMLMediaElement::sourceWasAdded): Ditto.
* html/track/AudioTrackList.cpp:
(AudioTrackList::activeDOMObjectName const): Added.
* html/track/AudioTrackList.h:
* html/track/AudioTrackList.idl:
* html/track/TextTrackList.cpp::
(TextTrackList::activeDOMObjectName const): Added.
* html/track/TextTrackList.h:
* html/track/TextTrackList.idl:
* html/track/TrackListBase.cpp:
(WebCore::TrackListBase): Made this an active DOM object.
(WebCore::TrackListBase::TrackListBase):
(WebCore::TrackListBase::canSuspendForDocumentSuspension const): Added. Return false when there are pending events
to match other media code.
(WebCore::TrackListBase::suspend): Added. Assert that the event queue is empty here.
(WebCore::TrackListBase::resume): Ditto.
(WebCore::TrackListBase::stop): Added. Stop the event queue to avoid the crash.
* html/track/TrackListBase.h:
* html/track/VideoTrackList.cpp:
(VideoTrackList::activeDOMObjectName const): Added.
* html/track/VideoTrackList.h:
* html/track/VideoTrackList.idl:

LayoutTests:

Added a regression test which reliably hits the newly added debug assertion.

* media/track/video-track-addition-and-frame-removal-expected.txt: Added.
* media/track/video-track-addition-and-frame-removal.html: Added.

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

27 files changed:
LayoutTests/ChangeLog
LayoutTests/media/track/video-track-addition-and-frame-removal-expected.txt [new file with mode: 0644]
LayoutTests/media/track/video-track-addition-and-frame-removal.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/encryptedmedia/legacy/WebKitMediaKeySession.cpp
Source/WebCore/Modules/encryptedmedia/legacy/WebKitMediaKeySession.h
Source/WebCore/Modules/mediasource/MediaSource.cpp
Source/WebCore/Modules/mediasource/MediaSource.h
Source/WebCore/Modules/mediasource/SourceBuffer.cpp
Source/WebCore/Modules/mediasource/SourceBuffer.h
Source/WebCore/Modules/mediasource/SourceBufferList.cpp
Source/WebCore/Modules/mediasource/SourceBufferList.h
Source/WebCore/Modules/mediasource/SourceBufferList.idl
Source/WebCore/dom/Document.h
Source/WebCore/dom/GenericEventQueue.cpp
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/track/AudioTrackList.cpp
Source/WebCore/html/track/AudioTrackList.h
Source/WebCore/html/track/AudioTrackList.idl
Source/WebCore/html/track/TextTrackList.cpp
Source/WebCore/html/track/TextTrackList.h
Source/WebCore/html/track/TextTrackList.idl
Source/WebCore/html/track/TrackListBase.cpp
Source/WebCore/html/track/TrackListBase.h
Source/WebCore/html/track/VideoTrackList.cpp
Source/WebCore/html/track/VideoTrackList.h
Source/WebCore/html/track/VideoTrackList.idl

index d5a8772..75b3025 100644 (file)
@@ -1,3 +1,15 @@
+2018-07-03  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Nullptr crash accessing Document in GenericEventQueue::dispatchOneEvent()
+        https://bugs.webkit.org/show_bug.cgi?id=187284
+
+        Reviewed by Eric Carlson.
+
+        Added a regression test which reliably hits the newly added debug assertion.
+
+        * media/track/video-track-addition-and-frame-removal-expected.txt: Added.
+        * media/track/video-track-addition-and-frame-removal.html: Added.
+
 2018-07-03  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [cairo] Doesn't paint box-shadow with zero blur-radius
diff --git a/LayoutTests/media/track/video-track-addition-and-frame-removal-expected.txt b/LayoutTests/media/track/video-track-addition-and-frame-removal-expected.txt
new file mode 100644 (file)
index 0000000..f662545
--- /dev/null
@@ -0,0 +1,4 @@
+This tests removing a frame immediately after inserting a new track element.
+WebKit should not hit any assertions.
+
+
diff --git a/LayoutTests/media/track/video-track-addition-and-frame-removal.html b/LayoutTests/media/track/video-track-addition-and-frame-removal.html
new file mode 100644 (file)
index 0000000..ef35557
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests removing a frame immediately after inserting a new track element.<br>
+WebKit should not hit any assertions.</p>
+<script src=../../resources/gc.js></script>
+<script>
+
+if (window.testRunner)
+    testRunner.waitUntilDone();
+
+function startTest()
+{
+    const doc = frame.contentDocument;
+    const trackElement = doc.createElement('track');
+    doc.querySelector('video').appendChild(trackElement);
+
+    frame.remove();
+    gc();
+    setTimeout(() => {
+        if (window.testRunner)
+            testRunner.notifyDone();        
+    }, 100);
+}
+
+</script>
+<iframe id="frame" src="./video-track-add-remove.html"></iframe>
+</body>
+</html>
index 7a572d4..e729fb3 100644 (file)
@@ -1,3 +1,86 @@
+2018-07-03  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Nullptr crash accessing Document in GenericEventQueue::dispatchOneEvent()
+        https://bugs.webkit.org/show_bug.cgi?id=187284
+
+        Reviewed by Eric Carlson.
+
+        The null pointer crash was caused by some GenericEventQueue dispatching an event in a stopped document,
+        which does not have a valid script execution context because some uses of GenericEventQueue in media code
+        was not closing the queue upon stopping of all active DOM objects.
+
+        Fixed all uses of GenericEventQueue which did not suspend or stop the queue with active DOM objects.
+        Made SourceBufferList and TrackListBase (along with AudioTrackList, TextTrackList, and VideoTrackList)
+        inherit from ActiveDOMObject instead of ContextDestructionObserver to do this.
+
+        Also fixed a bug that media elements inside a template element (and other cases where the document doesn't
+        have a browsing context) were scheduling events since this would hit the newly added debug assertion in
+        GenericEventQueue::dispatchOneEvent.
+
+        Test: media/track/video-track-addition-and-frame-removal.html
+
+        * Modules/encryptedmedia/legacy/WebKitMediaKeySession.cpp:
+        (WebCore::WebKitMediaKeySession::suspend): Assert that we never try to suspend when the document has
+        this object alive since canSuspendForDocumentSuspension always returns false.
+        (WebCore::WebKitMediaKeySession::resume): Ditto.
+        (WebCore::WebKitMediaKeySession::stop): Stop the event queue to avoid the crash.
+        * Modules/encryptedmedia/legacy/WebKitMediaKeySession.h:
+        * Modules/mediasource/MediaSource.cpp:
+        (WebCore::MediaSource::removeSourceBuffer): Don't do any work to update tracks when the active DOM
+        objects are stopped since this MediaSource and the related media objects are about to be destructed.
+        (WebCore::MediaSource::suspend): Assert that m_asyncEventQueue is empty as canSuspendForDocumentSuspension
+        returns false whenever the queue is not empty.
+        (WebCore::MediaSource::resume): Ditto.
+        * Modules/mediasource/MediaSource.h:
+        * Modules/mediasource/SourceBuffer.cpp:
+        (WebCore::SourceBuffer::suspend): Ditto.
+        (WebCore::SourceBuffer::resume): Ditto.
+        (WebCore::SourceBuffer::stop): Stop the event queue to avoid the crash.
+        * Modules/mediasource/SourceBuffer.h:
+        * Modules/mediasource/SourceBufferList.cpp:
+        (WebCore::SourceBufferList): Made this an active DOM object.
+        (WebCore::SourceBufferList::SourceBufferList):
+        (WebCore::SourceBufferList::canSuspendForDocumentSuspension const): Added. Return false when there are
+        pending events to match other media code.
+        (WebCore::SourceBufferList::suspend): Added. Assert that the event queue is empty here.
+        (WebCore::SourceBufferList::resume): Ditto.
+        (WebCore::SourceBufferList::stop): Added. Stop the event queue to avoid the crash.
+        (WebCore::SourceBufferList::activeDOMObjectName const): Added.
+        * Modules/mediasource/SourceBufferList.h:
+        (WebCore::SourceBufferList): Made this an active DOM object.
+        * Modules/mediasource/SourceBufferList.idl:
+        * dom/Document.h:
+        (WebCore::Document::hasBrowsingContext const): Added.
+        * dom/GenericEventQueue.cpp:
+        (WebCore::GenericEventQueue::dispatchOneEvent): Added an assertion to catch when an event is dispatched
+        inside a stopped document, which is never correct and causes this crash down the line.
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::playInternal): Exit early when the document doesn't have a browsing context;
+        e.g. when the media element is inside a template element.
+        (WebCore::HTMLMediaElement::pauseInternal): Ditto.
+        (WebCore::HTMLMediaElement::sourceWasAdded): Ditto.
+        * html/track/AudioTrackList.cpp:
+        (AudioTrackList::activeDOMObjectName const): Added.
+        * html/track/AudioTrackList.h:
+        * html/track/AudioTrackList.idl:
+        * html/track/TextTrackList.cpp::
+        (TextTrackList::activeDOMObjectName const): Added.
+        * html/track/TextTrackList.h:
+        * html/track/TextTrackList.idl:
+        * html/track/TrackListBase.cpp:
+        (WebCore::TrackListBase): Made this an active DOM object.
+        (WebCore::TrackListBase::TrackListBase): 
+        (WebCore::TrackListBase::canSuspendForDocumentSuspension const): Added. Return false when there are pending events
+        to match other media code.
+        (WebCore::TrackListBase::suspend): Added. Assert that the event queue is empty here.
+        (WebCore::TrackListBase::resume): Ditto.
+        (WebCore::TrackListBase::stop): Added. Stop the event queue to avoid the crash.
+        * html/track/TrackListBase.h:
+        * html/track/VideoTrackList.cpp:
+        (VideoTrackList::activeDOMObjectName const): Added.
+        * html/track/VideoTrackList.h:
+        * html/track/VideoTrackList.idl:
+
 2018-07-03  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [cairo] Doesn't paint box-shadow with zero blur-radius
index 73f2d26..f0bf1e0 100644 (file)
@@ -239,8 +239,19 @@ bool WebKitMediaKeySession::hasPendingActivity() const
     return (m_keys && m_session) || m_asyncEventQueue.hasPendingEvents();
 }
 
+void WebKitMediaKeySession::suspend(ReasonForSuspension)
+{
+    ASSERT_NOT_REACHED();
+}
+
+void WebKitMediaKeySession::resume()
+{
+    ASSERT_NOT_REACHED();
+}
+
 void WebKitMediaKeySession::stop()
 {
+    m_asyncEventQueue.close();
     close();
 }
 
index 1a22d32..1d0dc7c 100644 (file)
@@ -76,6 +76,8 @@ private:
     void refEventTarget() final { ref(); }
     void derefEventTarget() final { deref(); }
 
+    void suspend(ReasonForSuspension) final;
+    void resume() final;
     void stop() final;
     bool canSuspendForDocumentSuspension() const final;
     const char* activeDOMObjectName() const final;
index c88e394..ea620a8 100644 (file)
@@ -692,124 +692,127 @@ ExceptionOr<void> MediaSource::removeSourceBuffer(SourceBuffer& buffer)
     // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ...
     buffer.abortIfUpdating();
 
-    // 4. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks.
-    auto& audioTracks = buffer.audioTracks();
-
-    // 5. If the SourceBuffer audioTracks list is not empty, then run the following steps:
-    if (audioTracks.length()) {
-        // 5.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks
-        // attribute on the HTMLMediaElement.
-        // 5.2 Let the removed enabled audio track flag equal false.
-        bool removedEnabledAudioTrack = false;
-
-        // 5.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps:
-        while (audioTracks.length()) {
-            auto& track = *audioTracks.lastItem();
-
-            // 5.3.1 Set the sourceBuffer attribute on the AudioTrack object to null.
-            track.setSourceBuffer(nullptr);
-
-            // 5.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled
-            // audio track flag to true.
-            if (track.enabled())
-                removedEnabledAudioTrack = true;
-
-            // 5.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list.
-            // 5.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
-            // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list.
-            if (mediaElement())
-                mediaElement()->removeAudioTrack(track);
-
-            // 5.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list.
-            // 5.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
-            // cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list.
-            audioTracks.remove(track);
+    ASSERT(scriptExecutionContext());
+    if (!scriptExecutionContext()->activeDOMObjectsAreStopped()) {
+        // 4. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks.
+        auto& audioTracks = buffer.audioTracks();
+
+        // 5. If the SourceBuffer audioTracks list is not empty, then run the following steps:
+        if (audioTracks.length()) {
+            // 5.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks
+            // attribute on the HTMLMediaElement.
+            // 5.2 Let the removed enabled audio track flag equal false.
+            bool removedEnabledAudioTrack = false;
+
+            // 5.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps:
+            while (audioTracks.length()) {
+                auto& track = *audioTracks.lastItem();
+
+                // 5.3.1 Set the sourceBuffer attribute on the AudioTrack object to null.
+                track.setSourceBuffer(nullptr);
+
+                // 5.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled
+                // audio track flag to true.
+                if (track.enabled())
+                    removedEnabledAudioTrack = true;
+
+                // 5.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list.
+                // 5.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
+                // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list.
+                if (mediaElement())
+                    mediaElement()->removeAudioTrack(track);
+
+                // 5.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list.
+                // 5.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
+                // cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list.
+                audioTracks.remove(track);
+            }
+
+            // 5.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event
+            // named change at the HTMLMediaElement audioTracks list.
+            if (removedEnabledAudioTrack)
+                mediaElement()->audioTracks().scheduleChangeEvent();
         }
 
-        // 5.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event
-        // named change at the HTMLMediaElement audioTracks list.
-        if (removedEnabledAudioTrack)
-            mediaElement()->audioTracks().scheduleChangeEvent();
-    }
-
-    // 6. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks.
-    auto& videoTracks = buffer.videoTracks();
-
-    // 7. If the SourceBuffer videoTracks list is not empty, then run the following steps:
-    if (videoTracks.length()) {
-        // 7.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks
-        // attribute on the HTMLMediaElement.
-        // 7.2 Let the removed selected video track flag equal false.
-        bool removedSelectedVideoTrack = false;
-
-        // 7.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps:
-        while (videoTracks.length()) {
-            auto& track = *videoTracks.lastItem();
-
-            // 7.3.1 Set the sourceBuffer attribute on the VideoTrack object to null.
-            track.setSourceBuffer(nullptr);
-
-            // 7.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected
-            // video track flag to true.
-            if (track.selected())
-                removedSelectedVideoTrack = true;
-
-            // 7.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list.
-            // 7.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
-            // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list.
-            if (mediaElement())
-                mediaElement()->removeVideoTrack(track);
-
-            // 7.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list.
-            // 7.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
-            // cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list.
-            videoTracks.remove(track);
+        // 6. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks.
+        auto& videoTracks = buffer.videoTracks();
+
+        // 7. If the SourceBuffer videoTracks list is not empty, then run the following steps:
+        if (videoTracks.length()) {
+            // 7.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks
+            // attribute on the HTMLMediaElement.
+            // 7.2 Let the removed selected video track flag equal false.
+            bool removedSelectedVideoTrack = false;
+
+            // 7.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps:
+            while (videoTracks.length()) {
+                auto& track = *videoTracks.lastItem();
+
+                // 7.3.1 Set the sourceBuffer attribute on the VideoTrack object to null.
+                track.setSourceBuffer(nullptr);
+
+                // 7.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected
+                // video track flag to true.
+                if (track.selected())
+                    removedSelectedVideoTrack = true;
+
+                // 7.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list.
+                // 7.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
+                // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list.
+                if (mediaElement())
+                    mediaElement()->removeVideoTrack(track);
+
+                // 7.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list.
+                // 7.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
+                // cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list.
+                videoTracks.remove(track);
+            }
+
+            // 7.4 If the removed selected video track flag equals true, then queue a task to fire a simple event
+            // named change at the HTMLMediaElement videoTracks list.
+            if (removedSelectedVideoTrack)
+                mediaElement()->videoTracks().scheduleChangeEvent();
         }
 
-        // 7.4 If the removed selected video track flag equals true, then queue a task to fire a simple event
-        // named change at the HTMLMediaElement videoTracks list.
-        if (removedSelectedVideoTrack)
-            mediaElement()->videoTracks().scheduleChangeEvent();
-    }
-
-    // 8. Let SourceBuffer textTracks list equal the TextTrackList object returned by sourceBuffer.textTracks.
-    auto& textTracks = buffer.textTracks();
-
-    // 9. If the SourceBuffer textTracks list is not empty, then run the following steps:
-    if (textTracks.length()) {
-        // 9.1 Let HTMLMediaElement textTracks list equal the TextTrackList object returned by the textTracks
-        // attribute on the HTMLMediaElement.
-        // 9.2 Let the removed enabled text track flag equal false.
-        bool removedEnabledTextTrack = false;
-
-        // 9.3 For each TextTrack object in the SourceBuffer textTracks list, run the following steps:
-        while (textTracks.length()) {
-            auto& track = *textTracks.lastItem();
-
-            // 9.3.1 Set the sourceBuffer attribute on the TextTrack object to null.
-            track.setSourceBuffer(nullptr);
-
-            // 9.3.2 If the mode attribute on the TextTrack object is set to "showing" or "hidden", then
-            // set the removed enabled text track flag to true.
-            if (track.mode() == TextTrack::Mode::Showing || track.mode() == TextTrack::Mode::Hidden)
-                removedEnabledTextTrack = true;
-
-            // 9.3.3 Remove the TextTrack object from the HTMLMediaElement textTracks list.
-            // 9.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
-            // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement textTracks list.
-            if (mediaElement())
-                mediaElement()->removeTextTrack(track);
-
-            // 9.3.5 Remove the TextTrack object from the SourceBuffer textTracks list.
-            // 9.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
-            // cancelable, and that uses the TrackEvent interface, at the SourceBuffer textTracks list.
-            textTracks.remove(track);
+        // 8. Let SourceBuffer textTracks list equal the TextTrackList object returned by sourceBuffer.textTracks.
+        auto& textTracks = buffer.textTracks();
+
+        // 9. If the SourceBuffer textTracks list is not empty, then run the following steps:
+        if (textTracks.length()) {
+            // 9.1 Let HTMLMediaElement textTracks list equal the TextTrackList object returned by the textTracks
+            // attribute on the HTMLMediaElement.
+            // 9.2 Let the removed enabled text track flag equal false.
+            bool removedEnabledTextTrack = false;
+
+            // 9.3 For each TextTrack object in the SourceBuffer textTracks list, run the following steps:
+            while (textTracks.length()) {
+                auto& track = *textTracks.lastItem();
+
+                // 9.3.1 Set the sourceBuffer attribute on the TextTrack object to null.
+                track.setSourceBuffer(nullptr);
+
+                // 9.3.2 If the mode attribute on the TextTrack object is set to "showing" or "hidden", then
+                // set the removed enabled text track flag to true.
+                if (track.mode() == TextTrack::Mode::Showing || track.mode() == TextTrack::Mode::Hidden)
+                    removedEnabledTextTrack = true;
+
+                // 9.3.3 Remove the TextTrack object from the HTMLMediaElement textTracks list.
+                // 9.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
+                // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement textTracks list.
+                if (mediaElement())
+                    mediaElement()->removeTextTrack(track);
+
+                // 9.3.5 Remove the TextTrack object from the SourceBuffer textTracks list.
+                // 9.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
+                // cancelable, and that uses the TrackEvent interface, at the SourceBuffer textTracks list.
+                textTracks.remove(track);
+            }
+
+            // 9.4 If the removed enabled text track flag equals true, then queue a task to fire a simple event
+            // named change at the HTMLMediaElement textTracks list.
+            if (removedEnabledTextTrack)
+                mediaElement()->textTracks().scheduleChangeEvent();
         }
-
-        // 9.4 If the removed enabled text track flag equals true, then queue a task to fire a simple event
-        // named change at the HTMLMediaElement textTracks list.
-        if (removedEnabledTextTrack)
-            mediaElement()->textTracks().scheduleChangeEvent();
     }
 
     // 10. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ...
@@ -932,6 +935,16 @@ bool MediaSource::hasPendingActivity() const
         || ActiveDOMObject::hasPendingActivity();
 }
 
+void MediaSource::suspend(ReasonForSuspension)
+{
+    ASSERT(!m_asyncEventQueue.hasPendingEvents());
+}
+
+void MediaSource::resume()
+{
+    ASSERT(!m_asyncEventQueue.hasPendingEvents());
+}
+
 void MediaSource::stop()
 {
     m_asyncEventQueue.close();
index 2934657..bd5cd7c 100644 (file)
@@ -107,6 +107,8 @@ public:
 private:
     explicit MediaSource(ScriptExecutionContext&);
 
+    void suspend(ReasonForSuspension) final;
+    void resume() final;
     void stop() final;
     bool canSuspendForDocumentSuspension() const final;
     const char* activeDOMObjectName() const final;
index 868ac88..9fdbb13 100644 (file)
@@ -458,8 +458,19 @@ bool SourceBuffer::hasPendingActivity() const
     return m_source || m_asyncEventQueue.hasPendingEvents();
 }
 
+void SourceBuffer::suspend(ReasonForSuspension)
+{
+    ASSERT(!hasPendingActivity());
+}
+
+void SourceBuffer::resume()
+{
+    ASSERT(!hasPendingActivity());
+}
+
 void SourceBuffer::stop()
 {
+    m_asyncEventQueue.close();
     m_appendBufferTimer.stop();
     m_removeTimer.stop();
 }
index c1a3dc5..9a30799 100644 (file)
@@ -123,6 +123,8 @@ private:
     void refEventTarget() final { ref(); }
     void derefEventTarget() final { deref(); }
 
+    void suspend(ReasonForSuspension) final;
+    void resume() final;
     void stop() final;
     const char* activeDOMObjectName() const final;
     bool canSuspendForDocumentSuspension() const final;
index 4e8f520..611ded8 100644 (file)
 namespace WebCore {
 
 SourceBufferList::SourceBufferList(ScriptExecutionContext* context)
-    : ContextDestructionObserver(context)
+    : ActiveDOMObject(context)
     , m_asyncEventQueue(*this)
 {
+    suspendIfNeeded();
 }
 
 SourceBufferList::~SourceBufferList()
@@ -97,6 +98,30 @@ void SourceBufferList::scheduleEvent(const AtomicString& eventName)
     m_asyncEventQueue.enqueueEvent(WTFMove(event));
 }
 
+bool SourceBufferList::canSuspendForDocumentSuspension() const
+{
+    return !m_asyncEventQueue.hasPendingEvents();
+}
+
+void SourceBufferList::suspend(ReasonForSuspension)
+{
+    ASSERT(!m_asyncEventQueue.hasPendingEvents());
+}
+
+void SourceBufferList::resume()
+{
+    ASSERT(!m_asyncEventQueue.hasPendingEvents());
+}
+
+void SourceBufferList::stop()
+{
+    m_asyncEventQueue.close();
+}
+
+const char* SourceBufferList::activeDOMObjectName() const
+{
+    return "SourceBufferList";
+}
 
 } // namespace WebCore
 
index 4186b4f..073198d 100644 (file)
@@ -32,7 +32,7 @@
 
 #if ENABLE(MEDIA_SOURCE)
 
-#include "ContextDestructionObserver.h"
+#include "ActiveDOMObject.h"
 #include "EventTarget.h"
 #include "GenericEventQueue.h"
 #include <wtf/RefCounted.h>
@@ -42,7 +42,7 @@ namespace WebCore {
 
 class SourceBuffer;
 
-class SourceBufferList final : public RefCounted<SourceBufferList>, public EventTargetWithInlineData, public ContextDestructionObserver {
+class SourceBufferList final : public RefCounted<SourceBufferList>, public EventTargetWithInlineData, public ActiveDOMObject {
 public:
     static Ref<SourceBufferList> create(ScriptExecutionContext* context)
     {
@@ -77,6 +77,12 @@ private:
     void refEventTarget() override { ref(); }
     void derefEventTarget() override { deref(); }
 
+    bool canSuspendForDocumentSuspension() const final;
+    void suspend(ReasonForSuspension) final;
+    void resume() final;
+    void stop() final;
+    const char* activeDOMObjectName() const final;
+
     GenericEventQueue m_asyncEventQueue;
 
     Vector<RefPtr<SourceBuffer>> m_list;
index 6c2d564..99c185a 100644 (file)
@@ -29,6 +29,7 @@
  */
  
 [
+    ActiveDOMObject,
     Conditional=MEDIA_SOURCE,
     GenerateIsReachable=Impl,
 ] interface SourceBufferList : EventTarget {
index e944535..87b0160 100644 (file)
@@ -803,6 +803,8 @@ public:
     // In DOM Level 2, the Document's DOMWindow is called the defaultView.
     WEBCORE_EXPORT WindowProxy* windowProxy() const;
 
+    bool hasBrowsingContext() const { return !!frame(); }
+
     Document& contextDocument() const;
     void setContextDocument(Document& document) { m_contextDocument = makeWeakPtr(document); }
 
index 5a32483..8cfa8f0 100644 (file)
 #include "config.h"
 #include "GenericEventQueue.h"
 
+#include "Document.h"
 #include "Event.h"
 #include "EventTarget.h"
+#include "Node.h"
 #include "ScriptExecutionContext.h"
 #include "Timer.h"
 #include <wtf/MainThread.h>
@@ -65,6 +67,9 @@ void GenericEventQueue::dispatchOneEvent()
     Ref<EventTarget> protect(m_owner);
     RefPtr<Event> event = m_pendingEvents.takeFirst();
     EventTarget& target = event->target() ? *event->target() : m_owner;
+    ASSERT_WITH_MESSAGE(!target.scriptExecutionContext()->activeDOMObjectsAreStopped(),
+        "An attempt to dispatch an event on a stopped target by EventTargetInterface=%d (nodeName=%s target=%p owner=%p)",
+        m_owner.eventTargetInterface(), m_owner.isNode() ? static_cast<Node&>(m_owner).nodeName().ascii().data() : "", &target, &m_owner);
     target.dispatchEvent(*event);
 }
 
index d5a881a..96f0321 100644 (file)
@@ -1299,6 +1299,9 @@ void HTMLMediaElement::prepareForLoad()
     m_loadState = WaitingForSource;
     m_currentSourceNode = nullptr;
 
+    if (!document().hasBrowsingContext())
+        return;
+
     createMediaPlayer();
 
     // 2 - Let pending tasks be a list of all tasks from the media element's media element event task source in one of the task queues.
@@ -1416,7 +1419,6 @@ void HTMLMediaElement::selectMediaResource()
     // put into the background.
     m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
 
-
     m_resourceSelectionTaskQueue.enqueueTask([this]  {
         // 5. If the media element’s blocked-on-parser flag is false, then populate the list of pending text tracks.
 #if ENABLE(VIDEO_TRACK)
@@ -2893,7 +2895,7 @@ bool HTMLMediaElement::supportsScanning() const
 void HTMLMediaElement::prepareToPlay()
 {
     INFO_LOG(LOGIDENTIFIER);
-    if (m_havePreparedToPlay)
+    if (m_havePreparedToPlay || !document().hasBrowsingContext())
         return;
     m_havePreparedToPlay = true;
     if (m_player)
@@ -3488,6 +3490,11 @@ void HTMLMediaElement::playInternal()
         return;
     }
 
+    if (!document().hasBrowsingContext()) {
+        INFO_LOG(LOGIDENTIFIER, "  returning because there is no browsing context");
+        return;
+    }
+
     if (!m_mediaSession->clientWillBeginPlayback()) {
         ALWAYS_LOG(LOGIDENTIFIER, "  returning because of interruption");
         return;
@@ -3579,6 +3586,11 @@ void HTMLMediaElement::pauseInternal()
         return;
     }
 
+    if (!document().hasBrowsingContext()) {
+        INFO_LOG(LOGIDENTIFIER, "  returning because there is no browsing context");
+        return;
+    }
+
     if (!m_mediaSession->clientWillPausePlayback()) {
         ALWAYS_LOG(LOGIDENTIFIER, "  returning because of interruption");
         return;
@@ -4641,6 +4653,11 @@ void HTMLMediaElement::sourceWasAdded(HTMLSourceElement& source)
         INFO_LOG(LOGIDENTIFIER, "'src' is ", url);
     }
 
+    if (!document().hasBrowsingContext()) {
+        INFO_LOG(LOGIDENTIFIER, "<source> inserted inside a document without a browsing context is not loaded");
+        return;
+    }
+
     // We should only consider a <source> element when there is not src attribute at all.
     if (hasAttributeWithoutSynchronization(srcAttr))
         return;
index 3b07f05..f8b5d0a 100644 (file)
@@ -81,4 +81,9 @@ EventTargetInterface AudioTrackList::eventTargetInterface() const
     return AudioTrackListEventTargetInterfaceType;
 }
 
+const char* AudioTrackList::activeDOMObjectName() const
+{
+    return "AudioTrackList";
+}
+
 #endif
index 0c637dd..c490020 100644 (file)
@@ -52,6 +52,7 @@ public:
 
 private:
     AudioTrackList(HTMLMediaElement*, ScriptExecutionContext*);
+    const char* activeDOMObjectName() const final;
 };
 
 } // namespace WebCore
index cde73f4..7781c09 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 [
+    ActiveDOMObject,
     Conditional=VIDEO_TRACK,
     GenerateIsReachable=ImplElementRoot,
     JSCustomMarkFunction,
index aa964f2..9b691f5 100644 (file)
@@ -265,4 +265,9 @@ EventTargetInterface TextTrackList::eventTargetInterface() const
     return TextTrackListEventTargetInterfaceType;
 }
 
+const char* TextTrackList::activeDOMObjectName() const
+{
+    return "TextTrackList";
+}
+
 #endif
index c42f1e1..5204291 100644 (file)
@@ -62,6 +62,7 @@ private:
     TextTrackList(HTMLMediaElement*, ScriptExecutionContext*);
 
     void invalidateTrackIndexesAfterTrack(TextTrack&);
+    const char* activeDOMObjectName() const final;
 
     Vector<RefPtr<TrackBase>> m_addTrackTracks;
     Vector<RefPtr<TrackBase>> m_elementTracks;
index fedd73b..35820a5 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 [
+    ActiveDOMObject,
     Conditional=VIDEO_TRACK,
     GenerateIsReachable=ImplElementRoot,
     JSCustomMarkFunction,
index 2e96942..771b978 100644 (file)
 #include "ScriptExecutionContext.h"
 #include "TrackEvent.h"
 
-using namespace WebCore;
+namespace WebCore {
 
 TrackListBase::TrackListBase(HTMLMediaElement* element, ScriptExecutionContext* context)
-    : ContextDestructionObserver(context)
+    : ActiveDOMObject(context)
     , m_element(element)
     , m_asyncEventQueue(*this)
 {
-    ASSERT(is<Document>(context));
+    ASSERT(!context || is<Document>(context));
+    suspendIfNeeded();
 }
 
 TrackListBase::~TrackListBase()
@@ -174,4 +175,26 @@ bool TrackListBase::isAnyTrackEnabled() const
     return false;
 }
 
+bool TrackListBase::canSuspendForDocumentSuspension() const
+{
+    return !m_asyncEventQueue.hasPendingEvents();
+}
+
+void TrackListBase::suspend(ReasonForSuspension)
+{
+    ASSERT(!m_asyncEventQueue.hasPendingEvents());
+}
+
+void TrackListBase::resume()
+{
+    ASSERT(!m_asyncEventQueue.hasPendingEvents());
+}
+
+void TrackListBase::stop()
+{
+    m_asyncEventQueue.close();
+}
+
+} // namespace WebCore
+
 #endif
index f3d644d..fdae3b0 100644 (file)
@@ -27,7 +27,7 @@
 
 #if ENABLE(VIDEO_TRACK)
 
-#include "ContextDestructionObserver.h"
+#include "ActiveDOMObject.h"
 #include "EventTarget.h"
 #include "GenericEventQueue.h"
 #include <wtf/RefCounted.h>
@@ -39,7 +39,7 @@ class HTMLMediaElement;
 class Element;
 class TrackBase;
 
-class TrackListBase : public RefCounted<TrackListBase>, public EventTargetWithInlineData, public ContextDestructionObserver {
+class TrackListBase : public RefCounted<TrackListBase>, public EventTargetWithInlineData, public ActiveDOMObject {
 public:
     virtual ~TrackListBase();
 
@@ -74,6 +74,11 @@ protected:
 private:
     void scheduleTrackEvent(const AtomicString& eventName, Ref<TrackBase>&&);
 
+    bool canSuspendForDocumentSuspension() const final;
+    void suspend(ReasonForSuspension) final;
+    void resume() final;
+    void stop() final;
+
     // EventTarget
     void refEventTarget() final { ref(); }
     void derefEventTarget() final { deref(); }
index 3c5b5f1..a1549ab 100644 (file)
@@ -94,4 +94,9 @@ EventTargetInterface VideoTrackList::eventTargetInterface() const
     return VideoTrackListEventTargetInterfaceType;
 }
 
+const char* VideoTrackList::activeDOMObjectName() const
+{
+    return "VideoTrackList";
+}
+
 #endif
index 6b5be25..cc250fc 100644 (file)
@@ -53,6 +53,7 @@ public:
 
 private:
     VideoTrackList(HTMLMediaElement*, ScriptExecutionContext*);
+    const char* activeDOMObjectName() const final;
 };
 
 } // namespace WebCore
index 9c43fcd..18f40ed 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 [
+    ActiveDOMObject,
     Conditional=VIDEO_TRACK,
     GenerateIsReachable=ImplElementRoot,
     JSCustomMarkFunction,