| <!DOCTYPE html> |
| <title>Tests that GC preserves all of attached HTMLMediaElement+MSE when only part of that has a live reference.</title> |
| <script src="/w3c/resources/testharness.js"></script> |
| <script src="/w3c/resources/testharnessreport.js"></script> |
| <script> |
| |
| function TestScope() { |
| this.video = null; |
| this.media_source = null; |
| this.object_url = null; |
| this.source_buffer_list = null; |
| this.expect_durationchange = false; |
| this.expect_sourceclose = false; |
| this.received_sourceclose = false; |
| this.expect_error = false; |
| this.received_error = false; |
| } |
| |
| function setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) { |
| // |handler_setup_cb|, if not null, is synchronously called after |
| // test_scope is populated with HTMLME and MSE references, but prior to |
| // attaching HTMLME to MSE API. |
| // |
| // Next, attaches the HTMLME to the MediaSource object and sets a timeout to later |
| // call |setup_done_cb| so that hopefully any pending events or other work |
| // are complete before the main part of the test proceeds. |
| |
| test_scope.video = document.createElement("video"); |
| test_scope.media_source = new MediaSource(); |
| test_scope.object_url = URL.createObjectURL(test_scope.media_source); |
| if (handler_setup_cb != null) |
| handler_setup_cb(); |
| |
| test_scope.media_source.onsourceopen = test.step_func(function() { |
| test_scope.media_source.onsourceopen = null; |
| URL.revokeObjectURL(test_scope.object_url); |
| setTimeout(setup_done_cb, 0); |
| }); |
| |
| test_scope.video.src = test_scope.object_url; |
| } |
| |
| async_test(function(test) { |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = null; |
| |
| var setup_done_cb = test.step_func(function() { |
| test_scope.video = null; |
| // |test_scope.media_source| should be the only remaining reference to HTMLME+MSE. |
| // GC shouldn't collect HTMLME+MSE due to that live reference. |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| |
| // Debug note: setting test_scope.media_source to null here, and commenting out the assert_equals() |
| // and test.done() lines, below, demonstrates with debug logging that HTMLME+MSE is collected, followed |
| // eventually by a timeout. |
| gc(); |
| |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open after gc()."); |
| test.done(); |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource preserves at least MediaSource when only the MediaSource reference is held by JS"); |
| |
| async_test(function(test) { |
| // This test builds on the previous test. It should fail if the previous test fails. |
| |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = test.step_func(function() { |
| test_scope.video.ondurationchange = test.step_func(function() { |
| assert_true(test_scope.expect_durationchange, "HTMLME durationchange event is expected only after changing MediaSource duration"); |
| test.done(); |
| }); |
| }); |
| |
| var setup_done_cb = test.step_func(function() { |
| test_scope.video = null; |
| // |test_scope.media_source| should be the only remaining reference to HTMLME+MSE. |
| // GC shouldn't collect HTMLME+MSE due to that live reference. |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| gc(); |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open after gc()."); |
| |
| // Verify that HTMLME is still alive by changing the MediaSource object's duration, which causes |
| // a durationchange event to become queued for dispatch on the attached HTMLME (and such dispatch ends this |
| // test.) |
| // Debug note: commenting out the next line should result in test timeout. |
| test_scope.media_source.duration = 100; |
| test_scope.expect_durationchange = true; |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource preserves at least MediaSource and HTMLME when only the MediaSource reference is held by JS"); |
| |
| async_test(function(test) { |
| // This test builds on the previous two tests. It should fail if either of the previous two tests fails. |
| |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = test.step_func(function() { |
| test_scope.video.ondurationchange = test.step_func(function(e) { |
| assert_true(test_scope.expect_durationchange, "HTMLME durationchange event is expected only after changing MediaSource duration"); |
| assert_equals(e.target.custom_test_wrapper_update, "testing", "HTMLME wrapper, as adjusted by the test, should be retained"); |
| test.done(); |
| }); |
| }); |
| |
| var setup_done_cb = test.step_func(function() { |
| // Update the HTMLME JS wrapper with a custom property to be verified later. |
| test_scope.video.custom_test_wrapper_update = "testing"; |
| test_scope.video = null; |
| // |test_scope.media_source| should be the only remaining reference to HTMLME+MSE. |
| // GC shouldn't collect HTMLME+MSE due to that live reference. |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| gc(); |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open after gc()."); |
| |
| // Verify that HTMLME is still alive by changing the MediaSource object's duration, which causes |
| // a durationchange event to become queued for dispatch on the attached HTMLME (and such dispatch ends this |
| // test.) |
| // Debug note: commenting out the next line demonstrates with debug logging that HTMLME+MSE is collected, |
| // followed eventually by a timeout. |
| test_scope.media_source.duration = 100; |
| |
| test_scope.expect_durationchange = true; |
| test_scope.media_source = null; |
| gc(); |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource preserves at least HTMLME and its wrapper when no references held by JS, but there is a pending HTMLME event"); |
| |
| async_test(function(test) { |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = null; |
| |
| var setup_done_cb = test.step_func(function() { |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| test_scope.media_source = null; |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct before gc()."); |
| // |test_scope.video| should be the only remaining reference to HTMLME+MSE. |
| // GC shouldn't collect HTMLME+MSE due to that live reference. |
| |
| // Debug note: setting test_scope.video to null here, and commenting out the assert_equals() |
| // and test.done() lines, below, demonstrates with debug logging that HTMLME+MSE is collected, followed |
| // eventually by a timeout. |
| gc(); |
| |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct after gc()."); |
| test.done(); |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource preserves at least HTMLME when only the HTMLME reference is held by JS"); |
| |
| async_test(function(test) { |
| // This test builds on the previous test. It should fail if the previous test fails. |
| |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = test.step_func(function() { |
| test_scope.media_source.onsourceclose = test.step_func(function() { |
| assert_true(test_scope.expect_sourceclose, "MediaSource sourceclose event is expected only after clearing HTMLME src attribute"); |
| // Both HTMLME.onerror and MediaSource.onsourceclose are required to finish this test. |
| if (test_scope.received_error) |
| test.done(); |
| test_scope.received_sourceclose = true; |
| test_scope.expect_sourceclose = false; // Only one sourceclose is expected. |
| }); |
| |
| test_scope.video.onerror = test.step_func(function() { |
| assert_true(test_scope.expect_error, "HTMLME error event is expected only after clearing HTMLME src attribute"); |
| // Both HTMLME.onerror and MediaSource.onsourceclose are required to finish this test. |
| if (test_scope.received_sourceclose) |
| test.done(); |
| test_scope.received_error = true; |
| test_scope.expect_error = false; // Only one error is expected. |
| }); |
| }); |
| |
| var setup_done_cb = test.step_func(function() { |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| test_scope.media_source = null; |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct before gc()."); |
| // |test_scope.video| should be the only remaining reference to HTMLME+MSE. |
| // GC shouldn't collect HTMLME+MSE due to that live reference. |
| |
| gc(); |
| |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct after gc()."); |
| |
| // Verify that MediaSource is still alive by clearing the HTMLME object's src attribute, which causes a |
| // sourceclose event to become queued for dispatch on the previously attached MediaSource and an error |
| // event to become queued for dispatch on the HTMLME object (and such dispatches end this test.) |
| // Debug note: commenting out the next line should result in test timeout. |
| test_scope.video.src = ""; |
| test_scope.expect_sourceclose = true; |
| test_scope.expect_error = true; |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource preserves at least MediaSource and HTMLME when only the HTMLME reference is held by JS"); |
| |
| async_test(function(test) { |
| // This test builds on the previous two tests. It should fail if either of the previous two tests fails. |
| |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = test.step_func(function() { |
| test_scope.media_source.onsourceclose = test.step_func(function(e) { |
| assert_true(test_scope.expect_sourceclose, "MediaSource sourceclose event is expected only after clearing HTMLME src attribute"); |
| assert_equals(e.target.custom_test_wrapper_update, "testing-mediasource", "MediaSource wrapper, as adjusted by the test, should be retained"); |
| // Both HTMLME.onerror and MediaSource.onsourceclose are required to finish this test. |
| if (test_scope.received_error) |
| test.done(); |
| test_scope.received_sourceclose = true; |
| test_scope.expect_sourceclose = false; // Only one sourceclose is expected. |
| }); |
| |
| test_scope.video.onerror = test.step_func(function(e) { |
| assert_true(test_scope.expect_error, "HTMLME error event is expected only after clearing HTMLME src attribute"); |
| assert_equals(e.target.custom_test_wrapper_update, "testing-htmlme", "HTMLME wrapper, as adjusted by the test, should be retained"); |
| // Both HTMLME.onerror and MediaSource.onsourceclose are required to finish this test. |
| if (test_scope.received_sourceclose) |
| test.done(); |
| test_scope.received_error = true; |
| test_scope.expect_error = false; // Only one error is expected. |
| }); |
| }); |
| |
| var setup_done_cb = test.step_func(function() { |
| // Update the HTMLME and MediaSource JS wrappers with custom properties to be verified later. |
| test_scope.video.custom_test_wrapper_update = "testing-htmlme"; |
| test_scope.media_source.custom_test_wrapper_update = "testing-mediasource"; |
| |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| test_scope.media_source = null; |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct before gc()."); |
| // |test_scope.video| should be the only remaining reference to HTMLME+MSE. |
| // GC shouldn't collect HTMLME+MSE due to that live reference. |
| |
| gc(); |
| |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct after gc()."); |
| |
| // Verify that MediaSource is still alive by clearing the HTMLME object's src attribute, which causes a |
| // sourceclose event to become queued for dispatch on the previously attached MediaSource and an error |
| // event to become queued for dispatch on the HTMLME object (and such dispatches end this test.) |
| // Debug note: commenting out the next line demonstrates with debug logging that HTMLME+MSE is collected, |
| // followed eventually by a timeout. |
| test_scope.video.src = ""; |
| |
| test_scope.expect_sourceclose = true; |
| test_scope.expect_error = true; |
| test_scope.video = null; |
| gc(); |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource preserves at least MediaSource and HTMLME and their wrappers when no references held by JS, but there is a pending event on each of HTMLME and MediaSource"); |
| |
| async_test(function(test) { |
| var test_scope = new TestScope(); |
| |
| var handler_setup_cb = null; |
| |
| var setup_done_cb = test.step_func(function() { |
| var source_buffer = test_scope.media_source.addSourceBuffer('video/webm; codecs="vp8"'); |
| test_scope.source_buffer_list = test_scope.media_source.sourceBuffers; |
| source_buffer.custom_test_wrapper_update = "testing-sourcebuffer"; |
| assert_true(source_buffer === test_scope.source_buffer_list[0]); |
| |
| assert_equals(test_scope.media_source.readyState, "open", "MediaSource object is open before gc()."); |
| assert_equals(test_scope.video.src, test_scope.object_url, "HTMLME src attribute is correct before gc()."); |
| |
| source_buffer = null; |
| test_scope.media_source = null; |
| test_scope.video = null; |
| |
| |
| // |test_scope.source_buffer_list| should be the only remaining reference to HTMLME+MSE+SBL+SB. |
| // GC shouldn't collected this group due to that live reference. |
| |
| // Debug note: setting test_scope.source_buffer_list to null here, and commenting out the assert_equals() |
| // and test.done() lines, below, demonstrates with debug logging that HTMLME+MSE is collected, followed |
| // eventually by a timeout. |
| gc(); |
| |
| assert_equals(test_scope.source_buffer_list.length, 1, "SBL should survive gc()."); |
| assert_equals(test_scope.source_buffer_list[0].custom_test_wrapper_update, "testing-sourcebuffer", "SBL[0]'s wrapper should survive gc()."); |
| test.done(); |
| }); |
| |
| setup_htmlme_mse_lifetime_test(test, test_scope, handler_setup_cb, setup_done_cb) |
| }, "GC of HTMLME+MediaSource+SBL+SB preserves at least SBL+SB when only the SourceBufferList reference is held by JS"); |
| |
| // TODO(wolenetz): Consider further refactoring to extract specific testing concerns from the following test: |
| async_test(function(test) { |
| var video = document.createElement("video"); |
| var media_source = new MediaSource(); |
| var object_url = URL.createObjectURL(media_source); |
| var malformed_media_append_started = false; |
| |
| video.onerror = test.step_func(function() { |
| assert_true(malformed_media_append_started, "error should occur after append of malformed media bytestream started"); |
| test.done(); |
| }); |
| |
| media_source.onsourceopen = test.step_func(function() { |
| URL.revokeObjectURL(object_url); |
| var source_buffer = media_source.addSourceBuffer('video/webm; codecs="vp8"'); |
| var source_buffer_list = media_source.sourceBuffers; |
| assert_true(source_buffer === source_buffer_list[0]); |
| source_buffer = null; |
| media_source = null; |
| video = null; |
| // source_buffer_list's reference by us is the only thing keeping HTMLME+MSE alive. |
| gc(); |
| |
| setTimeout(test.step_func(function() { |
| gc(); |
| source_buffer_list[0].onupdatestart = test.step_func(function() { malformed_media_append_started = true; }); |
| |
| // Begin asynchronous append of a malformed media bytestream which |
| // should result eventually with HTMLME error event firing due to the MSE |
| // Append Error Algorithm. |
| source_buffer_list[0].appendBuffer(new Uint8Array(10)); |
| |
| // There should be a pending updatestart event on the SourceBuffer, so even if we drop the reference |
| // to the SourceBufferList here, gc() still shouldn't collect HTMLME+MSE due to pending event dispatch |
| // on MSE. This is verified by HTMLME eventually handling an error event due to the asynchronous append |
| // of malformed media. |
| source_buffer_list = null; |
| gc(); |
| }), 0); |
| }); |
| |
| video.src = object_url; |
| |
| // Debugging notes: If this test times out, it's likely due to failure in |
| // keeping HTMLME+MSE alive and pending event dispatch on their wrappers alive |
| // through gc(); either gc() over-collected HTMLME+MSE or event dispatch was |
| // perturbed by gc(). |
| // If the assert in video.onerror fails, it is self-explanatory. |
| }, "GC of HTMLME+MediaSource+SBL+SB preserves all when only SourceBufferList reference is alive, then SourceBuffer.onupdatestart and HTMLME.onerror are dispatched if they became pending during the asynchronous buffer append algorithm, of malformed bytestream, after all HTMLME+MSE references dropped"); |
| |
| </script> |