layout: allow only repaint when css background and border image loaded (#39201)

This change allows that only new display list is built when css
background and border image loaded.

Testing: This change should not change any behaviors so covered by
existing WPT tests.

Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
This commit is contained in:
JoeDow 2025-09-08 21:23:11 +08:00 committed by GitHub
parent e00bfb525b
commit 30d3706a2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 68 additions and 13 deletions

View file

@ -10,7 +10,8 @@ use fnv::FnvHashMap;
use fonts::FontContext; use fonts::FontContext;
use layout_api::wrapper_traits::ThreadSafeLayoutNode; use layout_api::wrapper_traits::ThreadSafeLayoutNode;
use layout_api::{ use layout_api::{
IFrameSizes, ImageAnimationState, PendingImage, PendingImageState, PendingRasterizationImage, IFrameSizes, ImageAnimationState, LayoutImageDestination, PendingImage, PendingImageState,
PendingRasterizationImage,
}; };
use net_traits::image_cache::{ use net_traits::image_cache::{
Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId, Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
@ -128,6 +129,7 @@ impl ImageResolver {
node: OpaqueNode, node: OpaqueNode,
url: ServoUrl, url: ServoUrl,
use_placeholder: UsePlaceholder, use_placeholder: UsePlaceholder,
destination: LayoutImageDestination,
) -> LayoutImageCacheResult { ) -> LayoutImageCacheResult {
// Check for available image or start tracking. // Check for available image or start tracking.
let cache_result = self.image_cache.get_cached_image_status( let cache_result = self.image_cache.get_cached_image_status(
@ -149,6 +151,7 @@ impl ImageResolver {
node: node.into(), node: node.into(),
id, id,
origin: self.origin.clone(), origin: self.origin.clone(),
destination,
}; };
self.pending_images.lock().push(image); self.pending_images.lock().push(image);
LayoutImageCacheResult::Pending LayoutImageCacheResult::Pending
@ -160,6 +163,7 @@ impl ImageResolver {
node: node.into(), node: node.into(),
id, id,
origin: self.origin.clone(), origin: self.origin.clone(),
destination,
}; };
self.pending_images.lock().push(image); self.pending_images.lock().push(image);
LayoutImageCacheResult::Pending LayoutImageCacheResult::Pending
@ -192,6 +196,7 @@ impl ImageResolver {
node: OpaqueNode, node: OpaqueNode,
url: ServoUrl, url: ServoUrl,
use_placeholder: UsePlaceholder, use_placeholder: UsePlaceholder,
destination: LayoutImageDestination,
) -> Result<CachedImage, ResolveImageError> { ) -> Result<CachedImage, ResolveImageError> {
if let Some(cached_image) = self if let Some(cached_image) = self
.resolved_images_cache .resolved_images_cache
@ -201,7 +206,8 @@ impl ImageResolver {
return cached_image.clone(); return cached_image.clone();
} }
let result = self.get_or_request_image_or_meta(node, url.clone(), use_placeholder); let result =
self.get_or_request_image_or_meta(node, url.clone(), use_placeholder, destination);
match result { match result {
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta { LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => { ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
@ -280,6 +286,7 @@ impl ImageResolver {
node, node,
image_url.clone().into(), image_url.clone().into(),
UsePlaceholder::No, UsePlaceholder::No,
LayoutImageDestination::DisplayListBuilding,
)?; )?;
let metadata = image.metadata(); let metadata = image.metadata();
let size = Size2D::new(metadata.width, metadata.height).to_f32(); let size = Size2D::new(metadata.width, metadata.height).to_f32();

View file

@ -539,6 +539,10 @@ impl Layout for LayoutThread {
self.need_new_display_list.get() self.need_new_display_list.get()
} }
fn set_needs_new_display_list(&self) {
self.need_new_display_list.set(true);
}
/// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function> /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function>
fn register_custom_property( fn register_custom_property(
&mut self, &mut self,

View file

@ -7,8 +7,8 @@ use base::id::{BrowsingContextId, PipelineId};
use data_url::DataUrl; use data_url::DataUrl;
use embedder_traits::ViewportDetails; use embedder_traits::ViewportDetails;
use euclid::{Scale, Size2D}; use euclid::{Scale, Size2D};
use layout_api::IFrameSize;
use layout_api::wrapper_traits::ThreadSafeLayoutNode; use layout_api::wrapper_traits::ThreadSafeLayoutNode;
use layout_api::{IFrameSize, LayoutImageDestination};
use malloc_size_of_derive::MallocSizeOf; use malloc_size_of_derive::MallocSizeOf;
use net_traits::image_cache::{Image, ImageOrMetadataAvailable, UsePlaceholder, VectorImage}; use net_traits::image_cache::{Image, ImageOrMetadataAvailable, UsePlaceholder, VectorImage};
use script::layout_dom::ServoThreadSafeLayoutNode; use script::layout_dom::ServoThreadSafeLayoutNode;
@ -189,7 +189,12 @@ impl ReplacedContents {
let result = context let result = context
.image_resolver .image_resolver
.get_cached_image_for_url(node.opaque(), svg_source, UsePlaceholder::No) .get_cached_image_for_url(
node.opaque(),
svg_source,
UsePlaceholder::No,
LayoutImageDestination::BoxTreeConstruction,
)
.ok(); .ok();
let vector_image = result.map(|result| match result { let vector_image = result.map(|result| match result {
@ -230,6 +235,7 @@ impl ReplacedContents {
node.opaque(), node.opaque(),
image_url.clone().into(), image_url.clone().into(),
UsePlaceholder::No, UsePlaceholder::No,
LayoutImageDestination::BoxTreeConstruction,
) { ) {
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta { LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => { ImageOrMetadataAvailable::ImageAvailable { image, .. } => {

View file

@ -55,9 +55,10 @@ use js::rust::{
}; };
use layout_api::{ use layout_api::{
BoxAreaType, ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout, BoxAreaType, ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout,
PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal, LayoutImageDestination, PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg,
ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason, ScrollContainerQueryType, ReflowGoal, ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason,
ScrollContainerResponse, TrustedNodeAddress, combine_id_with_fragment_type, ScrollContainerQueryType, ScrollContainerResponse, TrustedNodeAddress,
combine_id_with_fragment_type,
}; };
use malloc_size_of::MallocSizeOf; use malloc_size_of::MallocSizeOf;
use media::WindowGLContext; use media::WindowGLContext;
@ -235,6 +236,17 @@ pub(crate) struct OngoingNavigation(u32);
type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize); type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize);
/// Ancillary data of pending image request that was initiated by layout during a reflow.
/// This data is used to faciliate invalidating layout when the image data becomes available
/// at some point in the future.
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[derive(JSTraceable, MallocSizeOf)]
struct PendingLayoutImageAncillaryData {
node: Dom<Node>,
#[no_trace]
destination: LayoutImageDestination,
}
#[dom_struct] #[dom_struct]
pub(crate) struct Window { pub(crate) struct Window {
globalscope: GlobalScope, globalscope: GlobalScope,
@ -360,7 +372,8 @@ pub(crate) struct Window {
/// initiated by layout during a reflow. They are stored in the [`ScriptThread`] /// initiated by layout during a reflow. They are stored in the [`ScriptThread`]
/// to ensure that the element can be marked dirty when the image data becomes /// to ensure that the element can be marked dirty when the image data becomes
/// available at some point in the future. /// available at some point in the future.
pending_layout_images: DomRefCell<HashMapTracedValues<PendingImageId, Vec<Dom<Node>>>>, pending_layout_images:
DomRefCell<HashMapTracedValues<PendingImageId, Vec<PendingLayoutImageAncillaryData>>>,
/// Vector images for which layout has intiated rasterization at a specific size /// Vector images for which layout has intiated rasterization at a specific size
/// and whose results are not yet available. They are stored in the [`ScriptThread`] /// and whose results are not yet available. They are stored in the [`ScriptThread`]
@ -643,11 +656,22 @@ impl Window {
Entry::Occupied(nodes) => nodes, Entry::Occupied(nodes) => nodes,
Entry::Vacant(_) => return, Entry::Vacant(_) => return,
}; };
if matches!(response.response, ImageResponse::Loaded(_, _)) { if matches!(
for node in nodes.get() { response.response,
node.dirty(NodeDamage::Other); ImageResponse::Loaded(_, _) | ImageResponse::PlaceholderLoaded(_, _)
) {
for ancillary_data in nodes.get() {
match ancillary_data.destination {
LayoutImageDestination::BoxTreeConstruction => {
ancillary_data.node.dirty(NodeDamage::Other);
},
LayoutImageDestination::DisplayListBuilding => {
self.layout().set_needs_new_display_list();
},
}
} }
} }
match response.response { match response.response {
ImageResponse::MetadataLoaded(_) => {}, ImageResponse::MetadataLoaded(_) => {},
ImageResponse::Loaded(_, _) | ImageResponse::Loaded(_, _) |
@ -3106,8 +3130,11 @@ impl Window {
} }
let nodes = images.entry(id).or_default(); let nodes = images.entry(id).or_default();
if !nodes.iter().any(|n| std::ptr::eq(&**n, &*node)) { if !nodes.iter().any(|n| std::ptr::eq(&*(n.node), &*node)) {
nodes.push(Dom::from_ref(&*node)); nodes.push(PendingLayoutImageAncillaryData {
node: Dom::from_ref(&*node),
destination: image.destination,
});
} }
} }

View file

@ -154,6 +154,13 @@ pub enum PendingImageState {
PendingResponse, PendingResponse,
} }
/// The destination in layout where an image is needed.
#[derive(Debug, MallocSizeOf)]
pub enum LayoutImageDestination {
BoxTreeConstruction,
DisplayListBuilding,
}
/// The data associated with an image that is not yet present in the image cache. /// The data associated with an image that is not yet present in the image cache.
/// Used by the script thread to hold on to DOM elements that need to be repainted /// Used by the script thread to hold on to DOM elements that need to be repainted
/// when an image fetch is complete. /// when an image fetch is complete.
@ -163,6 +170,7 @@ pub struct PendingImage {
pub node: UntrustedNodeAddress, pub node: UntrustedNodeAddress,
pub id: PendingImageId, pub id: PendingImageId,
pub origin: ImmutableOrigin, pub origin: ImmutableOrigin,
pub destination: LayoutImageDestination,
} }
/// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG /// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG
@ -290,6 +298,9 @@ pub trait Layout {
/// Returns true if this layout needs to produce a new display list for rendering updates. /// Returns true if this layout needs to produce a new display list for rendering updates.
fn needs_new_display_list(&self) -> bool; fn needs_new_display_list(&self) -> bool;
/// Marks that this layout needs to produce a new display list for rendering updates.
fn set_needs_new_display_list(&self);
fn query_box_area(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Option<Rect<Au>>; fn query_box_area(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Option<Rect<Au>>;
fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<Rect<Au>>; fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<Rect<Au>>;
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>; fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;