feat(webcodecs): expose the native EncodedVideoChunk interface shell

This commit is contained in:
ldm0
2026-10-05 02:33:03 +08:00
committed by Donough Liu
parent d015bb48ce
commit d2e819fa90
10 changed files with 107 additions and 1 deletions
@@ -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,
@@ -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::{
@@ -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<'_>,
@@ -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(&[
@@ -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,
@@ -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,
@@ -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';
})()
@@ -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 = '<iframe id=child></iframe>'")
.unwrap();
assert_eq!(
vm.eval(include_str!("encoded_video_chunk_shell.js"))
.unwrap(),
"ok",
"{url}"
);
}
}
@@ -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;
@@ -141,6 +141,7 @@ interfaces! {
DynamicsCompressorNode;
Element: Node;
ElementInternals;
EncodedVideoChunk;
ErrorEvent: Event;
Event;
EventCounts;