script: Do not set up an IPC route for every image load (#35041)

Instead of setting up a route for every image load in the DOM / Layout,
route all incoming image cache responses through the `ScriptThread`.
This avoids creating a set of file descriptor for every image that is
loaded.

This change requires having the `ImageCache` track the `PipelineId` of
the original the listener so that the `ScriptThread` can route it
properly to the correct `Window`.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-01-25 10:39:04 +01:00 committed by GitHub
parent 2ce7709b8b
commit 37695c8c8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 260 additions and 272 deletions

View file

@ -15,16 +15,13 @@ use cssparser::{Parser, ParserInput};
use dom_struct::dom_struct;
use euclid::Point2D;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix, QualName};
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
use js::jsapi::JSAutoRealm;
use js::rust::HandleObject;
use mime::{self, Mime};
use net_traits::http_status::HttpStatus;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, PendingImageId,
PendingImageResponse, UsePlaceholder,
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
PendingImageId, UsePlaceholder,
};
use net_traits::request::{
CorsSettings, Destination, Initiator, Referrer, RequestBuilder, RequestId,
@ -89,7 +86,6 @@ use crate::dom::values::UNSIGNED_LONG_MAX;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
use crate::fetch::create_a_potential_cors_request;
use crate::image_listener::{generate_cache_listener_for_element, ImageCacheListener};
use crate::microtask::{Microtask, MicrotaskRunnable};
use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
use crate::realms::enter_realm;
@ -355,13 +351,11 @@ impl HTMLImageElement {
/// Update the current image with a valid URL.
fn fetch_image(&self, img_url: &ServoUrl, can_gc: CanGc) {
let window = self.owner_window();
let image_cache = window.image_cache();
let sender = generate_cache_listener_for_element(self);
let cache_result = image_cache.track_image(
let cache_result = window.image_cache().get_cached_image_status(
img_url.clone(),
window.origin().immutable().clone(),
cors_setting_for_element(self.upcast()),
sender,
UsePlaceholder::Yes,
);
@ -380,15 +374,63 @@ impl HTMLImageElement {
self.process_image_response(ImageResponse::Loaded(image, url), can_gc)
}
},
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
self.process_image_response(ImageResponse::MetadataLoaded(m), can_gc)
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
metadata,
id,
)) => {
self.process_image_response(ImageResponse::MetadataLoaded(metadata), can_gc);
self.register_image_cache_callback(id, ChangeType::Element);
},
ImageCacheResult::Pending(id) => {
self.register_image_cache_callback(id, ChangeType::Element);
},
ImageCacheResult::ReadyForRequest(id) => {
self.fetch_request(img_url, id);
self.register_image_cache_callback(id, ChangeType::Element);
},
ImageCacheResult::Pending(_) => (),
ImageCacheResult::ReadyForRequest(id) => self.fetch_request(img_url, id),
ImageCacheResult::LoadError => self.process_image_response(ImageResponse::None, can_gc),
};
}
fn register_image_cache_callback(&self, id: PendingImageId, change_type: ChangeType) {
let trusted_node = Trusted::new(self);
let generation = self.generation_id();
let window = self.owner_window();
let sender = window.register_image_cache_listener(id, move |response| {
let trusted_node = trusted_node.clone();
let window = trusted_node.root().owner_window();
let callback_type = change_type.clone();
window
.as_global_scope()
.task_manager()
.networking_task_source()
.queue(task!(process_image_response: move || {
let element = trusted_node.root();
// Ignore any image response for a previous request that has been discarded.
if generation != element.generation_id() {
return;
}
match callback_type {
ChangeType::Element => {
element.process_image_response(response.response, CanGc::note());
}
ChangeType::Environment { selected_source, selected_pixel_density } => {
element.process_image_response_for_environment_change(
response.response, selected_source, generation, selected_pixel_density, CanGc::note()
);
}
}
}));
});
window
.image_cache()
.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
}
fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
let document = self.owner_document();
let window = self.owner_window();
@ -1054,48 +1096,6 @@ impl HTMLImageElement {
/// Step 2-12 of <https://html.spec.whatwg.org/multipage/#img-environment-changes>
fn react_to_environment_changes_sync_steps(&self, generation: u32, can_gc: CanGc) {
// TODO reduce duplicacy of this code
fn generate_cache_listener_for_element(
elem: &HTMLImageElement,
selected_source: String,
selected_pixel_density: f64,
) -> IpcSender<PendingImageResponse> {
let trusted_node = Trusted::new(elem);
let (responder_sender, responder_receiver) = ipc::channel().unwrap();
let task_source = elem
.owner_global()
.task_manager()
.networking_task_source()
.to_sendable();
let generation = elem.generation.get();
ROUTER.add_typed_route(
responder_receiver,
Box::new(move |message| {
debug!("Got image {:?}", message);
// Return the image via a message to the script thread, which marks
// the element as dirty and triggers a reflow.
let element = trusted_node.clone();
let image: PendingImageResponse = message.unwrap();
let selected_source_clone = selected_source.clone();
task_source.queue(
task!(process_image_response_for_environment_change: move || {
let element = element.root();
// Ignore any image response for a previous request that has been discarded.
if generation == element.generation.get() {
element.process_image_response_for_environment_change(image.response,
USVString::from(selected_source_clone), generation,
selected_pixel_density, CanGc::note());
}
})
);
}),
);
responder_sender
}
let elem = self.upcast::<Element>();
let document = elem.owner_document();
let has_pending_request = matches!(self.image_request.get(), ImageRequestPhase::Pending);
@ -1143,23 +1143,20 @@ impl HTMLImageElement {
can_gc,
);
let window = self.owner_window();
let image_cache = window.image_cache();
// Step 14
let sender = generate_cache_listener_for_element(
self,
selected_source.0.clone(),
selected_pixel_density,
);
let cache_result = image_cache.track_image(
let window = self.owner_window();
let cache_result = window.image_cache().get_cached_image_status(
img_url.clone(),
window.origin().immutable().clone(),
cors_setting_for_element(self.upcast()),
sender,
UsePlaceholder::No,
);
let change_type = ChangeType::Environment {
selected_source: selected_source.clone(),
selected_pixel_density,
};
match cache_result {
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => {
// Step 15
@ -1169,7 +1166,7 @@ impl HTMLImageElement {
selected_pixel_density,
)
},
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => {
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m, id)) => {
self.process_image_response_for_environment_change(
ImageResponse::MetadataLoaded(m),
selected_source,
@ -1177,6 +1174,7 @@ impl HTMLImageElement {
selected_pixel_density,
can_gc,
);
self.register_image_cache_callback(id, change_type);
},
ImageCacheResult::LoadError => {
self.process_image_response_for_environment_change(
@ -1187,8 +1185,13 @@ impl HTMLImageElement {
can_gc,
);
},
ImageCacheResult::ReadyForRequest(id) => self.fetch_request(&img_url, id),
ImageCacheResult::Pending(_) => (),
ImageCacheResult::ReadyForRequest(id) => {
self.fetch_request(&img_url, id);
self.register_image_cache_callback(id, change_type);
},
ImageCacheResult::Pending(id) => {
self.register_image_cache_callback(id, change_type);
},
}
}
@ -1383,6 +1386,10 @@ impl HTMLImageElement {
.as_ref()
.is_some_and(|url| url.scheme() == "data" || url.origin().same_origin(origin))
}
fn generation_id(&self) -> u32 {
self.generation.get()
}
}
#[derive(JSTraceable, MallocSizeOf)]
@ -1882,16 +1889,6 @@ impl FormControl for HTMLImageElement {
}
}
impl ImageCacheListener for HTMLImageElement {
fn generation_id(&self) -> u32 {
self.generation.get()
}
fn process_image_response(&self, response: ImageResponse, can_gc: CanGc) {
self.process_image_response(response, can_gc);
}
}
fn image_dimension_setter(element: &Element, attr: LocalName, value: u32, can_gc: CanGc) {
// This setter is a bit weird: the IDL type is unsigned long, but it's parsed as
// a dimension for rendering.
@ -2186,3 +2183,12 @@ pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> {
}
candidates
}
#[derive(Clone)]
enum ChangeType {
Environment {
selected_source: USVString,
selected_pixel_density: f64,
},
Element,
}