mirror of
https://github.com/servo/servo
synced 2026-05-09 08:32:31 +02:00
Parsed and vectorized representations of SVGs in the image cache are never evicted when SVGs are updated, leading to stale SVGs staying in the image cache alongside updated ones. This PR: - adds a stringified uuid field to SVGSVGElement - adds a map of `SVGSVGElement` uuid to `PendingImageId` in `ImageCacheImpl` - adds an optional argument for the uuid to `ImageCacheImpl::rasterize_vector_image`, which, if provided, removes any existing parsed svgs associated with the uuid and stores a new entry of uuid -> `PendingImageId` - implements `unbind_from_tree` on SVGSVGElement, which clears the associated images/representations from the image_cache (from `vector_images`, `rasterized_vector_images`, and `completed_loads`) as well as from the layout image resolver image cache. Testing: Each of the following documents should display their (very flashy) content correctly and memory usage should stay constant <details> <summary>Changing SVGSVGElement</summary> ``` <svg height="100" width="100" xmlns="http://www.w3.org/2000/svg"> <circle id="c" r="1" cx="50" cy="50" fill="red" /> </svg> <script> let c = document.querySelector("#c"); let r = 1; setInterval(() => { r += 1; c.setAttribute("r", r.toString()); let tmp = document.createTextNode("ignored"); c.parentNode.appendChild(tmp); tmp.remove(); }, 100); </script> ``` </details> <details> <summary>Unbinding SVGSVGElements</summary> ``` <div id="parent_div"> <svg id="test" height="100" width="100" xmlns="http://www.w3.org/2000/svg"> <circle id="c" r="10" cx="50" cy="50" fill="red" /> </svg> </div> <script> let div = document.querySelector("#parent_div"); let svg_html_string = div.innerHTML; let svg = document.querySelector("#test"); let r = 10; setInterval(() => { svg.remove(); div.innerHTML = svg_html_string; svg = document.querySelector("#test"); let circle = document.querySelector("#c"); r += 1; circle.setAttribute("r", r.toString()); }, 100); </script> ``` </details> <details> <summary>Unbinding SVGSVGElements (and rebinding the same SVG)</summary> This didn't work until I also evicted the associated image from the layout image resolver image cache and the image cache's `completed_loads` on SVGSVGElement unbind, so it seems like a useful, if a bit redundant, test. ``` <div id="parent_div"> <svg id="test" height="100" width="100" xmlns="http://www.w3.org/2000/svg"> <circle id="c" r="10" cx="50" cy="50" fill="red" /> </svg> </div> <script> let div = document.querySelector("#parent_div"); let svg_html_string = div.innerHTML; let svg = document.querySelector("#test"); let r = 10; setInterval(() => { svg.remove(); div.innerHTML = svg_html_string; svg = document.querySelector("#test"); }, 100); </script> ``` </details> Fixes: #41070 --------- Signed-off-by: Tom Cummings <cummings.t287@gmail.com>
245 lines
8.2 KiB
Rust
245 lines
8.2 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::sync::Arc;
|
|
|
|
use base::id::{PipelineId, WebViewId};
|
|
use log::debug;
|
|
use malloc_size_of::MallocSizeOfOps;
|
|
use malloc_size_of_derive::MallocSizeOf;
|
|
use paint_api::CrossProcessPaintApi;
|
|
use pixels::{CorsStatus, ImageMetadata, RasterImage};
|
|
use profile_traits::mem::Report;
|
|
use serde::{Deserialize, Serialize};
|
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
|
use webrender_api::ImageKey;
|
|
use webrender_api::units::DeviceIntSize;
|
|
|
|
use crate::FetchResponseMsg;
|
|
use crate::request::CorsSettings;
|
|
|
|
// ======================================================================
|
|
// Aux structs and enums.
|
|
// ======================================================================
|
|
|
|
pub type VectorImageId = PendingImageId;
|
|
|
|
// Represents either a raster image for which the pixel data is available
|
|
// or a vector image for which only the natural dimensions are available
|
|
// and thus requires a further rasterization step to render.
|
|
#[derive(Clone, Debug, MallocSizeOf)]
|
|
pub enum Image {
|
|
Raster(#[conditional_malloc_size_of] Arc<RasterImage>),
|
|
Vector(VectorImage),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
|
pub struct VectorImage {
|
|
pub id: VectorImageId,
|
|
pub svg_id: Option<String>,
|
|
pub metadata: ImageMetadata,
|
|
pub cors_status: CorsStatus,
|
|
}
|
|
|
|
impl Image {
|
|
pub fn metadata(&self) -> ImageMetadata {
|
|
match self {
|
|
Image::Vector(image, ..) => image.metadata,
|
|
Image::Raster(image) => image.metadata,
|
|
}
|
|
}
|
|
|
|
pub fn cors_status(&self) -> CorsStatus {
|
|
match self {
|
|
Image::Vector(image) => image.cors_status,
|
|
Image::Raster(image) => image.cors_status,
|
|
}
|
|
}
|
|
|
|
pub fn as_raster_image(&self) -> Option<Arc<RasterImage>> {
|
|
match self {
|
|
Image::Raster(image) => Some(image.clone()),
|
|
Image::Vector(..) => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Indicating either entire image or just metadata availability
|
|
#[derive(Clone, Debug, MallocSizeOf)]
|
|
pub enum ImageOrMetadataAvailable {
|
|
ImageAvailable { image: Image, url: ServoUrl },
|
|
MetadataAvailable(ImageMetadata, PendingImageId),
|
|
}
|
|
|
|
pub type ImageCacheResponseCallback = Box<dyn Fn(ImageCacheResponseMessage) + Send + 'static>;
|
|
|
|
/// This is optionally passed to the image cache when requesting
|
|
/// and image, and returned to the specified event loop when the
|
|
/// image load completes. It is typically used to trigger a reflow
|
|
/// and/or repaint.
|
|
#[derive(MallocSizeOf)]
|
|
pub struct ImageLoadListener {
|
|
pipeline_id: PipelineId,
|
|
pub id: PendingImageId,
|
|
#[ignore_malloc_size_of = "Difficult to measure FnOnce"]
|
|
callback: ImageCacheResponseCallback,
|
|
}
|
|
|
|
impl ImageLoadListener {
|
|
pub fn new(
|
|
callback: ImageCacheResponseCallback,
|
|
pipeline_id: PipelineId,
|
|
id: PendingImageId,
|
|
) -> ImageLoadListener {
|
|
ImageLoadListener {
|
|
pipeline_id,
|
|
callback,
|
|
id,
|
|
}
|
|
}
|
|
|
|
pub fn respond(&self, response: ImageResponse) {
|
|
debug!("Notifying listener");
|
|
(self.callback)(ImageCacheResponseMessage::NotifyPendingImageLoadStatus(
|
|
PendingImageResponse {
|
|
pipeline_id: self.pipeline_id,
|
|
response,
|
|
id: self.id,
|
|
},
|
|
));
|
|
}
|
|
}
|
|
|
|
/// The returned image.
|
|
#[derive(Clone, Debug, MallocSizeOf)]
|
|
pub enum ImageResponse {
|
|
/// The requested image was loaded.
|
|
Loaded(Image, ServoUrl),
|
|
/// The request image metadata was loaded.
|
|
MetadataLoaded(ImageMetadata),
|
|
/// The requested image failed to load or decode.
|
|
FailedToLoadOrDecode,
|
|
}
|
|
|
|
/// The unique id for an image that has previously been requested.
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
|
pub struct PendingImageId(pub u64);
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PendingImageResponse {
|
|
pub pipeline_id: PipelineId,
|
|
pub response: ImageResponse,
|
|
pub id: PendingImageId,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
pub struct RasterizationCompleteResponse {
|
|
pub pipeline_id: PipelineId,
|
|
pub image_id: PendingImageId,
|
|
pub requested_size: DeviceIntSize,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum ImageCacheResponseMessage {
|
|
NotifyPendingImageLoadStatus(PendingImageResponse),
|
|
VectorImageRasterizationComplete(RasterizationCompleteResponse),
|
|
}
|
|
|
|
// ======================================================================
|
|
// ImageCache public API.
|
|
// ======================================================================
|
|
|
|
pub enum ImageCacheResult {
|
|
Available(ImageOrMetadataAvailable),
|
|
FailedToLoadOrDecode,
|
|
Pending(PendingImageId),
|
|
ReadyForRequest(PendingImageId),
|
|
}
|
|
|
|
/// A shared [`ImageCacheFactory`] is a per-process data structure used to create an [`ImageCache`]
|
|
/// inside that process in any `ScriptThread`. This allows sharing the same font database (for
|
|
/// SVGs) and also decoding thread pool among all [`ImageCache`]s in the same process.
|
|
pub trait ImageCacheFactory: Sync + Send {
|
|
fn create(
|
|
&self,
|
|
webview_id: WebViewId,
|
|
pipeline_id: PipelineId,
|
|
paint_api: &CrossProcessPaintApi,
|
|
) -> Arc<dyn ImageCache>;
|
|
}
|
|
|
|
/// An [`ImageCache`] manages fetching and decoding images for a single `Pipeline` for its
|
|
/// `Document` and all of its associated `Worker`s.
|
|
pub trait ImageCache: Sync + Send {
|
|
fn memory_reports(&self, prefix: &str, ops: &mut MallocSizeOfOps) -> Vec<Report>;
|
|
|
|
/// Get an [`ImageKey`] to be used for external WebRender image management for
|
|
/// things like canvas rendering. Returns `None` when an [`ImageKey`] cannot
|
|
/// be generated properly.
|
|
fn get_image_key(&self) -> Option<ImageKey>;
|
|
|
|
/// Definitively check whether there is a cached, fully loaded image available.
|
|
fn get_image(
|
|
&self,
|
|
url: ServoUrl,
|
|
origin: ImmutableOrigin,
|
|
cors_setting: Option<CorsSettings>,
|
|
) -> Option<Image>;
|
|
|
|
fn get_cached_image_status(
|
|
&self,
|
|
url: ServoUrl,
|
|
origin: ImmutableOrigin,
|
|
cors_setting: Option<CorsSettings>,
|
|
) -> ImageCacheResult;
|
|
|
|
/// Returns `Some` if the given `image_id` has already been rasterized at the given `size`.
|
|
/// Otherwise, triggers a new job to perform the rasterization. If a notification
|
|
/// is needed after rasterization is completed, the `add_rasterization_complete_listener`
|
|
/// API below can be used to add a listener.
|
|
fn rasterize_vector_image(
|
|
&self,
|
|
image_id: VectorImageId,
|
|
size: DeviceIntSize,
|
|
svg_id: Option<String>,
|
|
) -> Option<RasterImage>;
|
|
|
|
/// Adds a new listener to be notified once the given `image_id` has been rasterized at
|
|
/// the given `size`. The listener will receive a `VectorImageRasterizationComplete`
|
|
/// message on the given `sender`, even if the listener is called after rasterization
|
|
/// at has already completed.
|
|
fn add_rasterization_complete_listener(
|
|
&self,
|
|
pipeline_id: PipelineId,
|
|
image_id: VectorImageId,
|
|
size: DeviceIntSize,
|
|
callback: ImageCacheResponseCallback,
|
|
);
|
|
|
|
/// Removes the rasterized image from the image_cache, identified by the id of the SVG
|
|
fn evict_rasterized_image(&self, svg_id: &str);
|
|
|
|
/// Removes the completed image from the image_cache, identified by url, origin, and cors
|
|
fn evict_completed_image(
|
|
&self,
|
|
url: &ServoUrl,
|
|
origin: &ImmutableOrigin,
|
|
cors_setting: &Option<CorsSettings>,
|
|
);
|
|
|
|
/// Synchronously get the broken image icon for this [`ImageCache`]. This will
|
|
/// allocate space for this icon and upload it to WebRender.
|
|
fn get_broken_image_icon(&self) -> Option<Arc<RasterImage>>;
|
|
|
|
/// Add a new listener for the given pending image id. If the image is already present,
|
|
/// the responder will still receive the expected response.
|
|
fn add_listener(&self, listener: ImageLoadListener);
|
|
|
|
/// Inform the image cache about a response for a pending request.
|
|
fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg);
|
|
|
|
/// Fills the image cache with a batch of keys.
|
|
fn fill_key_cache_with_batch_of_keys(&self, image_keys: Vec<ImageKey>);
|
|
}
|