Commit 27d63ac8 authored by Matt Wolenetz's avatar Matt Wolenetz Committed by Commit Bot

MSE-in-Workers: Enable experimental proactive feature detection

Adds a readonly, boolean-valued, static attribute named
`canConstructInDedicatedWorker` to the MediaSource interface.
Currently, this attribute is only visible to web apps if the
RunTimeEnabledFeature "MediaSourceInWorkers" is enabled. When visible,
this attribute always returns true.

The primary goal of having this attribute is to enable web app's main
thread to proactively determine whether or not MSE is supported from a
dedicated worker context *before* deciding whether or not to create or
try using MSE from such a context.

As an initial example of this use case, the existing MSE-in-Workers
web_tests are updated to use this new attribute's existence and value
to fail-fast rather than potentially flakily fail (e.g. previously, the
...worker-terminate test might flakily pass/fail some of its test cases
on implementations lacking MSE-in-Workers support if the test completed
before handling receipt of error message from worker.)

A further test is added to ensure that, if the attribute is missing or
exists but is not `true`, a dedicated worker does not have ability to
construct a MediaSource instance. See also
https://github.com/w3c/media-source/issues/175 for further discussion
which led to this new attribute.

BUG=878133

Change-Id: I697ca6adc5b5dc65d5c5084ff67a541430a9237b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2487834
Commit-Queue: Matthew Wolenetz <wolenetz@chromium.org>
Reviewed-by: default avatarWill Cassella <cassew@google.com>
Cr-Commit-Position: refs/heads/master@{#819564}
parent 39275616
...@@ -420,6 +420,14 @@ bool MediaSource::isTypeSupported(ExecutionContext* context, ...@@ -420,6 +420,14 @@ bool MediaSource::isTypeSupported(ExecutionContext* context,
return result; return result;
} }
// static
bool MediaSource::canConstructInDedicatedWorker() {
// This method's visibility in IDL is restricted to MSE-in-Workers feature
// being enabled.
DCHECK(RuntimeEnabledFeatures::MediaSourceInWorkersEnabled());
return true;
}
void MediaSource::RecordIdentifiabilityMetric(ExecutionContext* context, void MediaSource::RecordIdentifiabilityMetric(ExecutionContext* context,
const String& type, const String& type,
bool result) { bool result) {
......
...@@ -86,6 +86,7 @@ class MediaSource final : public EventTargetWithInlineData, ...@@ -86,6 +86,7 @@ class MediaSource final : public EventTargetWithInlineData,
LOCKS_EXCLUDED(attachment_link_lock_); LOCKS_EXCLUDED(attachment_link_lock_);
static bool isTypeSupported(ExecutionContext* context, const String& type); static bool isTypeSupported(ExecutionContext* context, const String& type);
static bool canConstructInDedicatedWorker();
// Methods needed by a MediaSourceAttachmentSupplement to service operations // Methods needed by a MediaSourceAttachmentSupplement to service operations
// proxied from an HTMLMediaElement. // proxied from an HTMLMediaElement.
......
...@@ -59,4 +59,12 @@ enum EndOfStreamError { ...@@ -59,4 +59,12 @@ enum EndOfStreamError {
[RaisesException] void clearLiveSeekableRange(); [RaisesException] void clearLiveSeekableRange();
[CallWith=ExecutionContext] static boolean isTypeSupported (DOMString type); [CallWith=ExecutionContext] static boolean isTypeSupported (DOMString type);
// Enables proactive feature-detection of MSE-in-Workers support from the
// main thread (or anywhere this interface is exposed.) If the attribute is
// available, and if it is true, then the implementation claims it can
// support usage of the MSE API from dedicated worker contexts. See also
// https://github.com/w3c/media-source/issues/175 and
// https://crbug.com/878133.
[RuntimeEnabled=MediaSourceInWorkers] static readonly attribute boolean canConstructInDedicatedWorker;
}; };
importScripts("/resources/testharness.js");
test(t => {
// The Window test html conditionally fetches and runs these tests only if the
// implementation does not have a true-valued static
// canConstructInDedicatedWorker property on MediaSource in the Window
// context. So, the implementation must agree on lack of support here in the
// dedicated worker context.
// Ensure we're executing in a dedicated worker context.
assert_true(self instanceof DedicatedWorkerGlobalScope, "self instanceof DedicatedWorkerGlobalScope");
assert_true(self.MediaSource === undefined, "MediaSource is undefined in DedicatedWorker");
assert_throws_js(ReferenceError,
function() { var ms = new MediaSource(); },
"MediaSource construction in DedicatedWorker throws exception");
}, "MediaSource construction in DedicatedWorker context must fail if Window context did not claim MSE supported in DedicatedWorker");
done();
...@@ -5,9 +5,13 @@ ...@@ -5,9 +5,13 @@
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script> <script>
async_test((t) => { async_test(t => {
// Fail fast if MSE-in-Workers is not supported.
assert_true(MediaSource.hasOwnProperty("canConstructInDedicatedWorker"), "MediaSource hasOwnProperty 'canConstructInDedicatedWorker'");
assert_true(MediaSource.canConstructInDedicatedWorker, "MediaSource.canConstructInDedicatedWorker");
let worker = new Worker("mediasource-worker-util.js"); let worker = new Worker("mediasource-worker-util.js");
worker.onmessage = t.step_func((e) => { worker.onmessage = t.step_func(e => {
if (e.data.substr(0,6) == "Error:") { if (e.data.substr(0,6) == "Error:") {
assert_unreached("Worker error: " + e.data); assert_unreached("Worker error: " + e.data);
} else { } else {
...@@ -17,11 +21,18 @@ async_test((t) => { ...@@ -17,11 +21,18 @@ async_test((t) => {
t.done(); t.done();
} }
}); });
}, "Test main context revocation of worker MediaSource object URL"); }, "Test main context revocation of DedicatedWorker MediaSource object URL");
// Run some tests directly in another dedicated worker and get their results if (MediaSource.hasOwnProperty("canConstructInDedicatedWorker") && MediaSource.canConstructInDedicatedWorker === true) {
// merged into those from this page. // If implementation claims support for MSE-in-Workers, then fetch and run
fetch_tests_from_worker(new Worker("mediasource-worker-objecturl.js")); // some tests directly in another dedicated worker and get their results
// merged into those from this page.
fetch_tests_from_worker(new Worker("mediasource-worker-objecturl.js"));
} else {
// Otherwise, fetch and run a test that verifies lack of support of
// MediaSource construction in another dedicated worker.
fetch_tests_from_worker(new Worker("mediasource-worker-must-fail-if-unsupported.js"));
}
</script> </script>
</html> </html>
importScripts("/resources/testharness.js"); importScripts("/resources/testharness.js");
test((t) => { test(t => {
// The Window test html conditionally fetches and runs these tests only if the
// implementation exposes a true-valued static canConstructInDedicatedWorker
// attribute on MediaSource in the Window context. So, the implementation must
// agree on support here in the dedicated worker context.
// Ensure we're executing in a dedicated worker context.
assert_true(self instanceof DedicatedWorkerGlobalScope, "self instanceof DedicatedWorkerGlobalScope");
assert_true(MediaSource.hasOwnProperty("canConstructInDedicatedWorker", "DedicatedWorker MediaSource hasOwnProperty 'canConstructInDedicatedWorker'"));
assert_true(MediaSource.canConstructInDedicatedWorker, "DedicatedWorker MediaSource.canConstructInDedicatedWorker");
}, "MediaSource in DedicatedWorker context must have true-valued canConstructInDedicatedWorker if Window context had it");
test(t => {
const ms = new MediaSource(); const ms = new MediaSource();
assert_equals(ms.readyState, "closed"); assert_equals(ms.readyState, "closed");
}, "MediaSource construction succeeds with initial closed readyState in dedicated worker"); }, "MediaSource construction succeeds with initial closed readyState in DedicatedWorker");
test((t) => { test(t => {
const ms = new MediaSource(); const ms = new MediaSource();
const url = URL.createObjectURL(ms); const url = URL.createObjectURL(ms);
assert_true(url != null); assert_true(url != null);
assert_true(url.match(/^blob:.+/) != null); assert_true(url.match(/^blob:.+/) != null);
}, "URL.createObjectURL(mediaSource) in dedicated worker returns a Blob URI"); }, "URL.createObjectURL(mediaSource) in DedicatedWorker returns a Blob URI");
test((t) => { test(t => {
const ms = new MediaSource(); const ms = new MediaSource();
const url1 = URL.createObjectURL(ms); const url1 = URL.createObjectURL(ms);
const url2 = URL.createObjectURL(ms); const url2 = URL.createObjectURL(ms);
URL.revokeObjectURL(url1); URL.revokeObjectURL(url1);
URL.revokeObjectURL(url2); URL.revokeObjectURL(url2);
}, "URL.revokeObjectURL(mediaSource) in dedicated worker with two url for same MediaSource"); }, "URL.revokeObjectURL(mediaSource) in DedicatedWorker with two url for same MediaSource");
done(); done();
...@@ -18,13 +18,10 @@ function terminateWorkerAfterMultipleSetTimeouts(test, worker, timeouts_remainin ...@@ -18,13 +18,10 @@ function terminateWorkerAfterMultipleSetTimeouts(test, worker, timeouts_remainin
} }
function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to_await) { function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to_await) {
// TODO(https://crbug.com/878133): Enable main-thread feature detection of // Fail fast if MSE-in-Workers is not supported.
// whether or not the implementation supports MSE-in-Workers, and fail the assert_true(MediaSource.hasOwnProperty("canConstructInDedicatedWorker"), "MediaSource hasOwnProperty 'canConstructInDedicatedWorker'");
// test rapidly here rather than flakily pass/failing the test on those assert_true(MediaSource.canConstructInDedicatedWorker, "MediaSource.canConstructInDedicatedWorker");
// implementations. If the timeout occurs near to when the worker's report
// of lack of MSE support reaches the main thread, then the test could
// pass in some cases (when timeout occurs prior to handling that error)
// and fail in others (when worker.onerror dispatch occurs first).
const worker = new Worker("mediasource-worker-util.js"); const worker = new Worker("mediasource-worker-util.js");
worker.onerror = test.unreached_func("worker error"); worker.onerror = test.unreached_func("worker error");
...@@ -46,7 +43,7 @@ function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to ...@@ -46,7 +43,7 @@ function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to
terminateWorkerAfterMultipleSetTimeouts(test, worker, timeouts_to_await); terminateWorkerAfterMultipleSetTimeouts(test, worker, timeouts_to_await);
} }
worker.onmessage = test.step_func((e) => { worker.onmessage = test.step_func(e => {
if (e.data.substr(0,6) == "Error:") { if (e.data.substr(0,6) == "Error:") {
assert_unreached("Worker error: " + e.data); assert_unreached("Worker error: " + e.data);
} else { } else {
...@@ -68,9 +65,9 @@ function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to ...@@ -68,9 +65,9 @@ function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to
}); });
} }
[ "before setting src", "after setting src", "after first ended event" ].forEach((when) => { [ "before setting src", "after setting src", "after first ended event" ].forEach(when => {
for (let timeouts = 0; timeouts < 10; ++timeouts) { for (let timeouts = 0; timeouts < 10; ++timeouts) {
async_test((test) => { startWorkerAndTerminateWorker(test, when, timeouts); }, async_test(test => { startWorkerAndTerminateWorker(test, when, timeouts); },
"Test worker MediaSource termination after at least " + timeouts + "Test worker MediaSource termination after at least " + timeouts +
" main thread setTimeouts, starting counting " + when); " main thread setTimeouts, starting counting " + when);
} }
......
...@@ -6,15 +6,19 @@ ...@@ -6,15 +6,19 @@
<body> <body>
<script> <script>
async_test((t) => { async_test(t => {
const video = document.createElement('video'); // Fail fast if MSE-in-Workers is not supported.
assert_true(MediaSource.hasOwnProperty("canConstructInDedicatedWorker"), "MediaSource hasOwnProperty 'canConstructInDedicatedWorker'");
assert_true(MediaSource.canConstructInDedicatedWorker, "MediaSource.canConstructInDedicatedWorker");
const video = document.createElement("video");
document.body.appendChild(video); document.body.appendChild(video);
video.onerror = t.unreached_func("video element error"); video.onerror = t.unreached_func("video element error");
video.onended = t.step_func_done(); video.onended = t.step_func_done();
let worker = new Worker("mediasource-worker-util.js"); let worker = new Worker("mediasource-worker-util.js");
worker.onerror = t.unreached_func("worker error"); worker.onerror = t.unreached_func("worker error");
worker.onmessage = t.step_func((e) => { worker.onmessage = t.step_func(e => {
if (e.data.substr(0,6) == "Error:") { if (e.data.substr(0,6) == "Error:") {
assert_unreached("Worker error: " + e.data); assert_unreached("Worker error: " + e.data);
} else { } else {
......
...@@ -33,8 +33,8 @@ function loadBinaryAsync(url) { ...@@ -33,8 +33,8 @@ function loadBinaryAsync(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let request = new XMLHttpRequest(); let request = new XMLHttpRequest();
request.open("GET", url, true); request.open("GET", url, true);
request.responseType = 'arraybuffer'; request.responseType = "arraybuffer";
request.onerror = (event) => { reject(event); }; request.onerror = event => { reject(event); };
request.onload = () => { request.onload = () => {
if (request.status != 200) { if (request.status != 200) {
reject("Unexpected loadData_ status code : " + request.status); reject("Unexpected loadData_ status code : " + request.status);
...@@ -76,6 +76,6 @@ mediaSource.addEventListener("sourceopen", () => { ...@@ -76,6 +76,6 @@ mediaSource.addEventListener("sourceopen", () => {
mediaSource.endOfStream(); mediaSource.endOfStream();
}; };
}; };
mediaLoad.then( (mediaData) => { sourceBuffer.appendBuffer(mediaData); }, mediaLoad.then( mediaData => { sourceBuffer.appendBuffer(mediaData); },
(err) => { postMessage("Error: " + err) } ); err => { postMessage("Error: " + err) } );
}, { once : true }); }, { once : true });
...@@ -787,6 +787,7 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -787,6 +787,7 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method decodingInfo [Worker] method decodingInfo
[Worker] method encodingInfo [Worker] method encodingInfo
[Worker] interface MediaSource : EventTarget [Worker] interface MediaSource : EventTarget
[Worker] static getter canConstructInDedicatedWorker
[Worker] static method isTypeSupported [Worker] static method isTypeSupported
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] getter activeSourceBuffers [Worker] getter activeSourceBuffers
......
...@@ -5142,6 +5142,7 @@ interface MediaSession ...@@ -5142,6 +5142,7 @@ interface MediaSession
setter metadata setter metadata
setter playbackState setter playbackState
interface MediaSource : EventTarget interface MediaSource : EventTarget
static getter canConstructInDedicatedWorker
static method isTypeSupported static method isTypeSupported
attribute @@toStringTag attribute @@toStringTag
getter activeSourceBuffers getter activeSourceBuffers
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment