gpui: Prepaint Div children with image cache (#46039)

Marco Mihai Condrache created

Closes #39914

I was able to reproduce the issue on macOS using the same README.

The root cause was that `markdown_preview_view` attaches the image cache
to the root div, and while Div correctly applies that cache during
request_layout and paint, the prepaint phase didn’t have the same
context.

Markdown is rendered through a List, and `List::prepaint` renders its
items using `layout_as_root`. That meant images were getting loaded
during prepaint without the image cache in place. When that happened,
Img fell back to the global asset loader, which retains assets
indefinitely unless explicitly cleaned up.

So every image was loaded twice:

- once during layout/paint via RetainAllImageCache (correctly released
when the preview closed)
- once during prepaint via the global asset system (never released)

The result was doubled memory usage and a leak, since the globally
loaded images stuck around after the preview was closed.

Release Notes:

- Fixed a memory leak when previewing markdown files with images

Signed-off-by: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com>

Change summary

crates/gpui/src/elements/div.rs | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)

Detailed changes

crates/gpui/src/elements/div.rs 🔗

@@ -1425,6 +1425,11 @@ impl Element for Div {
         window: &mut Window,
         cx: &mut App,
     ) -> Option<Hitbox> {
+        let image_cache = self
+            .image_cache
+            .as_mut()
+            .map(|provider| provider.provide(window, cx));
+
         let has_prepaint_listener = self.prepaint_listener.is_some();
         let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
             request_layout.child_layout_ids.len()
@@ -1479,16 +1484,18 @@ impl Element for Div {
                     return hitbox;
                 }
 
-                window.with_element_offset(scroll_offset, |window| {
-                    for child in &mut self.children {
-                        child.prepaint(window, cx);
+                window.with_image_cache(image_cache, |window| {
+                    window.with_element_offset(scroll_offset, |window| {
+                        for child in &mut self.children {
+                            child.prepaint(window, cx);
+                        }
+                    });
+
+                    if let Some(listener) = self.prepaint_listener.as_ref() {
+                        listener(children_bounds, window, cx);
                     }
                 });
 
-                if let Some(listener) = self.prepaint_listener.as_ref() {
-                    listener(children_bounds, window, cx);
-                }
-
                 hitbox
             },
         )