-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Safari: audio elements with event listeners are not getting garbage c…
…ollected https://bugs.webkit.org/show_bug.cgi?id=262485 rdar://116347723 Reviewed by Youenn Fablet. Clarify the implementation of HTMLMediaElement::virtualHasPendingActivity() to explicitly handle the cases when an element may have eventListeners but will never fire an event without its state being mutated through JavaScript. * LayoutTests/media/media-garbage-collection-expected.txt: Added. * LayoutTests/media/media-garbage-collection.html: Added. * LayoutTests/media/video-test.js: * Source/WebCore/html/HTMLMediaElement.cpp: (WebCore::HTMLMediaElement::virtualHasPendingActivity const): (WebCore::HTMLMediaElement::hasLiveSource const): Deleted. * Source/WebCore/html/HTMLMediaElement.h: Canonical link: https://commits.webkit.org/269165@main
- Loading branch information
Showing
7 changed files
with
304 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
Test 1: | ||
Test that a never loaded media element can be collected | ||
EXPECTED (weakVideo.wasCollected == 'true') OK | ||
|
||
Test 2: | ||
Test that the media element is not collected between load() and "loadstart" | ||
EVENT(loadstart) | ||
EXPECTED (weakVideo.wasCollected == 'false') OK | ||
|
||
Test 3: | ||
Test that the media element is not collected during playback | ||
EVENT(playing) | ||
EVENT(timeupdate) | ||
EXPECTED (weakVideo.wasCollected == 'false') OK | ||
|
||
Test 4: | ||
Test that a paused media element will be collected, even if it has an event listener | ||
EVENT(suspend) | ||
EXPECTED (weakVideo.wasCollected == 'true') OK | ||
|
||
Test 5: | ||
Test that an ended media element will be collected, even if it has an event listener | ||
EVENT(canplay) | ||
EVENT(seeked) | ||
EVENT(ended) | ||
EXPECTED (weakVideo.wasCollected == 'true') OK | ||
|
||
Test 6: | ||
Test that an interrupted media element will not be collected | ||
EVENT(canplay) | ||
EVENT(timeupdate) | ||
RUN(internals.beginMediaSessionInterruption("System")) | ||
EVENT(pause) | ||
EXPECTED (weakVideo.wasCollected == 'false') OK | ||
RUN(internals.endMediaSessionInterruption("System")) | ||
|
||
Test 7: | ||
Test that a media element will be collected after it is unloaded | ||
EVENT(canplay) | ||
EVENT(emptied) | ||
EXPECTED (weakVideo.wasCollected == 'true') OK | ||
|
||
END OF TEST | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<title>media-garbage-collection</title> | ||
<script src="video-test.js"></script> | ||
<script src="media-file.js"></script> | ||
<script src=../resources/gc.js></script> | ||
<script> | ||
class WeakVideoRef { | ||
#weakRef; | ||
|
||
constructor(video) { | ||
this.#weakRef = new WeakRef(video); | ||
} | ||
|
||
get wasCollected() { return !this.#weakRef.deref(); } | ||
get video() { return this.#weakRef.deref() } | ||
|
||
waitFor(event) { return waitFor(this.video, event); } | ||
addEventListener(event, callback) { return this.video.addEventListener(event, callback); } | ||
unload() { | ||
if (this.wasCollected) | ||
return; | ||
this.video.pause(); | ||
this.video.src = ''; | ||
this.video.load(); | ||
} | ||
}; | ||
var weakVideo; | ||
|
||
function testExpectedEventuallyWhileGCing(testFuncString, expected, comparison) { | ||
return testExpectedEventuallyWhileRunningBetweenTests(testFuncString, expected, comparison, 1000, gc); | ||
} | ||
|
||
let tests = [ | ||
async () => { | ||
consoleWrite('Test that a never loaded media element can be collected'); | ||
weakVideo = new WeakVideoRef(document.createElement('video')); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', true, '=='); | ||
}, | ||
async () => { | ||
consoleWrite('Test that the media element is not collected between load() and "loadstart"') | ||
var video = document.createElement("video"); | ||
video.src = "null"; | ||
video.load(); | ||
weakVideo = new WeakVideoRef(video); | ||
video = null; | ||
|
||
let loadstartPromise = weakVideo.waitFor('loadstart'); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
video = (await loadstartPromise).target; | ||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', false, '=='); | ||
}, | ||
async () => { | ||
consoleWrite('Test that the media element is not collected during playback') | ||
var video = document.createElement("video"); | ||
video.src = findMediaFile('video', 'content/test'); | ||
video.muted = true; | ||
video.play(); | ||
await waitFor(video, 'playing'); | ||
weakVideo = new WeakVideoRef(video); | ||
video = null; | ||
|
||
let timeupdatePromise = weakVideo.waitFor('timeupdate'); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
video = (await timeupdatePromise).target; | ||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', false, '=='); | ||
}, | ||
async () => { | ||
consoleWrite('Test that a paused media element will be collected, even if it has an event listener') | ||
|
||
video = document.createElement("video"); | ||
video.src = findMediaFile('video', 'content/test'); | ||
video.muted = true; | ||
await waitFor(video, 'suspend'), | ||
weakVideo = new WeakVideoRef(video); | ||
video = null; | ||
|
||
weakVideo.addEventListener('ended', () => { }); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', true, '=='); | ||
}, | ||
async () => { | ||
consoleWrite('Test that an ended media element will be collected, even if it has an event listener') | ||
|
||
video = document.createElement("video"); | ||
video.src = findMediaFile('video', 'content/test'); | ||
await waitFor(video, 'canplay'); | ||
video.currentTime = video.duration - 0.1; | ||
await waitFor(video, 'seeked'); | ||
video.play(); | ||
await waitFor(video, 'ended'); | ||
weakVideo = new WeakVideoRef(video); | ||
video = null; | ||
|
||
weakVideo.addEventListener('playing', () => { }); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', true, '=='); | ||
}, | ||
async () => { | ||
consoleWrite('Test that an interrupted media element will not be collected') | ||
|
||
video = document.createElement("video"); | ||
video.src = findMediaFile('video', 'content/test'); | ||
await waitFor(video, 'canplay'); | ||
video.play(); | ||
await waitFor(video, 'timeupdate'); | ||
|
||
run('internals.beginMediaSessionInterruption("System")'); | ||
await waitFor(video, 'pause') | ||
weakVideo = new WeakVideoRef(video); | ||
video = null; | ||
|
||
weakVideo.addEventListener('playing', () => { }); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', false, '=='); | ||
run('internals.endMediaSessionInterruption("System")'); | ||
}, | ||
async () => { | ||
consoleWrite('Test that a media element will be collected after it is unloaded') | ||
|
||
video = document.createElement("video"); | ||
video.src = findMediaFile('video', 'content/test'); | ||
await waitFor(video, 'canplay'); | ||
video.src = ''; | ||
video.load(); | ||
await waitFor(video, 'emptied'); | ||
|
||
weakVideo = new WeakVideoRef(video); | ||
video = null; | ||
|
||
weakVideo.addEventListener('playing', () => { }); | ||
await sleepFor(1); | ||
|
||
gc(); | ||
|
||
await testExpectedEventuallyWhileGCing('weakVideo.wasCollected', true, '=='); | ||
}, | ||
]; | ||
|
||
async function runTests() { | ||
let index = 0; | ||
for (test of tests) { | ||
consoleWrite(`Test ${++index}:`) | ||
try { | ||
await test(); | ||
video = null; | ||
weakVideo.unload(); | ||
weakVideo = null; | ||
} catch(e) { logResult(Failed, e); } | ||
consoleWrite('') | ||
}; | ||
} | ||
|
||
window.addEventListener('load', event => { | ||
runTests().then(endTest).catch(failTest); | ||
}) | ||
</script> | ||
</head> | ||
<body> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters