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;