From 3f1badf9b20ee9ffbb95159c327e354bad6cf986 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 4 Apr 2023 09:26:02 -0400 Subject: [PATCH] LibWeb: Implement VideoTrack and VideoTrackList This implements the IDL for these types and some event handling around them. --- .../BindingsGenerator/IDLGenerators.cpp | 2 + Userland/Libraries/LibWeb/CMakeLists.txt | 4 +- Userland/Libraries/LibWeb/Forward.h | 2 + Userland/Libraries/LibWeb/HTML/VideoTrack.cpp | 88 ++++++++++++ Userland/Libraries/LibWeb/HTML/VideoTrack.h | 65 +++++++++ Userland/Libraries/LibWeb/HTML/VideoTrack.idl | 9 ++ .../Libraries/LibWeb/HTML/VideoTrackList.cpp | 130 ++++++++++++++++++ .../Libraries/LibWeb/HTML/VideoTrackList.h | 50 +++++++ .../Libraries/LibWeb/HTML/VideoTrackList.idl | 16 +++ Userland/Libraries/LibWeb/idl_files.cmake | 2 + 10 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibWeb/HTML/VideoTrack.cpp create mode 100644 Userland/Libraries/LibWeb/HTML/VideoTrack.h create mode 100644 Userland/Libraries/LibWeb/HTML/VideoTrack.idl create mode 100644 Userland/Libraries/LibWeb/HTML/VideoTrackList.cpp create mode 100644 Userland/Libraries/LibWeb/HTML/VideoTrackList.h create mode 100644 Userland/Libraries/LibWeb/HTML/VideoTrackList.idl diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 90ac677f2e7..7160359e20e 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -55,6 +55,8 @@ static bool is_platform_object(Type const& type) "Text"sv, "TextMetrics"sv, "URLSearchParams"sv, + "VideoTrack"sv, + "VideoTrackList"sv, "WebGLRenderingContext"sv, "Window"sv, "WritableStream"sv, diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 90dcae1ba84..eb04197fe85 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -343,6 +343,8 @@ set(SOURCES HTML/TagNames.cpp HTML/TextMetrics.cpp HTML/Timer.cpp + HTML/VideoTrack.cpp + HTML/VideoTrackList.cpp HTML/Window.cpp HTML/WindowEventHandlers.cpp HTML/WindowOrWorkerGlobalScope.cpp @@ -547,7 +549,7 @@ set(GENERATED_SOURCES serenity_lib(LibWeb web) # NOTE: We link with LibSoftGPU here instead of lazy loading it via dlopen() so that we do not have to unveil the library and pledge prot_exec. -target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibMarkdown LibHTTP LibGemini LibGL LibGUI LibGfx LibIPC LibLocale LibRegex LibSoftGPU LibSyntax LibTextCodec LibUnicode LibWasm LibXML LibIDL) +target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibMarkdown LibHTTP LibGemini LibGL LibGUI LibGfx LibIPC LibLocale LibRegex LibSoftGPU LibSyntax LibTextCodec LibUnicode LibVideo LibWasm LibXML LibIDL) link_with_locale_data(LibWeb) generate_js_bindings(LibWeb) diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index cf900b0de28..2d17e90beac 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -355,6 +355,8 @@ class Storage; class SubmitEvent; class TextMetrics; class Timer; +class VideoTrack; +class VideoTrackList; class Window; class WindowEnvironmentSettingsObject; class WindowProxy; diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp b/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp new file mode 100644 index 00000000000..14983a22845 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +static IDAllocator s_video_track_id_allocator; + +VideoTrack::VideoTrack(JS::Realm& realm, JS::NonnullGCPtr media_element, NonnullOwnPtr demuxer, Video::Track track) + : PlatformObject(realm) + , m_media_element(media_element) + , m_demuxer(move(demuxer)) + , m_track(track) +{ +} + +VideoTrack::~VideoTrack() +{ + auto id = m_id.to_number(); + VERIFY(id.has_value()); + + s_video_track_id_allocator.deallocate(id.value()); +} + +JS::ThrowCompletionOr VideoTrack::initialize(JS::Realm& realm) +{ + MUST_OR_THROW_OOM(Base::initialize(realm)); + set_prototype(&Bindings::ensure_web_prototype(realm, "VideoTrack")); + + auto id = s_video_track_id_allocator.allocate(); + m_id = TRY_OR_THROW_OOM(realm.vm(), String::number(id)); + + return {}; +} + +void VideoTrack::visit_edges(Cell::Visitor& visitor) +{ + visitor.visit(m_media_element); + visitor.visit(m_video_track_list); +} + +// https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-selected +void VideoTrack::set_selected(bool selected) +{ + // On setting, it must select the track if the new value is true, and unselect it otherwise. + if (m_selected == selected) + return; + + // If the track is in a VideoTrackList, then all the other VideoTrack objects in that list must be unselected. (If the track is + // no longer in a VideoTrackList object, then the track being selected or unselected has no effect beyond changing the value of + // the attribute on the VideoTrack object.) + if (m_video_track_list) { + for (auto video_track : m_video_track_list->video_tracks({})) { + if (video_track.ptr() != this) + video_track->m_selected = false; + } + + // Whenever a track in a VideoTrackList that was previously not selected is selected, and whenever the selected track in a + // VideoTrackList is unselected without a new track being selected in its stead, the user agent must queue a media element + // task given the media element to fire an event named change at the VideoTrackList object. This task must be queued before + // the task that fires the resize event, if any. + auto previously_unselected_track_is_selected = !m_selected && selected; + auto selected_track_was_unselected_without_another_selection = m_selected && !selected; + + if (previously_unselected_track_is_selected || selected_track_was_unselected_without_another_selection) { + m_media_element->queue_a_media_element_task([this]() { + m_video_track_list->dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change.to_deprecated_fly_string()).release_value_but_fixme_should_propagate_errors()); + }); + } + } + + m_selected = selected; +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrack.h b/Userland/Libraries/LibWeb/HTML/VideoTrack.h new file mode 100644 index 00000000000..4f2264dba7f --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/VideoTrack.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::HTML { + +class VideoTrack final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(VideoTrack, Bindings::PlatformObject); + +public: + virtual ~VideoTrack() override; + + void set_video_track_list(Badge, JS::GCPtr video_track_list) { m_video_track_list = video_track_list; } + + Time duration() const { return m_track.video_data().duration; } + u64 pixel_width() const { return m_track.video_data().pixel_width; } + u64 pixel_height() const { return m_track.video_data().pixel_height; } + + String const& id() const { return m_id; } + String const& kind() const { return m_kind; } + String const& label() const { return m_label; } + String const& language() const { return m_language; } + + bool selected() const { return m_selected; } + void set_selected(bool selected); + +private: + explicit VideoTrack(JS::Realm&, JS::NonnullGCPtr, NonnullOwnPtr, Video::Track); + + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + // https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-id + String m_id; + + // https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-kind + String m_kind; + + // https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-label + String m_label; + + // https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-language + String m_language; + + // https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-selected + bool m_selected { false }; + + JS::NonnullGCPtr m_media_element; + JS::GCPtr m_video_track_list; + + NonnullOwnPtr m_demuxer; + Video::Track m_track; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrack.idl b/Userland/Libraries/LibWeb/HTML/VideoTrack.idl new file mode 100644 index 00000000000..00036a865b1 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/VideoTrack.idl @@ -0,0 +1,9 @@ +// https://html.spec.whatwg.org/multipage/media.html#videotrack +[Exposed=Window] +interface VideoTrack { + readonly attribute DOMString id; + readonly attribute DOMString kind; + readonly attribute DOMString label; + readonly attribute DOMString language; + attribute boolean selected; +}; diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrackList.cpp b/Userland/Libraries/LibWeb/HTML/VideoTrackList.cpp new file mode 100644 index 00000000000..f590c9fedc2 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/VideoTrackList.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +VideoTrackList::VideoTrackList(JS::Realm& realm) + : DOM::EventTarget(realm) + , m_video_tracks(realm.heap()) +{ +} + +JS::ThrowCompletionOr VideoTrackList::initialize(JS::Realm& realm) +{ + MUST_OR_THROW_OOM(Base::initialize(realm)); + set_prototype(&Bindings::ensure_web_prototype(realm, "VideoTrackList")); + + return {}; +} + +// https://html.spec.whatwg.org/multipage/media.html#dom-tracklist-item +JS::ThrowCompletionOr> VideoTrackList::internal_get_own_property(JS::PropertyKey const& property_name) const +{ + // To determine the value of an indexed property for a given index index in an AudioTrackList or VideoTrackList + // object list, the user agent must return the AudioTrack or VideoTrack object that represents the indexth track + // in list. + if (property_name.is_number()) { + if (auto index = property_name.as_number(); index < m_video_tracks.size()) { + JS::PropertyDescriptor descriptor; + descriptor.value = m_video_tracks.at(index); + + return descriptor; + } + } + + return Base::internal_get_own_property(property_name); +} + +ErrorOr VideoTrackList::add_track(Badge, JS::NonnullGCPtr video_track) +{ + TRY(m_video_tracks.try_append(video_track)); + video_track->set_video_track_list({}, this); + + return {}; +} + +void VideoTrackList::remove_all_tracks(Badge) +{ + m_video_tracks.clear(); +} + +// https://html.spec.whatwg.org/multipage/media.html#dom-videotracklist-gettrackbyid +JS::GCPtr VideoTrackList::get_track_by_id(StringView id) const +{ + // The AudioTrackList getTrackById(id) and VideoTrackList getTrackById(id) methods must return the first AudioTrack + // or VideoTrack object (respectively) in the AudioTrackList or VideoTrackList object (respectively) whose identifier + // is equal to the value of the id argument (in the natural order of the list, as defined above). + auto it = m_video_tracks.find_if([&](auto const& video_track) { + return video_track->id() == id; + }); + + // When no tracks match the given argument, the methods must return null. + if (it == m_video_tracks.end()) + return nullptr; + + return *it; +} + +// https://html.spec.whatwg.org/multipage/media.html#dom-videotracklist-selectedindex +i32 VideoTrackList::selected_index() const +{ + // The VideoTrackList selectedIndex attribute must return the index of the currently selected track, if any. + auto it = m_video_tracks.find_if([&](auto const& video_track) { + return video_track->selected(); + }); + + // If the VideoTrackList object does not currently represent any tracks, or if none of the tracks are selected, + // it must instead return −1. + if (it == m_video_tracks.end()) + return -1; + + return static_cast(it.index()); +} + +// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onchange +void VideoTrackList::set_onchange(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::change, event_handler); +} + +// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onchange +WebIDL::CallbackType* VideoTrackList::onchange() +{ + return event_handler_attribute(HTML::EventNames::change); +} + +// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onaddtrack +void VideoTrackList::set_onaddtrack(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::addtrack, event_handler); +} + +// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onaddtrack +WebIDL::CallbackType* VideoTrackList::onaddtrack() +{ + return event_handler_attribute(HTML::EventNames::addtrack); +} + +// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onremovetrack +void VideoTrackList::set_onremovetrack(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::removetrack, event_handler); +} + +// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onremovetrack +WebIDL::CallbackType* VideoTrackList::onremovetrack() +{ + return event_handler_attribute(HTML::EventNames::removetrack); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrackList.h b/Userland/Libraries/LibWeb/HTML/VideoTrackList.h new file mode 100644 index 00000000000..d0f9732d126 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/VideoTrackList.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::HTML { + +class VideoTrackList final : public DOM::EventTarget { + WEB_PLATFORM_OBJECT(VideoTrackList, DOM::EventTarget); + +public: + ErrorOr add_track(Badge, JS::NonnullGCPtr); + void remove_all_tracks(Badge); + + Span> video_tracks(Badge) { return m_video_tracks; } + + // https://html.spec.whatwg.org/multipage/media.html#dom-videotracklist-length + size_t length() const { return m_video_tracks.size(); } + + JS::GCPtr get_track_by_id(StringView id) const; + i32 selected_index() const; + + void set_onchange(WebIDL::CallbackType*); + WebIDL::CallbackType* onchange(); + + void set_onaddtrack(WebIDL::CallbackType*); + WebIDL::CallbackType* onaddtrack(); + + void set_onremovetrack(WebIDL::CallbackType*); + WebIDL::CallbackType* onremovetrack(); + +private: + explicit VideoTrackList(JS::Realm&); + + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + virtual JS::ThrowCompletionOr> internal_get_own_property(JS::PropertyKey const& property_name) const override; + + JS::MarkedVector> m_video_tracks; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrackList.idl b/Userland/Libraries/LibWeb/HTML/VideoTrackList.idl new file mode 100644 index 00000000000..3c54de0c179 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/VideoTrackList.idl @@ -0,0 +1,16 @@ +#import +#import +#import + +// https://html.spec.whatwg.org/multipage/media.html#videotracklist +[Exposed=Window] +interface VideoTrackList : EventTarget { + readonly attribute unsigned long length; + getter VideoTrack (unsigned long index); + VideoTrack? getTrackById(DOMString id); + readonly attribute long selectedIndex; + + attribute EventHandler onchange; + attribute EventHandler onaddtrack; + attribute EventHandler onremovetrack; +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 3f4461e2c01..55d4d84dda6 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -165,6 +165,8 @@ libweb_js_bindings(HTML/PromiseRejectionEvent) libweb_js_bindings(HTML/Storage) libweb_js_bindings(HTML/SubmitEvent) libweb_js_bindings(HTML/TextMetrics) +libweb_js_bindings(HTML/VideoTrack) +libweb_js_bindings(HTML/VideoTrackList) libweb_js_bindings(HTML/Window GLOBAL) libweb_js_bindings(HTML/Worker) libweb_js_bindings(HTML/WorkerGlobalScope)