diff --git a/moli-renderer-v8/src/context_bootstrap/assets/constructor_templates.rs b/moli-renderer-v8/src/context_bootstrap/assets/constructor_templates.rs index 92a6363fa1..c5ea7875d3 100644 --- a/moli-renderer-v8/src/context_bootstrap/assets/constructor_templates.rs +++ b/moli-renderer-v8/src/context_bootstrap/assets/constructor_templates.rs @@ -94,6 +94,11 @@ pub(in crate::context_bootstrap) fn build_constructor_template<'s>( .length(0) .build(scope) } + ConstructorKind::Shell { length } => { + v8::FunctionTemplate::builder(shell_constructor_callback) + .length(i32::from(length)) + .build(scope) + } ConstructorKind::Event => { v8::FunctionTemplate::builder(moli_webapi_declare::web_api_constructor!( web_api_interfaces::Event, diff --git a/moli-renderer-v8/src/context_bootstrap/constructors.rs b/moli-renderer-v8/src/context_bootstrap/constructors.rs index d5e3fb7319..b760451fd2 100644 --- a/moli-renderer-v8/src/context_bootstrap/constructors.rs +++ b/moli-renderer-v8/src/context_bootstrap/constructors.rs @@ -21,7 +21,7 @@ mod elements; mod text_codecs; pub(super) use core::{ - event_target_constructor_callback, illegal_constructor_callback, + event_target_constructor_callback, illegal_constructor_callback, shell_constructor_callback, unsupported_constructor_callback, xpath_evaluator_constructor_callback, }; pub(super) use custom_elements_registry::{ diff --git a/moli-renderer-v8/src/context_bootstrap/constructors/core.rs b/moli-renderer-v8/src/context_bootstrap/constructors/core.rs index 460ec67a95..10d059d465 100644 --- a/moli-renderer-v8/src/context_bootstrap/constructors/core.rs +++ b/moli-renderer-v8/src/context_bootstrap/constructors/core.rs @@ -41,6 +41,18 @@ pub(in crate::context_bootstrap) fn illegal_constructor_callback( throw_type_error(scope, "Illegal constructor"); } +pub(in crate::context_bootstrap) fn shell_constructor_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'s, v8::Value>, +) { + if !args.is_construct_call() { + throw_type_error(scope, "Constructor requires the 'new' operator."); + return; + } + unsupported_constructor_callback(scope, args, rv); +} + pub(in crate::context_bootstrap) fn unsupported_constructor_callback( scope: &mut v8::PinScope<'_, '_>, _args: v8::FunctionCallbackArguments<'_>, 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 b352507499..6978388a28 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/metadata.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/metadata.rs @@ -54,6 +54,7 @@ pub(in crate::context_bootstrap) const WORKER_SHARED_INTERFACE_NAMES: &[&str] = "BroadcastChannel", "Notification", "ImageData", + "EncodedVideoChunk", "ImageBitmap", "CanvasGradient", "CanvasPattern", @@ -242,6 +243,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); @@ -400,6 +402,7 @@ pub(super) fn installation_for_spec(spec: ConstructorSpec) -> GlobalInstallation fn exposure_for_name(name: &str) -> ExposureSet { match name { "FileSystemSyncAccessHandle" => ExposureSet::DEDICATED_WORKER_ONLY, + "EncodedVideoChunk" => ExposureSet::WINDOW_AND_DEDICATED_WORKER, _ if WINDOW_DEDICATED_AND_SHARED_WORKER_INTERFACE_NAMES.contains(&name) => { ExposureSet::WINDOW_DEDICATED_AND_SHARED_WORKER } @@ -494,6 +497,32 @@ mod tests { assert_eq!(child.installation, GlobalInstallation::Lazy); } + #[test] + fn shell_encoded_video_chunk_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 ["EncodedVideoChunk"] { + 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, true), + (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 storage_policy_matches_window_and_worker_exposure() { 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 e2175cedbb..78ee21b556 100644 --- a/moli-renderer-v8/src/context_bootstrap/specs/registry.rs +++ b/moli-renderer-v8/src/context_bootstrap/specs/registry.rs @@ -923,6 +923,10 @@ const CONSTRUCTOR_SPECS_AFTER_STREAMS: &[ConstructorSpec] = &[ interface: web_api_interfaces::ImageBitmap::DESCRIPTOR, kind: ConstructorKind::Illegal, }, + ConstructorSpec { + interface: web_api_interfaces::EncodedVideoChunk::DESCRIPTOR, + kind: ConstructorKind::Shell { length: 1 }, + }, ConstructorSpec { interface: web_api_interfaces::RTCPeerConnection::DESCRIPTOR, kind: ConstructorKind::RtcPeerConnection, diff --git a/moli-renderer-v8/src/context_bootstrap/specs/types.rs b/moli-renderer-v8/src/context_bootstrap/specs/types.rs index d62842dc0b..8f0d77ad9d 100644 --- a/moli-renderer-v8/src/context_bootstrap/specs/types.rs +++ b/moli-renderer-v8/src/context_bootstrap/specs/types.rs @@ -2,6 +2,10 @@ pub(in crate::context_bootstrap) enum ConstructorKind { Illegal, Unsupported, + /// Interface exposure and inheritance without a constructor implementation. + Shell { + length: u8, + }, Event, AnimationEvent, TransitionEvent, diff --git a/moli-renderer-v8/src/script_vm/tests/encoded_video_chunk_shell.js b/moli-renderer-v8/src/script_vm/tests/encoded_video_chunk_shell.js new file mode 100644 index 0000000000..1dea2990be --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/encoded_video_chunk_shell.js @@ -0,0 +1,30 @@ +(() => { + const assert = (ok, message) => { if (!ok) throw Error(message); }; + const interfaces = [["EncodedVideoChunk", null, 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/encoded_video_chunk_shell.rs b/moli-renderer-v8/src/script_vm/tests/encoded_video_chunk_shell.rs new file mode 100644 index 0000000000..6de293cb03 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/encoded_video_chunk_shell.rs @@ -0,0 +1,20 @@ +use super::*; + +#[test] +fn shell_encoded_video_chunk_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!("encoded_video_chunk_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 c36aeb02e0..4097adaa0b 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -2141,6 +2141,7 @@ mod device_events; mod dom_elements; mod dom_xhr; mod element_click; +mod encoded_video_chunk_shell; mod event_receivers; mod headers_list; mod http_fixture; diff --git a/moli-renderer-v8/src/web_api_interfaces.rs b/moli-renderer-v8/src/web_api_interfaces.rs index 5c4acccc69..fc7e312c89 100644 --- a/moli-renderer-v8/src/web_api_interfaces.rs +++ b/moli-renderer-v8/src/web_api_interfaces.rs @@ -141,6 +141,7 @@ interfaces! { DynamicsCompressorNode; Element: Node; ElementInternals; + EncodedVideoChunk; ErrorEvent: Event; Event; EventCounts;