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;