feat(media): expose the native MediaRecorder interface shell

This commit is contained in:
ldm0
2026-10-05 05:26:00 +08:00
committed by Donough Liu
parent 0423b34df4
commit 4b4fd2e7f3
6 changed files with 81 additions and 0 deletions
@@ -592,6 +592,32 @@ mod tests {
assert!(worker_location.is_exposed(RealmKind::ServiceWorker, true));
}
#[test]
fn shell_media_recorder_uses_the_expected_exposure_and_lazy_policy() {
let table = ExposedInterfaceMetadataTable::from_constructor_specs(
&crate::context_bootstrap::specs::constructor_specs(),
)
.expect("shell exposure metadata");
for name in ["MediaRecorder"] {
let metadata = table.metadata_by_name(name).expect("interface metadata");
assert_eq!(metadata.installation, GlobalInstallation::Lazy, "{name}");
for (realm, expected) in [
(RealmKind::Window, true),
(RealmKind::DedicatedWorker, false),
(RealmKind::SharedWorker, false),
(RealmKind::ServiceWorker, false),
] {
assert_eq!(metadata.is_exposed(realm, true), expected, "{name}");
assert_eq!(metadata.is_exposed(realm, false), expected, "{name}");
assert_eq!(
metadata.is_supported_by(TemplateBuildProfile::for_realm(realm)),
expected,
"{name}"
);
}
}
}
#[test]
fn chromium_worker_exposure_exceptions_are_preserved() {
let table = ExposedInterfaceMetadataTable::from_constructor_specs(&[
@@ -1195,6 +1195,10 @@ const CONSTRUCTOR_SPECS_AFTER_STREAMS: &[ConstructorSpec] = &[
interface: web_api_interfaces::MediaSource::DESCRIPTOR,
kind: ConstructorKind::MediaSource,
},
ConstructorSpec {
interface: web_api_interfaces::MediaRecorder::DESCRIPTOR,
kind: ConstructorKind::Shell { length: 1 },
},
ConstructorSpec {
interface: web_api_interfaces::MediaError::DESCRIPTOR,
kind: ConstructorKind::MediaError,
@@ -0,0 +1,30 @@
(() => {
const assert = (ok, message) => { if (!ok) throw Error(message); };
const interfaces = [["MediaRecorder", "EventTarget", 1]];
for (const realm of [window, document.getElementById('child').contentWindow]) {
for (const [name, parentName, length] of interfaces) {
const constructor = realm[name], parent = realm[parentName ?? 'Object'];
assert(typeof constructor === 'function' && constructor.name === name && constructor.length === length,
name + ' interface object');
const descriptor = Object.getOwnPropertyDescriptor(realm, name);
assert(descriptor.writable && descriptor.configurable && !descriptor.enumerable, name + ' global descriptor');
assert(Object.getPrototypeOf(constructor.prototype) === parent.prototype, name + ' prototype inheritance');
assert(Object.getPrototypeOf(constructor) === (parentName ? parent : realm.Function.prototype), name + ' interface inheritance');
assert(constructor.prototype.constructor === constructor, name + ' prototype constructor');
const tag = Object.getOwnPropertyDescriptor(constructor.prototype, Symbol.toStringTag);
assert(tag.value === name && !tag.writable && !tag.enumerable && tag.configurable, name + ' prototype tag');
let callError;
try { constructor(); } catch (error) { callError = error; }
assert(callError instanceof realm.TypeError, name + ' call requires new in the callee realm');
if (realm !== window) assert(!(callError instanceof TypeError), name + ' foreign TypeError');
// These are interface shells. Argument conversion and backend operations remain unavailable.
let constructionError;
try { Reflect.construct(constructor, []); } catch (error) { constructionError = error; }
assert(constructionError instanceof realm.DOMException && constructionError.name === 'NotSupportedError' && constructionError.code === 9,
name + ' unavailable construction is explicit in the callee realm');
if (realm !== window) assert(!(constructionError instanceof DOMException), name + ' foreign DOMException');
assert(realm[name] === constructor, name + ' materialization remains stable');
}
}
return 'ok';
})()
@@ -0,0 +1,19 @@
use super::*;
#[test]
fn shell_media_recorder_preserves_native_identity_and_callee_error_realms() {
for url in [
"https://shell-interfaces.test/",
"http://shell-interfaces.test/",
"http://localhost/",
] {
let mut vm = new_storage_page_task_executor_test_vm(url);
vm.eval("document.body.innerHTML = '<iframe id=child></iframe>'")
.unwrap();
assert_eq!(
vm.eval(include_str!("media_recorder_shell.js")).unwrap(),
"ok",
"{url}"
);
}
}
@@ -2146,6 +2146,7 @@ mod event_receivers;
mod headers_list;
mod http_fixture;
mod media_owner_playback_interfaces;
mod media_recorder_shell;
mod svg_animation_interfaces;
mod audio_event_interfaces;
@@ -297,6 +297,7 @@ interfaces! {
MediaList;
MediaQueryList: EventTarget;
MediaQueryListEvent: Event;
MediaRecorder: EventTarget;
MediaSession;
MediaSource: EventTarget;
MemoryInfo;