Coverage Report

Created: 2026-02-16 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/SharedResourceRequest.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/HashTable.h>
8
#include <LibGfx/Bitmap.h>
9
#include <LibWeb/Fetch/Fetching/Fetching.h>
10
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
11
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
12
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
13
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
14
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
15
#include <LibWeb/HTML/DecodedImageData.h>
16
#include <LibWeb/HTML/SharedResourceRequest.h>
17
#include <LibWeb/Page/Page.h>
18
#include <LibWeb/Platform/ImageCodecPlugin.h>
19
#include <LibWeb/SVG/SVGDecodedImageData.h>
20
21
namespace Web::HTML {
22
23
JS_DEFINE_ALLOCATOR(SharedResourceRequest);
24
25
JS::NonnullGCPtr<SharedResourceRequest> SharedResourceRequest::get_or_create(JS::Realm& realm, JS::NonnullGCPtr<Page> page, URL::URL const& url)
26
0
{
27
0
    auto document = Bindings::host_defined_environment_settings_object(realm).responsible_document();
28
0
    VERIFY(document);
29
0
    auto& shared_resource_requests = document->shared_resource_requests();
30
0
    if (auto it = shared_resource_requests.find(url); it != shared_resource_requests.end())
31
0
        return *it->value;
32
0
    auto request = realm.heap().allocate<SharedResourceRequest>(realm, page, url, *document);
33
0
    shared_resource_requests.set(url, request);
34
0
    return request;
35
0
}
36
37
SharedResourceRequest::SharedResourceRequest(JS::NonnullGCPtr<Page> page, URL::URL url, JS::NonnullGCPtr<DOM::Document> document)
38
0
    : m_page(page)
39
0
    , m_url(move(url))
40
0
    , m_document(document)
41
0
{
42
0
}
43
44
0
SharedResourceRequest::~SharedResourceRequest() = default;
45
46
void SharedResourceRequest::finalize()
47
0
{
48
0
    Base::finalize();
49
0
    auto& shared_resource_requests = m_document->shared_resource_requests();
50
0
    shared_resource_requests.remove(m_url);
51
0
}
52
53
void SharedResourceRequest::visit_edges(JS::Cell::Visitor& visitor)
54
0
{
55
0
    Base::visit_edges(visitor);
56
0
    visitor.visit(m_fetch_controller);
57
0
    visitor.visit(m_document);
58
0
    visitor.visit(m_page);
59
0
    for (auto& callback : m_callbacks) {
60
0
        visitor.visit(callback.on_finish);
61
0
        visitor.visit(callback.on_fail);
62
0
    }
63
0
    visitor.visit(m_image_data);
64
0
}
65
66
JS::GCPtr<DecodedImageData> SharedResourceRequest::image_data() const
67
0
{
68
0
    return m_image_data;
69
0
}
70
71
JS::GCPtr<Fetch::Infrastructure::FetchController> SharedResourceRequest::fetch_controller()
72
0
{
73
0
    return m_fetch_controller.ptr();
74
0
}
75
76
void SharedResourceRequest::set_fetch_controller(JS::GCPtr<Fetch::Infrastructure::FetchController> fetch_controller)
77
0
{
78
0
    m_fetch_controller = move(fetch_controller);
79
0
}
80
81
void SharedResourceRequest::fetch_resource(JS::Realm& realm, JS::NonnullGCPtr<Fetch::Infrastructure::Request> request)
82
0
{
83
0
    Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
84
0
    fetch_algorithms_input.process_response = [this, &realm, request](JS::NonnullGCPtr<Fetch::Infrastructure::Response> response) {
85
        // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
86
        //        https://github.com/whatwg/html/issues/9355
87
0
        response = response->unsafe_response();
88
89
0
        auto process_body = JS::create_heap_function(heap(), [this, request, response](ByteBuffer data) {
90
0
            auto extracted_mime_type = response->header_list()->extract_mime_type();
91
0
            auto mime_type = extracted_mime_type.has_value() ? extracted_mime_type.value().essence().bytes_as_string_view() : StringView {};
92
0
            handle_successful_fetch(request->url(), mime_type, move(data));
93
0
        });
94
0
        auto process_body_error = JS::create_heap_function(heap(), [this](JS::Value) {
95
0
            handle_failed_fetch();
96
0
        });
97
98
        // Check for failed fetch response
99
0
        if (!Fetch::Infrastructure::is_ok_status(response->status()) || !response->body()) {
100
0
            handle_failed_fetch();
101
0
            return;
102
0
        }
103
104
0
        response->body()->fully_read(realm, process_body, process_body_error, JS::NonnullGCPtr { realm.global_object() });
105
0
    };
106
107
0
    m_state = State::Fetching;
108
109
0
    auto fetch_controller = Fetch::Fetching::fetch(
110
0
        realm,
111
0
        request,
112
0
        Fetch::Infrastructure::FetchAlgorithms::create(realm.vm(), move(fetch_algorithms_input)))
113
0
                                .release_value_but_fixme_should_propagate_errors();
114
115
0
    set_fetch_controller(fetch_controller);
116
0
}
117
118
void SharedResourceRequest::add_callbacks(Function<void()> on_finish, Function<void()> on_fail)
119
0
{
120
0
    if (m_state == State::Finished) {
121
0
        if (on_finish)
122
0
            on_finish();
123
0
        return;
124
0
    }
125
126
0
    if (m_state == State::Failed) {
127
0
        if (on_fail)
128
0
            on_fail();
129
0
        return;
130
0
    }
131
132
0
    Callbacks callbacks;
133
0
    if (on_finish)
134
0
        callbacks.on_finish = JS::create_heap_function(vm().heap(), move(on_finish));
135
0
    if (on_fail)
136
0
        callbacks.on_fail = JS::create_heap_function(vm().heap(), move(on_fail));
137
138
0
    m_callbacks.append(move(callbacks));
139
0
}
140
141
void SharedResourceRequest::handle_successful_fetch(URL::URL const& url_string, StringView mime_type, ByteBuffer data)
142
0
{
143
    // AD-HOC: At this point, things gets very ad-hoc.
144
    // FIXME: Bring this closer to spec.
145
146
0
    bool const is_svg_image = mime_type == "image/svg+xml"sv || url_string.basename().ends_with(".svg"sv);
147
148
0
    if (is_svg_image) {
149
0
        auto result = SVG::SVGDecodedImageData::create(m_document->realm(), m_page, url_string, data);
150
0
        if (result.is_error()) {
151
0
            handle_failed_fetch();
152
0
        } else {
153
0
            m_image_data = result.release_value();
154
0
            handle_successful_resource_load();
155
0
        }
156
0
        return;
157
0
    }
158
159
0
    auto handle_successful_bitmap_decode = [strong_this = JS::Handle(*this)](Web::Platform::DecodedImage& result) -> ErrorOr<void> {
160
0
        Vector<AnimatedBitmapDecodedImageData::Frame> frames;
161
0
        for (auto& frame : result.frames) {
162
0
            frames.append(AnimatedBitmapDecodedImageData::Frame {
163
0
                .bitmap = Gfx::ImmutableBitmap::create(*frame.bitmap),
164
0
                .duration = static_cast<int>(frame.duration),
165
0
            });
166
0
        }
167
0
        strong_this->m_image_data = AnimatedBitmapDecodedImageData::create(strong_this->m_document->realm(), move(frames), result.loop_count, result.is_animated).release_value_but_fixme_should_propagate_errors();
168
0
        strong_this->handle_successful_resource_load();
169
0
        return {};
170
0
    };
171
172
0
    auto handle_failed_decode = [strong_this = JS::Handle(*this)](Error&) -> void {
173
0
        strong_this->handle_failed_fetch();
174
0
    };
175
176
0
    (void)Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes(), move(handle_successful_bitmap_decode), move(handle_failed_decode));
177
0
}
178
179
void SharedResourceRequest::handle_failed_fetch()
180
0
{
181
0
    m_state = State::Failed;
182
0
    for (auto& callback : m_callbacks) {
183
0
        if (callback.on_fail)
184
0
            callback.on_fail->function()();
185
0
    }
186
0
    m_callbacks.clear();
187
0
}
188
189
void SharedResourceRequest::handle_successful_resource_load()
190
0
{
191
0
    m_state = State::Finished;
192
0
    for (auto& callback : m_callbacks) {
193
0
        if (callback.on_finish)
194
0
            callback.on_finish->function()();
195
0
    }
196
0
    m_callbacks.clear();
197
0
}
198
199
bool SharedResourceRequest::needs_fetching() const
200
0
{
201
0
    return m_state == State::New;
202
0
}
203
204
bool SharedResourceRequest::is_fetching() const
205
0
{
206
0
    return m_state == State::Fetching;
207
0
}
208
209
}