mirror of
https://github.com/RGBCube/serenity
synced 2025-05-22 19:45:08 +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:
parent
fedd087546
commit
a9aecbbd6f
1 changed files with 66 additions and 30 deletions
|
@ -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,44 +507,46 @@ 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]() {
|
||||||
VERIFY(image_request->shared_image_request());
|
batching_dispatcher().enqueue([this, image_request, maybe_omit_events, url_string, previous_url] {
|
||||||
auto image_data = image_request->shared_image_request()->image_data();
|
VERIFY(image_request->shared_image_request());
|
||||||
image_request->set_image_data(image_data);
|
auto image_data = image_request->shared_image_request()->image_data();
|
||||||
|
image_request->set_image_data(image_data);
|
||||||
|
|
||||||
ListOfAvailableImages::Key key;
|
ListOfAvailableImages::Key key;
|
||||||
key.url = url_string;
|
key.url = url_string;
|
||||||
key.mode = m_cors_setting;
|
key.mode = m_cors_setting;
|
||||||
key.origin = document().origin();
|
key.origin = document().origin();
|
||||||
|
|
||||||
// 1. If image request is the pending request, abort the image request for the current request,
|
// 1. If image request is the pending request, abort the image request for the current request,
|
||||||
// upgrade the pending request to the current request
|
// upgrade the pending request to the current request
|
||||||
// and prepare image request for presentation given the img element.
|
// and prepare image request for presentation given the img element.
|
||||||
if (image_request == m_pending_request) {
|
if (image_request == m_pending_request) {
|
||||||
abort_the_image_request(realm(), m_current_request);
|
abort_the_image_request(realm(), m_current_request);
|
||||||
upgrade_pending_request_to_current_request();
|
upgrade_pending_request_to_current_request();
|
||||||
image_request->prepare_for_presentation(*this);
|
image_request->prepare_for_presentation(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Set image request to the completely available state.
|
// 2. Set image request to the completely available state.
|
||||||
image_request->set_state(ImageRequest::State::CompletelyAvailable);
|
image_request->set_state(ImageRequest::State::CompletelyAvailable);
|
||||||
|
|
||||||
// 3. Add the image to the list of available images using the key key, with the ignore higher-layer caching flag set.
|
// 3. Add the image to the list of available images using the key key, with the ignore higher-layer caching flag set.
|
||||||
document().list_of_available_images().add(key, *image_data, true).release_value_but_fixme_should_propagate_errors();
|
document().list_of_available_images().add(key, *image_data, true).release_value_but_fixme_should_propagate_errors();
|
||||||
|
|
||||||
// 4. If maybe omit events is not set or previousURL is not equal to urlString, then fire an event named load at the img element.
|
// 4. If maybe omit events is not set or previousURL is not equal to urlString, then fire an event named load at the img element.
|
||||||
if (!maybe_omit_events || previous_url != url_string)
|
if (!maybe_omit_events || previous_url != url_string)
|
||||||
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
|
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
|
||||||
|
|
||||||
set_needs_style_update(true);
|
set_needs_style_update(true);
|
||||||
document().set_needs_layout();
|
document().set_needs_layout();
|
||||||
|
|
||||||
if (image_data->is_animated() && image_data->frame_count() > 1) {
|
if (image_data->is_animated() && image_data->frame_count() > 1) {
|
||||||
m_current_frame_index = 0;
|
m_current_frame_index = 0;
|
||||||
m_animation_timer->set_interval(image_data->frame_duration(0));
|
m_animation_timer->set_interval(image_data->frame_duration(0));
|
||||||
m_animation_timer->start();
|
m_animation_timer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue