Skip to content

Commit

Permalink
MSE-in-Workers: Enable experimental proactive feature detection
Browse files Browse the repository at this point in the history
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
w3c/media-source#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: Will Cassella <cassew@google.com>
Cr-Commit-Position: refs/heads/master@{#819564}
GitOrigin-RevId: 27d63ac81be017698d0b799365cd2ef4cbdd8813
  • Loading branch information
wolenetz authored and copybara-github committed Oct 21, 2020
1 parent 3681209 commit c825c3e
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 29 deletions.
8 changes: 8 additions & 0 deletions blink/renderer/modules/mediasource/media_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,14 @@ bool MediaSource::isTypeSupported(ExecutionContext* context,
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,
const String& type,
bool result) {
Expand Down
1 change: 1 addition & 0 deletions blink/renderer/modules/mediasource/media_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class MediaSource final : public EventTargetWithInlineData,
LOCKS_EXCLUDED(attachment_link_lock_);

static bool isTypeSupported(ExecutionContext* context, const String& type);
static bool canConstructInDedicatedWorker();

// Methods needed by a MediaSourceAttachmentSupplement to service operations
// proxied from an HTMLMediaElement.
Expand Down
8 changes: 8 additions & 0 deletions blink/renderer/modules/mediasource/media_source.idl
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,12 @@ enum EndOfStreamError {
[RaisesException] void clearLiveSeekableRange();

[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;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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();
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
<script src="/resources/testharnessreport.js"></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");
worker.onmessage = t.step_func((e) => {
worker.onmessage = t.step_func(e => {
if (e.data.substr(0,6) == "Error:") {
assert_unreached("Worker error: " + e.data);
} else {
Expand All @@ -17,11 +21,18 @@
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
// merged into those from this page.
fetch_tests_from_worker(new Worker("mediasource-worker-objecturl.js"));
if (MediaSource.hasOwnProperty("canConstructInDedicatedWorker") && MediaSource.canConstructInDedicatedWorker === true) {
// If implementation claims support for MSE-in-Workers, then fetch and run
// 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>
</html>
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
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();
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 url = URL.createObjectURL(ms);
assert_true(url != 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 url1 = URL.createObjectURL(ms);
const url2 = URL.createObjectURL(ms);
URL.revokeObjectURL(url1);
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();
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@
}

function startWorkerAndTerminateWorker(test, when_to_start_timeouts, timeouts_to_await) {
// TODO(https://crbug.com/878133): Enable main-thread feature detection of
// whether or not the implementation supports MSE-in-Workers, and fail the
// test rapidly here rather than flakily pass/failing the test on those
// 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).
// Fail fast if MSE-in-Workers is not supported.
assert_true(MediaSource.hasOwnProperty("canConstructInDedicatedWorker"), "MediaSource hasOwnProperty 'canConstructInDedicatedWorker'");
assert_true(MediaSource.canConstructInDedicatedWorker, "MediaSource.canConstructInDedicatedWorker");

const worker = new Worker("mediasource-worker-util.js");
worker.onerror = test.unreached_func("worker error");

Expand All @@ -46,7 +43,7 @@
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:") {
assert_unreached("Worker error: " + e.data);
} else {
Expand All @@ -68,9 +65,9 @@
});
}

[ "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) {
async_test((test) => { startWorkerAndTerminateWorker(test, when, timeouts); },
async_test(test => { startWorkerAndTerminateWorker(test, when, timeouts); },
"Test worker MediaSource termination after at least " + timeouts +
" main thread setTimeouts, starting counting " + when);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@
<body>
<script>

async_test((t) => {
const video = document.createElement('video');
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");

const video = document.createElement("video");
document.body.appendChild(video);
video.onerror = t.unreached_func("video element error");
video.onended = t.step_func_done();

let worker = new Worker("mediasource-worker-util.js");
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:") {
assert_unreached("Worker error: " + e.data);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ function loadBinaryAsync(url) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = 'arraybuffer';
request.onerror = (event) => { reject(event); };
request.responseType = "arraybuffer";
request.onerror = event => { reject(event); };
request.onload = () => {
if (request.status != 200) {
reject("Unexpected loadData_ status code : " + request.status);
Expand Down Expand Up @@ -76,6 +76,6 @@ mediaSource.addEventListener("sourceopen", () => {
mediaSource.endOfStream();
};
};
mediaLoad.then( (mediaData) => { sourceBuffer.appendBuffer(mediaData); },
(err) => { postMessage("Error: " + err) } );
mediaLoad.then( mediaData => { sourceBuffer.appendBuffer(mediaData); },
err => { postMessage("Error: " + err) } );
}, { once : true });
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method decodingInfo
[Worker] method encodingInfo
[Worker] interface MediaSource : EventTarget
[Worker] static getter canConstructInDedicatedWorker
[Worker] static method isTypeSupported
[Worker] attribute @@toStringTag
[Worker] getter activeSourceBuffers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5142,6 +5142,7 @@ interface MediaSession
setter metadata
setter playbackState
interface MediaSource : EventTarget
static getter canConstructInDedicatedWorker
static method isTypeSupported
attribute @@toStringTag
getter activeSourceBuffers
Expand Down

0 comments on commit c825c3e

Please sign in to comment.