1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-23 13:15:06 +00:00

LibWeb: Batch processing of successfully downloaded images

Before this change, we would process each image as it finished
downloading. This often led to a situation where we'd decode 1 image,
schedule a layout, do the layout, then decode another image, schedule
a layout, do the layout, etc. Basically decoding and layouts would get
interleaved even though we had multiple images fetched and ready for
decoding.

This patch adds a simple BatchingDispatcher thingy that HTMLImageElement
uses to batch the handling of successful fetches.

With this, the number of layouts while loading https://shopify.com/ goes
from 48 to 6, and the page loads noticeably faster. :^)
This commit is contained in:
Andreas Kling 2023-07-27 15:05:42 +02:00
parent fedd087546
commit a9aecbbd6f

View file

@ -262,6 +262,40 @@ bool HTMLImageElement::uses_srcset_or_picture() const
return has_attribute(HTML::AttributeNames::srcset) || (parent() && is<HTMLPictureElement>(*parent())); return has_attribute(HTML::AttributeNames::srcset) || (parent() && is<HTMLPictureElement>(*parent()));
} }
// We batch handling of successfully fetched images to avoid interleaving 1 image, 1 layout, 1 image, 1 layout, etc.
// The processing timer is 1ms instead of 0ms, since layout is driven by a 0ms timer, and if we use 0ms here,
// the event loop will process them in insertion order. This is a bit of a hack, but it works.
struct BatchingDispatcher {
public:
BatchingDispatcher()
: m_timer(Core::Timer::create_single_shot(1, [this] { process(); }).release_value_but_fixme_should_propagate_errors())
{
}
void enqueue(JS::SafeFunction<void()> callback)
{
m_queue.append(move(callback));
m_timer->restart();
}
private:
void process()
{
auto queue = move(m_queue);
for (auto& callback : queue)
callback();
}
NonnullRefPtr<Core::Timer> m_timer;
Vector<JS::SafeFunction<void()>> m_queue;
};
static BatchingDispatcher& batching_dispatcher()
{
static BatchingDispatcher dispatcher;
return dispatcher;
}
// https://html.spec.whatwg.org/multipage/images.html#update-the-image-data // https://html.spec.whatwg.org/multipage/images.html#update-the-image-data
ErrorOr<void> HTMLImageElement::update_the_image_data(bool restart_animations, bool maybe_omit_events) ErrorOr<void> HTMLImageElement::update_the_image_data(bool restart_animations, bool maybe_omit_events)
{ {
@ -473,6 +507,7 @@ after_step_7:
image_request->add_callbacks( image_request->add_callbacks(
[this, image_request, maybe_omit_events, url_string, previous_url]() { [this, image_request, maybe_omit_events, url_string, previous_url]() {
batching_dispatcher().enqueue([this, image_request, maybe_omit_events, url_string, previous_url] {
VERIFY(image_request->shared_image_request()); VERIFY(image_request->shared_image_request());
auto image_data = image_request->shared_image_request()->image_data(); auto image_data = image_request->shared_image_request()->image_data();
image_request->set_image_data(image_data); image_request->set_image_data(image_data);
@ -511,6 +546,7 @@ after_step_7:
} }
m_load_event_delayer.clear(); m_load_event_delayer.clear();
});
}, },
[this, image_request, maybe_omit_events, url_string, previous_url, selected_source]() { [this, image_request, maybe_omit_events, url_string, previous_url, selected_source]() {
// The image data is not in a supported file format; // The image data is not in a supported file format;