mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Add support for static SVG images using resvg
crate (#36721)
This change adds support for rendering static SVG images using the `resvg` crate, allowing svg sources in the `img` tag and in CSS `background` and `content` properties. There are some limitations in using resvg: 1. There is no support for animations or interactivity as these would require implementing the full DOM layer of SVG specification. 2. Only system fonts can be used for text rendering. There is some mechanism to provide a custom font resolver to usvg, but that is not explored in this change. 3. resvg's handling of certain edge cases involving lack of explicit `width` and `height` on the root svg element deviates from what the specification expects from browsers. For example, resvg uses the values in `viewBox` to derive the missing width or height dimension, but without scaling that dimension to preserve the aspect ratio. It also doesn't allow overriding this behavior. Demo screenshot:  <details> <summary>Source</summary> ``` <style> #svg1 { border: 1px solid red; } #svg2 { border: 1px solid red; width: 300px; } #svg3 { border: 1px solid red; width: 300px; height: 200px; object-fit: contain; } #svg4 { border: 1px solid red; width: 300px; height: 200px; object-fit: cover; } #svg5 { border: 1px solid red; width: 300px; height: 200px; object-fit: fill; } #svg6 { border: 1px solid red; width: 300px; height: 200px; object-fit: none; } </style> </head> <body> <div> <img id="svg1" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo"> </div> <div> <img id="svg2" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo"> <img id="svg3" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo"> <img id="svg4" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo"> </div> <div> <img id="svg5" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo"> <img id="svg6" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo"> </div> </body> ``` </details> --------- Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
324196351e
commit
8a20e42de4
267 changed files with 2374 additions and 544 deletions
234
Cargo.lock
generated
234
Cargo.lock
generated
|
@ -737,6 +737,12 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
|
@ -2130,6 +2136,12 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
|
||||
[[package]]
|
||||
name = "float-ord"
|
||||
version = "0.3.2"
|
||||
|
@ -2173,6 +2185,29 @@ dependencies = [
|
|||
"yeslogic-fontconfig-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
|
||||
dependencies = [
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonts"
|
||||
version = "0.0.1"
|
||||
|
@ -3910,6 +3945,22 @@ dependencies = [
|
|||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||
|
||||
[[package]]
|
||||
name = "imsz"
|
||||
version = "0.2.2"
|
||||
|
@ -4149,6 +4200,16 @@ version = "3.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "layout"
|
||||
version = "0.0.1"
|
||||
|
@ -4832,6 +4893,7 @@ dependencies = [
|
|||
"pixels",
|
||||
"profile_traits",
|
||||
"rayon",
|
||||
"resvg",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
|
@ -4886,6 +4948,7 @@ dependencies = [
|
|||
"servo_url",
|
||||
"url",
|
||||
"uuid",
|
||||
"webrender_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5613,6 +5676,12 @@ dependencies = [
|
|||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
|
@ -5900,6 +5969,12 @@ dependencies = [
|
|||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
|
@ -6118,6 +6193,32 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
|
||||
dependencies = [
|
||||
"gif",
|
||||
"image-webp",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
|
@ -6145,6 +6246,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -6236,6 +6343,24 @@ version = "1.0.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
@ -6857,6 +6982,8 @@ dependencies = [
|
|||
"ipc-channel",
|
||||
"keyboard-types",
|
||||
"markup5ever",
|
||||
"mime",
|
||||
"resvg",
|
||||
"servo_allocator",
|
||||
"servo_arc",
|
||||
"smallvec",
|
||||
|
@ -7028,6 +7155,15 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
|
@ -7182,6 +7318,9 @@ name = "strict-num"
|
|||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
dependencies = [
|
||||
"float-cmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
|
@ -7425,6 +7564,16 @@ version = "0.4.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sw-composite"
|
||||
version = "0.7.16"
|
||||
|
@ -7712,6 +7861,7 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"png",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
|
@ -7746,6 +7896,21 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "to_shmem"
|
||||
version = "0.2.0"
|
||||
|
@ -7991,6 +8156,9 @@ name = "ttf-parser"
|
|||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
|
@ -8092,6 +8260,18 @@ version = "0.3.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
@ -8116,6 +8296,12 @@ version = "1.12.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
|
@ -8172,6 +8358,33 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"imagesize",
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"strict-num",
|
||||
"svgtypes",
|
||||
"tiny-skia-path",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
|
@ -9469,6 +9682,12 @@ dependencies = [
|
|||
"markup5ever",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlwriter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yeslogic-fontconfig-sys"
|
||||
version = "6.0.0"
|
||||
|
@ -9585,6 +9804,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
|
@ -9593,3 +9818,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
|||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
|
|
@ -113,6 +113,7 @@ rand_isaac = "0.3"
|
|||
raw-window-handle = "0.6"
|
||||
rayon = "1"
|
||||
regex = "1.11"
|
||||
resvg = "0.45.0"
|
||||
rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] }
|
||||
rustls-pemfile = "2.0"
|
||||
rustls-pki-types = "1.12"
|
||||
|
|
|
@ -32,7 +32,7 @@ use fnv::FnvHashMap;
|
|||
use ipc_channel::ipc::{self, IpcSharedMemory};
|
||||
use libc::c_void;
|
||||
use log::{debug, info, trace, warn};
|
||||
use pixels::{CorsStatus, Image, ImageFrame, PixelFormat};
|
||||
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
|
||||
use profile_traits::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind};
|
||||
use profile_traits::time::{self as profile_time, ProfilerCategory};
|
||||
use profile_traits::{path, time_profile};
|
||||
|
@ -1431,7 +1431,7 @@ impl IOCompositor {
|
|||
&mut self,
|
||||
webview_id: WebViewId,
|
||||
page_rect: Option<Rect<f32, CSSPixel>>,
|
||||
) -> Result<Option<Image>, UnableToComposite> {
|
||||
) -> Result<Option<RasterImage>, UnableToComposite> {
|
||||
self.render_inner()?;
|
||||
|
||||
let size = self.rendering_context.size2d().to_i32();
|
||||
|
@ -1458,9 +1458,11 @@ impl IOCompositor {
|
|||
Ok(self
|
||||
.rendering_context
|
||||
.read_to_image(rect)
|
||||
.map(|image| Image {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
.map(|image| RasterImage {
|
||||
metadata: ImageMetadata {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
},
|
||||
format: PixelFormat::RGBA8,
|
||||
frames: vec![ImageFrame {
|
||||
delay: None,
|
||||
|
|
|
@ -10,17 +10,21 @@ use fnv::FnvHashMap;
|
|||
use fonts::FontContext;
|
||||
use fxhash::FxHashMap;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
||||
Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
|
||||
UsePlaceholder,
|
||||
};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use pixels::Image as PixelImage;
|
||||
use script_layout_interface::{IFrameSizes, ImageAnimationState, PendingImage, PendingImageState};
|
||||
use pixels::RasterImage;
|
||||
use script_layout_interface::{
|
||||
IFrameSizes, ImageAnimationState, PendingImage, PendingImageState, PendingRasterizationImage,
|
||||
};
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use style::context::SharedStyleContext;
|
||||
use style::dom::OpaqueNode;
|
||||
use style::values::computed::image::{Gradient, Image};
|
||||
use webrender_api::units::{DeviceIntSize, DeviceSize};
|
||||
|
||||
use crate::display_list::WebRenderImageInfo;
|
||||
pub(crate) type CachedImageOrError = Result<CachedImage, ResolveImageError>;
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
pub id: PipelineId,
|
||||
|
@ -39,11 +43,17 @@ pub struct LayoutContext<'a> {
|
|||
/// A list of in-progress image loads to be shared with the script thread.
|
||||
pub pending_images: Mutex<Vec<PendingImage>>,
|
||||
|
||||
/// A list of fully loaded vector images that need to be rasterized to a specific
|
||||
/// size determined by layout. This will be shared with the script thread.
|
||||
pub pending_rasterization_images: Mutex<Vec<PendingRasterizationImage>>,
|
||||
|
||||
/// A collection of `<iframe>` sizes to send back to script.
|
||||
pub iframe_sizes: Mutex<IFrameSizes>,
|
||||
|
||||
pub webrender_image_cache:
|
||||
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
|
||||
// A cache that maps image resources used in CSS (e.g as the `url()` value
|
||||
// for `background-image` or `content` property) to the final resolved image data.
|
||||
pub resolved_images_cache:
|
||||
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
|
||||
|
||||
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
|
||||
|
||||
|
@ -53,18 +63,24 @@ pub struct LayoutContext<'a> {
|
|||
|
||||
pub enum ResolvedImage<'a> {
|
||||
Gradient(&'a Gradient),
|
||||
Image(WebRenderImageInfo),
|
||||
// The size is tracked explicitly as image-set images can specify their
|
||||
// natural resolution which affects the final size for raster images.
|
||||
Image {
|
||||
image: CachedImage,
|
||||
size: DeviceSize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Drop for LayoutContext<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !std::thread::panicking() {
|
||||
assert!(self.pending_images.lock().is_empty());
|
||||
assert!(self.pending_rasterization_images.lock().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ResolveImageError {
|
||||
LoadError,
|
||||
ImagePending,
|
||||
|
@ -78,18 +94,24 @@ pub enum ResolveImageError {
|
|||
None,
|
||||
}
|
||||
|
||||
pub(crate) enum LayoutImageCacheResult {
|
||||
Pending,
|
||||
DataAvailable(ImageOrMetadataAvailable),
|
||||
LoadError,
|
||||
}
|
||||
|
||||
impl LayoutContext<'_> {
|
||||
#[inline(always)]
|
||||
pub fn shared_context(&self) -> &SharedStyleContext {
|
||||
&self.style_context
|
||||
}
|
||||
|
||||
pub fn get_or_request_image_or_meta(
|
||||
pub(crate) fn get_or_request_image_or_meta(
|
||||
&self,
|
||||
node: OpaqueNode,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
) -> Result<ImageOrMetadataAvailable, ResolveImageError> {
|
||||
) -> LayoutImageCacheResult {
|
||||
// Check for available image or start tracking.
|
||||
let cache_result = self.image_cache.get_cached_image_status(
|
||||
url.clone(),
|
||||
|
@ -99,7 +121,9 @@ impl LayoutContext<'_> {
|
|||
);
|
||||
|
||||
match cache_result {
|
||||
ImageCacheResult::Available(img_or_meta) => Ok(img_or_meta),
|
||||
ImageCacheResult::Available(img_or_meta) => {
|
||||
LayoutImageCacheResult::DataAvailable(img_or_meta)
|
||||
},
|
||||
// Image has been requested, is still pending. Return no image for this paint loop.
|
||||
// When the image loads it will trigger a reflow and/or repaint.
|
||||
ImageCacheResult::Pending(id) => {
|
||||
|
@ -110,7 +134,7 @@ impl LayoutContext<'_> {
|
|||
origin: self.origin.clone(),
|
||||
};
|
||||
self.pending_images.lock().push(image);
|
||||
Result::Err(ResolveImageError::ImagePending)
|
||||
LayoutImageCacheResult::Pending
|
||||
},
|
||||
// Not yet requested - request image or metadata from the cache
|
||||
ImageCacheResult::ReadyForRequest(id) => {
|
||||
|
@ -121,14 +145,14 @@ impl LayoutContext<'_> {
|
|||
origin: self.origin.clone(),
|
||||
};
|
||||
self.pending_images.lock().push(image);
|
||||
Result::Err(ResolveImageError::ImageRequested)
|
||||
LayoutImageCacheResult::Pending
|
||||
},
|
||||
// Image failed to load, so just return nothing
|
||||
ImageCacheResult::LoadError => Result::Err(ResolveImageError::LoadError),
|
||||
// Image failed to load, so just return the same error.
|
||||
ImageCacheResult::LoadError => LayoutImageCacheResult::LoadError,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<PixelImage>) {
|
||||
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<RasterImage>) {
|
||||
let mut store = self.node_image_animation_map.write();
|
||||
|
||||
// 1. first check whether node previously being track for animated image.
|
||||
|
@ -157,42 +181,66 @@ impl LayoutContext<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_webrender_image_for_url(
|
||||
fn get_cached_image_for_url(
|
||||
&self,
|
||||
node: OpaqueNode,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
) -> Result<WebRenderImageInfo, ResolveImageError> {
|
||||
if let Some(existing_webrender_image) = self
|
||||
.webrender_image_cache
|
||||
) -> Result<CachedImage, ResolveImageError> {
|
||||
if let Some(cached_image) = self
|
||||
.resolved_images_cache
|
||||
.read()
|
||||
.get(&(url.clone(), use_placeholder))
|
||||
{
|
||||
return Ok(*existing_webrender_image);
|
||||
return cached_image.clone();
|
||||
}
|
||||
let image_or_meta =
|
||||
self.get_or_request_image_or_meta(node, url.clone(), use_placeholder)?;
|
||||
match image_or_meta {
|
||||
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||
self.handle_animated_image(node, image.clone());
|
||||
let image_info = WebRenderImageInfo {
|
||||
size: Size2D::new(image.width, image.height),
|
||||
key: image.id,
|
||||
};
|
||||
if image_info.key.is_none() {
|
||||
Ok(image_info)
|
||||
} else {
|
||||
let mut webrender_image_cache = self.webrender_image_cache.write();
|
||||
webrender_image_cache.insert((url, use_placeholder), image_info);
|
||||
Ok(image_info)
|
||||
}
|
||||
|
||||
let result = self.get_or_request_image_or_meta(node, url.clone(), use_placeholder);
|
||||
match result {
|
||||
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
|
||||
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||
if let Some(image) = image.as_raster_image() {
|
||||
self.handle_animated_image(node, image.clone());
|
||||
}
|
||||
|
||||
let mut resolved_images_cache = self.resolved_images_cache.write();
|
||||
resolved_images_cache.insert((url, use_placeholder), Ok(image.clone()));
|
||||
Ok(image)
|
||||
},
|
||||
ImageOrMetadataAvailable::MetadataAvailable(..) => {
|
||||
Result::Err(ResolveImageError::OnlyMetadata)
|
||||
},
|
||||
},
|
||||
ImageOrMetadataAvailable::MetadataAvailable(..) => {
|
||||
Result::Err(ResolveImageError::OnlyMetadata)
|
||||
LayoutImageCacheResult::Pending => Result::Err(ResolveImageError::ImagePending),
|
||||
LayoutImageCacheResult::LoadError => {
|
||||
let error = Err(ResolveImageError::LoadError);
|
||||
self.resolved_images_cache
|
||||
.write()
|
||||
.insert((url, use_placeholder), error.clone());
|
||||
error
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rasterize_vector_image(
|
||||
&self,
|
||||
image_id: PendingImageId,
|
||||
size: DeviceIntSize,
|
||||
node: OpaqueNode,
|
||||
) -> Option<RasterImage> {
|
||||
let result = self.image_cache.rasterize_vector_image(image_id, size);
|
||||
if result.is_none() {
|
||||
self.pending_rasterization_images
|
||||
.lock()
|
||||
.push(PendingRasterizationImage {
|
||||
id: image_id,
|
||||
node: node.into(),
|
||||
size,
|
||||
});
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn resolve_image<'a>(
|
||||
&self,
|
||||
node: Option<OpaqueNode>,
|
||||
|
@ -215,12 +263,14 @@ impl LayoutContext<'_> {
|
|||
// element and not just the node.
|
||||
let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
|
||||
let node = node.ok_or(ResolveImageError::MissingNode)?;
|
||||
let webrender_info = self.get_webrender_image_for_url(
|
||||
let image = self.get_cached_image_for_url(
|
||||
node,
|
||||
image_url.clone().into(),
|
||||
UsePlaceholder::No,
|
||||
)?;
|
||||
Ok(ResolvedImage::Image(webrender_info))
|
||||
let metadata = image.metadata();
|
||||
let size = Size2D::new(metadata.width, metadata.height).to_f32();
|
||||
Ok(ResolvedImage::Image { image, size })
|
||||
},
|
||||
Image::ImageSet(image_set) => {
|
||||
image_set
|
||||
|
@ -230,17 +280,32 @@ impl LayoutContext<'_> {
|
|||
.and_then(|image| {
|
||||
self.resolve_image(node, &image.image)
|
||||
.map(|info| match info {
|
||||
ResolvedImage::Image(mut image_info) => {
|
||||
ResolvedImage::Image {
|
||||
image: cached_image,
|
||||
..
|
||||
} => {
|
||||
// From <https://drafts.csswg.org/css-images-4/#image-set-notation>:
|
||||
// > A <resolution> (optional). This is used to help the UA decide
|
||||
// > which <image-set-option> to choose. If the image reference is
|
||||
// > for a raster image, it also specifies the image’s natural
|
||||
// > resolution, overriding any other source of data that might
|
||||
// > supply a natural resolution.
|
||||
image_info.size = (image_info.size.to_f32() /
|
||||
image.resolution.dppx())
|
||||
.to_u32();
|
||||
ResolvedImage::Image(image_info)
|
||||
let image_metadata = cached_image.metadata();
|
||||
let size = if cached_image.as_raster_image().is_some() {
|
||||
let scale_factor = image.resolution.dppx();
|
||||
Size2D::new(
|
||||
image_metadata.width as f32 / scale_factor,
|
||||
image_metadata.height as f32 / scale_factor,
|
||||
)
|
||||
} else {
|
||||
Size2D::new(image_metadata.width, image_metadata.height)
|
||||
.to_f32()
|
||||
};
|
||||
|
||||
ResolvedImage::Image {
|
||||
image: cached_image,
|
||||
size,
|
||||
}
|
||||
},
|
||||
_ => info,
|
||||
})
|
||||
|
|
|
@ -14,6 +14,7 @@ use embedder_traits::Cursor;
|
|||
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
|
||||
use fonts::GlyphStore;
|
||||
use gradient::WebRenderGradient;
|
||||
use net_traits::image_cache::Image as CachedImage;
|
||||
use range::Range as ServoRange;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_config::opts::DebugOptions;
|
||||
|
@ -37,7 +38,7 @@ use style::values::generics::rect::Rect;
|
|||
use style::values::specified::text::TextDecorationLine;
|
||||
use style::values::specified::ui::CursorKind;
|
||||
use style_traits::CSSPixel;
|
||||
use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
|
||||
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
|
||||
use webrender_api::{
|
||||
self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode,
|
||||
CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder,
|
||||
|
@ -68,12 +69,6 @@ mod stacking_context;
|
|||
use background::BackgroundPainter;
|
||||
pub use stacking_context::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WebRenderImageInfo {
|
||||
pub size: Size2D<u32, UnknownUnit>,
|
||||
pub key: Option<wr::ImageKey>,
|
||||
}
|
||||
|
||||
// webrender's `ItemTag` is private.
|
||||
type ItemTag = (u64, u16);
|
||||
type HitInfo = Option<ItemTag>;
|
||||
|
@ -1280,20 +1275,41 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
},
|
||||
}
|
||||
},
|
||||
Ok(ResolvedImage::Image(image_info)) => {
|
||||
Ok(ResolvedImage::Image { image, size }) => {
|
||||
// FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution
|
||||
let dppx = 1.0;
|
||||
let intrinsic = NaturalSizes::from_width_and_height(
|
||||
image_info.size.width as f32 / dppx,
|
||||
image_info.size.height as f32 / dppx,
|
||||
);
|
||||
let Some(image_key) = image_info.key else {
|
||||
let intrinsic =
|
||||
NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
|
||||
let layer = background::layout_layer(self, painter, builder, index, intrinsic);
|
||||
let image_wr_key = match image {
|
||||
CachedImage::Raster(raster_image) => raster_image.id,
|
||||
CachedImage::Vector(vector_image) => {
|
||||
let scale = builder.context.shared_context().device_pixel_ratio().0;
|
||||
let default_size: DeviceIntSize =
|
||||
Size2D::new(size.width * scale, size.height * scale).to_i32();
|
||||
let layer_size = layer.as_ref().map(|layer| {
|
||||
Size2D::new(
|
||||
layer.tile_size.width * scale,
|
||||
layer.tile_size.height * scale,
|
||||
)
|
||||
.to_i32()
|
||||
});
|
||||
|
||||
node.and_then(|node| {
|
||||
let size = layer_size.unwrap_or(default_size);
|
||||
builder
|
||||
.context
|
||||
.rasterize_vector_image(vector_image.id, size, node)
|
||||
})
|
||||
.and_then(|rasterized_image| rasterized_image.id)
|
||||
},
|
||||
};
|
||||
|
||||
let Some(image_key) = image_wr_key else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(layer) =
|
||||
background::layout_layer(self, painter, builder, index, intrinsic)
|
||||
{
|
||||
if let Some(layer) = layer {
|
||||
if layer.repeat {
|
||||
builder.wr().push_repeating_image(
|
||||
&layer.common,
|
||||
|
@ -1469,13 +1485,17 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
.resolve_image(node, &border.border_image_source)
|
||||
{
|
||||
Err(_) => return false,
|
||||
Ok(ResolvedImage::Image(image_info)) => {
|
||||
let Some(key) = image_info.key else {
|
||||
Ok(ResolvedImage::Image { image, size }) => {
|
||||
let Some(image) = image.as_raster_image() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
width = image_info.size.width as f32;
|
||||
height = image_info.size.height as f32;
|
||||
let Some(key) = image.id else {
|
||||
return false;
|
||||
};
|
||||
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
NinePatchBorderSource::Image(key, ImageRendering::Auto)
|
||||
},
|
||||
Ok(ResolvedImage::Gradient(gradient)) => {
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
|
||||
use std::any::Any;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use html5ever::{local_name, ns};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use pixels::Image;
|
||||
use net_traits::image_cache::Image;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use script_layout_interface::wrapper_traits::{
|
||||
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
|
@ -197,7 +196,7 @@ impl Drop for BoxSlot<'_> {
|
|||
pub(crate) trait NodeExt<'dom> {
|
||||
/// Returns the image if it’s loaded, and its size in image pixels
|
||||
/// adjusted for `image_density`.
|
||||
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)>;
|
||||
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)>;
|
||||
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
|
||||
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
|
||||
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
|
||||
|
@ -220,12 +219,15 @@ pub(crate) trait NodeExt<'dom> {
|
|||
}
|
||||
|
||||
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
|
||||
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)> {
|
||||
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)> {
|
||||
let node = self.to_threadsafe();
|
||||
let (resource, metadata) = node.image_data()?;
|
||||
let (width, height) = resource
|
||||
.as_ref()
|
||||
.map(|image| (image.width, image.height))
|
||||
.map(|image| {
|
||||
let image_metadata = image.metadata();
|
||||
(image_metadata.width, image_metadata.height)
|
||||
})
|
||||
.or_else(|| metadata.map(|metadata| (metadata.width, metadata.height)))
|
||||
.unwrap_or((0, 0));
|
||||
let (mut width, mut height) = (width as f64, height as f64);
|
||||
|
|
|
@ -76,8 +76,8 @@ use url::Url;
|
|||
use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, LayoutSize};
|
||||
use webrender_api::{ExternalScrollId, HitTestFlags};
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo};
|
||||
use crate::context::{CachedImageOrError, LayoutContext};
|
||||
use crate::display_list::{DisplayListBuilder, StackingContextTree};
|
||||
use crate::query::{
|
||||
get_the_text_steps, process_client_rect_request, process_content_box_request,
|
||||
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
|
||||
|
@ -153,7 +153,10 @@ pub struct LayoutThread {
|
|||
/// Scroll offsets of nodes that scroll.
|
||||
scroll_offsets: RefCell<HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>>,
|
||||
|
||||
webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
|
||||
// A cache that maps image resources specified in CSS (e.g as the `url()` value
|
||||
// for `background-image` or `content` properties) to either the final resolved
|
||||
// image data, or an error if the image cache failed to load/decode the image.
|
||||
resolved_images_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
|
||||
|
||||
/// The executors for paint worklets.
|
||||
registered_painters: RegisteredPaintersImpl,
|
||||
|
@ -525,7 +528,7 @@ impl LayoutThread {
|
|||
compositor_api: config.compositor_api,
|
||||
scroll_offsets: Default::default(),
|
||||
stylist: Stylist::new(device, QuirksMode::NoQuirks),
|
||||
webrender_image_cache: Default::default(),
|
||||
resolved_images_cache: Default::default(),
|
||||
debug: opts::get().debug.clone(),
|
||||
}
|
||||
}
|
||||
|
@ -635,8 +638,9 @@ impl LayoutThread {
|
|||
),
|
||||
image_cache: self.image_cache.clone(),
|
||||
font_context: self.font_context.clone(),
|
||||
webrender_image_cache: self.webrender_image_cache.clone(),
|
||||
resolved_images_cache: self.resolved_images_cache.clone(),
|
||||
pending_images: Mutex::default(),
|
||||
pending_rasterization_images: Mutex::default(),
|
||||
node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
|
||||
&mut reflow_request.node_to_image_animation_map,
|
||||
))),
|
||||
|
@ -662,12 +666,15 @@ impl LayoutThread {
|
|||
}
|
||||
|
||||
let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
|
||||
let pending_rasterization_images =
|
||||
std::mem::take(&mut *layout_context.pending_rasterization_images.lock());
|
||||
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
|
||||
let node_to_image_animation_map =
|
||||
std::mem::take(&mut *layout_context.node_image_animation_map.write());
|
||||
|
||||
Some(ReflowResult {
|
||||
pending_images,
|
||||
pending_rasterization_images,
|
||||
iframe_sizes,
|
||||
node_to_image_animation_map,
|
||||
})
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::LazyCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_units::Au;
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
|
@ -11,8 +10,7 @@ use data_url::DataUrl;
|
|||
use embedder_traits::ViewportDetails;
|
||||
use euclid::{Scale, Size2D};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
|
||||
use pixels::Image;
|
||||
use net_traits::image_cache::{Image, ImageOrMetadataAvailable, UsePlaceholder};
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use script_layout_interface::IFrameSize;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
|
@ -28,7 +26,7 @@ use url::Url;
|
|||
use webrender_api::ImageKey;
|
||||
|
||||
use crate::cell::ArcRefCell;
|
||||
use crate::context::LayoutContext;
|
||||
use crate::context::{LayoutContext, LayoutImageCacheResult};
|
||||
use crate::dom::NodeExt;
|
||||
use crate::fragment_tree::{BaseFragmentInfo, Fragment, IFrameFragment, ImageFragment};
|
||||
use crate::geom::{
|
||||
|
@ -115,7 +113,7 @@ pub(crate) struct VideoInfo {
|
|||
|
||||
#[derive(Debug, MallocSizeOf)]
|
||||
pub(crate) enum ReplacedContentKind {
|
||||
Image(#[conditional_malloc_size_of] Option<Arc<Image>>),
|
||||
Image(Option<Image>),
|
||||
IFrame(IFrameInfo),
|
||||
Canvas(CanvasInfo),
|
||||
Video(Option<VideoInfo>),
|
||||
|
@ -162,7 +160,7 @@ impl ReplacedContents {
|
|||
}
|
||||
};
|
||||
|
||||
if let ReplacedContentKind::Image(Some(ref image)) = kind {
|
||||
if let ReplacedContentKind::Image(Some(Image::Raster(ref image))) = kind {
|
||||
context.handle_animated_image(element.opaque(), image.clone());
|
||||
}
|
||||
|
||||
|
@ -197,13 +195,20 @@ impl ReplacedContents {
|
|||
image_url.clone().into(),
|
||||
UsePlaceholder::No,
|
||||
) {
|
||||
Ok(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
|
||||
(Some(image.clone()), image.width as f32, image.height as f32)
|
||||
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
|
||||
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||
let metadata = image.metadata();
|
||||
(
|
||||
Some(image.clone()),
|
||||
metadata.width as f32,
|
||||
metadata.height as f32,
|
||||
)
|
||||
},
|
||||
ImageOrMetadataAvailable::MetadataAvailable(metadata, _id) => {
|
||||
(None, metadata.width as f32, metadata.height as f32)
|
||||
},
|
||||
},
|
||||
Ok(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => {
|
||||
(None, metadata.width as f32, metadata.height as f32)
|
||||
},
|
||||
Err(_) => return None,
|
||||
LayoutImageCacheResult::Pending | LayoutImageCacheResult::LoadError => return None,
|
||||
};
|
||||
|
||||
return Some(Self {
|
||||
|
@ -315,7 +320,19 @@ impl ReplacedContents {
|
|||
match &self.kind {
|
||||
ReplacedContentKind::Image(image) => image
|
||||
.as_ref()
|
||||
.and_then(|image| image.id)
|
||||
.and_then(|image| match image {
|
||||
Image::Raster(raster_image) => raster_image.id,
|
||||
Image::Vector(vector_image) => {
|
||||
let scale = layout_context.shared_context().device_pixel_ratio();
|
||||
let width = object_fit_size.width.scale_by(scale.0).to_px();
|
||||
let height = object_fit_size.height.scale_by(scale.0).to_px();
|
||||
let size = Size2D::new(width, height);
|
||||
let tag = self.base_fragment_info.tag?;
|
||||
layout_context
|
||||
.rasterize_vector_image(vector_image.id, size, tag.node)
|
||||
.and_then(|i| i.id)
|
||||
},
|
||||
})
|
||||
.map(|image_key| {
|
||||
Fragment::Image(ArcRefCell::new(ImageFragment {
|
||||
base: self.base_fragment_info.into(),
|
||||
|
|
|
@ -22,6 +22,8 @@ indexmap = { workspace = true }
|
|||
ipc-channel = { workspace = true }
|
||||
keyboard-types = { workspace = true }
|
||||
markup5ever = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
servo_allocator = { path = "../allocator" }
|
||||
servo_arc = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
|
|
|
@ -774,6 +774,7 @@ malloc_size_of_is_0!(content_security_policy::Destination);
|
|||
malloc_size_of_is_0!(http::StatusCode);
|
||||
malloc_size_of_is_0!(app_units::Au);
|
||||
malloc_size_of_is_0!(keyboard_types::Modifiers);
|
||||
malloc_size_of_is_0!(mime::Mime);
|
||||
malloc_size_of_is_0!(std::num::NonZeroU64);
|
||||
malloc_size_of_is_0!(std::num::NonZeroUsize);
|
||||
malloc_size_of_is_0!(std::sync::atomic::AtomicBool);
|
||||
|
@ -782,6 +783,7 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize);
|
|||
malloc_size_of_is_0!(std::time::Duration);
|
||||
malloc_size_of_is_0!(std::time::Instant);
|
||||
malloc_size_of_is_0!(std::time::SystemTime);
|
||||
malloc_size_of_is_0!(resvg::usvg::Tree);
|
||||
malloc_size_of_is_0!(style::data::ElementData);
|
||||
malloc_size_of_is_0!(style::font_face::SourceList);
|
||||
malloc_size_of_is_0!(style::properties::ComputedValues);
|
||||
|
|
|
@ -56,6 +56,7 @@ rayon = { workspace = true }
|
|||
rustls = { workspace = true }
|
||||
rustls-pemfile = { workspace = true }
|
||||
rustls-pki-types = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
servo_arc = { workspace = true }
|
||||
|
|
|
@ -7,21 +7,25 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
|
|||
use std::sync::{Arc, Mutex};
|
||||
use std::{mem, thread};
|
||||
|
||||
use base::id::PipelineId;
|
||||
use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData};
|
||||
use imsz::imsz_from_reader;
|
||||
use ipc_channel::ipc::IpcSharedMemory;
|
||||
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
||||
use log::{debug, warn};
|
||||
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use mime::Mime;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
||||
PendingImageId, UsePlaceholder,
|
||||
Image, ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
|
||||
ImageOrMetadataAvailable, ImageResponse, PendingImageId, RasterizationCompleteResponse,
|
||||
UsePlaceholder, VectorImage,
|
||||
};
|
||||
use net_traits::request::CorsSettings;
|
||||
use net_traits::{FetchMetadata, FetchResponseMsg, FilteredMetadata, NetworkError};
|
||||
use pixels::{CorsStatus, Image, ImageMetadata, PixelFormat, load_from_memory};
|
||||
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, load_from_memory};
|
||||
use profile_traits::mem::{Report, ReportKind};
|
||||
use profile_traits::path;
|
||||
use resvg::{tiny_skia, usvg};
|
||||
use servo_config::pref;
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use webrender_api::units::DeviceIntSize;
|
||||
|
@ -48,12 +52,53 @@ const FALLBACK_RIPPY: &[u8] = include_bytes!("../../resources/rippy.png");
|
|||
// Helper functions.
|
||||
// ======================================================================
|
||||
|
||||
fn decode_bytes_sync(key: LoadKey, bytes: &[u8], cors: CorsStatus) -> DecoderMsg {
|
||||
let image = load_from_memory(bytes, cors);
|
||||
fn parse_svg_document_in_memory(bytes: &[u8]) -> Result<usvg::Tree, &'static str> {
|
||||
let image_string_href_resolver = Box::new(move |_: &str, _: &usvg::Options| {
|
||||
// Do not try to load `href` in <image> as local file path.
|
||||
None
|
||||
});
|
||||
|
||||
let mut opt = usvg::Options {
|
||||
image_href_resolver: usvg::ImageHrefResolver {
|
||||
resolve_data: usvg::ImageHrefResolver::default_data_resolver(),
|
||||
resolve_string: image_string_href_resolver,
|
||||
},
|
||||
..usvg::Options::default()
|
||||
};
|
||||
|
||||
opt.fontdb_mut().load_system_fonts();
|
||||
|
||||
usvg::Tree::from_data(bytes, &opt)
|
||||
.inspect_err(|error| {
|
||||
warn!("Error when parsing SVG data: {error}");
|
||||
})
|
||||
.map_err(|_| "Not a valid SVG document")
|
||||
}
|
||||
|
||||
fn decode_bytes_sync(
|
||||
key: LoadKey,
|
||||
bytes: &[u8],
|
||||
cors: CorsStatus,
|
||||
content_type: Option<Mime>,
|
||||
) -> DecoderMsg {
|
||||
let image = if content_type == Some(mime::IMAGE_SVG) {
|
||||
parse_svg_document_in_memory(bytes).ok().map(|svg_tree| {
|
||||
DecodedImage::Vector(VectorImageData {
|
||||
svg_tree: Arc::new(svg_tree),
|
||||
cors_status: cors,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
load_from_memory(bytes, cors).map(DecodedImage::Raster)
|
||||
};
|
||||
|
||||
DecoderMsg { key, image }
|
||||
}
|
||||
|
||||
fn get_placeholder_image(compositor_api: &CrossProcessCompositorApi, data: &[u8]) -> Arc<Image> {
|
||||
fn get_placeholder_image(
|
||||
compositor_api: &CrossProcessCompositorApi,
|
||||
data: &[u8],
|
||||
) -> Arc<RasterImage> {
|
||||
let mut image = load_from_memory(data, CorsStatus::Unsafe)
|
||||
.or_else(|| load_from_memory(FALLBACK_RIPPY, CorsStatus::Unsafe))
|
||||
.expect("load fallback image failed");
|
||||
|
@ -61,14 +106,14 @@ fn get_placeholder_image(compositor_api: &CrossProcessCompositorApi, data: &[u8]
|
|||
Arc::new(image)
|
||||
}
|
||||
|
||||
fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &mut Image) {
|
||||
fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &mut RasterImage) {
|
||||
if image.id.is_some() {
|
||||
return;
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
let frame_bytes = image.first_frame().bytes;
|
||||
let is_opaque = match image.format {
|
||||
PixelFormat::BGRA8 => {
|
||||
PixelFormat::BGRA8 | PixelFormat::RGBA8 => {
|
||||
bytes.extend_from_slice(frame_bytes);
|
||||
pixels::rgba8_premultiply_inplace(bytes.as_mut_slice())
|
||||
},
|
||||
|
@ -80,16 +125,24 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m
|
|||
|
||||
true
|
||||
},
|
||||
PixelFormat::K8 | PixelFormat::KA8 | PixelFormat::RGBA8 => {
|
||||
PixelFormat::K8 | PixelFormat::KA8 => {
|
||||
panic!("Not support by webrender yet");
|
||||
},
|
||||
};
|
||||
let format = if matches!(image.format, PixelFormat::RGBA8) {
|
||||
ImageFormat::RGBA8
|
||||
} else {
|
||||
ImageFormat::BGRA8
|
||||
};
|
||||
|
||||
let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
|
||||
flags.set(ImageDescriptorFlags::IS_OPAQUE, is_opaque);
|
||||
|
||||
let size = DeviceIntSize::new(image.metadata.width as i32, image.metadata.height as i32);
|
||||
let descriptor = ImageDescriptor {
|
||||
size: DeviceIntSize::new(image.width as i32, image.height as i32),
|
||||
size,
|
||||
stride: None,
|
||||
format: ImageFormat::BGRA8,
|
||||
format,
|
||||
offset: 0,
|
||||
flags,
|
||||
};
|
||||
|
@ -204,10 +257,22 @@ impl CompletedLoad {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, MallocSizeOf)]
|
||||
struct VectorImageData {
|
||||
#[conditional_malloc_size_of]
|
||||
svg_tree: Arc<usvg::Tree>,
|
||||
cors_status: CorsStatus,
|
||||
}
|
||||
|
||||
enum DecodedImage {
|
||||
Raster(RasterImage),
|
||||
Vector(VectorImageData),
|
||||
}
|
||||
|
||||
/// Message that the decoder worker threads send to the image cache.
|
||||
struct DecoderMsg {
|
||||
key: LoadKey,
|
||||
image: Option<Image>,
|
||||
image: Option<DecodedImage>,
|
||||
}
|
||||
|
||||
#[derive(MallocSizeOf)]
|
||||
|
@ -265,8 +330,9 @@ impl LoadKeyGenerator {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum LoadResult {
|
||||
Loaded(Image),
|
||||
PlaceholderLoaded(Arc<Image>),
|
||||
LoadedRasterImage(RasterImage),
|
||||
LoadedVectorImage(VectorImageData),
|
||||
PlaceholderLoaded(Arc<RasterImage>),
|
||||
None,
|
||||
}
|
||||
|
||||
|
@ -285,7 +351,7 @@ struct PendingLoad {
|
|||
result: Option<Result<(), NetworkError>>,
|
||||
|
||||
/// The listeners that are waiting for this response to complete.
|
||||
listeners: Vec<ImageResponder>,
|
||||
listeners: Vec<ImageLoadListener>,
|
||||
|
||||
/// The url being loaded. Do not forget that this may be several Mb
|
||||
/// if we are loading a data: url.
|
||||
|
@ -302,6 +368,9 @@ struct PendingLoad {
|
|||
|
||||
/// The URL of the final response that contains a body.
|
||||
final_url: Option<ServoUrl>,
|
||||
|
||||
/// The MIME type from the `Content-type` header of the HTTP response, if any.
|
||||
content_type: Option<Mime>,
|
||||
}
|
||||
|
||||
impl PendingLoad {
|
||||
|
@ -320,33 +389,48 @@ impl PendingLoad {
|
|||
final_url: None,
|
||||
cors_setting,
|
||||
cors_status: CorsStatus::Unsafe,
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: ImageResponder) {
|
||||
fn add_listener(&mut self, listener: ImageLoadListener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// Image cache implementation.
|
||||
// ======================================================================
|
||||
#[derive(Default, MallocSizeOf)]
|
||||
struct RasterizationTask {
|
||||
listeners: Vec<(PipelineId, IpcSender<ImageCacheResponseMessage>)>,
|
||||
result: Option<RasterImage>,
|
||||
}
|
||||
|
||||
/// ## Image cache implementation.
|
||||
#[derive(MallocSizeOf)]
|
||||
struct ImageCacheStore {
|
||||
// Images that are loading over network, or decoding.
|
||||
/// Images that are loading over network, or decoding.
|
||||
pending_loads: AllPendingLoads,
|
||||
|
||||
// Images that have finished loading (successful or not)
|
||||
/// Images that have finished loading (successful or not)
|
||||
completed_loads: HashMap<ImageKey, CompletedLoad>,
|
||||
|
||||
// The placeholder image used when an image fails to load
|
||||
#[conditional_malloc_size_of]
|
||||
placeholder_image: Arc<Image>,
|
||||
/// Vector (e.g. SVG) images that have been sucessfully loaded and parsed
|
||||
/// but are yet to be rasterized. Since the same SVG data can be used for
|
||||
/// rasterizing at different sizes, we use this hasmap to share the data.
|
||||
vector_images: HashMap<PendingImageId, VectorImageData>,
|
||||
|
||||
// The URL used for the placeholder image
|
||||
/// Vector images for which rasterization at a particular size has started
|
||||
/// or completed. If completed, the `result` member of `RasterizationTask`
|
||||
/// contains the rasterized image.
|
||||
rasterized_vector_images: HashMap<(PendingImageId, DeviceIntSize), RasterizationTask>,
|
||||
|
||||
/// The placeholder image used when an image fails to load
|
||||
#[conditional_malloc_size_of]
|
||||
placeholder_image: Arc<RasterImage>,
|
||||
|
||||
/// The URL used for the placeholder image
|
||||
placeholder_url: ServoUrl,
|
||||
|
||||
// Cross-process compositor API instance.
|
||||
/// Cross-process compositor API instance.
|
||||
#[ignore_malloc_size_of = "Channel from another crate"]
|
||||
compositor_api: CrossProcessCompositorApi,
|
||||
}
|
||||
|
@ -361,15 +445,34 @@ impl ImageCacheStore {
|
|||
};
|
||||
|
||||
match load_result {
|
||||
LoadResult::Loaded(ref mut image) => {
|
||||
set_webrender_image_key(&self.compositor_api, image)
|
||||
LoadResult::LoadedRasterImage(ref mut raster_image) => {
|
||||
set_webrender_image_key(&self.compositor_api, raster_image)
|
||||
},
|
||||
LoadResult::LoadedVectorImage(ref vector_image) => {
|
||||
self.vector_images.insert(key, vector_image.clone());
|
||||
},
|
||||
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {},
|
||||
}
|
||||
|
||||
let url = pending_load.final_url.clone();
|
||||
let image_response = match load_result {
|
||||
LoadResult::Loaded(image) => ImageResponse::Loaded(Arc::new(image), url.unwrap()),
|
||||
LoadResult::LoadedRasterImage(raster_image) => {
|
||||
ImageResponse::Loaded(Image::Raster(Arc::new(raster_image)), url.unwrap())
|
||||
},
|
||||
LoadResult::LoadedVectorImage(vector_image) => {
|
||||
let natural_dimensions = vector_image.svg_tree.size().to_int_size();
|
||||
let metadata = ImageMetadata {
|
||||
width: natural_dimensions.width(),
|
||||
height: natural_dimensions.height(),
|
||||
};
|
||||
|
||||
let vector_image = VectorImage {
|
||||
id: key,
|
||||
metadata,
|
||||
cors_status: vector_image.cors_status,
|
||||
};
|
||||
ImageResponse::Loaded(Image::Vector(vector_image), url.unwrap())
|
||||
},
|
||||
LoadResult::PlaceholderLoaded(image) => {
|
||||
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone())
|
||||
},
|
||||
|
@ -399,19 +502,18 @@ impl ImageCacheStore {
|
|||
origin: ImmutableOrigin,
|
||||
cors_setting: Option<CorsSettings>,
|
||||
placeholder: UsePlaceholder,
|
||||
) -> Option<Result<(Arc<Image>, ServoUrl), ()>> {
|
||||
) -> Option<Result<(Image, ServoUrl), ()>> {
|
||||
self.completed_loads
|
||||
.get(&(url, origin, cors_setting))
|
||||
.map(
|
||||
|completed_load| match (&completed_load.image_response, placeholder) {
|
||||
(&ImageResponse::Loaded(ref image, ref url), _) |
|
||||
(
|
||||
&ImageResponse::PlaceholderLoaded(ref image, ref url),
|
||||
UsePlaceholder::Yes,
|
||||
) => Ok((image.clone(), url.clone())),
|
||||
(&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
|
||||
(&ImageResponse::None, _) |
|
||||
(&ImageResponse::MetadataLoaded(_), _) => Err(()),
|
||||
(ImageResponse::Loaded(image, url), _) => Ok((image.clone(), url.clone())),
|
||||
(ImageResponse::PlaceholderLoaded(image, url), UsePlaceholder::Yes) => {
|
||||
Ok((Image::Raster(image.clone()), url.clone()))
|
||||
},
|
||||
(ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
|
||||
(ImageResponse::None, _) |
|
||||
(ImageResponse::MetadataLoaded(_), _) => Err(()),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -421,7 +523,10 @@ impl ImageCacheStore {
|
|||
fn handle_decoder(&mut self, msg: DecoderMsg) {
|
||||
let image = match msg.image {
|
||||
None => LoadResult::None,
|
||||
Some(image) => LoadResult::Loaded(image),
|
||||
Some(DecodedImage::Raster(raster_image)) => LoadResult::LoadedRasterImage(raster_image),
|
||||
Some(DecodedImage::Vector(vector_image_data)) => {
|
||||
LoadResult::LoadedVectorImage(vector_image_data)
|
||||
},
|
||||
};
|
||||
self.complete_load(msg.key, image);
|
||||
}
|
||||
|
@ -450,6 +555,8 @@ impl ImageCache for ImageCacheImpl {
|
|||
store: Arc::new(Mutex::new(ImageCacheStore {
|
||||
pending_loads: AllPendingLoads::new(),
|
||||
completed_loads: HashMap::new(),
|
||||
vector_images: HashMap::new(),
|
||||
rasterized_vector_images: HashMap::new(),
|
||||
placeholder_image: get_placeholder_image(&compositor_api, &rippy_data),
|
||||
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
|
||||
compositor_api,
|
||||
|
@ -475,7 +582,7 @@ impl ImageCache for ImageCacheImpl {
|
|||
url: ServoUrl,
|
||||
origin: ImmutableOrigin,
|
||||
cors_setting: Option<CorsSettings>,
|
||||
) -> Option<Arc<Image>> {
|
||||
) -> Option<Image> {
|
||||
let store = self.store.lock().unwrap();
|
||||
let result =
|
||||
store.get_completed_image_if_available(url, origin, cors_setting, UsePlaceholder::No);
|
||||
|
@ -524,12 +631,17 @@ impl ImageCache for ImageCacheImpl {
|
|||
CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) {
|
||||
(&Some(Ok(_)), _) => {
|
||||
debug!("Sync decoding {} ({:?})", url, key);
|
||||
decode_bytes_sync(key, pl.bytes.as_slice(), pl.cors_status)
|
||||
decode_bytes_sync(
|
||||
key,
|
||||
pl.bytes.as_slice(),
|
||||
pl.cors_status,
|
||||
pl.content_type.clone(),
|
||||
)
|
||||
},
|
||||
(&None, Some(meta)) => {
|
||||
debug!("Metadata available for {} ({:?})", url, key);
|
||||
return ImageCacheResult::Available(
|
||||
ImageOrMetadataAvailable::MetadataAvailable(meta.clone(), key),
|
||||
ImageOrMetadataAvailable::MetadataAvailable(*meta, key),
|
||||
);
|
||||
},
|
||||
(&Some(Err(_)), _) | (&None, &None) => {
|
||||
|
@ -566,9 +678,137 @@ impl ImageCache for ImageCacheImpl {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_rasterization_complete_listener(
|
||||
&self,
|
||||
pipeline_id: PipelineId,
|
||||
image_id: PendingImageId,
|
||||
requested_size: DeviceIntSize,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
) {
|
||||
let completed = {
|
||||
let mut store = self.store.lock().unwrap();
|
||||
let key = (image_id, requested_size);
|
||||
if !store.vector_images.contains_key(&image_id) {
|
||||
warn!("Unknown image requested for rasterization for key {key:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(task) = store.rasterized_vector_images.get_mut(&key) else {
|
||||
warn!("Image rasterization task not found in the cache for key {key:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
match task.result {
|
||||
Some(_) => true,
|
||||
None => {
|
||||
task.listeners.push((pipeline_id, sender.clone()));
|
||||
false
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if completed {
|
||||
let _ = sender.send(ImageCacheResponseMessage::VectorImageRasterizationComplete(
|
||||
RasterizationCompleteResponse {
|
||||
pipeline_id,
|
||||
image_id,
|
||||
requested_size,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn rasterize_vector_image(
|
||||
&self,
|
||||
image_id: PendingImageId,
|
||||
requested_size: DeviceIntSize,
|
||||
) -> Option<RasterImage> {
|
||||
let mut store = self.store.lock().unwrap();
|
||||
let Some(vector_image) = store.vector_images.get(&image_id).cloned() else {
|
||||
warn!("Unknown image id {image_id:?} requested for rasterization");
|
||||
return None;
|
||||
};
|
||||
|
||||
// This early return relies on the fact that the result of image rasterization cannot
|
||||
// ever be `None`. If that were the case we would need to check whether the entry
|
||||
// in the `HashMap` was `Occupied` or not.
|
||||
let entry = store
|
||||
.rasterized_vector_images
|
||||
.entry((image_id, requested_size))
|
||||
.or_default();
|
||||
if let Some(result) = entry.result.as_ref() {
|
||||
return Some(result.clone());
|
||||
}
|
||||
|
||||
let store = self.store.clone();
|
||||
self.thread_pool.spawn(move || {
|
||||
let natural_size = vector_image.svg_tree.size().to_int_size();
|
||||
let tinyskia_requested_size = {
|
||||
let width = requested_size.width.try_into().unwrap_or(0);
|
||||
let height = requested_size.height.try_into().unwrap_or(0);
|
||||
tiny_skia::IntSize::from_wh(width, height).unwrap_or(natural_size)
|
||||
};
|
||||
let transform = tiny_skia::Transform::from_scale(
|
||||
tinyskia_requested_size.width() as f32 / natural_size.width() as f32,
|
||||
tinyskia_requested_size.height() as f32 / natural_size.height() as f32,
|
||||
);
|
||||
let mut pixmap = tiny_skia::Pixmap::new(
|
||||
tinyskia_requested_size.width(),
|
||||
tinyskia_requested_size.height(),
|
||||
)
|
||||
.unwrap();
|
||||
resvg::render(&vector_image.svg_tree, transform, &mut pixmap.as_mut());
|
||||
|
||||
let bytes = pixmap.take();
|
||||
let frame = ImageFrame {
|
||||
delay: None,
|
||||
byte_range: 0..bytes.len(),
|
||||
width: tinyskia_requested_size.width(),
|
||||
height: tinyskia_requested_size.height(),
|
||||
};
|
||||
|
||||
let mut rasterized_image = RasterImage {
|
||||
metadata: ImageMetadata {
|
||||
width: tinyskia_requested_size.width(),
|
||||
height: tinyskia_requested_size.height(),
|
||||
},
|
||||
format: PixelFormat::RGBA8,
|
||||
frames: vec![frame],
|
||||
bytes: IpcSharedMemory::from_bytes(&bytes),
|
||||
id: None,
|
||||
cors_status: vector_image.cors_status,
|
||||
};
|
||||
|
||||
let listeners = {
|
||||
let mut store = store.lock().unwrap();
|
||||
set_webrender_image_key(&store.compositor_api, &mut rasterized_image);
|
||||
store
|
||||
.rasterized_vector_images
|
||||
.get_mut(&(image_id, requested_size))
|
||||
.map(|task| {
|
||||
task.result = Some(rasterized_image);
|
||||
std::mem::take(&mut task.listeners)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
for (pipeline_id, sender) in listeners {
|
||||
let _ = sender.send(ImageCacheResponseMessage::VectorImageRasterizationComplete(
|
||||
RasterizationCompleteResponse {
|
||||
pipeline_id,
|
||||
image_id,
|
||||
requested_size,
|
||||
},
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// 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: ImageResponder) {
|
||||
fn add_listener(&self, listener: ImageLoadListener) {
|
||||
let mut store = self.store.lock().unwrap();
|
||||
self.add_listener_with_store(&mut store, listener);
|
||||
}
|
||||
|
@ -603,6 +843,10 @@ impl ImageCache for ImageCacheImpl {
|
|||
let final_url = metadata.as_ref().map(|m| m.final_url.clone());
|
||||
pending_load.final_url = final_url;
|
||||
pending_load.cors_status = cors_status;
|
||||
pending_load.content_type = metadata
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.content_type.clone())
|
||||
.map(|content_type| content_type.into_inner().into());
|
||||
},
|
||||
(FetchResponseMsg::ProcessResponseChunk(_, data), _) => {
|
||||
debug!("Got some data for {:?}", id);
|
||||
|
@ -619,7 +863,7 @@ impl ImageCache for ImageCacheImpl {
|
|||
height: info.height as u32,
|
||||
};
|
||||
for listener in &pending_load.listeners {
|
||||
listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone()));
|
||||
listener.respond(ImageResponse::MetadataLoaded(img_metadata));
|
||||
}
|
||||
pending_load.metadata = Some(img_metadata);
|
||||
}
|
||||
|
@ -629,17 +873,21 @@ impl ImageCache for ImageCacheImpl {
|
|||
debug!("Received EOF for {:?}", key);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let (bytes, cors_status) = {
|
||||
let (bytes, cors_status, content_type) = {
|
||||
let mut store = self.store.lock().unwrap();
|
||||
let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap();
|
||||
pending_load.result = Some(Ok(()));
|
||||
debug!("Async decoding {} ({:?})", pending_load.url, key);
|
||||
(pending_load.bytes.mark_complete(), pending_load.cors_status)
|
||||
(
|
||||
pending_load.bytes.mark_complete(),
|
||||
pending_load.cors_status,
|
||||
pending_load.content_type.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let local_store = self.store.clone();
|
||||
self.thread_pool.spawn(move || {
|
||||
let msg = decode_bytes_sync(key, &bytes, cors_status);
|
||||
let msg = decode_bytes_sync(key, &bytes, cors_status, content_type);
|
||||
debug!("Image decoded");
|
||||
local_store.lock().unwrap().handle_decoder(msg);
|
||||
});
|
||||
|
@ -669,6 +917,8 @@ impl ImageCache for ImageCacheImpl {
|
|||
placeholder_image,
|
||||
placeholder_url,
|
||||
compositor_api,
|
||||
vector_images: HashMap::new(),
|
||||
rasterized_vector_images: HashMap::new(),
|
||||
})),
|
||||
thread_pool: self.thread_pool.clone(),
|
||||
})
|
||||
|
@ -681,9 +931,16 @@ impl Drop for ImageCacheStore {
|
|||
.completed_loads
|
||||
.values()
|
||||
.filter_map(|load| match &load.image_response {
|
||||
ImageResponse::Loaded(image, _) => image.id.map(ImageUpdate::DeleteImage),
|
||||
ImageResponse::Loaded(Image::Raster(image), _) => {
|
||||
image.id.map(ImageUpdate::DeleteImage)
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.chain(
|
||||
self.rasterized_vector_images
|
||||
.values()
|
||||
.filter_map(|task| task.result.as_ref()?.id.map(ImageUpdate::DeleteImage)),
|
||||
)
|
||||
.collect();
|
||||
self.compositor_api.update_images(image_updates);
|
||||
}
|
||||
|
@ -691,11 +948,11 @@ impl Drop for ImageCacheStore {
|
|||
|
||||
impl ImageCacheImpl {
|
||||
/// Require self.store.lock() before calling.
|
||||
fn add_listener_with_store(&self, store: &mut ImageCacheStore, listener: ImageResponder) {
|
||||
fn add_listener_with_store(&self, store: &mut ImageCacheStore, listener: ImageLoadListener) {
|
||||
let id = listener.id;
|
||||
if let Some(load) = store.pending_loads.get_by_key_mut(&id) {
|
||||
if let Some(ref metadata) = load.metadata {
|
||||
listener.respond(ImageResponse::MetadataLoaded(metadata.clone()));
|
||||
listener.respond(ImageResponse::MetadataLoaded(*metadata));
|
||||
}
|
||||
load.add_listener(listener);
|
||||
return;
|
||||
|
|
|
@ -121,9 +121,8 @@ pub enum CorsStatus {
|
|||
}
|
||||
|
||||
#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub struct RasterImage {
|
||||
pub metadata: ImageMetadata,
|
||||
pub format: PixelFormat,
|
||||
pub id: Option<ImageKey>,
|
||||
pub cors_status: CorsStatus,
|
||||
|
@ -149,7 +148,7 @@ pub struct ImageFrameView<'a> {
|
|||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
impl RasterImage {
|
||||
pub fn should_animate(&self) -> bool {
|
||||
self.frames.len() > 1
|
||||
}
|
||||
|
@ -170,17 +169,17 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Image {
|
||||
impl fmt::Debug for RasterImage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
|
||||
self.width, self.height, self.format, self.id
|
||||
self.metadata.width, self.metadata.height, self.format, self.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub struct ImageMetadata {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
|
@ -189,7 +188,7 @@ pub struct ImageMetadata {
|
|||
// FIXME: Images must not be copied every frame. Instead we should atomically
|
||||
// reference count them.
|
||||
|
||||
pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
|
||||
pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
|
||||
if buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -212,9 +211,11 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image>
|
|||
width: rgba.width(),
|
||||
height: rgba.height(),
|
||||
};
|
||||
Some(Image {
|
||||
width: rgba.width(),
|
||||
height: rgba.height(),
|
||||
Some(RasterImage {
|
||||
metadata: ImageMetadata {
|
||||
width: rgba.width(),
|
||||
height: rgba.height(),
|
||||
},
|
||||
format: PixelFormat::BGRA8,
|
||||
frames: vec![frame],
|
||||
bytes: IpcSharedMemory::from_bytes(&rgba),
|
||||
|
@ -374,7 +375,7 @@ fn is_webp(buffer: &[u8]) -> bool {
|
|||
buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
|
||||
}
|
||||
|
||||
fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
|
||||
fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
|
||||
let Ok(decoded_gif) = GifDecoder::new(Cursor::new(buffer)) else {
|
||||
return None;
|
||||
};
|
||||
|
@ -430,9 +431,8 @@ fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
|
|||
bytes.extend_from_slice(frame.buffer());
|
||||
}
|
||||
|
||||
Some(Image {
|
||||
width,
|
||||
height,
|
||||
Some(RasterImage {
|
||||
metadata: ImageMetadata { width, height },
|
||||
cors_status,
|
||||
frames,
|
||||
id: None,
|
||||
|
|
|
@ -328,7 +328,15 @@ impl CanvasState {
|
|||
cors_setting: Option<CorsSettings>,
|
||||
) -> Option<snapshot::Snapshot> {
|
||||
let img = match self.request_image_from_cache(url, cors_setting) {
|
||||
ImageResponse::Loaded(img, _) => img,
|
||||
ImageResponse::Loaded(image, _) => {
|
||||
if let Some(image) = image.as_raster_image() {
|
||||
image
|
||||
} else {
|
||||
// TODO: https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
|
||||
warn!("Vector images are not supported as image source in canvas2d");
|
||||
return None;
|
||||
}
|
||||
},
|
||||
ImageResponse::PlaceholderLoaded(_, _) |
|
||||
ImageResponse::None |
|
||||
ImageResponse::MetadataLoaded(_) => {
|
||||
|
@ -336,7 +344,7 @@ impl CanvasState {
|
|||
},
|
||||
};
|
||||
|
||||
let size = Size2D::new(img.width, img.height);
|
||||
let size = Size2D::new(img.metadata.width, img.metadata.height);
|
||||
let format = match img.format {
|
||||
PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA,
|
||||
PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA,
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::rc::Rc;
|
|||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::{HandleObject, MutableHandleValue};
|
||||
use net_traits::image_cache::Image;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::DataTransferBinding::DataTransferMethods;
|
||||
|
@ -155,7 +156,10 @@ impl DataTransferMethods<crate::DomTypeHolder> for DataTransfer {
|
|||
|
||||
// Step 3
|
||||
if let Some(image) = image.downcast::<HTMLImageElement>() {
|
||||
data_store.set_bitmap(image.image_data(), x, y);
|
||||
match image.image_data().as_ref().and_then(Image::as_raster_image) {
|
||||
Some(image) => data_store.set_bitmap(Some(image), x, y),
|
||||
None => warn!("Vector images are not yet supported in setDragImage"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ use js::rust::HandleObject;
|
|||
use mime::{self, Mime};
|
||||
use net_traits::http_status::HttpStatus;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
||||
PendingImageId, UsePlaceholder,
|
||||
Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
|
||||
ImageResponse, PendingImageId, UsePlaceholder,
|
||||
};
|
||||
use net_traits::request::{Destination, Initiator, RequestId};
|
||||
use net_traits::{
|
||||
|
@ -29,7 +29,7 @@ use net_traits::{
|
|||
ResourceFetchTiming, ResourceTimingType,
|
||||
};
|
||||
use num_traits::ToPrimitive;
|
||||
use pixels::{CorsStatus, Image, ImageMetadata};
|
||||
use pixels::{CorsStatus, ImageMetadata};
|
||||
use servo_url::ServoUrl;
|
||||
use servo_url::origin::MutableOrigin;
|
||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_integer, parse_length};
|
||||
|
@ -146,9 +146,8 @@ struct ImageRequest {
|
|||
parsed_url: Option<ServoUrl>,
|
||||
source_url: Option<USVString>,
|
||||
blocker: DomRefCell<Option<LoadBlocker>>,
|
||||
#[conditional_malloc_size_of]
|
||||
#[no_trace]
|
||||
image: Option<Arc<Image>>,
|
||||
image: Option<Image>,
|
||||
#[no_trace]
|
||||
metadata: Option<ImageMetadata>,
|
||||
#[no_trace]
|
||||
|
@ -177,7 +176,8 @@ impl HTMLImageElement {
|
|||
pub(crate) fn is_usable(&self) -> Fallible<bool> {
|
||||
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
|
||||
if let Some(image) = &self.current_request.borrow().image {
|
||||
if image.width == 0 || image.height == 0 {
|
||||
let intrinsic_size = image.metadata();
|
||||
if intrinsic_size.width == 0 || intrinsic_size.height == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ impl HTMLImageElement {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn image_data(&self) -> Option<Arc<Image>> {
|
||||
pub(crate) fn image_data(&self) -> Option<Image> {
|
||||
self.current_request.borrow().image.clone()
|
||||
}
|
||||
}
|
||||
|
@ -341,10 +341,12 @@ impl HTMLImageElement {
|
|||
is_placeholder,
|
||||
}) => {
|
||||
if is_placeholder {
|
||||
self.process_image_response(
|
||||
ImageResponse::PlaceholderLoaded(image, url),
|
||||
can_gc,
|
||||
)
|
||||
if let Some(raster_image) = image.as_raster_image() {
|
||||
self.process_image_response(
|
||||
ImageResponse::PlaceholderLoaded(raster_image, url),
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
self.process_image_response(ImageResponse::Loaded(image, url), can_gc)
|
||||
}
|
||||
|
@ -403,7 +405,7 @@ impl HTMLImageElement {
|
|||
|
||||
window
|
||||
.image_cache()
|
||||
.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
|
||||
.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
|
||||
}
|
||||
|
||||
fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
|
||||
|
@ -448,11 +450,8 @@ impl HTMLImageElement {
|
|||
}
|
||||
|
||||
// Steps common to when an image has been loaded.
|
||||
fn handle_loaded_image(&self, image: Arc<Image>, url: ServoUrl, can_gc: CanGc) {
|
||||
self.current_request.borrow_mut().metadata = Some(ImageMetadata {
|
||||
height: image.height,
|
||||
width: image.width,
|
||||
});
|
||||
fn handle_loaded_image(&self, image: Image, url: ServoUrl, can_gc: CanGc) {
|
||||
self.current_request.borrow_mut().metadata = Some(image.metadata());
|
||||
self.current_request.borrow_mut().final_url = Some(url);
|
||||
self.current_request.borrow_mut().image = Some(image);
|
||||
self.current_request.borrow_mut().state = State::CompletelyAvailable;
|
||||
|
@ -471,7 +470,7 @@ impl HTMLImageElement {
|
|||
(true, false)
|
||||
},
|
||||
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
|
||||
self.handle_loaded_image(image, url, can_gc);
|
||||
self.handle_loaded_image(Image::Raster(image), url, can_gc);
|
||||
(false, true)
|
||||
},
|
||||
(ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
|
||||
|
@ -483,7 +482,7 @@ impl HTMLImageElement {
|
|||
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
|
||||
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
|
||||
self.image_request.set(ImageRequestPhase::Current);
|
||||
self.handle_loaded_image(image, url, can_gc);
|
||||
self.handle_loaded_image(Image::Raster(image), url, can_gc);
|
||||
(false, true)
|
||||
},
|
||||
(ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
|
||||
|
@ -536,11 +535,15 @@ impl HTMLImageElement {
|
|||
can_gc: CanGc,
|
||||
) {
|
||||
match image {
|
||||
ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
|
||||
self.pending_request.borrow_mut().metadata = Some(ImageMetadata {
|
||||
height: image.height,
|
||||
width: image.width,
|
||||
});
|
||||
ImageResponse::Loaded(image, url) => {
|
||||
self.pending_request.borrow_mut().metadata = Some(image.metadata());
|
||||
self.pending_request.borrow_mut().final_url = Some(url);
|
||||
self.pending_request.borrow_mut().image = Some(image);
|
||||
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
|
||||
},
|
||||
ImageResponse::PlaceholderLoaded(image, url) => {
|
||||
let image = Image::Raster(image);
|
||||
self.pending_request.borrow_mut().metadata = Some(image.metadata());
|
||||
self.pending_request.borrow_mut().final_url = Some(url);
|
||||
self.pending_request.borrow_mut().image = Some(image);
|
||||
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
|
||||
|
@ -1020,10 +1023,7 @@ impl HTMLImageElement {
|
|||
// set on this element.
|
||||
self.generation.set(self.generation.get() + 1);
|
||||
// Step 6.3
|
||||
let metadata = ImageMetadata {
|
||||
height: image.height,
|
||||
width: image.width,
|
||||
};
|
||||
let metadata = image.metadata();
|
||||
// Step 6.3.2 abort requests
|
||||
self.abort_request(
|
||||
State::CompletelyAvailable,
|
||||
|
@ -1033,7 +1033,7 @@ impl HTMLImageElement {
|
|||
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
|
||||
let mut current_request = self.current_request.borrow_mut();
|
||||
current_request.final_url = Some(img_url.clone());
|
||||
current_request.image = Some(image.clone());
|
||||
current_request.image = Some(image);
|
||||
current_request.metadata = Some(metadata);
|
||||
// Step 6.3.6
|
||||
current_request.current_pixel_density = pixel_density;
|
||||
|
@ -1360,7 +1360,7 @@ impl HTMLImageElement {
|
|||
|
||||
pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
|
||||
if let Some(ref image) = self.current_request.borrow().image {
|
||||
return image.cors_status == CorsStatus::Safe;
|
||||
return image.cors_status() == CorsStatus::Safe;
|
||||
}
|
||||
|
||||
self.current_request
|
||||
|
@ -1432,7 +1432,7 @@ impl MicrotaskRunnable for ImageElementMicrotask {
|
|||
pub(crate) trait LayoutHTMLImageElementHelpers {
|
||||
fn image_url(self) -> Option<ServoUrl>;
|
||||
fn image_density(self) -> Option<f64>;
|
||||
fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>);
|
||||
fn image_data(self) -> (Option<Image>, Option<ImageMetadata>);
|
||||
fn get_width(self) -> LengthOrPercentageOrAuto;
|
||||
fn get_height(self) -> LengthOrPercentageOrAuto;
|
||||
}
|
||||
|
@ -1449,12 +1449,9 @@ impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
|
|||
self.current_request().parsed_url.clone()
|
||||
}
|
||||
|
||||
fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>) {
|
||||
fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
|
||||
let current_request = self.current_request();
|
||||
(
|
||||
current_request.image.clone(),
|
||||
current_request.metadata.clone(),
|
||||
)
|
||||
(current_request.image.clone(), current_request.metadata)
|
||||
}
|
||||
|
||||
fn image_density(self) -> Option<f64> {
|
||||
|
|
|
@ -27,7 +27,7 @@ use net_traits::{
|
|||
FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming,
|
||||
ResourceTimingType,
|
||||
};
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use script_bindings::codegen::GenericBindings::TimeRangesBinding::TimeRangesMethods;
|
||||
use script_bindings::codegen::InheritTypes::{
|
||||
ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
|
||||
|
@ -186,12 +186,12 @@ impl MediaFrameRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_poster_frame(&mut self, image: Arc<Image>) {
|
||||
fn render_poster_frame(&mut self, image: Arc<RasterImage>) {
|
||||
if let Some(image_key) = image.id {
|
||||
self.current_frame = Some(MediaFrame {
|
||||
image_key,
|
||||
width: image.width as i32,
|
||||
height: image.height as i32,
|
||||
width: image.metadata.width as i32,
|
||||
height: image.metadata.height as i32,
|
||||
});
|
||||
self.show_poster = true;
|
||||
}
|
||||
|
@ -1358,13 +1358,13 @@ impl HTMLMediaElement {
|
|||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
|
||||
pub(crate) fn process_poster_image_loaded(&self, image: Arc<Image>) {
|
||||
pub(crate) fn process_poster_image_loaded(&self, image: Arc<RasterImage>) {
|
||||
if !self.show_poster.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
self.handle_resize(Some(image.width), Some(image.height));
|
||||
self.handle_resize(Some(image.metadata.width), Some(image.metadata.height));
|
||||
self.video_renderer
|
||||
.lock()
|
||||
.unwrap()
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::default::Default;
|
|||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||
use js::rust::HandleObject;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use servo_arc::Arc;
|
||||
|
||||
use crate::dom::attr::Attr;
|
||||
|
@ -31,7 +31,7 @@ pub(crate) struct HTMLObjectElement {
|
|||
htmlelement: HTMLElement,
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
#[no_trace]
|
||||
image: DomRefCell<Option<Arc<Image>>>,
|
||||
image: DomRefCell<Option<Arc<RasterImage>>>,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use html5ever::{LocalName, Prefix, local_name, ns};
|
|||
use ipc_channel::ipc;
|
||||
use js::rust::HandleObject;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
||||
ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse,
|
||||
PendingImageId, UsePlaceholder,
|
||||
};
|
||||
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
|
||||
|
@ -217,7 +217,7 @@ impl HTMLVideoElement {
|
|||
element.process_image_response(response.response, CanGc::note());
|
||||
});
|
||||
|
||||
image_cache.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
|
||||
image_cache.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
|
||||
|
@ -262,7 +262,10 @@ impl HTMLVideoElement {
|
|||
match response {
|
||||
ImageResponse::Loaded(image, url) => {
|
||||
debug!("Loaded poster image for video element: {:?}", url);
|
||||
self.htmlmediaelement.process_poster_image_loaded(image);
|
||||
match image.as_raster_image() {
|
||||
Some(image) => self.htmlmediaelement.process_poster_image_loaded(image),
|
||||
None => warn!("Vector images are not yet supported in video poster"),
|
||||
}
|
||||
LoadBlocker::terminate(&self.load_blocker, can_gc);
|
||||
},
|
||||
ImageResponse::MetadataLoaded(..) => {},
|
||||
|
|
|
@ -10,7 +10,6 @@ use std::default::Default;
|
|||
use std::f64::consts::PI;
|
||||
use std::ops::Range;
|
||||
use std::slice::from_ref;
|
||||
use std::sync::Arc as StdArc;
|
||||
use std::{cmp, fmt, iter};
|
||||
|
||||
use app_units::Au;
|
||||
|
@ -26,7 +25,8 @@ use js::jsapi::JSObject;
|
|||
use js::rust::HandleObject;
|
||||
use libc::{self, c_void, uintptr_t};
|
||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||
use pixels::{Image, ImageMetadata};
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use script_bindings::codegen::InheritTypes::DocumentFragmentTypeId;
|
||||
use script_layout_interface::{
|
||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
|
||||
|
@ -1618,7 +1618,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
|
|||
fn selection(self) -> Option<Range<usize>>;
|
||||
fn image_url(self) -> Option<ServoUrl>;
|
||||
fn image_density(self) -> Option<f64>;
|
||||
fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>;
|
||||
fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
|
||||
fn canvas_data(self) -> Option<HTMLCanvasData>;
|
||||
fn media_data(self) -> Option<HTMLMediaData>;
|
||||
fn svg_data(self) -> Option<SVGSVGData>;
|
||||
|
@ -1831,7 +1831,7 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
.image_url()
|
||||
}
|
||||
|
||||
fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> {
|
||||
fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
|
||||
self.downcast::<HTMLImageElement>().map(|e| e.image_data())
|
||||
}
|
||||
|
||||
|
|
|
@ -21,15 +21,15 @@ use js::jsval::JSVal;
|
|||
use js::rust::{HandleObject, MutableHandleValue};
|
||||
use net_traits::http_status::HttpStatus;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
||||
PendingImageId, PendingImageResponse, UsePlaceholder,
|
||||
ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
|
||||
ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder,
|
||||
};
|
||||
use net_traits::request::{RequestBuilder, RequestId};
|
||||
use net_traits::{
|
||||
FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
|
||||
ResourceTimingType,
|
||||
};
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -114,15 +114,15 @@ pub(crate) struct Notification {
|
|||
/// <https://notifications.spec.whatwg.org/#image-resource>
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
#[no_trace]
|
||||
image_resource: DomRefCell<Option<Arc<Image>>>,
|
||||
image_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||
/// <https://notifications.spec.whatwg.org/#icon-resource>
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
#[no_trace]
|
||||
icon_resource: DomRefCell<Option<Arc<Image>>>,
|
||||
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||
/// <https://notifications.spec.whatwg.org/#badge-resource>
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
#[no_trace]
|
||||
badge_resource: DomRefCell<Option<Arc<Image>>>,
|
||||
badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||
}
|
||||
|
||||
impl Notification {
|
||||
|
@ -561,7 +561,7 @@ struct Action {
|
|||
/// <https://notifications.spec.whatwg.org/#action-icon-resource>
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
#[no_trace]
|
||||
icon_resource: DomRefCell<Option<Arc<Image>>>,
|
||||
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||
}
|
||||
|
||||
/// <https://notifications.spec.whatwg.org/#create-a-notification-with-a-settings-object>
|
||||
|
@ -886,7 +886,11 @@ impl Notification {
|
|||
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
|
||||
image, ..
|
||||
}) => {
|
||||
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
|
||||
let image = image.as_raster_image();
|
||||
if image.is_none() {
|
||||
warn!("Vector images are not supported in notifications yet");
|
||||
};
|
||||
self.set_resource_and_show_when_ready(request_id, &resource_type, image);
|
||||
},
|
||||
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
|
||||
_,
|
||||
|
@ -925,8 +929,7 @@ impl Notification {
|
|||
pending_image_id: PendingImageId,
|
||||
resource_type: ResourceType,
|
||||
) {
|
||||
let (sender, receiver) =
|
||||
ipc::channel::<PendingImageResponse>().expect("ipc channel failure");
|
||||
let (sender, receiver) = ipc::channel().expect("ipc channel failure");
|
||||
|
||||
let global: &GlobalScope = &self.global();
|
||||
|
||||
|
@ -942,7 +945,11 @@ impl Notification {
|
|||
task_source.queue(task!(handle_response: move || {
|
||||
let this = trusted_this.root();
|
||||
if let Ok(response) = response {
|
||||
this.handle_image_cache_response(request_id, response.response, resource_type);
|
||||
let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
|
||||
warn!("Received unexpected message from image cache: {response:?}");
|
||||
return;
|
||||
};
|
||||
this.handle_image_cache_response(request_id, status.response, resource_type);
|
||||
} else {
|
||||
this.handle_image_cache_response(request_id, ImageResponse::None, resource_type);
|
||||
}
|
||||
|
@ -950,7 +957,7 @@ impl Notification {
|
|||
}),
|
||||
);
|
||||
|
||||
global.image_cache().add_listener(ImageResponder::new(
|
||||
global.image_cache().add_listener(ImageLoadListener::new(
|
||||
sender,
|
||||
global.pipeline_id(),
|
||||
pending_image_id,
|
||||
|
@ -965,7 +972,11 @@ impl Notification {
|
|||
) {
|
||||
match response {
|
||||
ImageResponse::Loaded(image, _) => {
|
||||
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
|
||||
let image = image.as_raster_image();
|
||||
if image.is_none() {
|
||||
warn!("Vector images are not yet supported in notification attribute");
|
||||
};
|
||||
self.set_resource_and_show_when_ready(request_id, &resource_type, image);
|
||||
},
|
||||
ImageResponse::PlaceholderLoaded(image, _) => {
|
||||
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
|
||||
|
@ -981,7 +992,7 @@ impl Notification {
|
|||
&self,
|
||||
request_id: RequestId,
|
||||
resource_type: &ResourceType,
|
||||
image: Option<Arc<Image>>,
|
||||
image: Option<Arc<RasterImage>>,
|
||||
) {
|
||||
match resource_type {
|
||||
ResourceType::Image => {
|
||||
|
|
|
@ -609,15 +609,31 @@ impl WebGLRenderingContext {
|
|||
};
|
||||
let cors_setting = cors_setting_for_element(image.upcast());
|
||||
|
||||
let img =
|
||||
match canvas_utils::request_image_from_cache(&window, img_url, cors_setting) {
|
||||
ImageResponse::Loaded(img, _) => img,
|
||||
ImageResponse::PlaceholderLoaded(_, _) |
|
||||
ImageResponse::None |
|
||||
ImageResponse::MetadataLoaded(_) => return Ok(None),
|
||||
};
|
||||
let img = match canvas_utils::request_image_from_cache(
|
||||
&window,
|
||||
img_url,
|
||||
cors_setting,
|
||||
) {
|
||||
ImageResponse::Loaded(image, _) => {
|
||||
match image.as_raster_image() {
|
||||
Some(image) => image,
|
||||
None => {
|
||||
// Vector images are not currently supported here and there are some open questions
|
||||
// in the specification about how to handle them:
|
||||
// See https://github.com/KhronosGroup/WebGL/issues/1503.
|
||||
warn!(
|
||||
"Vector images as are not yet supported as WebGL texture source"
|
||||
);
|
||||
return Ok(None);
|
||||
},
|
||||
}
|
||||
},
|
||||
ImageResponse::PlaceholderLoaded(_, _) |
|
||||
ImageResponse::None |
|
||||
ImageResponse::MetadataLoaded(_) => return Ok(None),
|
||||
};
|
||||
|
||||
let size = Size2D::new(img.width, img.height);
|
||||
let size = Size2D::new(img.metadata.width, img.metadata.height);
|
||||
|
||||
let data = IpcSharedMemory::from_bytes(img.first_frame().bytes);
|
||||
TexPixels::new(data, size, img.format, false)
|
||||
|
|
|
@ -55,7 +55,8 @@ use malloc_size_of::MallocSizeOf;
|
|||
use media::WindowGLContext;
|
||||
use net_traits::ResourceThreads;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageResponder, ImageResponse, PendingImageId, PendingImageResponse,
|
||||
ImageCache, ImageCacheResponseMessage, ImageLoadListener, ImageResponse, PendingImageId,
|
||||
PendingImageResponse, RasterizationCompleteResponse,
|
||||
};
|
||||
use net_traits::storage_thread::StorageType;
|
||||
use num_traits::ToPrimitive;
|
||||
|
@ -87,7 +88,7 @@ use style_traits::CSSPixel;
|
|||
use stylo_atoms::Atom;
|
||||
use url::Position;
|
||||
use webrender_api::ExternalScrollId;
|
||||
use webrender_api::units::{DevicePixel, LayoutPixel};
|
||||
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel};
|
||||
|
||||
use super::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
|
||||
use super::bindings::trace::HashMapTracedValues;
|
||||
|
@ -218,6 +219,8 @@ impl LayoutBlocker {
|
|||
}
|
||||
}
|
||||
|
||||
type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize);
|
||||
|
||||
#[dom_struct]
|
||||
pub(crate) struct Window {
|
||||
globalscope: GlobalScope,
|
||||
|
@ -240,7 +243,7 @@ pub(crate) struct Window {
|
|||
#[no_trace]
|
||||
image_cache: Arc<dyn ImageCache>,
|
||||
#[no_trace]
|
||||
image_cache_sender: IpcSender<PendingImageResponse>,
|
||||
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
|
||||
window_proxy: MutNullableDom<WindowProxy>,
|
||||
document: MutNullableDom<Document>,
|
||||
location: MutNullableDom<Location>,
|
||||
|
@ -343,11 +346,17 @@ pub(crate) struct Window {
|
|||
pending_image_callbacks: DomRefCell<HashMap<PendingImageId, Vec<PendingImageCallback>>>,
|
||||
|
||||
/// All of the elements that have an outstanding image request that was
|
||||
/// initiated by layout during a reflow. They are stored in the script thread
|
||||
/// 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
|
||||
/// available at some point in the future.
|
||||
pending_layout_images: DomRefCell<HashMapTracedValues<PendingImageId, Vec<Dom<Node>>>>,
|
||||
|
||||
/// 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`]
|
||||
/// so that the element can be marked dirty once the rasterization is completed.
|
||||
pending_images_for_rasterization:
|
||||
DomRefCell<HashMapTracedValues<PendingImageRasterizationKey, Vec<Dom<Node>>>>,
|
||||
|
||||
/// Directory to store unminified css for this window if unminify-css
|
||||
/// opt is enabled.
|
||||
unminified_css_dir: DomRefCell<Option<String>>,
|
||||
|
@ -568,7 +577,7 @@ impl Window {
|
|||
&self,
|
||||
id: PendingImageId,
|
||||
callback: impl Fn(PendingImageResponse) + 'static,
|
||||
) -> IpcSender<PendingImageResponse> {
|
||||
) -> IpcSender<ImageCacheResponseMessage> {
|
||||
self.pending_image_callbacks
|
||||
.borrow_mut()
|
||||
.entry(id)
|
||||
|
@ -597,6 +606,22 @@ impl Window {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_image_rasterization_complete_notification(
|
||||
&self,
|
||||
response: RasterizationCompleteResponse,
|
||||
) {
|
||||
let mut images = self.pending_images_for_rasterization.borrow_mut();
|
||||
let nodes = images.entry((response.image_id, response.requested_size));
|
||||
let nodes = match nodes {
|
||||
Entry::Occupied(nodes) => nodes,
|
||||
Entry::Vacant(_) => return,
|
||||
};
|
||||
for node in nodes.get() {
|
||||
node.dirty(NodeDamage::OtherNodeDamage);
|
||||
}
|
||||
nodes.remove();
|
||||
}
|
||||
|
||||
pub(crate) fn pending_image_notification(&self, response: PendingImageResponse) {
|
||||
// We take the images here, in order to prevent maintaining a mutable borrow when
|
||||
// image callbacks are called. These, in turn, can trigger garbage collection.
|
||||
|
@ -2224,8 +2249,11 @@ impl Window {
|
|||
.pending_layout_image_notification(response);
|
||||
});
|
||||
|
||||
self.image_cache
|
||||
.add_listener(ImageResponder::new(sender, self.pipeline_id(), id));
|
||||
self.image_cache.add_listener(ImageLoadListener::new(
|
||||
sender,
|
||||
self.pipeline_id(),
|
||||
id,
|
||||
));
|
||||
}
|
||||
|
||||
let nodes = images.entry(id).or_default();
|
||||
|
@ -2234,6 +2262,25 @@ impl Window {
|
|||
}
|
||||
}
|
||||
|
||||
for image in results.pending_rasterization_images {
|
||||
let node = unsafe { from_untrusted_node_address(image.node) };
|
||||
|
||||
let mut images = self.pending_images_for_rasterization.borrow_mut();
|
||||
if !images.contains_key(&(image.id, image.size)) {
|
||||
self.image_cache.add_rasterization_complete_listener(
|
||||
pipeline_id,
|
||||
image.id,
|
||||
image.size,
|
||||
self.image_cache_sender.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let nodes = images.entry((image.id, image.size)).or_default();
|
||||
if !nodes.iter().any(|n| std::ptr::eq(&**n, &*node)) {
|
||||
nodes.push(Dom::from_ref(&*node));
|
||||
}
|
||||
}
|
||||
|
||||
let size_messages = self
|
||||
.Document()
|
||||
.iframes_mut()
|
||||
|
@ -2325,12 +2372,13 @@ impl Window {
|
|||
});
|
||||
|
||||
let has_sent_idle_message = self.has_sent_idle_message.get();
|
||||
let pending_images = !self.pending_layout_images.borrow().is_empty();
|
||||
let no_pending_images = self.pending_layout_images.borrow().is_empty() &&
|
||||
self.pending_images_for_rasterization.borrow().is_empty();
|
||||
|
||||
if !has_sent_idle_message &&
|
||||
is_ready_state_complete &&
|
||||
!reftest_wait &&
|
||||
!pending_images &&
|
||||
no_pending_images &&
|
||||
!waiting_for_web_fonts_to_load
|
||||
{
|
||||
debug!(
|
||||
|
@ -3005,7 +3053,7 @@ impl Window {
|
|||
script_chan: Sender<MainThreadScriptMsg>,
|
||||
layout: Box<dyn Layout>,
|
||||
font_context: Arc<FontContext>,
|
||||
image_cache_sender: IpcSender<PendingImageResponse>,
|
||||
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
|
||||
image_cache: Arc<dyn ImageCache>,
|
||||
resource_threads: ResourceThreads,
|
||||
#[cfg(feature = "bluetooth")] bluetooth_thread: IpcSender<BluetoothRequest>,
|
||||
|
@ -3104,6 +3152,7 @@ impl Window {
|
|||
webxr_registry,
|
||||
pending_image_callbacks: Default::default(),
|
||||
pending_layout_images: Default::default(),
|
||||
pending_images_for_rasterization: Default::default(),
|
||||
unminified_css_dir: Default::default(),
|
||||
local_script_source,
|
||||
test_worklet: Default::default(),
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||
|
||||
use constellation_traits::BlobImpl;
|
||||
use indexmap::IndexMap;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
|
@ -70,7 +70,7 @@ impl Kind {
|
|||
/// <https://html.spec.whatwg.org/multipage/#drag-data-store-bitmap>
|
||||
#[allow(dead_code)] // TODO this used by DragEvent.
|
||||
struct Bitmap {
|
||||
image: Option<Arc<Image>>,
|
||||
image: Option<Arc<RasterImage>>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ impl DragDataStore {
|
|||
self.mode = mode;
|
||||
}
|
||||
|
||||
pub(crate) fn set_bitmap(&mut self, image: Option<Arc<Image>>, x: i32, y: i32) {
|
||||
pub(crate) fn set_bitmap(&mut self, image: Option<Arc<RasterImage>>, x: i32, y: i32) {
|
||||
self.bitmap = Some(Bitmap { image, x, y });
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,10 @@ impl ImageAnimationManager {
|
|||
image.id.unwrap(),
|
||||
ImageDescriptor {
|
||||
format: ImageFormat::BGRA8,
|
||||
size: DeviceIntSize::new(image.width as i32, image.height as i32),
|
||||
size: DeviceIntSize::new(
|
||||
image.metadata.width as i32,
|
||||
image.metadata.height as i32,
|
||||
),
|
||||
stride: None,
|
||||
offset: 0,
|
||||
flags: ImageDescriptorFlags::ALLOW_MIPMAPS,
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::sync::Arc as StdArc;
|
||||
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use fonts_traits::ByteIndex;
|
||||
use html5ever::{local_name, ns};
|
||||
use pixels::{Image, ImageMetadata};
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use range::Range;
|
||||
use script_layout_interface::wrapper_traits::{LayoutDataTrait, LayoutNode, ThreadSafeLayoutNode};
|
||||
use script_layout_interface::{
|
||||
|
@ -371,7 +371,7 @@ impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
this.image_density()
|
||||
}
|
||||
|
||||
fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> {
|
||||
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.image_data()
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use crossbeam_channel::{Receiver, SendError, Sender, select};
|
|||
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use net_traits::FetchResponseMsg;
|
||||
use net_traits::image_cache::PendingImageResponse;
|
||||
use net_traits::image_cache::ImageCacheResponseMessage;
|
||||
use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan};
|
||||
use profile_traits::time::{self as profile_time};
|
||||
use script_traits::{Painter, ScriptThreadMessage};
|
||||
|
@ -40,7 +40,7 @@ pub(crate) enum MixedMessage {
|
|||
FromConstellation(ScriptThreadMessage),
|
||||
FromScript(MainThreadScriptMsg),
|
||||
FromDevtools(DevtoolScriptControlMsg),
|
||||
FromImageCache(PendingImageResponse),
|
||||
FromImageCache(ImageCacheResponseMessage),
|
||||
#[cfg(feature = "webgpu")]
|
||||
FromWebGPUServer(WebGPUMsg),
|
||||
TimerFired,
|
||||
|
@ -104,7 +104,14 @@ impl MixedMessage {
|
|||
MainThreadScriptMsg::Inactive => None,
|
||||
MainThreadScriptMsg::WakeUp => None,
|
||||
},
|
||||
MixedMessage::FromImageCache(response) => Some(response.pipeline_id),
|
||||
MixedMessage::FromImageCache(response) => match response {
|
||||
ImageCacheResponseMessage::NotifyPendingImageLoadStatus(response) => {
|
||||
Some(response.pipeline_id)
|
||||
},
|
||||
ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
|
||||
Some(response.pipeline_id)
|
||||
},
|
||||
},
|
||||
MixedMessage::FromDevtools(_) | MixedMessage::TimerFired => None,
|
||||
#[cfg(feature = "webgpu")]
|
||||
MixedMessage::FromWebGPUServer(..) => None,
|
||||
|
@ -326,7 +333,7 @@ pub(crate) struct ScriptThreadSenders {
|
|||
/// messages on this channel are routed to crossbeam [`Sender`] on the router thread, which
|
||||
/// in turn sends messages to [`ScriptThreadReceivers::image_cache_receiver`].
|
||||
#[no_trace]
|
||||
pub(crate) image_cache_sender: IpcSender<PendingImageResponse>,
|
||||
pub(crate) image_cache_sender: IpcSender<ImageCacheResponseMessage>,
|
||||
|
||||
/// For providing contact with the time profiler.
|
||||
#[no_trace]
|
||||
|
@ -355,7 +362,7 @@ pub(crate) struct ScriptThreadReceivers {
|
|||
|
||||
/// The [`Receiver`] which receives incoming messages from the `ImageCache`.
|
||||
#[no_trace]
|
||||
pub(crate) image_cache_receiver: Receiver<PendingImageResponse>,
|
||||
pub(crate) image_cache_receiver: Receiver<ImageCacheResponseMessage>,
|
||||
|
||||
/// For receiving commands from an optional devtools server. Will be ignored if no such server
|
||||
/// exists. When devtools are not active this will be [`crossbeam_channel::never()`].
|
||||
|
|
|
@ -71,7 +71,7 @@ use js::jsval::UndefinedValue;
|
|||
use js::rust::ParentRuntime;
|
||||
use media::WindowGLContext;
|
||||
use metrics::MAX_TASK_NS;
|
||||
use net_traits::image_cache::{ImageCache, PendingImageResponse};
|
||||
use net_traits::image_cache::{ImageCache, ImageCacheResponseMessage};
|
||||
use net_traits::request::{Referrer, RequestId};
|
||||
use net_traits::response::ResponseInit;
|
||||
use net_traits::storage_thread::StorageType;
|
||||
|
@ -2095,11 +2095,24 @@ impl ScriptThread {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_msg_from_image_cache(&self, response: PendingImageResponse) {
|
||||
let window = self.documents.borrow().find_window(response.pipeline_id);
|
||||
if let Some(ref window) = window {
|
||||
window.pending_image_notification(response);
|
||||
}
|
||||
fn handle_msg_from_image_cache(&self, response: ImageCacheResponseMessage) {
|
||||
match response {
|
||||
ImageCacheResponseMessage::NotifyPendingImageLoadStatus(pending_image_response) => {
|
||||
let window = self
|
||||
.documents
|
||||
.borrow()
|
||||
.find_window(pending_image_response.pipeline_id);
|
||||
if let Some(ref window) = window {
|
||||
window.pending_image_notification(pending_image_response);
|
||||
}
|
||||
},
|
||||
ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
|
||||
let window = self.documents.borrow().find_window(response.pipeline_id);
|
||||
if let Some(ref window) = window {
|
||||
window.handle_image_rasterization_complete_notification(response);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn handle_webdriver_msg(
|
||||
|
|
|
@ -16,7 +16,7 @@ use euclid::Rect;
|
|||
use ipc_channel::ipc::IpcSender;
|
||||
use log::warn;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use strum_macros::IntoStaticStr;
|
||||
use style_traits::CSSPixel;
|
||||
use webrender_api::DocumentId;
|
||||
|
@ -83,7 +83,7 @@ pub enum CompositorMsg {
|
|||
CreatePng(
|
||||
WebViewId,
|
||||
Option<Rect<f32, CSSPixel>>,
|
||||
IpcSender<Option<Image>>,
|
||||
IpcSender<Option<RasterImage>>,
|
||||
),
|
||||
/// A reply to the compositor asking if the output image is stable.
|
||||
IsReadyToSaveImageReply(bool),
|
||||
|
|
|
@ -30,7 +30,7 @@ use log::warn;
|
|||
use malloc_size_of::malloc_size_of_is_0;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use num_derive::FromPrimitive;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use servo_url::ServoUrl;
|
||||
use strum_macros::IntoStaticStr;
|
||||
|
@ -674,16 +674,16 @@ pub struct Notification {
|
|||
/// The URL of an icon. The icon will be displayed as part of the notification.
|
||||
pub icon_url: Option<ServoUrl>,
|
||||
/// Icon's raw image data and metadata.
|
||||
pub icon_resource: Option<Arc<Image>>,
|
||||
pub icon_resource: Option<Arc<RasterImage>>,
|
||||
/// The URL of a badge. The badge is used when there is no enough space to display the notification,
|
||||
/// such as on a mobile device's notification bar.
|
||||
pub badge_url: Option<ServoUrl>,
|
||||
/// Badge's raw image data and metadata.
|
||||
pub badge_resource: Option<Arc<Image>>,
|
||||
pub badge_resource: Option<Arc<RasterImage>>,
|
||||
/// The URL of an image. The image will be displayed as part of the notification.
|
||||
pub image_url: Option<ServoUrl>,
|
||||
/// Image's raw image data and metadata.
|
||||
pub image_resource: Option<Arc<Image>>,
|
||||
pub image_resource: Option<Arc<RasterImage>>,
|
||||
/// Actions available for users to choose from for interacting with the notification.
|
||||
pub actions: Vec<NotificationAction>,
|
||||
}
|
||||
|
@ -698,7 +698,7 @@ pub struct NotificationAction {
|
|||
/// The URL of an icon. The icon will be displayed with the action.
|
||||
pub icon_url: Option<ServoUrl>,
|
||||
/// Icon's raw image data and metadata.
|
||||
pub icon_resource: Option<Arc<Image>>,
|
||||
pub icon_resource: Option<Arc<RasterImage>>,
|
||||
}
|
||||
|
||||
/// Information about a `WebView`'s screen geometry and offset. This is used
|
||||
|
|
|
@ -14,7 +14,7 @@ use hyper_serde::Serde;
|
|||
use ipc_channel::ipc::IpcSender;
|
||||
use keyboard_types::KeyboardEvent;
|
||||
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_url::ServoUrl;
|
||||
use style_traits::CSSPixel;
|
||||
|
@ -69,7 +69,7 @@ pub enum WebDriverCommandMsg {
|
|||
TakeScreenshot(
|
||||
WebViewId,
|
||||
Option<Rect<f32, CSSPixel>>,
|
||||
IpcSender<Option<Image>>,
|
||||
IpcSender<Option<RasterImage>>,
|
||||
),
|
||||
/// Create a new webview that loads about:blank. The constellation will use
|
||||
/// the provided channels to return the top level browsing context id
|
||||
|
|
|
@ -41,6 +41,7 @@ servo_rand = { path = "../../rand" }
|
|||
servo_url = { path = "../../url" }
|
||||
url = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
webrender_api = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
embedder_traits = { workspace = true, features = ["baked-default-resources"] }
|
||||
|
|
|
@ -10,10 +10,11 @@ use ipc_channel::ipc::IpcSender;
|
|||
use log::debug;
|
||||
use malloc_size_of::MallocSizeOfOps;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use pixels::{Image, ImageMetadata};
|
||||
use pixels::{CorsStatus, ImageMetadata, RasterImage};
|
||||
use profile_traits::mem::Report;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use webrender_api::units::DeviceIntSize;
|
||||
|
||||
use crate::FetchResponseMsg;
|
||||
use crate::request::CorsSettings;
|
||||
|
@ -22,12 +23,52 @@ 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, Deserialize, MallocSizeOf, Serialize)]
|
||||
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 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, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub enum ImageOrMetadataAvailable {
|
||||
ImageAvailable {
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
image: Arc<Image>,
|
||||
image: Image,
|
||||
url: ServoUrl,
|
||||
is_placeholder: bool,
|
||||
},
|
||||
|
@ -39,19 +80,19 @@ pub enum ImageOrMetadataAvailable {
|
|||
/// image load completes. It is typically used to trigger a reflow
|
||||
/// and/or repaint.
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct ImageResponder {
|
||||
pub struct ImageLoadListener {
|
||||
pipeline_id: PipelineId,
|
||||
pub id: PendingImageId,
|
||||
sender: IpcSender<PendingImageResponse>,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
}
|
||||
|
||||
impl ImageResponder {
|
||||
impl ImageLoadListener {
|
||||
pub fn new(
|
||||
sender: IpcSender<PendingImageResponse>,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
pipeline_id: PipelineId,
|
||||
id: PendingImageId,
|
||||
) -> ImageResponder {
|
||||
ImageResponder {
|
||||
) -> ImageLoadListener {
|
||||
ImageLoadListener {
|
||||
pipeline_id,
|
||||
sender,
|
||||
id,
|
||||
|
@ -63,11 +104,15 @@ impl ImageResponder {
|
|||
// This send can fail if thread waiting for this notification has panicked.
|
||||
// That's not a case that's worth warning about.
|
||||
// TODO(#15501): are there cases in which we should perform cleanup?
|
||||
let _ = self.sender.send(PendingImageResponse {
|
||||
pipeline_id: self.pipeline_id,
|
||||
response,
|
||||
id: self.id,
|
||||
});
|
||||
let _ = self
|
||||
.sender
|
||||
.send(ImageCacheResponseMessage::NotifyPendingImageLoadStatus(
|
||||
PendingImageResponse {
|
||||
pipeline_id: self.pipeline_id,
|
||||
response,
|
||||
id: self.id,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,11 +120,11 @@ impl ImageResponder {
|
|||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub enum ImageResponse {
|
||||
/// The requested image was loaded.
|
||||
Loaded(#[conditional_malloc_size_of] Arc<Image>, ServoUrl),
|
||||
Loaded(Image, ServoUrl),
|
||||
/// The request image metadata was loaded.
|
||||
MetadataLoaded(ImageMetadata),
|
||||
/// The requested image failed to load, so a placeholder was loaded instead.
|
||||
PlaceholderLoaded(#[conditional_malloc_size_of] Arc<Image>, ServoUrl),
|
||||
PlaceholderLoaded(#[conditional_malloc_size_of] Arc<RasterImage>, ServoUrl),
|
||||
/// Neither the requested image nor the placeholder could be loaded.
|
||||
None,
|
||||
}
|
||||
|
@ -95,6 +140,19 @@ pub struct PendingImageResponse {
|
|||
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, Deserialize, Serialize)]
|
||||
pub enum ImageCacheResponseMessage {
|
||||
NotifyPendingImageLoadStatus(PendingImageResponse),
|
||||
VectorImageRasterizationComplete(RasterizationCompleteResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum UsePlaceholder {
|
||||
No,
|
||||
|
@ -125,7 +183,7 @@ pub trait ImageCache: Sync + Send {
|
|||
url: ServoUrl,
|
||||
origin: ImmutableOrigin,
|
||||
cors_setting: Option<CorsSettings>,
|
||||
) -> Option<Arc<Image>>;
|
||||
) -> Option<Image>;
|
||||
|
||||
fn get_cached_image_status(
|
||||
&self,
|
||||
|
@ -135,9 +193,31 @@ pub trait ImageCache: Sync + Send {
|
|||
use_placeholder: UsePlaceholder,
|
||||
) -> 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,
|
||||
) -> 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,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
);
|
||||
|
||||
/// 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: ImageResponder);
|
||||
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);
|
||||
|
|
|
@ -30,7 +30,7 @@ use libc::c_void;
|
|||
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use net_traits::image_cache::{ImageCache, PendingImageId};
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use profile_traits::mem::Report;
|
||||
use profile_traits::time;
|
||||
use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
|
||||
|
@ -49,6 +49,7 @@ use style::properties::style_structs::Font;
|
|||
use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
|
||||
use style::stylesheets::Stylesheet;
|
||||
use webrender_api::ImageKey;
|
||||
use webrender_api::units::DeviceIntSize;
|
||||
|
||||
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
@ -153,6 +154,16 @@ pub struct PendingImage {
|
|||
pub origin: ImmutableOrigin,
|
||||
}
|
||||
|
||||
/// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG
|
||||
/// tree) but not yet rasterized to the size needed by layout. The rasterization is
|
||||
/// happening in the image cache.
|
||||
#[derive(Debug)]
|
||||
pub struct PendingRasterizationImage {
|
||||
pub node: UntrustedNodeAddress,
|
||||
pub id: PendingImageId,
|
||||
pub size: DeviceIntSize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MediaFrame {
|
||||
pub image_key: webrender_api::ImageKey,
|
||||
|
@ -392,6 +403,8 @@ pub type IFrameSizes = FnvHashMap<BrowsingContextId, IFrameSize>;
|
|||
pub struct ReflowResult {
|
||||
/// The list of images that were encountered that are in progress.
|
||||
pub pending_images: Vec<PendingImage>,
|
||||
/// The list of vector images that were encountered that still need to be rasterized.
|
||||
pub pending_rasterization_images: Vec<PendingRasterizationImage>,
|
||||
/// The list of iframes in this layout and their sizes, used in order
|
||||
/// to communicate them with the Constellation and also the `Window`
|
||||
/// element of their content pages.
|
||||
|
@ -507,13 +520,13 @@ pub fn node_id_from_scroll_id(id: usize) -> Option<usize> {
|
|||
#[derive(Clone, Debug, MallocSizeOf)]
|
||||
pub struct ImageAnimationState {
|
||||
#[ignore_malloc_size_of = "Arc is hard"]
|
||||
pub image: Arc<Image>,
|
||||
pub image: Arc<RasterImage>,
|
||||
pub active_frame: usize,
|
||||
last_update_time: f64,
|
||||
}
|
||||
|
||||
impl ImageAnimationState {
|
||||
pub fn new(image: Arc<Image>, last_update_time: f64) -> Self {
|
||||
pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
|
||||
Self {
|
||||
image,
|
||||
active_frame: 0,
|
||||
|
@ -579,7 +592,7 @@ mod test {
|
|||
use std::time::Duration;
|
||||
|
||||
use ipc_channel::ipc::IpcSharedMemory;
|
||||
use pixels::{CorsStatus, Image, ImageFrame, PixelFormat};
|
||||
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
|
||||
|
||||
use crate::ImageAnimationState;
|
||||
|
||||
|
@ -593,9 +606,11 @@ mod test {
|
|||
})
|
||||
.take(10)
|
||||
.collect();
|
||||
let image = Image {
|
||||
width: 100,
|
||||
height: 100,
|
||||
let image = RasterImage {
|
||||
metadata: ImageMetadata {
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
format: PixelFormat::BGRA8,
|
||||
id: None,
|
||||
bytes: IpcSharedMemory::from_byte(1, 1),
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc as StdArc;
|
||||
|
||||
use atomic_refcell::AtomicRef;
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use fonts_traits::ByteIndex;
|
||||
use html5ever::{LocalName, Namespace};
|
||||
use pixels::{Image, ImageMetadata};
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use range::Range;
|
||||
use servo_arc::Arc;
|
||||
use servo_url::ServoUrl;
|
||||
|
@ -222,7 +222,7 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE
|
|||
fn image_density(&self) -> Option<f64>;
|
||||
|
||||
/// If this is an image element, returns its image data. Otherwise, returns `None`.
|
||||
fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>;
|
||||
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
|
||||
|
||||
fn canvas_data(&self) -> Option<HTMLCanvasData>;
|
||||
|
||||
|
|
|
@ -1760,8 +1760,12 @@ impl Handler {
|
|||
"Unexpected screenshot pixel format"
|
||||
);
|
||||
|
||||
let rgb =
|
||||
RgbaImage::from_raw(img.width, img.height, img.first_frame().bytes.to_vec()).unwrap();
|
||||
let rgb = RgbaImage::from_raw(
|
||||
img.metadata.width,
|
||||
img.metadata.height,
|
||||
img.first_frame().bytes.to_vec(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut png_data = Cursor::new(Vec::new());
|
||||
DynamicImage::ImageRgba8(rgb)
|
||||
.write_to(&mut png_data, ImageFormat::Png)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[background-intrinsic-002.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-intrinsic-009.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[mix-blend-mode-paragraph-background-image.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-001.html.ini
vendored
Normal file
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-001.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[root-element-background-image-transparency-001.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-002.html.ini
vendored
Normal file
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-002.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[root-element-background-image-transparency-002.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-003.html.ini
vendored
Normal file
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-003.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[root-element-background-image-transparency-003.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-004.html.ini
vendored
Normal file
2
tests/wpt/meta/css/compositing/root-element-background-image-transparency-004.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[root-element-background-image-transparency-004.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[mix-blend-mode-in-svg-image.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-cover-svg.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-near-zero-svg.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-vector-001.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-vector-002.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-vector-009.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-vector-013.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-vector-019.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[background-size-vector-020.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--auto--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--auto-32px--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--auto-32px--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--auto-32px--omitted-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--auto-32px--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/tall--contain--height.html.ini
vendored
Normal file
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/tall--contain--height.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[tall--contain--height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--contain--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--contain--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--contain--omitted-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--contain--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/tall--contain--width.html.ini
vendored
Normal file
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/tall--contain--width.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[tall--contain--width.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--nonpercent-width-nonpercent-height--crisp.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--nonpercent-width-nonpercent-height-viewbox--crisp.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--nonpercent-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--omitted-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--omitted-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--omitted-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--omitted-width-omitted-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--omitted-width-percent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--omitted-width-percent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--percent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--percent-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--percent-width-omitted-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--percent-width-percent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--percent-width-percent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[tall--cover--width.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--12px-auto--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--12px-auto--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--12px-auto--omitted-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--12px-auto--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto-32px--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto-32px--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto-32px--omitted-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--auto-32px--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/wide--contain--height.html.ini
vendored
Normal file
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/wide--contain--height.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[wide--contain--height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--contain--nonpercent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--contain--nonpercent-width-nonpercent-height.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--contain--omitted-width-omitted-height-viewbox.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--contain--percent-width-nonpercent-height-viewbox.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/wide--contain--width.html.ini
vendored
Normal file
2
tests/wpt/meta/css/css-backgrounds/background-size/vector/wide--contain--width.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[wide--contain--width.html]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[wide--cover--height.html]
|
||||
expected: FAIL
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue