mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
fix(media): derive capability configuration snapshots
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
use super::super::*;
|
||||
use crate::web_api_interfaces;
|
||||
use crate::{util::get_private_value, webidl};
|
||||
use crate::{
|
||||
util::get_private_value,
|
||||
webidl::{self, SnapshotPrototype, WebIdlDictionarySerialize},
|
||||
};
|
||||
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject};
|
||||
|
||||
const MEDIA_CAPABILITIES_SECURE_CONTEXT_SLOT: &str = "__moliMediaCapabilitiesSecureContext";
|
||||
@@ -69,16 +72,6 @@ enum MediaDecodingType {
|
||||
Webrtc,
|
||||
}
|
||||
|
||||
impl MediaDecodingType {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::File => "file",
|
||||
Self::MediaSource => "media-source",
|
||||
Self::Webrtc => "webrtc",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "MediaEncodingType")]
|
||||
enum MediaEncodingType {
|
||||
@@ -86,15 +79,6 @@ enum MediaEncodingType {
|
||||
Webrtc,
|
||||
}
|
||||
|
||||
impl MediaEncodingType {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Record => "record",
|
||||
Self::Webrtc => "webrtc",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "HdrMetadataType")]
|
||||
enum HdrMetadataType {
|
||||
@@ -106,16 +90,6 @@ enum HdrMetadataType {
|
||||
SmpteSt2094_40,
|
||||
}
|
||||
|
||||
impl HdrMetadataType {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::SmpteSt2086 => "smpteSt2086",
|
||||
Self::SmpteSt2094_10 => "smpteSt2094-10",
|
||||
Self::SmpteSt2094_40 => "smpteSt2094-40",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "ColorGamut")]
|
||||
enum ColorGamut {
|
||||
@@ -124,16 +98,6 @@ enum ColorGamut {
|
||||
Rec2020,
|
||||
}
|
||||
|
||||
impl ColorGamut {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Srgb => "srgb",
|
||||
Self::P3 => "p3",
|
||||
Self::Rec2020 => "rec2020",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "TransferFunction")]
|
||||
enum TransferFunction {
|
||||
@@ -142,16 +106,6 @@ enum TransferFunction {
|
||||
Hlg,
|
||||
}
|
||||
|
||||
impl TransferFunction {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Srgb => "srgb",
|
||||
Self::Pq => "pq",
|
||||
Self::Hlg => "hlg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "MediaKeysRequirement", rename_all = "kebab-case")]
|
||||
enum MediaKeysRequirement {
|
||||
@@ -160,100 +114,99 @@ enum MediaKeysRequirement {
|
||||
NotAllowed,
|
||||
}
|
||||
|
||||
impl MediaKeysRequirement {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Required => "required",
|
||||
Self::Optional => "optional",
|
||||
Self::NotAllowed => "not-allowed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "AudioConfiguration")]
|
||||
#[webidl(prefix = "AudioConfiguration", snapshot)]
|
||||
struct AudioConfiguration {
|
||||
#[webidl(required)]
|
||||
// Snapshot order preserves result keys independently of input conversion.
|
||||
#[webidl(required, snapshot_order = 0)]
|
||||
content_type: String,
|
||||
#[webidl(snapshot_order = 1)]
|
||||
channels: Option<String>,
|
||||
#[webidl(snapshot_order = 2)]
|
||||
bitrate: Option<u64>,
|
||||
#[webidl(snapshot_order = 3)]
|
||||
samplerate: Option<u32>,
|
||||
#[webidl(snapshot_order = 4)]
|
||||
spatial_rendering: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "VideoConfiguration")]
|
||||
#[webidl(prefix = "VideoConfiguration", snapshot)]
|
||||
struct VideoConfiguration {
|
||||
#[webidl(required)]
|
||||
#[webidl(required, snapshot_order = 0)]
|
||||
content_type: String,
|
||||
#[webidl(required)]
|
||||
#[webidl(required, snapshot_order = 1)]
|
||||
width: u32,
|
||||
#[webidl(required)]
|
||||
#[webidl(required, snapshot_order = 2)]
|
||||
height: u32,
|
||||
#[webidl(required)]
|
||||
#[webidl(required, snapshot_order = 3)]
|
||||
bitrate: u64,
|
||||
#[webidl(required, converter = "double")]
|
||||
#[webidl(required, converter = "double", snapshot_order = 4)]
|
||||
framerate: f64,
|
||||
#[webidl(snapshot_order = 5)]
|
||||
has_alpha_channel: Option<bool>,
|
||||
#[webidl(converter = "enum")]
|
||||
#[webidl(converter = "enum", snapshot_order = 6)]
|
||||
hdr_metadata_type: Option<HdrMetadataType>,
|
||||
#[webidl(converter = "enum")]
|
||||
#[webidl(converter = "enum", snapshot_order = 7)]
|
||||
color_gamut: Option<ColorGamut>,
|
||||
#[webidl(converter = "enum")]
|
||||
#[webidl(converter = "enum", snapshot_order = 8)]
|
||||
transfer_function: Option<TransferFunction>,
|
||||
#[webidl(snapshot_order = 9)]
|
||||
scalability_mode: Option<String>,
|
||||
#[webidl(snapshot_order = 10)]
|
||||
spatial_scalability: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "KeySystemTrackConfiguration")]
|
||||
#[webidl(prefix = "KeySystemTrackConfiguration", snapshot)]
|
||||
struct KeySystemTrackConfiguration {
|
||||
#[webidl(default = "")]
|
||||
#[webidl(default = "", snapshot_order = 0)]
|
||||
robustness: String,
|
||||
#[webidl(with = optional_nullable_dom_string_member)]
|
||||
#[webidl(with = optional_nullable_dom_string_member, snapshot_order = 1)]
|
||||
#[webidl(snapshot_kind = webidl::SnapshotKind::Nullable(&webidl::SnapshotKind::Scalar))]
|
||||
encryption_scheme: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "MediaCapabilitiesKeySystemConfiguration")]
|
||||
#[webidl(prefix = "MediaCapabilitiesKeySystemConfiguration", snapshot)]
|
||||
struct MediaCapabilitiesKeySystemConfiguration {
|
||||
#[webidl(required)]
|
||||
#[webidl(required, snapshot_order = 0)]
|
||||
key_system: String,
|
||||
#[webidl(default = "")]
|
||||
#[webidl(default = "", snapshot_order = 1)]
|
||||
init_data_type: String,
|
||||
#[webidl(converter = "enum", default = MediaKeysRequirement::Optional)]
|
||||
#[webidl(converter = "enum", default = MediaKeysRequirement::Optional, snapshot_order = 2)]
|
||||
distinctive_identifier: MediaKeysRequirement,
|
||||
#[webidl(converter = "enum", default = MediaKeysRequirement::Optional)]
|
||||
#[webidl(converter = "enum", default = MediaKeysRequirement::Optional, snapshot_order = 3)]
|
||||
persistent_state: MediaKeysRequirement,
|
||||
#[webidl(sequence)]
|
||||
#[webidl(sequence, snapshot_order = 4)]
|
||||
session_types: Option<Vec<String>>,
|
||||
#[webidl(dictionary)]
|
||||
#[webidl(dictionary, snapshot_order = 5)]
|
||||
audio: Option<KeySystemTrackConfiguration>,
|
||||
#[webidl(dictionary)]
|
||||
#[webidl(dictionary, snapshot_order = 6)]
|
||||
video: Option<KeySystemTrackConfiguration>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "MediaDecodingConfiguration")]
|
||||
#[webidl(prefix = "MediaDecodingConfiguration", snapshot)]
|
||||
struct MediaDecodingConfiguration {
|
||||
#[webidl(dictionary)]
|
||||
#[webidl(dictionary, snapshot_order = 0)]
|
||||
audio: Option<AudioConfiguration>,
|
||||
#[webidl(name = "keySystemConfiguration", dictionary)]
|
||||
#[webidl(name = "keySystemConfiguration", dictionary, snapshot_order = 3)]
|
||||
key_system_configuration: Option<MediaCapabilitiesKeySystemConfiguration>,
|
||||
#[webidl(name = "type", required, converter = "enum")]
|
||||
#[webidl(name = "type", required, converter = "enum", snapshot_order = 2)]
|
||||
decoding_type: MediaDecodingType,
|
||||
#[webidl(dictionary)]
|
||||
#[webidl(dictionary, snapshot_order = 1)]
|
||||
video: Option<VideoConfiguration>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "MediaEncodingConfiguration")]
|
||||
#[webidl(prefix = "MediaEncodingConfiguration", snapshot)]
|
||||
struct MediaEncodingConfiguration {
|
||||
#[webidl(dictionary)]
|
||||
#[webidl(dictionary, snapshot_order = 0)]
|
||||
audio: Option<AudioConfiguration>,
|
||||
#[webidl(name = "type", required, converter = "enum")]
|
||||
#[webidl(name = "type", required, converter = "enum", snapshot_order = 2)]
|
||||
encoding_type: MediaEncodingType,
|
||||
#[webidl(dictionary)]
|
||||
#[webidl(dictionary, snapshot_order = 1)]
|
||||
video: Option<VideoConfiguration>,
|
||||
}
|
||||
|
||||
@@ -326,7 +279,8 @@ fn media_capabilities_decoding_info_callback<'s>(
|
||||
"Encrypted media capabilities require a secure context",
|
||||
)
|
||||
} else {
|
||||
let Some(configuration_object) = decoding_configuration_object(scope, &configuration)
|
||||
let Some(configuration_object) =
|
||||
configuration.to_js_snapshot(scope, SnapshotPrototype::Ordinary)
|
||||
else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
@@ -350,7 +304,9 @@ fn media_capabilities_decoding_info_callback<'s>(
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(configuration_object) = decoding_configuration_object(scope, &configuration) else {
|
||||
let Some(configuration_object) =
|
||||
configuration.to_js_snapshot(scope, SnapshotPrototype::Ordinary)
|
||||
else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
@@ -388,7 +344,9 @@ fn media_capabilities_encoding_info_callback<'s>(
|
||||
set_rejected_promise(scope, &mut rv, reason);
|
||||
return;
|
||||
}
|
||||
let Some(configuration_object) = encoding_configuration_object(scope, &configuration) else {
|
||||
let Some(configuration_object) =
|
||||
configuration.to_js_snapshot(scope, SnapshotPrototype::Ordinary)
|
||||
else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
@@ -639,210 +597,3 @@ fn configuration_supported(
|
||||
!= moli_web_mime::MediaMimeSupport::Unsupported
|
||||
})
|
||||
}
|
||||
|
||||
fn decoding_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
configuration: &MediaDecodingConfiguration,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = media_configuration_object(
|
||||
scope,
|
||||
configuration.audio.as_ref(),
|
||||
configuration.video.as_ref(),
|
||||
)?;
|
||||
set_string_property(scope, object, "type", configuration.decoding_type.as_str())?;
|
||||
if let Some(key_system) = configuration.key_system_configuration.as_ref() {
|
||||
let value = key_system_configuration_object(scope, key_system)?;
|
||||
set_object_property(scope, object, "keySystemConfiguration", value)?;
|
||||
}
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn encoding_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
configuration: &MediaEncodingConfiguration,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = media_configuration_object(
|
||||
scope,
|
||||
configuration.audio.as_ref(),
|
||||
configuration.video.as_ref(),
|
||||
)?;
|
||||
set_string_property(scope, object, "type", configuration.encoding_type.as_str())?;
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn media_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
audio: Option<&AudioConfiguration>,
|
||||
video: Option<&VideoConfiguration>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = v8::Object::new(scope);
|
||||
if let Some(audio) = audio {
|
||||
let audio = audio_configuration_object(scope, audio)?;
|
||||
set_object_property(scope, object, "audio", audio)?;
|
||||
}
|
||||
if let Some(video) = video {
|
||||
let video = video_configuration_object(scope, video)?;
|
||||
set_object_property(scope, object, "video", video)?;
|
||||
}
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn audio_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
configuration: &AudioConfiguration,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = v8::Object::new(scope);
|
||||
set_string_property(scope, object, "contentType", &configuration.content_type)?;
|
||||
if let Some(value) = configuration.channels.as_deref() {
|
||||
set_string_property(scope, object, "channels", value)?;
|
||||
}
|
||||
if let Some(value) = configuration.bitrate {
|
||||
set_number_property(scope, object, "bitrate", value as f64)?;
|
||||
}
|
||||
if let Some(value) = configuration.samplerate {
|
||||
set_number_property(scope, object, "samplerate", f64::from(value))?;
|
||||
}
|
||||
if let Some(value) = configuration.spatial_rendering {
|
||||
set_bool_property(scope, object, "spatialRendering", value)?;
|
||||
}
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn video_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
configuration: &VideoConfiguration,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = v8::Object::new(scope);
|
||||
set_string_property(scope, object, "contentType", &configuration.content_type)?;
|
||||
set_number_property(scope, object, "width", f64::from(configuration.width))?;
|
||||
set_number_property(scope, object, "height", f64::from(configuration.height))?;
|
||||
set_number_property(scope, object, "bitrate", configuration.bitrate as f64)?;
|
||||
set_number_property(scope, object, "framerate", configuration.framerate)?;
|
||||
if let Some(value) = configuration.has_alpha_channel {
|
||||
set_bool_property(scope, object, "hasAlphaChannel", value)?;
|
||||
}
|
||||
if let Some(value) = configuration.hdr_metadata_type {
|
||||
set_string_property(scope, object, "hdrMetadataType", value.as_str())?;
|
||||
}
|
||||
if let Some(value) = configuration.color_gamut {
|
||||
set_string_property(scope, object, "colorGamut", value.as_str())?;
|
||||
}
|
||||
if let Some(value) = configuration.transfer_function {
|
||||
set_string_property(scope, object, "transferFunction", value.as_str())?;
|
||||
}
|
||||
if let Some(value) = configuration.scalability_mode.as_deref() {
|
||||
set_string_property(scope, object, "scalabilityMode", value)?;
|
||||
}
|
||||
if let Some(value) = configuration.spatial_scalability {
|
||||
set_bool_property(scope, object, "spatialScalability", value)?;
|
||||
}
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn key_system_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
configuration: &MediaCapabilitiesKeySystemConfiguration,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = v8::Object::new(scope);
|
||||
set_string_property(scope, object, "keySystem", &configuration.key_system)?;
|
||||
set_string_property(scope, object, "initDataType", &configuration.init_data_type)?;
|
||||
set_string_property(
|
||||
scope,
|
||||
object,
|
||||
"distinctiveIdentifier",
|
||||
configuration.distinctive_identifier.as_str(),
|
||||
)?;
|
||||
set_string_property(
|
||||
scope,
|
||||
object,
|
||||
"persistentState",
|
||||
configuration.persistent_state.as_str(),
|
||||
)?;
|
||||
if let Some(session_types) = configuration.session_types.as_ref() {
|
||||
let values = session_types
|
||||
.iter()
|
||||
.map(|value| v8_string(scope, value).map(v8::Local::into))
|
||||
.collect::<Option<Vec<v8::Local<'s, v8::Value>>>>()?;
|
||||
let array = v8::Array::new(scope, values.len() as i32);
|
||||
for (index, value) in values.into_iter().enumerate() {
|
||||
array.set_index(scope, index as u32, value)?;
|
||||
}
|
||||
set_value_property(scope, object, "sessionTypes", array.into())?;
|
||||
}
|
||||
if let Some(audio) = configuration.audio.as_ref() {
|
||||
let value = key_system_track_configuration_object(scope, audio)?;
|
||||
set_object_property(scope, object, "audio", value)?;
|
||||
}
|
||||
if let Some(video) = configuration.video.as_ref() {
|
||||
let value = key_system_track_configuration_object(scope, video)?;
|
||||
set_object_property(scope, object, "video", value)?;
|
||||
}
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn key_system_track_configuration_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
configuration: &KeySystemTrackConfiguration,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let object = v8::Object::new(scope);
|
||||
set_string_property(scope, object, "robustness", &configuration.robustness)?;
|
||||
if let Some(value) = configuration.encryption_scheme.as_ref() {
|
||||
let value = value
|
||||
.as_deref()
|
||||
.and_then(|value| v8_string(scope, value).map(v8::Local::into))
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
set_value_property(scope, object, "encryptionScheme", value)?;
|
||||
}
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn set_string_property(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
object: v8::Local<'_, v8::Object>,
|
||||
name: &str,
|
||||
value: &str,
|
||||
) -> Option<()> {
|
||||
let value = v8_string(scope, value)?;
|
||||
set_value_property(scope, object, name, value.into())
|
||||
}
|
||||
|
||||
fn set_number_property(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
object: v8::Local<'_, v8::Object>,
|
||||
name: &str,
|
||||
value: f64,
|
||||
) -> Option<()> {
|
||||
let value = v8::Number::new(scope, value);
|
||||
set_value_property(scope, object, name, value.into())
|
||||
}
|
||||
|
||||
fn set_bool_property(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
object: v8::Local<'_, v8::Object>,
|
||||
name: &str,
|
||||
value: bool,
|
||||
) -> Option<()> {
|
||||
let value = v8::Boolean::new(scope, value);
|
||||
set_value_property(scope, object, name, value.into())
|
||||
}
|
||||
|
||||
fn set_object_property(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
object: v8::Local<'_, v8::Object>,
|
||||
name: &str,
|
||||
value: v8::Local<'_, v8::Object>,
|
||||
) -> Option<()> {
|
||||
set_value_property(scope, object, name, value.into())
|
||||
}
|
||||
|
||||
fn set_value_property(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
object: v8::Local<'_, v8::Object>,
|
||||
name: &str,
|
||||
value: v8::Local<'_, v8::Value>,
|
||||
) -> Option<()> {
|
||||
object
|
||||
.set(scope, v8_string(scope, name)?.into(), value)
|
||||
.filter(|set| *set)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
globalThis.__mediaCapabilitiesSnapshotResult = 'pending';
|
||||
(async () => {
|
||||
const check = (ok, message) => { if (!ok) throw new Error(message); };
|
||||
const capabilities = navigator.mediaCapabilities;
|
||||
const audio = () => ({contentType: 'audio/webm; codecs="opus"', channels: '2', bitrate: 128000, samplerate: 48000, spatialRendering: false});
|
||||
const video = () => ({contentType: 'video/webm; codecs="vp09.00.10.08"', width: 320, height: 240, bitrate: 200000, framerate: 24, hasAlphaChannel: false, hdrMetadataType: 'smpteSt2086', colorGamut: 'srgb', transferFunction: 'srgb'});
|
||||
function dataProperties(value, realm) {
|
||||
if (value === null || typeof value !== 'object') return;
|
||||
check(Object.getPrototypeOf(value) === (Array.isArray(value) ? realm.Array.prototype : realm.Object.prototype), 'result realm');
|
||||
for (const key of Object.keys(value)) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
||||
check(Object.hasOwn(descriptor, 'value') && descriptor.writable && descriptor.enumerable && descriptor.configurable, 'own data property: ' + key);
|
||||
dataProperties(descriptor.value, realm);
|
||||
}
|
||||
}
|
||||
for (const method of ['decodingInfo', 'encodingInfo']) {
|
||||
let reads = '';
|
||||
const child = {};
|
||||
for (const [key, value] of Object.entries(audio())) Object.defineProperty(child, key, {configurable: true, get() { reads += key + ','; return value; }});
|
||||
const input = {
|
||||
get audio() { reads += 'audio,'; return child; },
|
||||
get type() { reads += 'type,'; return method === 'decodingInfo' ? 'file' : 'record'; },
|
||||
get video() { reads += 'video,'; return undefined; }
|
||||
};
|
||||
const promise = capabilities[method](input);
|
||||
check(promise instanceof Promise, 'query returns a Promise');
|
||||
check(reads === 'audio,bitrate,channels,contentType,samplerate,spatialRendering,type,video,', 'lexical conversion order');
|
||||
for (const key of Object.keys(input)) Object.defineProperty(input, key, {get() { throw new Error('input read after conversion'); }});
|
||||
for (const key of Object.getOwnPropertyNames(child)) Object.defineProperty(child, key, {get() { throw new Error('nested input read after conversion'); }});
|
||||
const result = await promise;
|
||||
check(result.configuration.audio.channels === '2', 'converted nested result');
|
||||
check(Object.keys(result.configuration).join() === 'audio,type', 'configuration output order');
|
||||
check(Object.keys(result.configuration.audio).join() === 'contentType,channels,bitrate,samplerate,spatialRendering', 'audio output order');
|
||||
dataProperties(result, window);
|
||||
const stable = Object.freeze({type: method === 'decodingInfo' ? 'file' : 'record', audio: Object.freeze(audio())});
|
||||
const first = await capabilities[method](stable);
|
||||
const second = await capabilities[method](stable);
|
||||
check(first !== second && first.configuration !== second.configuration && first.configuration.audio !== second.configuration.audio, 'fresh nested output');
|
||||
first.configuration.audio.channels = 'changed';
|
||||
check(second.configuration.audio.channels === '2' && stable.audio.channels === '2', 'no input or result alias');
|
||||
Object.freeze(second.configuration.audio);
|
||||
check((await capabilities[method](stable)).configuration.audio.channels === '2', 'frozen prior result');
|
||||
}
|
||||
for (const scheme of [undefined, null, 'cenc']) {
|
||||
const track = {};
|
||||
if (scheme !== undefined) track.encryptionScheme = scheme;
|
||||
const input = {type: 'file', audio: audio(), keySystemConfiguration: {keySystem: 'org.w3.clearkey', sessionTypes: ['temporary', 'temporary'], audio: track}};
|
||||
const promise = capabilities.decodingInfo(input);
|
||||
input.keySystemConfiguration.sessionTypes[0] = 'changed';
|
||||
const result = await promise;
|
||||
const key = result.configuration.keySystemConfiguration;
|
||||
check(Object.keys(result.configuration).join() === 'audio,type,keySystemConfiguration', 'decoding output order');
|
||||
check(key.distinctiveIdentifier === 'optional' && key.persistentState === 'optional' && key.initDataType === '', 'dictionary defaults');
|
||||
check(key.audio.robustness === '', 'nested dictionary default');
|
||||
check(Object.hasOwn(key.audio, 'encryptionScheme') === (scheme !== undefined), 'absent versus nullable member');
|
||||
if (scheme !== undefined) check(key.audio.encryptionScheme === scheme, 'nullable member value');
|
||||
check(key.sessionTypes.join() === 'temporary,temporary' && key.sessionTypes !== input.keySystemConfiguration.sessionTypes, 'copied sequence preserves duplicates');
|
||||
dataProperties(result, window);
|
||||
}
|
||||
const names = ['audio', 'video', 'type', 'configuration', 'contentType', 'width', 'sessionTypes', 'keySystemConfiguration', 'encryptionScheme'];
|
||||
const marker = {};
|
||||
for (const mode of ['count', 'throw', 'readonly', 'index']) {
|
||||
const input = {type: 'file', audio: audio(), video: video(), keySystemConfiguration: {keySystem: 'org.w3.clearkey', sessionTypes: ['temporary', 'temporary'], audio: {encryptionScheme: null}}};
|
||||
const expected = JSON.stringify(input);
|
||||
let writes = 0;
|
||||
let result;
|
||||
try {
|
||||
if (mode !== 'index') for (const name of names) Object.defineProperty(Object.prototype, name, {
|
||||
configurable: true,
|
||||
get() { return undefined; },
|
||||
...(mode === 'readonly' ? {} : {set() { writes++; if (mode === 'throw') throw marker; }})
|
||||
});
|
||||
if (mode === 'index') Object.defineProperty(Array.prototype, '0', {configurable: true, get() { return undefined; }, set() { writes++; throw marker; }});
|
||||
result = await capabilities.decodingInfo(input);
|
||||
} finally {
|
||||
if (mode !== 'index') for (const name of names) delete Object.prototype[name];
|
||||
if (mode === 'index') delete Array.prototype[0];
|
||||
}
|
||||
check(writes === 0, 'prototype setter invoked: ' + mode);
|
||||
check(result.configuration.audio.contentType === input.audio.contentType && result.configuration.video.width === 320, 'pollution cannot remove output');
|
||||
check(result.configuration.keySystemConfiguration.sessionTypes.join() === 'temporary,temporary', 'indexed own data');
|
||||
check(JSON.stringify(input) === expected, 'input remains unchanged');
|
||||
dataProperties(result, window);
|
||||
}
|
||||
const frame = document.createElement('iframe');
|
||||
document.body.append(frame);
|
||||
const foreign = frame.contentWindow;
|
||||
for (const realm of [window, foreign]) {
|
||||
const receiver = realm === window ? foreign.navigator.mediaCapabilities : capabilities;
|
||||
const promise = realm.MediaCapabilities.prototype.decodingInfo.call(receiver, {type: 'file', audio: audio()});
|
||||
check(Object.getPrototypeOf(promise) === realm.Promise.prototype, 'callee Promise realm');
|
||||
const result = await promise;
|
||||
dataProperties(result, realm);
|
||||
check(result.configuration.audio.channels === '2', 'foreign receiver output');
|
||||
}
|
||||
frame.remove();
|
||||
return 'ok';
|
||||
})().then(value => globalThis.__mediaCapabilitiesSnapshotResult = value,
|
||||
error => globalThis.__mediaCapabilitiesSnapshotResult = String(error));
|
||||
@@ -1,5 +1,15 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn media_capabilities_dictionary_snapshots_preserve_conversion_and_define_own_data() {
|
||||
let mut vm =
|
||||
new_storage_page_task_executor_test_vm("https://media-capabilities-snapshot.test/");
|
||||
vm.eval(include_str!("media_capabilities_snapshot.js"))
|
||||
.unwrap();
|
||||
vm.eval("0").unwrap();
|
||||
assert_eq!(vm.eval("__mediaCapabilitiesSnapshotResult").unwrap(), "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_capabilities_nested_dictionaries_validate_null_before_later_getters() {
|
||||
let mut vm = new_storage_test_vm("https://media-capabilities-dictionary.test/");
|
||||
|
||||
Reference in New Issue
Block a user