diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/metadata.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/metadata.rs index 6978388a28..5aa0368201 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/metadata.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/metadata.rs @@ -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(&[ diff --git a/moli-renderer-v8/src/context_bootstrap/specs/registry.rs b/moli-renderer-v8/src/context_bootstrap/specs/registry.rs index ed070c881a..4bb47140ad 100644 --- a/moli-renderer-v8/src/context_bootstrap/specs/registry.rs +++ b/moli-renderer-v8/src/context_bootstrap/specs/registry.rs @@ -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, diff --git a/moli-renderer-v8/src/script_vm/tests/media_recorder_shell.js b/moli-renderer-v8/src/script_vm/tests/media_recorder_shell.js new file mode 100644 index 0000000000..ae4904467d --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/media_recorder_shell.js @@ -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'; +})() diff --git a/moli-renderer-v8/src/script_vm/tests/media_recorder_shell.rs b/moli-renderer-v8/src/script_vm/tests/media_recorder_shell.rs new file mode 100644 index 0000000000..d753460439 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/media_recorder_shell.rs @@ -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 = ''") + .unwrap(); + assert_eq!( + vm.eval(include_str!("media_recorder_shell.js")).unwrap(), + "ok", + "{url}" + ); + } +} diff --git a/moli-renderer-v8/src/script_vm/tests/mod.rs b/moli-renderer-v8/src/script_vm/tests/mod.rs index 4097adaa0b..3072e1adda 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -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; diff --git a/moli-renderer-v8/src/web_api_interfaces.rs b/moli-renderer-v8/src/web_api_interfaces.rs index fc7e312c89..7c51ca130c 100644 --- a/moli-renderer-v8/src/web_api_interfaces.rs +++ b/moli-renderer-v8/src/web_api_interfaces.rs @@ -297,6 +297,7 @@ interfaces! { MediaList; MediaQueryList: EventTarget; MediaQueryListEvent: Event; + MediaRecorder: EventTarget; MediaSession; MediaSource: EventTarget; MemoryInfo;