mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 22:08:59 +00:00
As it turns out, making everyone piggyback on HTML::ImageRequest had some major flaws, as HTMLImageElement may decide to abort an ongoing fetch or wipe out image data, even when someone else is using the same image request. To avoid this issue, this patch introduces SharedImageRequest, and then implements ImageRequest on top of that. Other clients of the ImageRequest API are moved to SharedImageRequest as well, and ImageRequest is now only used by HTMLImageElement. This fixes an issue with image data disappearing and leading to asserts and/or visually absent images.
178 lines
5.7 KiB
C++
178 lines
5.7 KiB
C++
/*
|
|
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/HashTable.h>
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
|
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
|
|
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
|
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
|
#include <LibWeb/HTML/DecodedImageData.h>
|
|
#include <LibWeb/HTML/SharedImageRequest.h>
|
|
#include <LibWeb/Platform/ImageCodecPlugin.h>
|
|
#include <LibWeb/SVG/SVGDecodedImageData.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
static HashMap<AK::URL, SharedImageRequest*>& shared_image_requests()
|
|
{
|
|
static HashMap<AK::URL, SharedImageRequest*> requests;
|
|
return requests;
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<SharedImageRequest>> SharedImageRequest::get_or_create(Page& page, AK::URL const& url)
|
|
{
|
|
if (auto it = shared_image_requests().find(url); it != shared_image_requests().end())
|
|
return *it->value;
|
|
auto request = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) SharedImageRequest(page, url)));
|
|
shared_image_requests().set(url, request);
|
|
return request;
|
|
}
|
|
|
|
SharedImageRequest::SharedImageRequest(Page& page, AK::URL url)
|
|
: m_page(page)
|
|
, m_url(move(url))
|
|
{
|
|
}
|
|
|
|
SharedImageRequest::~SharedImageRequest()
|
|
{
|
|
shared_image_requests().remove(m_url);
|
|
}
|
|
|
|
RefPtr<DecodedImageData const> SharedImageRequest::image_data() const
|
|
{
|
|
return m_image_data;
|
|
}
|
|
|
|
JS::GCPtr<Fetch::Infrastructure::FetchController> SharedImageRequest::fetch_controller()
|
|
{
|
|
return m_fetch_controller.ptr();
|
|
}
|
|
|
|
void SharedImageRequest::set_fetch_controller(JS::GCPtr<Fetch::Infrastructure::FetchController> fetch_controller)
|
|
{
|
|
m_fetch_controller = move(fetch_controller);
|
|
}
|
|
|
|
void SharedImageRequest::fetch_image(JS::Realm& realm, JS::NonnullGCPtr<Fetch::Infrastructure::Request> request)
|
|
{
|
|
Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
|
|
fetch_algorithms_input.process_response = [this, &realm, request](JS::NonnullGCPtr<Fetch::Infrastructure::Response> response) {
|
|
// FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
|
|
// https://github.com/whatwg/html/issues/9355
|
|
response = response->unsafe_response();
|
|
|
|
auto process_body = [this, request, response](ByteBuffer data) {
|
|
auto extracted_mime_type = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors();
|
|
auto mime_type = extracted_mime_type.has_value() ? extracted_mime_type.value().essence().bytes_as_string_view() : StringView {};
|
|
handle_successful_fetch(request->url(), mime_type, move(data));
|
|
};
|
|
auto process_body_error = [this](auto) {
|
|
handle_failed_fetch();
|
|
};
|
|
|
|
if (response->body().has_value())
|
|
response->body().value().fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() }).release_value_but_fixme_should_propagate_errors();
|
|
};
|
|
|
|
m_state = State::Fetching;
|
|
|
|
auto fetch_controller = Fetch::Fetching::fetch(
|
|
realm,
|
|
request,
|
|
Fetch::Infrastructure::FetchAlgorithms::create(realm.vm(), move(fetch_algorithms_input)))
|
|
.release_value_but_fixme_should_propagate_errors();
|
|
|
|
set_fetch_controller(fetch_controller);
|
|
}
|
|
|
|
void SharedImageRequest::add_callbacks(JS::SafeFunction<void()> on_finish, JS::SafeFunction<void()> on_fail)
|
|
{
|
|
if (m_state == State::Finished) {
|
|
if (on_finish)
|
|
on_finish();
|
|
return;
|
|
}
|
|
|
|
if (m_state == State::Failed) {
|
|
if (on_fail)
|
|
on_fail();
|
|
return;
|
|
}
|
|
|
|
m_callbacks.append({ move(on_finish), move(on_fail) });
|
|
}
|
|
|
|
void SharedImageRequest::handle_successful_fetch(AK::URL const& url_string, StringView mime_type, ByteBuffer data)
|
|
{
|
|
// AD-HOC: At this point, things gets very ad-hoc.
|
|
// FIXME: Bring this closer to spec.
|
|
|
|
bool const is_svg_image = mime_type == "image/svg+xml"sv || url_string.basename().ends_with(".svg"sv);
|
|
|
|
RefPtr<DecodedImageData> image_data;
|
|
|
|
auto handle_failed_decode = [&] {
|
|
m_state = State::Failed;
|
|
for (auto& callback : m_callbacks) {
|
|
if (callback.on_fail)
|
|
callback.on_fail();
|
|
}
|
|
};
|
|
|
|
if (is_svg_image) {
|
|
auto result = SVG::SVGDecodedImageData::create(m_page, url_string, data);
|
|
if (result.is_error())
|
|
return handle_failed_decode();
|
|
|
|
image_data = result.release_value();
|
|
} else {
|
|
auto result = Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes());
|
|
if (!result.has_value())
|
|
return handle_failed_decode();
|
|
|
|
Vector<AnimatedBitmapDecodedImageData::Frame> frames;
|
|
for (auto& frame : result.value().frames) {
|
|
frames.append(AnimatedBitmapDecodedImageData::Frame {
|
|
.bitmap = frame.bitmap,
|
|
.duration = static_cast<int>(frame.duration),
|
|
});
|
|
}
|
|
image_data = AnimatedBitmapDecodedImageData::create(move(frames), result.value().loop_count, result.value().is_animated).release_value_but_fixme_should_propagate_errors();
|
|
}
|
|
|
|
m_image_data = move(image_data);
|
|
|
|
m_state = State::Finished;
|
|
|
|
for (auto& callback : m_callbacks) {
|
|
if (callback.on_finish)
|
|
callback.on_finish();
|
|
}
|
|
}
|
|
|
|
void SharedImageRequest::handle_failed_fetch()
|
|
{
|
|
m_state = State::Failed;
|
|
for (auto& callback : m_callbacks) {
|
|
if (callback.on_fail)
|
|
callback.on_fail();
|
|
}
|
|
}
|
|
|
|
bool SharedImageRequest::needs_fetching() const
|
|
{
|
|
return m_state == State::New;
|
|
}
|
|
|
|
bool SharedImageRequest::is_fetching() const
|
|
{
|
|
return m_state == State::Fetching;
|
|
}
|
|
|
|
}
|