From 0206697d36538e1ae877e3cdb64c3856a36da2d3 Mon Sep 17 00:00:00 2001 From: Psychpsyo Date: Wed, 12 Feb 2025 17:29:29 +0100 Subject: [PATCH] LibWeb: Implement MediaCapabilities.decodingInfo() --- .../EncryptedMediaExtensions.h | 18 + .../EncryptedMediaExtensions.idl | 6 + Libraries/LibWeb/Forward.h | 17 + Libraries/LibWeb/HTML/EventLoop/Task.h | 3 + .../MediaCapabilities.cpp | 214 ++++++++ .../MediaCapabilitiesAPI/MediaCapabilities.h | 111 ++++ .../MediaCapabilities.idl | 110 +++- .../media-capabilities/decodingInfo.any.txt | 46 ++ .../media-capabilities/decodingInfo.any.html | 15 + .../media-capabilities/decodingInfo.any.js | 508 ++++++++++++++++++ 10 files changed, 1047 insertions(+), 1 deletion(-) create mode 100644 Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.h create mode 100644 Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.idl create mode 100644 Tests/LibWeb/Text/expected/wpt-import/media-capabilities/decodingInfo.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.js diff --git a/Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.h b/Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.h new file mode 100644 index 00000000000..c3b5f4cb954 --- /dev/null +++ b/Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025, Psychpsyo + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Web::Bindings { + +// https://w3c.github.io/encrypted-media/#dom-mediakeysrequirement +enum class MediaKeysRequirement { + Required, + Optional, + NotAllowed +}; + +} diff --git a/Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.idl b/Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.idl new file mode 100644 index 00000000000..12ff2ff748a --- /dev/null +++ b/Libraries/LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.idl @@ -0,0 +1,6 @@ +// https://w3c.github.io/encrypted-media/#dom-mediakeysrequirement +enum MediaKeysRequirement { + "required", + "optional", + "not-allowed" +}; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 01106a0a39f..923a7926cc0 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -64,9 +64,14 @@ enum class CanPlayTypeResult; enum class CanvasFillRule; enum class CanvasTextAlign; enum class CanvasTextBaseline; +enum class ColorGamut; enum class DOMParserSupportedType; enum class EndingType; +enum class HdrMetadataType; enum class ImageSmoothingQuality; +enum class MediaDecodingType; +enum class MediaEncodingType; +enum class MediaKeysRequirement; enum class ReadableStreamReaderMode; enum class ReferrerPolicy; enum class RequestCache; @@ -79,6 +84,7 @@ enum class RequestRedirect; enum class ResizeObserverBoxOptions; enum class ResponseType; enum class TextTrackKind; +enum class TransferFunction; enum class XMLHttpRequestResponseType; } @@ -656,6 +662,17 @@ class MathMLElement; namespace Web::MediaCapabilitiesAPI { class MediaCapabilities; + +struct AudioConfiguration; +struct KeySystemTrackConfiguration; +struct MediaCapabilitiesDecodingInfo; +struct MediaCapabilitiesEncodingInfo; +struct MediaCapabilitiesInfo; +struct MediaCapabilitiesKeySystemConfiguration; +struct MediaConfiguration; +struct MediaDecodingConfiguration; +struct MediaEncodingConfiguration; +struct VideoConfiguration; } namespace Web::MediaSourceExtensions { diff --git a/Libraries/LibWeb/HTML/EventLoop/Task.h b/Libraries/LibWeb/HTML/EventLoop/Task.h index ed7e6be651c..8d61fbf2dbd 100644 --- a/Libraries/LibWeb/HTML/EventLoop/Task.h +++ b/Libraries/LibWeb/HTML/EventLoop/Task.h @@ -71,6 +71,9 @@ public: // https://websockets.spec.whatwg.org/#websocket-task-source WebSocket, + // https://w3c.github.io/media-capabilities/#media-capabilities-task-source + MediaCapabilities, + // !!! IMPORTANT: Keep this field last! // This serves as the base value of all unique task sources. // Some elements, such as the HTMLMediaElement, must have a unique task source per instance. diff --git a/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.cpp b/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.cpp index 40198bf43ef..854a3be8443 100644 --- a/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.cpp +++ b/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.cpp @@ -1,16 +1,114 @@ /* * Copyright (c) 2024, Jamie Mansfield + * Copyright (c) 2025, Psychpsyo * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include +#include #include #include +#include namespace Web::MediaCapabilitiesAPI { +// https://w3c.github.io/media-capabilities/#valid-mediaconfiguration +bool MediaConfiguration::is_valid_media_configuration() const +{ + // For a MediaConfiguration to be a valid MediaConfiguration, all of the following conditions MUST be true: + + // 1. audio and/or video MUST exist. + if (!audio.has_value() && !video.has_value()) + return false; + + // 2. audio MUST be a valid audio configuration if it exists. + if (audio.has_value() && !audio.value().is_valid_audio_configuration()) + return false; + + // 3. video MUST be a valid video configuration if it exists. + if (video.has_value() && !video.value().is_valid_video_configuration()) + return false; + + return true; +} + +// https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration +bool MediaDecodingConfiguration::is_valid_media_decoding_configuration() const +{ + // For a MediaDecodingConfiguration to be a valid MediaDecodingConfiguration, all of the following + // conditions MUST be true: + + // 1. It MUST be a valid MediaConfiguration. + if (!is_valid_media_configuration()) + return false; + + // 2. If keySystemConfiguration exists: + // FIXME: Implement this. + + return true; +} + +// https://w3c.github.io/media-capabilities/#valid-audio-mime-type +bool is_valid_audio_mime_type(StringView string) +{ + // A valid audio MIME type is a string that is a valid media MIME type and for which the type per [RFC9110] is + // either audio or application. + auto mime_type = MimeSniff::MimeType::parse(string); + if (!mime_type.has_value()) + return false; + return mime_type->type() == "audio"sv || mime_type->type() == "application"sv; +} + +// https://w3c.github.io/media-capabilities/#valid-video-mime-type +bool is_valid_video_mime_type(StringView string) +{ + // A valid video MIME type is a string that is a valid media MIME type and for which the type per [RFC9110] is + // either video or application. + auto mime_type = MimeSniff::MimeType::parse(string); + if (!mime_type.has_value()) + return false; + return mime_type->type() == "video"sv || mime_type->type() == "application"sv; +} + +// https://w3c.github.io/media-capabilities/#valid-video-configuration +bool VideoConfiguration::is_valid_video_configuration() const +{ + // To check if a VideoConfiguration configuration is a valid video configuration, the following steps MUST be + // run: + + // 1. If configuration’s contentType is not a valid video MIME type, return false and abort these steps. + if (!is_valid_video_mime_type(content_type)) + return false; + + // 2. If framerate is not finite or is not greater than 0, return false and abort these steps. + if (!isfinite(framerate) || framerate <= 0) + return false; + + // 3. If an optional member is specified for a MediaDecodingType or MediaEncodingType to which it’s not + // applicable, return false and abort these steps. See applicability rules in the member definitions below. + // FIXME: Implement this. + + // 4. Return true. + return true; +} + +// https://w3c.github.io/media-capabilities/#valid-video-configuration +bool AudioConfiguration::is_valid_audio_configuration() const +{ + // To check if a AudioConfiguration configuration is a valid audio configuration, the following steps MUST be + // run: + + // 1. If configuration’s contentType is not a valid audio MIME type, return false and abort these steps. + if (!is_valid_audio_mime_type(content_type)) + return false; + + // 2. Return true. + return true; +} + GC_DEFINE_ALLOCATOR(MediaCapabilities); GC::Ref MediaCapabilities::create(JS::Realm& realm) @@ -29,4 +127,120 @@ void MediaCapabilities::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaCapabilities); } +// https://w3c.github.io/media-capabilities/#queue-a-media-capabilities-task +void queue_a_media_capabilities_task(JS::VM& vm, Function steps) +{ + // When an algorithm queues a Media Capabilities task T, the user agent MUST queue a global task T on the + // media capabilities task source using the global object of the the current realm record. + queue_global_task(HTML::Task::Source::MediaCapabilities, vm.current_realm()->global_object(), GC::create_function(vm.current_realm()->heap(), move(steps))); +} + +// https://w3c.github.io/media-capabilities/#dom-mediacapabilities-decodinginfo +GC::Ref MediaCapabilities::decoding_info(MediaDecodingConfiguration const& configuration) +{ + auto& realm = this->realm(); + // The decodingInfo() method MUST run the following steps: + + // 1. If configuration is not a valid MediaDecodingConfiguration, return a Promise rejected with a newly created + // TypeError. + if (!configuration.is_valid_media_decoding_configuration()) { + return WebIDL::create_rejected_promise_from_exception(realm, vm().throw_completion("The given configuration is not a valid MediaDecodingConfiguration"sv)); + } + + // 2. If configuration.keySystemConfiguration exists, run the following substeps: + // FIXME: Implement this. + + // 3. Let p be a new Promise. + auto p = WebIDL::create_promise(realm); + + // 4. Run the following steps in parallel: + auto& vm = this->vm(); + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&vm, &realm, p, configuration]() mutable { + HTML::TemporaryExecutionContext context(realm); + // 1. Run the Create a MediaCapabilitiesDecodingInfo algorithm with configuration. + auto result = create_a_media_capabilities_decoding_info(configuration).to_object(realm); + + // Queue a Media Capabilities task to resolve p with its result. + queue_a_media_capabilities_task(vm, [&realm, p, result] { + HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + WebIDL::resolve_promise(realm, p, JS::Value(result)); + }); + })); + + // 5. Return p. + return p; +} + +// https://w3c.github.io/media-capabilities/#create-a-mediacapabilitiesdecodinginfo +MediaCapabilitiesDecodingInfo create_a_media_capabilities_decoding_info(MediaDecodingConfiguration configuration) +{ + // 1. Let info be a new MediaCapabilitiesDecodingInfo instance. Unless stated otherwise, reading and + // writing apply to info for the next steps. + MediaCapabilitiesDecodingInfo info = {}; + + // 2. Set configuration to be a new MediaDecodingConfiguration. For every property in configuration create + // a new property with the same name and value in configuration. + info.configuration = { { configuration.video, configuration.audio }, configuration.type, configuration.key_system_configuration }; + + // 3. If configuration.keySystemConfiguration exists: + if (false) { + // FIXME: Implement this. + } + // 4. Otherwise, run the following steps: + else { + // 1. Set keySystemAccess to null. + // FIXME: Implement this. + + // 2. If the user agent is able to decode the media represented by configuration, set supported to true. + // 3. Otherwise, set it to false. + info.supported = is_able_to_decode_media(configuration); + } + + // 5. If the user agent is able to decode the media represented by configuration at the indicated framerate without + // dropping frames, set smooth to true. Otherwise set it to false. + // FIXME: Actually check this. + info.smooth = false; + + // 6. If the user agent is able to decode the media represented by configuration in a power efficient manner, set + // powerEfficient to true. Otherwise set it to false. + // FIXME: Actually check this... somehow. + info.power_efficient = false; + + // 7. Return info. + return info; +} + +bool is_able_to_decode_media(MediaDecodingConfiguration configuration) +{ + if (configuration.type != Bindings::MediaDecodingType::MediaSource) + return false; + + if (configuration.video.has_value()) { + auto video_mime_type = MimeSniff::MimeType::parse(configuration.video.value().content_type); + if (!video_mime_type.has_value() || !Web::HTML::HTMLMediaElement::supported_video_subtypes.contains_slow(video_mime_type->subtype())) + return false; + } + + if (configuration.audio.has_value()) { + auto audio_mime_type = MimeSniff::MimeType::parse(configuration.audio.value().content_type); + if (!audio_mime_type.has_value() || !Web::HTML::HTMLMediaElement::supported_audio_subtypes.contains_slow(audio_mime_type->subtype())) + return false; + } + + return true; +} + +GC::Ref MediaCapabilitiesDecodingInfo::to_object(JS::Realm& realm) +{ + auto object = JS::Object::create(realm, realm.intrinsics().object_prototype()); + + // FIXME: Also include configuration in this object. + + MUST(object->create_data_property("supported", JS::BooleanObject::create(realm, supported))); + MUST(object->create_data_property("smooth", JS::BooleanObject::create(realm, smooth))); + MUST(object->create_data_property("powerEfficent", JS::BooleanObject::create(realm, power_efficient))); + + return object; +} + } diff --git a/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.h b/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.h index 13006bec44d..ad31b681a0b 100644 --- a/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.h +++ b/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Jamie Mansfield + * Copyright (c) 2025, Psychpsyo * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,9 +8,100 @@ #pragma once #include +#include namespace Web::MediaCapabilitiesAPI { +// https://w3c.github.io/media-capabilities/#dictdef-videoconfiguration +struct VideoConfiguration { + String content_type; + WebIDL::UnsignedLong width; + WebIDL::UnsignedLong height; + Optional bitrate; + double framerate; + Optional has_alpha_channel; + Optional hdr_metadata_type; + Optional color_gamut; + Optional transfer_function; + Optional scalability_mode; + Optional spatial_scalability; + + // https://w3c.github.io/media-capabilities/#valid-video-configuration + bool is_valid_video_configuration() const; +}; + +// https://w3c.github.io/media-capabilities/#dictdef-audioconfiguration +struct AudioConfiguration { + String content_type; + Optional channels; + Optional bitrate; + Optional samplerate; + Optional spatial_rendering; + + // https://w3c.github.io/media-capabilities/#valid-audio-configuration + bool is_valid_audio_configuration() const; +}; + +// https://w3c.github.io/media-capabilities/#dictdef-mediaconfiguration +struct MediaConfiguration { + Optional video; + Optional audio; + + // https://w3c.github.io/media-capabilities/#valid-mediaconfiguration + bool is_valid_media_configuration() const; +}; + +// https://w3c.github.io/media-capabilities/#keysystemtrackconfiguration +struct KeySystemTrackConfiguration { + String robustness; + Optional encryption_scheme; +}; + +// https://w3c.github.io/media-capabilities/#mediacapabilitieskeysystemconfiguration +struct MediaCapabilitiesKeySystemConfiguration { + String key_system; + String init_data_type; + Bindings::MediaKeysRequirement distinctive_identifier; + Bindings::MediaKeysRequirement persistent_state; + Optional> session_types; + Optional audio; + Optional video; +}; + +// https://w3c.github.io/media-capabilities/#dictdef-mediadecodingconfiguration +struct MediaDecodingConfiguration : public MediaConfiguration { + Bindings::MediaDecodingType type; + Optional key_system_configuration; + + // https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration + bool is_valid_media_decoding_configuration() const; +}; + +// https://w3c.github.io/media-capabilities/#dictdef-mediaencodingconfiguration +struct MediaEncodingConfiguration : public MediaConfiguration { + Bindings::MediaEncodingType type; +}; + +// https://w3c.github.io/media-capabilities/#media-capabilities-info +struct MediaCapabilitiesInfo { + bool supported; + bool smooth; + bool power_efficient; +}; + +// https://w3c.github.io/media-capabilities/#dictdef-mediacapabilitiesdecodinginfo +struct MediaCapabilitiesDecodingInfo : public MediaCapabilitiesInfo { + MediaDecodingConfiguration configuration; + Optional key_system_configuration; + + GC::Ref to_object(JS::Realm&); +}; + +struct MediaCapabilitiesEncodingInfo : public MediaCapabilitiesInfo { + Optional configuration; +}; + +// https://w3c.github.io/media-capabilities/#media-capabilities-interface class MediaCapabilities final : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(MediaCapabilities, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(MediaCapabilities); @@ -18,10 +110,29 @@ public: static GC::Ref create(JS::Realm&); virtual ~MediaCapabilities() override = default; + // https://w3c.github.io/media-capabilities/#dom-mediacapabilities-decodinginfo + GC::Ref decoding_info(MediaDecodingConfiguration const&); + private: MediaCapabilities(JS::Realm&); virtual void initialize(JS::Realm&) override; + + // https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration + bool is_valid_media_decoding_configuration() const; }; +// https://w3c.github.io/media-capabilities/#queue-a-media-capabilities-task +void queue_a_media_capabilities_task(JS::VM& vm, Function); + +// https://w3c.github.io/media-capabilities/#create-a-mediacapabilitiesdecodinginfo +MediaCapabilitiesDecodingInfo create_a_media_capabilities_decoding_info(MediaDecodingConfiguration); + +bool is_able_to_decode_media(MediaDecodingConfiguration configuration); + +// https://w3c.github.io/media-capabilities/#valid-audio-mime-type +bool is_valid_audio_mime_type(StringView); +// https://w3c.github.io/media-capabilities/#valid-video-mime-type +bool is_valid_video_mime_type(StringView); + } diff --git a/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.idl b/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.idl index e47203be647..6a0957e9e7a 100644 --- a/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.idl +++ b/Libraries/LibWeb/MediaCapabilitiesAPI/MediaCapabilities.idl @@ -1,6 +1,114 @@ +#import + +// https://w3c.github.io/media-capabilities/#mediaconfiguration +dictionary MediaConfiguration { + VideoConfiguration video; + AudioConfiguration audio; +}; + +dictionary MediaDecodingConfiguration : MediaConfiguration { + required MediaDecodingType type; + MediaCapabilitiesKeySystemConfiguration keySystemConfiguration; +}; + +dictionary MediaEncodingConfiguration : MediaConfiguration { + required MediaEncodingType type; +}; + +// https://w3c.github.io/media-capabilities/#enumdef-mediadecodingtype +enum MediaDecodingType { + "file", + "media-source", + "webrtc" +}; + +// https://w3c.github.io/media-capabilities/#enumdef-mediaencodingtype +enum MediaEncodingType { + "record", + "webrtc" +}; + +// https://w3c.github.io/media-capabilities/#dictdef-videoconfiguration +dictionary VideoConfiguration { + required DOMString contentType; + required unsigned long width; + required unsigned long height; + required unsigned long long bitrate; + required double framerate; + boolean hasAlphaChannel; + HdrMetadataType hdrMetadataType; + ColorGamut colorGamut; + TransferFunction transferFunction; + DOMString scalabilityMode; + boolean spatialScalability; +}; + +// https://w3c.github.io/media-capabilities/#enumdef-hdrmetadatatype +enum HdrMetadataType { + "smpteSt2086", + "smpteSt2094-10", + "smpteSt2094-40" +}; + +// https://w3c.github.io/media-capabilities/#enumdef-colorgamut +enum ColorGamut { + "srgb", + "p3", + "rec2020" +}; + +// https://w3c.github.io/media-capabilities/#enumdef-transferfunction +enum TransferFunction { + "srgb", + "pq", + "hlg" +}; + +// https://w3c.github.io/media-capabilities/#dictdef-audioconfiguration +dictionary AudioConfiguration { + required DOMString contentType; + DOMString channels; + unsigned long long bitrate; + unsigned long samplerate; + boolean spatialRendering; +}; + +// https://w3c.github.io/media-capabilities/#mediacapabilitieskeysystemconfiguration +dictionary MediaCapabilitiesKeySystemConfiguration { + required DOMString keySystem; + DOMString initDataType = ""; + MediaKeysRequirement distinctiveIdentifier = "optional"; + MediaKeysRequirement persistentState = "optional"; + sequence sessionTypes; + KeySystemTrackConfiguration audio; + KeySystemTrackConfiguration video; +}; + +// https://w3c.github.io/media-capabilities/#keysystemtrackconfiguration +dictionary KeySystemTrackConfiguration { + DOMString robustness = ""; + DOMString? encryptionScheme = null; +}; + +// https://w3c.github.io/media-capabilities/#media-capabilities-info +dictionary MediaCapabilitiesInfo { + required boolean supported; + required boolean smooth; + required boolean powerEfficient; +}; + +dictionary MediaCapabilitiesDecodingInfo : MediaCapabilitiesInfo { + [FIXME, required] MediaKeySystemAccess keySystemAccess; + MediaDecodingConfiguration configuration; +}; + +dictionary MediaCapabilitiesEncodingInfo : MediaCapabilitiesInfo { + MediaEncodingConfiguration configuration; +}; + // https://w3c.github.io/media-capabilities/#mediacapabilities [Exposed=(Window, Worker)] interface MediaCapabilities { - [FIXME, NewObject] Promise decodingInfo(MediaDecodingConfiguration configuration); + [NewObject] Promise decodingInfo(MediaDecodingConfiguration configuration); [FIXME, NewObject] Promise encodingInfo(MediaEncodingConfiguration configuration); }; diff --git a/Tests/LibWeb/Text/expected/wpt-import/media-capabilities/decodingInfo.any.txt b/Tests/LibWeb/Text/expected/wpt-import/media-capabilities/decodingInfo.any.txt new file mode 100644 index 00000000000..9668d2db9fa --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/media-capabilities/decodingInfo.any.txt @@ -0,0 +1,46 @@ +Harness status: OK + +Found 40 tests + +22 Pass +18 Fail +Pass Test that decodingInfo rejects if it doesn't get a configuration +Pass Test that decodingInfo rejects if the MediaConfiguration isn't valid +Pass Test that decodingInfo rejects if the MediaConfiguration does not have a type +Pass Test that decodingInfo rejects if the configuration doesn't have an audio or video field +Pass Test that decodingInfo rejects if the video configuration has a negative framerate +Pass Test that decodingInfo rejects if the video configuration has a framerate set to 0 +Pass Test that decodingInfo rejects if the video configuration has a framerate set to Infinity +Pass Test that decodingInfo rejects if the video configuration contentType doesn't parse +Fail Test that decodingInfo rejects if the video configuration contentType is not a valid MIME type string +Pass Test that decodingInfo rejects if the video configuration contentType isn't of type video +Fail Test that decodingInfo rejects if the video configuration contentType is of type audio +Fail Test that decodingInfo rejects if the audio configuration contentType is of type video +Fail Test that decodingInfo rejects if the video configuration contentType has more than one parameter +Fail Test that decodingInfo rejects if the video configuration contentType has one parameter that isn't codecs +Fail Test that decodingInfo rejects if the video configuration contentType does not imply a single media codec but has no codecs parameter +Fail Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates multiple video codecs +Fail Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates both an audio and a video codec +Pass Test that decodingInfo rejects framerate in the form of x/y +Pass Test that decodingInfo rejects framerate in the form of x/0 +Pass Test that decodingInfo rejects framerate in the form of 0/y +Pass Test that decodingInfo rejects framerate in the form of -x/y +Pass Test that decodingInfo rejects framerate in the form of x/-y +Pass Test that decodingInfo rejects framerate in the form of x/ +Pass Test that decodingInfo rejects framerate with trailing unallowed characters +Pass Test that decodingInfo rejects if the audio configuration contentType doesn't parse +Fail Test that decodingInfo rejects if the audio configuration contentType is not a valid MIME type string +Pass Test that decodingInfo rejects if the audio configuration contentType isn't of type audio +Fail Test that decodingInfo rejects if the audio configuration contentType has more than one parameter +Fail Test that decodingInfo rejects if the audio configuration contentType has one parameter that isn't codecs +Fail Test that decodingInfo rejects if the audio configuration contentType does not imply a single media codec but has no codecs parameter +Fail Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates multiple audio codecs +Fail Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates both an audio and a video codec +Fail Test that decodingInfo returns a valid MediaCapabilitiesInfo objects +Pass Test that decodingInfo rejects if the MediaConfiguration does not have a valid type +Fail Test that decodingInfo with spatialRendering set returns a valid MediaCapabilitiesInfo objects +Fail Test that decodingInfo with hdrMetadataType, colorGamut, and transferFunction set returns a valid MediaCapabilitiesInfo objects +Fail Test that decodingInfo with mismatched codec color space is unsupported +Pass Test that decodingInfo rejects if the video configuration has an empty hdrMetadataType +Pass Test that decodingInfo rejects if the video configuration has a colorGamut set to true +Pass Test that decodingInfo rejects if the video configuration has a transferFunction set to 3 \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.html b/Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.html new file mode 100644 index 00000000000..e6ae159806b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.js b/Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.js new file mode 100644 index 00000000000..2fd3743b442 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/media-capabilities/decodingInfo.any.js @@ -0,0 +1,508 @@ +// META: timeout=long +'use strict'; + +// Minimal VideoConfiguration that will be allowed per spec. All optional +// properties are missing. +const minimalVideoConfiguration = { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, +}; + +// Minimal AudioConfiguration that will be allowed per spec. All optional +// properties are missing. +const minimalAudioConfiguration = { + contentType: 'audio/webm; codecs="opus"', +}; + +// AudioConfiguration with optional spatialRendering param. +const audioConfigurationWithSpatialRendering = { + contentType: 'audio/webm; codecs="opus"', + spatialRendering: true, +}; + +// VideoConfiguration with optional hdrMetadataType, colorGamut, and +// transferFunction properties. +const videoConfigurationWithDynamicRange = { + contentType: 'video/webm; codecs="vp09.00.10.08.00.09.16.09.00"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + hdrMetadataType: 'smpteSt2086', + colorGamut: 'rec2020', + transferFunction: 'pq', +}; + + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo()); +}, "Test that decodingInfo rejects if it doesn't get a configuration"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({})); +}, "Test that decodingInfo rejects if the MediaConfiguration isn't valid"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + video: minimalVideoConfiguration, + audio: minimalAudioConfiguration, + })); +}, "Test that decodingInfo rejects if the MediaConfiguration does not have a type"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + })); +}, "Test that decodingInfo rejects if the configuration doesn't have an audio or video field"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: -1, + }, + })); +}, "Test that decodingInfo rejects if the video configuration has a negative framerate"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 0, + }, + })); +}, "Test that decodingInfo rejects if the video configuration has a framerate set to 0"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: Infinity, + }, + })); +}, "Test that decodingInfo rejects if the video configuration has a framerate set to Infinity"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'fgeoa', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType doesn't parse"); + +// See https://mimesniff.spec.whatwg.org/#example-valid-mime-type-string +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm;', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType is not a valid MIME type string"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'audio/fgeoa', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType isn't of type video"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'application/ogg; codec=vorbis', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType is of type audio"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { + contentType: 'application/ogg; codec=theora', + channels: 2, + }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType is of type video"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"; foo="bar"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType has more than one parameter"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; foo="bar"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType has one parameter that isn't codecs"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }, + })); +}, "Test that decodingInfo rejects if the video configuration contentType does not imply a single media codec but has no codecs parameter"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08, vp8"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + } + })); +}, "Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates multiple video codecs"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08, opus"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + } + })); +}, "Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates both an audio and a video codec"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '24000/1001', + } + })); +}, "Test that decodingInfo rejects framerate in the form of x/y"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '24000/0', + } + })); +}, "Test that decodingInfo rejects framerate in the form of x/0"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '0/10001', + } + })); +}, "Test that decodingInfo rejects framerate in the form of 0/y"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '-24000/10001', + } + })); +}, "Test that decodingInfo rejects framerate in the form of -x/y"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '24000/-10001', + } + })); +}, "Test that decodingInfo rejects framerate in the form of x/-y"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '24000/', + } + })); +}, "Test that decodingInfo rejects framerate in the form of x/"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: '1/3x', + } + })); +}, "Test that decodingInfo rejects framerate with trailing unallowed characters"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'fgeoa' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType doesn't parse"); + +// See https://mimesniff.spec.whatwg.org/#example-valid-mime-type-string +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'audio/mpeg;' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType is not a valid MIME type string"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'video/fgeoa' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType isn't of type audio"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'audio/webm; codecs="opus"; foo="bar"' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType has more than one parameter"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'audio/webm; foo="bar"' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType has one parameter that isn't codecs"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'audio/webm' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType does not imply a single media codec but has no codecs parameter"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'audio/webm; codecs="vorbis, opus"' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates multiple audio codecs"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: { contentType: 'audio/webm; codecs="vp09.00.10.08, opus"' }, + })); +}, "Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates both an audio and a video codec"); + +promise_test(t => { + return navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: minimalVideoConfiguration, + audio: minimalAudioConfiguration, + }).then(ability => { + assert_equals(typeof ability.supported, "boolean"); + assert_equals(typeof ability.smooth, "boolean"); + assert_equals(typeof ability.powerEfficient, "boolean"); + assert_equals(typeof ability.keySystemAccess, "object"); + }); +}, "Test that decodingInfo returns a valid MediaCapabilitiesInfo objects"); + +async_test(t => { + var validTypes = [ 'file', 'media-source' ]; + var invalidTypes = [ undefined, null, '', 'foobar', 'mse', 'MediaSource', + 'record', 'transmission' ]; + + var validPromises = []; + var invalidCaught = 0; + + validTypes.forEach(type => { + validPromises.push(navigator.mediaCapabilities.decodingInfo({ + type: type, + video: minimalVideoConfiguration, + audio: minimalAudioConfiguration, + })); + }); + + // validTypes are tested via Promise.all(validPromises) because if one of the + // promises fail, Promise.all() will reject. This mechanism can't be used for + // invalid types which will be tested individually and increment invalidCaught + // when rejected until the amount of rejection matches the expectation. + Promise.all(validPromises).then(t.step_func(() => { + for (var i = 0; i < invalidTypes.length; ++i) { + navigator.mediaCapabilities.decodingInfo({ + type: invalidTypes[i], + video: minimalVideoConfiguration, + audio: minimalAudioConfiguration, + }).then(t.unreached_func(), t.step_func(e => { + assert_equals(e.name, 'TypeError'); + ++invalidCaught; + if (invalidCaught == invalidTypes.length) + t.done(); + })); + } + }), t.unreached_func('Promise.all should not reject for valid types')); +}, "Test that decodingInfo rejects if the MediaConfiguration does not have a valid type"); + +promise_test(t => { + return navigator.mediaCapabilities.decodingInfo({ + type: 'file', + audio: audioConfigurationWithSpatialRendering, + }).then(ability => { + assert_equals(typeof ability.supported, "boolean"); + assert_equals(typeof ability.smooth, "boolean"); + assert_equals(typeof ability.powerEfficient, "boolean"); + assert_equals(typeof ability.keySystemAccess, "object"); + }); +}, "Test that decodingInfo with spatialRendering set returns a valid MediaCapabilitiesInfo objects"); + +promise_test(t => { + return navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: videoConfigurationWithDynamicRange, + }).then(ability => { + assert_equals(typeof ability.supported, "boolean"); + assert_equals(typeof ability.smooth, "boolean"); + assert_equals(typeof ability.powerEfficient, "boolean"); + assert_equals(typeof ability.keySystemAccess, "object"); + }); +}, "Test that decodingInfo with hdrMetadataType, colorGamut, and transferFunction set returns a valid MediaCapabilitiesInfo objects"); + +promise_test(t => { + // VP9 has a default color space of BT.709 in the codec string. So this will + // mismatch against the provided colorGamut and transferFunction. + let bt709Config = videoConfigurationWithDynamicRange; + bt709Config.contentType = 'video/webm; codecs="vp09.00.10.08"'; + return navigator.mediaCapabilities + .decodingInfo({ + type: 'file', + video: bt709Config, + }) + .then(ability => { + assert_equals(typeof ability.supported, 'boolean'); + assert_equals(typeof ability.smooth, 'boolean'); + assert_equals(typeof ability.powerEfficient, 'boolean'); + assert_equals(typeof ability.keySystemAccess, 'object'); + assert_false(ability.supported); + }); +}, 'Test that decodingInfo with mismatched codec color space is unsupported'); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + hdrMetadataType: "" + }, + })); +}, "Test that decodingInfo rejects if the video configuration has an empty hdrMetadataType"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + colorGamut: true + }, + })); +}, "Test that decodingInfo rejects if the video configuration has a colorGamut set to true"); + +promise_test(t => { + return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + transferFunction: 3 + }, + })); +}, "Test that decodingInfo rejects if the video configuration has a transferFunction set to 3");