Title: [233560] trunk
Revision
233560
Author
[email protected]
Date
2018-07-05 20:20:20 -0700 (Thu, 05 Jul 2018)

Log Message

Youtube video pages crash after a couple of minutes
https://bugs.webkit.org/show_bug.cgi?id=187316

Reviewed by Antti Koivisto.

Source/WebCore:

The crash was caused by HTMLMediaElement::stopWithoutDestroyingMediaPlayer invoking updatePlaybackControlsManager,
which traverses all media players across different documents including the one in the main frame while its iframe
is getting removed (to update the Touch Bar's media control).

Fixed the bug by making this code async in both stopWithoutDestroyingMediaPlayer and ~HTMLMediaElement. To do this,
this patch moves the timer to update the playback controls manager from HTMLMediaElement to Page since scheduling
a timer owned by HTMLMediaElement in its destructor wouldn't work as the timer would get destructed immediately.

Also replaced the call to clientWillPausePlayback by a call to stopSession in stopWithoutDestroyingMediaPlayer
since the former also updates the layout synchronously via updateNowPlayingInfo; the latter function schedules
a timer via scheduleUpdateNowPlayingInfo instead.

Test: media/remove-video-best-media-element-in-main-frame-crash.html

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::~HTMLMediaElement): Call scheduleUpdatePlaybackControlsManager now that timer has been
moved to Page.
(WebCore::HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager): Made this return a RefPtr instead of
a raw pointer while we're at it.
(WebCore::HTMLMediaElement::clearMediaPlayer): Call scheduleUpdatePlaybackControlsManager.
(WebCore::HTMLMediaElement::stopWithoutDestroyingMediaPlayer): Ditto. Also invoke stopSession instead of
clientWillPausePlayback on MediaSession since clientWillPausePlayback will synchronously try to update the layout.
(WebCore::HTMLMediaElement::contextDestroyed):
(WebCore::HTMLMediaElement::stop):
(WebCore::HTMLMediaElement::schedulePlaybackControlsManagerUpdate): Renamed from scheduleUpdatePlaybackControlsManager.
(WebCore::HTMLMediaElement::updatePlaybackControlsManager): Moved to Page::playbackControlsManagerUpdateTimerFired.
* html/HTMLMediaElement.h:
* page/Page.cpp:
(WebCore::Page::Page):
(WebCore::Page::schedulePlaybackControlsManagerUpdate): Added.
(WebCore::Page::playbackControlsManagerUpdateTimerFired): Moved from HTMLMediaElement::updatePlaybackControlsManager.
* page/Page.h:
* testing/Internals.cpp:
(WebCore::Internals::bestMediaElementForShowingPlaybackControlsManager):
* testing/Internals.h:

LayoutTests:

Added a regression test to remove an iframe with a video while there is a main content
which is eligible to be shown in the Touch Bar.

* media/remove-video-best-media-element-in-main-frame-crash-expected.txt: Added.
* media/remove-video-best-media-element-in-main-frame-crash.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (233559 => 233560)


--- trunk/LayoutTests/ChangeLog	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/LayoutTests/ChangeLog	2018-07-06 03:20:20 UTC (rev 233560)
@@ -1,3 +1,16 @@
+2018-07-05  Ryosuke Niwa  <[email protected]>
+
+        Youtube video pages crash after a couple of minutes
+        https://bugs.webkit.org/show_bug.cgi?id=187316
+
+        Reviewed by Antti Koivisto.
+
+        Added a regression test to remove an iframe with a video while there is a main content
+        which is eligible to be shown in the Touch Bar.
+
+        * media/remove-video-best-media-element-in-main-frame-crash-expected.txt: Added.
+        * media/remove-video-best-media-element-in-main-frame-crash.html: Added.
+
 2018-07-05  Fujii Hironori  <[email protected]>
 
         REGRESSION(r233495) [cairo] drawGlyphsShadow should use the fast path for zero blur-radius

Added: trunk/LayoutTests/media/remove-video-best-media-element-in-main-frame-crash-expected.txt (0 => 233560)


--- trunk/LayoutTests/media/remove-video-best-media-element-in-main-frame-crash-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/remove-video-best-media-element-in-main-frame-crash-expected.txt	2018-07-06 03:20:20 UTC (rev 233560)
@@ -0,0 +1,4 @@
+This tests removing a video element inside an iframe while another video in the main frame is playing does not hit a release assertion.
+
+PASS
+

