Pārlūkot izejas kodu

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. :^)
Andreas Kling 1 gadu atpakaļ
vecāks
revīzija
a9aecbbd6f
1 mainītis faili ar 74 papildinājumiem un 38 dzēšanām
  1. 74 38
      Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp

+ 74 - 38
Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp

@@ -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());
-                auto image_data = image_request->shared_image_request()->image_data();
-                image_request->set_image_data(image_data);
-
-                ListOfAvailableImages::Key key;
-                key.url = url_string;
-                key.mode = m_cors_setting;
-                key.origin = document().origin();
-
-                // 1. If image request is the pending request, abort the image request for the current request,
-                //    upgrade the pending request to the current request
-                //    and prepare image request for presentation given the img element.
-                if (image_request == m_pending_request) {
-                    abort_the_image_request(realm(), m_current_request);
-                    upgrade_pending_request_to_current_request();
-                    image_request->prepare_for_presentation(*this);
-                }
-
-                // 2. Set image request to the completely available state.
-                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.
-                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.
-                if (!maybe_omit_events || previous_url != url_string)
-                    dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
-
-                set_needs_style_update(true);
-                document().set_needs_layout();
-
-                if (image_data->is_animated() && image_data->frame_count() > 1) {
-                    m_current_frame_index = 0;
-                    m_animation_timer->set_interval(image_data->frame_duration(0));
-                    m_animation_timer->start();
-                }
-
-                m_load_event_delayer.clear();
+                batching_dispatcher().enqueue([this, image_request, maybe_omit_events, url_string, previous_url] {
+                    VERIFY(image_request->shared_image_request());
+                    auto image_data = image_request->shared_image_request()->image_data();
+                    image_request->set_image_data(image_data);
+
+                    ListOfAvailableImages::Key key;
+                    key.url = url_string;
+                    key.mode = m_cors_setting;
+                    key.origin = document().origin();
+
+                    // 1. If image request is the pending request, abort the image request for the current request,
+                    //    upgrade the pending request to the current request
+                    //    and prepare image request for presentation given the img element.
+                    if (image_request == m_pending_request) {
+                        abort_the_image_request(realm(), m_current_request);
+                        upgrade_pending_request_to_current_request();
+                        image_request->prepare_for_presentation(*this);
+                    }
+
+                    // 2. Set image request to the completely available state.
+                    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.
+                    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.
+                    if (!maybe_omit_events || previous_url != url_string)
+                        dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
+
+                    set_needs_style_update(true);
+                    document().set_needs_layout();
+
+                    if (image_data->is_animated() && image_data->frame_count() > 1) {
+                        m_current_frame_index = 0;
+                        m_animation_timer->set_interval(image_data->frame_duration(0));
+                        m_animation_timer->start();
+                    }
+
+                    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;