From e0ccba9c85b2c1b49ebbb3803763c64a68f0cd63 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 20 Apr 2023 07:00:26 -0400 Subject: [PATCH] LibWeb: Implement the HTMLVideoElement poster attribute This will fetch the URL indicated by the poster attribute when it's set, changed, or removed. The spec doesn't say how to handle animated poster images, so we just grab the first frame of the image, which seems to match other implementations. --- .../LibWeb/HTML/HTMLVideoElement.cpp | 98 +++++++++++++++++++ .../Libraries/LibWeb/HTML/HTMLVideoElement.h | 12 +++ 2 files changed, 110 insertions(+) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp index adfec5c96e..d2f71cc9ef 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp @@ -8,9 +8,15 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include +#include namespace Web::HTML { @@ -33,6 +39,23 @@ void HTMLVideoElement::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_video_track); + visitor.visit(m_fetch_controller); +} + +void HTMLVideoElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) +{ + Base::parse_attribute(name, value); + + if (name == HTML::AttributeNames::poster) + determine_element_poster_frame(value).release_value_but_fixme_should_propagate_errors(); +} + +void HTMLVideoElement::did_remove_attribute(DeprecatedFlyString const& name) +{ + Base::did_remove_attribute(name); + + if (name == HTML::AttributeNames::poster) + determine_element_poster_frame({}).release_value_but_fixme_should_propagate_errors(); } JS::GCPtr HTMLVideoElement::create_layout_node(NonnullRefPtr style) @@ -107,4 +130,79 @@ void HTMLVideoElement::on_seek(double position, MediaSeekMode seek_mode) m_video_track->seek(Time::from_milliseconds(position * 1000.0), seek_mode); } +// https://html.spec.whatwg.org/multipage/media.html#attr-video-poster +WebIDL::ExceptionOr HTMLVideoElement::determine_element_poster_frame(Optional const& poster) +{ + auto& realm = this->realm(); + auto& vm = realm.vm(); + + m_poster_frame = nullptr; + + // 1. If there is an existing instance of this algorithm running for this video element, abort that instance of + // this algorithm without changing the poster frame. + if (m_fetch_controller) + m_fetch_controller->stop_fetch(); + + // 2. If the poster attribute's value is the empty string or if the attribute is absent, then there is no poster + // frame; return. + if (!poster.has_value() || poster->is_empty()) + return {}; + + // 3. Parse the poster attribute's value relative to the element's node document. If this fails, then there is no + // poster frame; return. + auto url_record = document().parse_url(*poster); + if (!url_record.is_valid()) + return {}; + + // 4. Let request be a new request whose URL is the resulting URL record, client is the element's node document's + // relevant settings object, destination is "image", initiator type is "video", credentials mode is "include", + // and whose use-URL-credentials flag is set. + auto request = Fetch::Infrastructure::Request::create(vm); + request->set_url(move(url_record)); + request->set_client(&document().relevant_settings_object()); + request->set_destination(Fetch::Infrastructure::Request::Destination::Image); + request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Video); + request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include); + request->set_use_url_credentials(true); + + // 5. Fetch request. This must delay the load event of the element's node document. + Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; + m_load_event_delayer.emplace(document()); + + fetch_algorithms_input.process_response = [this](auto response) mutable { + ScopeGuard guard { [&] { m_load_event_delayer.clear(); } }; + + auto& realm = this->realm(); + auto& global = document().realm().global_object(); + + if (response->is_network_error()) + return; + + if (response->type() == Fetch::Infrastructure::Response::Type::Opaque || response->type() == Fetch::Infrastructure::Response::Type::OpaqueRedirect) { + auto& filtered_response = static_cast(*response); + response = filtered_response.internal_response(); + } + + auto on_image_data_read = [this](auto image_data) mutable { + m_fetch_controller = nullptr; + + // 6. If an image is thus obtained, the poster frame is that image. Otherwise, there is no poster frame. + auto image = Platform::ImageCodecPlugin::the().decode_image(image_data); + if (!image.has_value() || image->frames.is_empty()) + return; + + m_poster_frame = move(image.release_value().frames[0].bitmap); + }; + + VERIFY(response->body().has_value()); + auto empty_algorithm = [](auto&) {}; + + response->body()->fully_read(realm, move(on_image_data_read), move(empty_algorithm), JS::NonnullGCPtr { global }).release_value_but_fixme_should_propagate_errors(); + }; + + m_fetch_controller = TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); + + return {}; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h index 3de683d52a..1c2d3e59e1 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h @@ -8,8 +8,10 @@ #include #include +#include #include #include +#include namespace Web::HTML { @@ -37,6 +39,7 @@ public: void set_current_frame(Badge, RefPtr frame, double position); VideoFrame const& current_frame() const { return m_current_frame; } + RefPtr const& poster_frame() const { return m_poster_frame; } void set_layout_mouse_position(Badge, Optional mouse_position) { m_mouse_position = move(mouse_position); } Optional const& layout_mouse_position(Badge) const { return m_mouse_position; } @@ -54,18 +57,27 @@ private: virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; + virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; + virtual void did_remove_attribute(DeprecatedFlyString const&) override; + virtual JS::GCPtr create_layout_node(NonnullRefPtr) override; virtual void on_playing() override; virtual void on_paused() override; virtual void on_seek(double, MediaSeekMode) override; + WebIDL::ExceptionOr determine_element_poster_frame(Optional const& poster); + JS::GCPtr m_video_track; VideoFrame m_current_frame; + RefPtr m_poster_frame; u32 m_video_width { 0 }; u32 m_video_height { 0 }; + JS::GCPtr m_fetch_controller; + Optional m_load_event_delayer; + // Cached state for layout Optional m_mouse_position; mutable CachedLayoutBoxes m_layout_boxes;