feat(media): expose MediaSource handle and buffer interface shells

This commit is contained in:
ldm0
2026-10-01 23:11:42 +08:00
parent 4c264b2d92
commit 9bc97b0cc4
6 changed files with 102 additions and 0 deletions
@@ -19,6 +19,9 @@ pub(in crate::context_bootstrap) const STORAGE_INTERFACE_NAMES: &[&str] = &[
];
pub(in crate::context_bootstrap) const WORKER_SHARED_INTERFACE_NAMES: &[&str] = &[
"MediaSourceHandle",
"SourceBuffer",
"SourceBufferList",
"EventTarget",
"Event",
"CustomEvent",
@@ -118,6 +121,8 @@ const WINDOW_DEDICATED_AND_SHARED_WORKER_INTERFACE_NAMES: &[&str] = &[
"XMLHttpRequest",
];
const DEDICATED_AND_SHARED_WORKER_INTERFACE_NAMES: &[&str] = &["FileReaderSync"];
const WINDOW_AND_DEDICATED_WORKER_INTERFACE_NAMES: &[&str] =
&["MediaSourceHandle", "SourceBuffer", "SourceBufferList"];
pub(in crate::context_bootstrap) const INDEXED_DB_INTERFACE_NAMES: &[&str] = &[
"DOMStringList",
@@ -233,6 +238,7 @@ impl ExposureSet {
const SERVICE_WORKER: u8 = 1 << 3;
const WINDOW_ONLY: Self = Self(Self::WINDOW);
const WINDOW_AND_DEDICATED_WORKER: Self = Self(Self::WINDOW | Self::DEDICATED_WORKER);
const ALL_REALMS: Self =
Self(Self::WINDOW | Self::DEDICATED_WORKER | Self::SHARED_WORKER | Self::SERVICE_WORKER);
const WORKERS: Self = Self(Self::DEDICATED_WORKER | Self::SHARED_WORKER | Self::SERVICE_WORKER);
@@ -394,6 +400,9 @@ fn exposure_for_name(name: &str) -> ExposureSet {
_ if WINDOW_DEDICATED_AND_SHARED_WORKER_INTERFACE_NAMES.contains(&name) => {
ExposureSet::WINDOW_DEDICATED_AND_SHARED_WORKER
}
_ if WINDOW_AND_DEDICATED_WORKER_INTERFACE_NAMES.contains(&name) => {
ExposureSet::WINDOW_AND_DEDICATED_WORKER
}
_ if DEDICATED_AND_SHARED_WORKER_INTERFACE_NAMES.contains(&name) => {
ExposureSet::DEDICATED_AND_SHARED_WORKER
}
@@ -386,3 +386,40 @@ fn worker_realm_lazy_properties_follow_chromium_exposure_sets() {
vec![false, false, false, false, false, true, false, false]
);
}
#[test]
fn source_buffer_interfaces_are_exposed_only_to_window_and_dedicated_workers() {
crate::ensure_v8_for_test();
for (realm, expected) in [
(super::RealmKind::DedicatedWorker, true),
(super::RealmKind::SharedWorker, false),
(super::RealmKind::ServiceWorker, false),
] {
let mut isolate = v8::Isolate::new(Default::default());
let scope = pin!(v8::HandleScope::new(&mut isolate));
let scope = &mut scope.init();
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
let global = context.global(scope);
crate::context_bootstrap::install_worker_lazy_exposed_interfaces(
scope, global, realm, true,
)
.expect("worker interfaces");
for name in ["MediaSourceHandle", "SourceBuffer", "SourceBufferList"] {
assert_eq!(
global.has_own_property(scope, v8str(scope, name).into()),
Some(expected),
"{realm:?}/{name}"
);
if expected {
assert!(
global
.get(scope, v8str(scope, name).into())
.unwrap()
.is_function(),
"{name} materialization"
);
}
}
}
}
@@ -4,6 +4,18 @@ use crate::web_api_interfaces;
use std::collections::HashSet;
const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
ConstructorSpec {
interface: web_api_interfaces::MediaSourceHandle::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SourceBuffer::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SourceBufferList::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::External::DESCRIPTOR,
kind: ConstructorKind::Illegal,
@@ -0,0 +1,39 @@
use super::*;
#[test]
fn media_source_buffer_interface_exposure_interfaces_have_realm_local_constructors_and_exposure() {
for url in [
"https://interface-exposure.test/",
"http://interface-exposure.test/",
] {
let mut vm = new_storage_test_vm(url);
assert_eq!(vm.eval(r#"
(() => {
const assert = (ok, message) => { if (!ok) throw Error(message); };
const names = ["MediaSourceHandle", "SourceBuffer", "SourceBufferList"];
const parents = {"MediaSourceHandle": null, "SourceBuffer": "EventTarget", "SourceBufferList": "EventTarget"};
if (!document.documentElement) document.appendChild(document.createElement('html'));
if (!document.body) document.documentElement.appendChild(document.createElement('body'));
const child = document.createElement('iframe');
document.body.appendChild(child);
const other = child.contentWindow;
for (const realm of [window, other]) {
for (const name of names) {
const C = realm[name];
assert(typeof C === 'function' && C.name === name && C.length === 0, name + ' constructor');
assert(C.prototype.constructor === C, name + ' prototype identity');
assert(Object.getPrototypeOf(C.prototype) === (parents[name] ? realm[parents[name]].prototype : realm.Object.prototype), name + ' prototype inheritance');
assert(realm[name] === C, name + ' stable lazy materialization');
for (const invoke of [() => C(), () => new C(), () => Reflect.construct(C, [], function Subclass() {})]) {
let error;
try { invoke(); } catch (caught) { error = caught; }
assert(error instanceof realm.TypeError, name + ' rejects construction in callee realm');
}
assert(C !== (realm === window ? other[name] : window[name]), name + ' distinct realm constructors');
}
}
return 'passed';
})()
"#).unwrap(), "passed", "{url}");
}
}
@@ -2180,3 +2180,5 @@ mod response_blob_mime;
mod intersection_target_order;
mod media_device_interfaces;
mod media_source_buffer_interface_exposure_interfaces;
@@ -20,6 +20,9 @@ macro_rules! interfaces {
}
interfaces! {
MediaSourceHandle;
SourceBuffer: EventTarget;
SourceBufferList: EventTarget;
AbortController;
AbortSignal: EventTarget;
AbstractRange;