Added: trunk/LayoutTests/media/remove-video-best-media-element-in-main-frame-crash.html (0 => 233560)


--- trunk/LayoutTests/media/remove-video-best-media-element-in-main-frame-crash.html	                        (rev 0)
+++ trunk/LayoutTests/media/remove-video-best-media-element-in-main-frame-crash.html	2018-07-06 03:20:20 UTC (rev 233560)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p id="description">This tests removing a video element inside an iframe while another video in the main frame is playing does not hit a release assertion.</p>
+<div id="result"></div>
+<video id="video" autoplay width="500"></video>
+<script src=""
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+let count = 0;
+function continueTest() {
+    count++;
+    if (count < 2)
+        return;
+    setTimeout(() => {
+        iframe.remove();
+        result.textContent = 'PASS';
+        if (window.testRunner)
+            testRunner.notifyDone();
+    }, 0);
+}
+
+if (window.internals) {
+    testRunner.overridePreference("WebKitMainContentUserGestureOverrideEnabled", 1);
+    internals.setMediaElementRestrictions(video, "OverrideUserGestureRequirementForMainContent");
+} else
+    description.innerHTML += '<br>This test requries OverrideUserGestureRequirementForMainContent. Manually set this setting or use WebKitTestRunner.';
+
+const videoURL = findMediaFile("video", "content/test");
+const iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+video.src = ""
+video.addEventListener('loadeddata', continueTest);
+
+const doc = iframe.contentDocument;
+doc.open();
+doc.write(`<!DOCTYPE html><html><body>`);
+doc.write(`<video autoplay src=""
+doc.write(`<script>document.querySelector('video').addEventListener('loadeddata', () => top.continueTest());</` + `script>`);
+doc.close();
+
+</script>
+</body>
+</html>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (233559 => 233560)


--- trunk/Source/WebCore/ChangeLog	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/ChangeLog	2018-07-06 03:20:20 UTC (rev 233560)
@@ -1,5 +1,48 @@
 2018-07-05  Ryosuke Niwa  <[email protected]>
 
+        Youtube video pages crash after a couple of minutes
+        https://bugs.webkit.org/show_bug.cgi?id=187316
+
+        Reviewed by Antti Koivisto.
+
+        The crash was caused by HTMLMediaElement::stopWithoutDestroyingMediaPlayer invoking updatePlaybackControlsManager,
+        which traverses all media players across different documents including the one in the main frame while its iframe
+        is getting removed (to update the Touch Bar's media control).
+
+        Fixed the bug by making this code async in both stopWithoutDestroyingMediaPlayer and ~HTMLMediaElement. To do this,
+        this patch moves the timer to update the playback controls manager from HTMLMediaElement to Page since scheduling
+        a timer owned by HTMLMediaElement in its destructor wouldn't work as the timer would get destructed immediately.
+
+        Also replaced the call to clientWillPausePlayback by a call to stopSession in stopWithoutDestroyingMediaPlayer
+        since the former also updates the layout synchronously via updateNowPlayingInfo; the latter function schedules
+        a timer via scheduleUpdateNowPlayingInfo instead.
+
+        Test: media/remove-video-best-media-element-in-main-frame-crash.html
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::~HTMLMediaElement): Call scheduleUpdatePlaybackControlsManager now that timer has been
+        moved to Page.
+        (WebCore::HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager): Made this return a RefPtr instead of
+        a raw pointer while we're at it.
+        (WebCore::HTMLMediaElement::clearMediaPlayer): Call scheduleUpdatePlaybackControlsManager.
+        (WebCore::HTMLMediaElement::stopWithoutDestroyingMediaPlayer): Ditto. Also invoke stopSession instead of
+        clientWillPausePlayback on MediaSession since clientWillPausePlayback will synchronously try to update the layout.
+        (WebCore::HTMLMediaElement::contextDestroyed):
+        (WebCore::HTMLMediaElement::stop):
+        (WebCore::HTMLMediaElement::schedulePlaybackControlsManagerUpdate): Renamed from scheduleUpdatePlaybackControlsManager.
+        (WebCore::HTMLMediaElement::updatePlaybackControlsManager): Moved to Page::playbackControlsManagerUpdateTimerFired.
+        * html/HTMLMediaElement.h:
+        * page/Page.cpp:
+        (WebCore::Page::Page):
+        (WebCore::Page::schedulePlaybackControlsManagerUpdate): Added.
+        (WebCore::Page::playbackControlsManagerUpdateTimerFired): Moved from HTMLMediaElement::updatePlaybackControlsManager.
+        * page/Page.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::bestMediaElementForShowingPlaybackControlsManager):
+        * testing/Internals.h:
+
+2018-07-05  Ryosuke Niwa  <[email protected]>
+
         REGRESSION(r233496): Crash in WebCore::VideoTrack::clearClient()
         https://bugs.webkit.org/show_bug.cgi?id=187377
 

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (233559 => 233560)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2018-07-06 03:20:20 UTC (rev 233560)
@@ -680,7 +680,6 @@
     m_seekTaskQueue.close();
     m_promiseTaskQueue.close();
     m_pauseAfterDetachedTaskQueue.close();
-    m_updatePlaybackControlsManagerQueue.close();
     m_playbackControlsManagerBehaviorRestrictionsQueue.close();
     m_resourceSelectionTaskQueue.close();
     m_visibilityChangeTaskQueue.close();
@@ -696,7 +695,7 @@
     }
 
     m_mediaSession = nullptr;
-    updatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
 }
 
 static bool needsAutoplayPlayPauseEventsQuirk(const Document& document)
@@ -709,7 +708,7 @@
     return loader && loader->allowedAutoplayQuirks().contains(AutoplayQuirk::SynthesizedPauseEvents);
 }
 
-HTMLMediaElement* HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose purpose)
+RefPtr<HTMLMediaElement> HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose purpose)
 {
     auto allSessions = PlatformMediaSessionManager::sharedManager().currentSessionsMatching([] (const PlatformMediaSession& session) {
         return is<MediaElementSession>(session);
@@ -1140,7 +1139,7 @@
     dispatchEvent(Event::create(eventNames().playingEvent, false, true));
     resolvePendingPlayPromises(WTFMove(pendingPlayPromises));
 
-    scheduleUpdatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
 }
 
 bool HTMLMediaElement::hasEverNotifiedAboutPlaying() const
@@ -3744,7 +3743,7 @@
         m_mediaSession->canProduceAudioChanged();
     }
 
-    scheduleUpdatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
 }
 
 #if USE(AUDIO_SESSION) && PLATFORM(MAC)
@@ -4403,7 +4402,7 @@
 
     if (!m_receivedLayoutSizeChanged) {
         m_receivedLayoutSizeChanged = true;
-        scheduleUpdatePlaybackControlsManager();
+        schedulePlaybackControlsManagerUpdate();
     }
 
     // If the video is a candidate for main content, we should register it for viewport visibility callbacks
@@ -5329,7 +5328,7 @@
     }
 
     if (shouldBePlaying) {
-        scheduleUpdatePlaybackControlsManager();
+        schedulePlaybackControlsManagerUpdate();
 
         setDisplayMode(Video);
         invalidateCachedTime();
@@ -5358,7 +5357,7 @@
         startPlaybackProgressTimer();
         setPlaying(true);
     } else {
-        scheduleUpdatePlaybackControlsManager();
+        schedulePlaybackControlsManagerUpdate();
 
         if (!playerPaused)
             m_player->pause();
@@ -5522,7 +5521,7 @@
         m_player->invalidate();
         m_player = nullptr;
     }
-    updatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
 
     stopPeriodicTimers();
     m_pendingActionTimer.stop();
@@ -5562,13 +5561,13 @@
 
     setPreparedToReturnVideoLayerToInline(true);
 
-    updatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
     setInActiveDocument(false);
 
     // Stop the playback without generating events
     setPlaying(false);
     setPausedInternal(true);
-    m_mediaSession->clientWillPausePlayback();
+    m_mediaSession->stopSession();
 
     setPlaybackWithoutUserGesture(PlaybackWithoutUserGesture::None);
 
@@ -5587,7 +5586,6 @@
     m_shadowDOMTaskQueue.close();
     m_promiseTaskQueue.close();
     m_pauseAfterDetachedTaskQueue.close();
-    m_updatePlaybackControlsManagerQueue.close();
 #if ENABLE(ENCRYPTED_MEDIA)
     m_encryptedMediaQueue.close();
 #endif
@@ -5606,7 +5604,6 @@
 
     m_asyncEventQueue.close();
     m_promiseTaskQueue.close();
-    m_updatePlaybackControlsManagerQueue.close();
     m_resourceSelectionTaskQueue.close();
 
     // Once an active DOM object has been stopped it can not be restarted, so we can deallocate
@@ -6553,7 +6550,7 @@
 #endif
     m_player = MediaPlayer::create(*this);
     m_player->setShouldBufferData(m_shouldBufferData);
-    scheduleUpdatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
 
 #if ENABLE(WEB_AUDIO)
     if (m_audioSourceNode) {
@@ -7865,7 +7862,7 @@
     m_visibilityChangeTaskQueue.enqueueTask([this] {
         m_mediaSession->isVisibleInViewportChanged();
         updateShouldAutoplay();
-        scheduleUpdatePlaybackControlsManager();
+        schedulePlaybackControlsManagerUpdate();
     });
 }
 
@@ -7908,25 +7905,14 @@
     return renderer && renderer->visibleInViewportState() == VisibleInViewportState::Yes;
 }
 
-void HTMLMediaElement::updatePlaybackControlsManager()
+void HTMLMediaElement::schedulePlaybackControlsManagerUpdate()
 {
     Page* page = document().page();
     if (!page)
         return;
-
-    // FIXME: Ensure that the renderer here should be up to date.
-    if (auto bestMediaElement = bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose::ControlsManager))
-        page->chrome().client().setUpPlaybackControlsManager(*bestMediaElement);
-    else
-        page->chrome().client().clearPlaybackControlsManager();
+    page->schedulePlaybackControlsManagerUpdate();
 }
 
-void HTMLMediaElement::scheduleUpdatePlaybackControlsManager()
-{
-    if (!m_updatePlaybackControlsManagerQueue.hasPendingTasks())
-        m_updatePlaybackControlsManagerQueue.enqueueTask(std::bind(&HTMLMediaElement::updatePlaybackControlsManager, this));
-}
-
 void HTMLMediaElement::playbackControlsManagerBehaviorRestrictionsTimerFired()
 {
     if (m_playbackControlsManagerBehaviorRestrictionsQueue.hasPendingTasks())
@@ -7942,7 +7928,7 @@
             return;
 
         mediaElementSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
-        protectedThis->scheduleUpdatePlaybackControlsManager();
+        protectedThis->schedulePlaybackControlsManagerUpdate();
     });
 }
 
@@ -7963,7 +7949,7 @@
 
     m_videoFullscreenMode = mode;
     visibilityStateChanged();
-    scheduleUpdatePlaybackControlsManager();
+    schedulePlaybackControlsManagerUpdate();
 }
 
 #if !RELEASE_LOG_DISABLED

Modified: trunk/Source/WebCore/html/HTMLMediaElement.h (233559 => 233560)


--- trunk/Source/WebCore/html/HTMLMediaElement.h	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/html/HTMLMediaElement.h	2018-07-06 03:20:20 UTC (rev 233560)
@@ -155,7 +155,7 @@
 
     static HashSet<HTMLMediaElement*>& allMediaElements();
 
-    WEBCORE_EXPORT static HTMLMediaElement* bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose);
+    WEBCORE_EXPORT static RefPtr<HTMLMediaElement> bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose);
 
     static bool isRunningDestructor();
 
@@ -898,7 +898,7 @@
 
     void pauseAfterDetachedTask();
     void updatePlaybackControlsManager();
-    void scheduleUpdatePlaybackControlsManager();
+    void schedulePlaybackControlsManagerUpdate();
     void playbackControlsManagerBehaviorRestrictionsTimerFired();
 
     void updateRenderer();
@@ -933,7 +933,6 @@
     GenericTaskQueue<Timer> m_shadowDOMTaskQueue;
     GenericTaskQueue<Timer> m_promiseTaskQueue;
     GenericTaskQueue<Timer> m_pauseAfterDetachedTaskQueue;
-    GenericTaskQueue<Timer> m_updatePlaybackControlsManagerQueue;
     GenericTaskQueue<Timer> m_playbackControlsManagerBehaviorRestrictionsQueue;
     GenericTaskQueue<Timer> m_resourceSelectionTaskQueue;
     GenericTaskQueue<Timer> m_visibilityChangeTaskQueue;

Modified: trunk/Source/WebCore/page/Page.cpp (233559 => 233560)


--- trunk/Source/WebCore/page/Page.cpp	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/page/Page.cpp	2018-07-06 03:20:20 UTC (rev 233560)
@@ -239,6 +239,7 @@
     , m_userContentProvider(*WTFMove(pageConfiguration.userContentProvider))
     , m_visitedLinkStore(*WTFMove(pageConfiguration.visitedLinkStore))
     , m_sessionID(PAL::SessionID::defaultSessionID())
+    , m_playbackControlsManagerUpdateTimer(*this, &Page::playbackControlsManagerUpdateTimerFired)
     , m_isUtilityPage(isUtilityPageChromeClient(chrome().client()))
     , m_performanceMonitor(isUtilityPage() ? nullptr : std::make_unique<PerformanceMonitor>(*this))
     , m_lowPowerModeNotifier(std::make_unique<LowPowerModeNotifier>([this](bool isLowPowerModeEnabled) { handleLowModePowerChange(isLowPowerModeEnabled); }))
@@ -1487,6 +1488,20 @@
     chrome().client().isPlayingMediaDidChange(state, sourceElementID);
 }
 
+void Page::schedulePlaybackControlsManagerUpdate()
+{
+    if (!m_playbackControlsManagerUpdateTimer.isActive())
+        m_playbackControlsManagerUpdateTimer.startOneShot(0_s);
+}
+
+void Page::playbackControlsManagerUpdateTimerFired()
+{
+    if (auto bestMediaElement = HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose::ControlsManager))
+        chrome().client().setUpPlaybackControlsManager(*bestMediaElement);
+    else
+        chrome().client().clearPlaybackControlsManager();
+}
+
 void Page::setMuted(MediaProducer::MutedStateFlags muted)
 {
     if (m_mutedState == muted)

Modified: trunk/Source/WebCore/page/Page.h (233559 => 233560)


--- trunk/Source/WebCore/page/Page.h	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/page/Page.h	2018-07-06 03:20:20 UTC (rev 233560)
@@ -569,6 +569,7 @@
     MediaProducer::MutedStateFlags mutedState() const { return m_mutedState; }
     bool isAudioMuted() const { return m_mutedState & MediaProducer::AudioIsMuted; }
     bool isMediaCaptureMuted() const { return m_mutedState & MediaProducer::CaptureDevicesAreMuted; };
+    void schedulePlaybackControlsManagerUpdate();
     WEBCORE_EXPORT void setMuted(MediaProducer::MutedStateFlags);
     WEBCORE_EXPORT void stopMediaCapture();
 
@@ -674,6 +675,8 @@
 
     std::optional<std::pair<MediaCanStartListener&, Document&>> takeAnyMediaCanStartListener();
 
+    void playbackControlsManagerUpdateTimerFired();
+
     Vector<Ref<PluginViewBase>> pluginViews();
 
     void handleLowModePowerChange(bool);
@@ -849,7 +852,9 @@
     bool m_isRestoringCachedPage { false };
 
     MediaProducer::MediaStateFlags m_mediaState { MediaProducer::IsNotPlaying };
-    
+
+    Timer m_playbackControlsManagerUpdateTimer;
+
     bool m_allowsMediaDocumentInlinePlayback { false };
     bool m_allowsPlaybackControlsForAutoplayingAudio { false };
     bool m_showAllPlugins { false };

Modified: trunk/Source/WebCore/testing/Internals.cpp (233559 => 233560)


--- trunk/Source/WebCore/testing/Internals.cpp	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/testing/Internals.cpp	2018-07-06 03:20:20 UTC (rev 233560)
@@ -3750,7 +3750,7 @@
 }
 
 #if ENABLE(VIDEO)
-HTMLMediaElement* Internals::bestMediaElementForShowingPlaybackControlsManager(Internals::PlaybackControlsPurpose purpose)
+RefPtr<HTMLMediaElement> Internals::bestMediaElementForShowingPlaybackControlsManager(Internals::PlaybackControlsPurpose purpose)
 {
     return HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(purpose);
 }

Modified: trunk/Source/WebCore/testing/Internals.h (233559 => 233560)


--- trunk/Source/WebCore/testing/Internals.h	2018-07-06 02:44:34 UTC (rev 233559)
+++ trunk/Source/WebCore/testing/Internals.h	2018-07-06 03:20:20 UTC (rev 233560)
@@ -704,7 +704,7 @@
 
 #if ENABLE(VIDEO)
     using PlaybackControlsPurpose = MediaElementSession::PlaybackControlsPurpose;
-    HTMLMediaElement* bestMediaElementForShowingPlaybackControlsManager(PlaybackControlsPurpose);
+    RefPtr<HTMLMediaElement> bestMediaElementForShowingPlaybackControlsManager(PlaybackControlsPurpose);
 
     using MediaSessionState = PlatformMediaSession::State;
     MediaSessionState mediaSessionState(HTMLMediaElement&);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to