summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp
blob: d2f71cc9ef028e75f7de9658151128a4821a9a91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/*
 * Copyright (c) 2020, the SerenityOS developers.
 * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibGfx/Bitmap.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/HTML/HTMLVideoElement.h>
#include <LibWeb/HTML/VideoTrack.h>
#include <LibWeb/Layout/VideoBox.h>
#include <LibWeb/Platform/ImageCodecPlugin.h>

namespace Web::HTML {

HTMLVideoElement::HTMLVideoElement(DOM::Document& document, DOM::QualifiedName qualified_name)
    : HTMLMediaElement(document, move(qualified_name))
{
}

HTMLVideoElement::~HTMLVideoElement() = default;

JS::ThrowCompletionOr<void> HTMLVideoElement::initialize(JS::Realm& realm)
{
    MUST_OR_THROW_OOM(Base::initialize(realm));
    set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLVideoElementPrototype>(realm, "HTMLVideoElement"));

    return {};
}

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<Layout::Node> HTMLVideoElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
{
    return heap().allocate_without_realm<Layout::VideoBox>(document(), *this, move(style));
}

Layout::VideoBox* HTMLVideoElement::layout_node()
{
    return static_cast<Layout::VideoBox*>(Node::layout_node());
}

Layout::VideoBox const* HTMLVideoElement::layout_node() const
{
    return static_cast<Layout::VideoBox const*>(Node::layout_node());
}

// https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
u32 HTMLVideoElement::video_width() const
{
    // The videoWidth IDL attribute must return the intrinsic width of the video in CSS pixels. The videoHeight IDL
    // attribute must return the intrinsic height of the video in CSS pixels. If the element's readyState attribute
    // is HAVE_NOTHING, then the attributes must return 0.
    if (ready_state() == ReadyState::HaveNothing)
        return 0;
    return m_video_width;
}

// https://html.spec.whatwg.org/multipage/media.html#dom-video-videoheight
u32 HTMLVideoElement::video_height() const
{
    // The videoWidth IDL attribute must return the intrinsic width of the video in CSS pixels. The videoHeight IDL
    // attribute must return the intrinsic height of the video in CSS pixels. If the element's readyState attribute
    // is HAVE_NOTHING, then the attributes must return 0.
    if (ready_state() == ReadyState::HaveNothing)
        return 0;
    return m_video_height;
}

void HTMLVideoElement::set_video_track(JS::GCPtr<HTML::VideoTrack> video_track)
{
    set_needs_style_update(true);
    document().set_needs_layout();

    if (m_video_track)
        m_video_track->pause_video({});

    m_video_track = video_track;
}

void HTMLVideoElement::set_current_frame(Badge<VideoTrack>, RefPtr<Gfx::Bitmap> frame, double position)
{
    m_current_frame = { move(frame), position };
    layout_node()->set_needs_display();
}

void HTMLVideoElement::on_playing()
{
    if (m_video_track)
        m_video_track->play_video({});
}

void HTMLVideoElement::on_paused()
{
    if (m_video_track)
        m_video_track->pause_video({});
}

void HTMLVideoElement::on_seek(double position, MediaSeekMode seek_mode)
{
    if (m_video_track)
        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<void> HTMLVideoElement::determine_element_poster_frame(Optional<StringView> 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<Fetch::Infrastructure::FilteredResponse&>(*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 {};
}

}