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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.10.1"
|
version = "1.10.1"
|
||||||
|
@ -2130,6 +2136,12 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "float-ord"
|
name = "float-ord"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -2173,6 +2185,29 @@ dependencies = [
|
||||||
"yeslogic-fontconfig-sys",
|
"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]]
|
[[package]]
|
||||||
name = "fonts"
|
name = "fonts"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -3910,6 +3945,22 @@ dependencies = [
|
||||||
"tiff",
|
"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]]
|
[[package]]
|
||||||
name = "imsz"
|
name = "imsz"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -4149,6 +4200,16 @@ version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
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]]
|
[[package]]
|
||||||
name = "layout"
|
name = "layout"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -4832,6 +4893,7 @@ dependencies = [
|
||||||
"pixels",
|
"pixels",
|
||||||
"profile_traits",
|
"profile_traits",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"resvg",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
@ -4886,6 +4948,7 @@ dependencies = [
|
||||||
"servo_url",
|
"servo_url",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"webrender_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5613,6 +5676,12 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
|
@ -5900,6 +5969,12 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.37.5"
|
version = "0.37.5"
|
||||||
|
@ -6118,6 +6193,32 @@ version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
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]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
|
@ -6145,6 +6246,12 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roxmltree"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
|
@ -6236,6 +6343,24 @@ version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
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]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
|
@ -6857,6 +6982,8 @@ dependencies = [
|
||||||
"ipc-channel",
|
"ipc-channel",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"markup5ever",
|
"markup5ever",
|
||||||
|
"mime",
|
||||||
|
"resvg",
|
||||||
"servo_allocator",
|
"servo_allocator",
|
||||||
"servo_arc",
|
"servo_arc",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
@ -7028,6 +7155,15 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -7182,6 +7318,9 @@ name = "strict-num"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
dependencies = [
|
||||||
|
"float-cmp",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
|
@ -7425,6 +7564,16 @@ version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
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]]
|
[[package]]
|
||||||
name = "sw-composite"
|
name = "sw-composite"
|
||||||
version = "0.7.16"
|
version = "0.7.16"
|
||||||
|
@ -7712,6 +7861,7 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
|
"png",
|
||||||
"tiny-skia-path",
|
"tiny-skia-path",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -7746,6 +7896,21 @@ dependencies = [
|
||||||
"serde_json",
|
"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]]
|
[[package]]
|
||||||
name = "to_shmem"
|
name = "to_shmem"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -7991,6 +8156,9 @@ name = "ttf-parser"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||||
|
dependencies = [
|
||||||
|
"core_maths",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
|
@ -8092,6 +8260,18 @@ version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
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]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -8116,6 +8296,12 @@ version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-vo"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
|
@ -8172,6 +8358,33 @@ dependencies = [
|
||||||
"url",
|
"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]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -9469,6 +9682,12 @@ dependencies = [
|
||||||
"markup5ever",
|
"markup5ever",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmlwriter"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yeslogic-fontconfig-sys"
|
name = "yeslogic-fontconfig-sys"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
|
@ -9585,6 +9804,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-inflate"
|
name = "zune-inflate"
|
||||||
version = "0.2.54"
|
version = "0.2.54"
|
||||||
|
@ -9593,3 +9818,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"simd-adler32",
|
"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"
|
raw-window-handle = "0.6"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
|
resvg = "0.45.0"
|
||||||
rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] }
|
rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] }
|
||||||
rustls-pemfile = "2.0"
|
rustls-pemfile = "2.0"
|
||||||
rustls-pki-types = "1.12"
|
rustls-pki-types = "1.12"
|
||||||
|
|
|
@ -32,7 +32,7 @@ use fnv::FnvHashMap;
|
||||||
use ipc_channel::ipc::{self, IpcSharedMemory};
|
use ipc_channel::ipc::{self, IpcSharedMemory};
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use log::{debug, info, trace, warn};
|
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::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind};
|
||||||
use profile_traits::time::{self as profile_time, ProfilerCategory};
|
use profile_traits::time::{self as profile_time, ProfilerCategory};
|
||||||
use profile_traits::{path, time_profile};
|
use profile_traits::{path, time_profile};
|
||||||
|
@ -1431,7 +1431,7 @@ impl IOCompositor {
|
||||||
&mut self,
|
&mut self,
|
||||||
webview_id: WebViewId,
|
webview_id: WebViewId,
|
||||||
page_rect: Option<Rect<f32, CSSPixel>>,
|
page_rect: Option<Rect<f32, CSSPixel>>,
|
||||||
) -> Result<Option<Image>, UnableToComposite> {
|
) -> Result<Option<RasterImage>, UnableToComposite> {
|
||||||
self.render_inner()?;
|
self.render_inner()?;
|
||||||
|
|
||||||
let size = self.rendering_context.size2d().to_i32();
|
let size = self.rendering_context.size2d().to_i32();
|
||||||
|
@ -1458,9 +1458,11 @@ impl IOCompositor {
|
||||||
Ok(self
|
Ok(self
|
||||||
.rendering_context
|
.rendering_context
|
||||||
.read_to_image(rect)
|
.read_to_image(rect)
|
||||||
.map(|image| Image {
|
.map(|image| RasterImage {
|
||||||
|
metadata: ImageMetadata {
|
||||||
width: image.width(),
|
width: image.width(),
|
||||||
height: image.height(),
|
height: image.height(),
|
||||||
|
},
|
||||||
format: PixelFormat::RGBA8,
|
format: PixelFormat::RGBA8,
|
||||||
frames: vec![ImageFrame {
|
frames: vec![ImageFrame {
|
||||||
delay: None,
|
delay: None,
|
||||||
|
|
|
@ -10,17 +10,21 @@ use fnv::FnvHashMap;
|
||||||
use fonts::FontContext;
|
use fonts::FontContext;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
|
||||||
|
UsePlaceholder,
|
||||||
};
|
};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use pixels::Image as PixelImage;
|
use pixels::RasterImage;
|
||||||
use script_layout_interface::{IFrameSizes, ImageAnimationState, PendingImage, PendingImageState};
|
use script_layout_interface::{
|
||||||
|
IFrameSizes, ImageAnimationState, PendingImage, PendingImageState, PendingRasterizationImage,
|
||||||
|
};
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
use style::context::SharedStyleContext;
|
use style::context::SharedStyleContext;
|
||||||
use style::dom::OpaqueNode;
|
use style::dom::OpaqueNode;
|
||||||
use style::values::computed::image::{Gradient, Image};
|
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 struct LayoutContext<'a> {
|
||||||
pub id: PipelineId,
|
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.
|
/// A list of in-progress image loads to be shared with the script thread.
|
||||||
pub pending_images: Mutex<Vec<PendingImage>>,
|
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.
|
/// A collection of `<iframe>` sizes to send back to script.
|
||||||
pub iframe_sizes: Mutex<IFrameSizes>,
|
pub iframe_sizes: Mutex<IFrameSizes>,
|
||||||
|
|
||||||
pub webrender_image_cache:
|
// A cache that maps image resources used in CSS (e.g as the `url()` value
|
||||||
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
|
// 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>>>,
|
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
|
||||||
|
|
||||||
|
@ -53,18 +63,24 @@ pub struct LayoutContext<'a> {
|
||||||
|
|
||||||
pub enum ResolvedImage<'a> {
|
pub enum ResolvedImage<'a> {
|
||||||
Gradient(&'a Gradient),
|
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<'_> {
|
impl Drop for LayoutContext<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !std::thread::panicking() {
|
if !std::thread::panicking() {
|
||||||
assert!(self.pending_images.lock().is_empty());
|
assert!(self.pending_images.lock().is_empty());
|
||||||
|
assert!(self.pending_rasterization_images.lock().is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum ResolveImageError {
|
pub enum ResolveImageError {
|
||||||
LoadError,
|
LoadError,
|
||||||
ImagePending,
|
ImagePending,
|
||||||
|
@ -78,18 +94,24 @@ pub enum ResolveImageError {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum LayoutImageCacheResult {
|
||||||
|
Pending,
|
||||||
|
DataAvailable(ImageOrMetadataAvailable),
|
||||||
|
LoadError,
|
||||||
|
}
|
||||||
|
|
||||||
impl LayoutContext<'_> {
|
impl LayoutContext<'_> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn shared_context(&self) -> &SharedStyleContext {
|
pub fn shared_context(&self) -> &SharedStyleContext {
|
||||||
&self.style_context
|
&self.style_context
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_request_image_or_meta(
|
pub(crate) fn get_or_request_image_or_meta(
|
||||||
&self,
|
&self,
|
||||||
node: OpaqueNode,
|
node: OpaqueNode,
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
use_placeholder: UsePlaceholder,
|
use_placeholder: UsePlaceholder,
|
||||||
) -> Result<ImageOrMetadataAvailable, ResolveImageError> {
|
) -> LayoutImageCacheResult {
|
||||||
// Check for available image or start tracking.
|
// Check for available image or start tracking.
|
||||||
let cache_result = self.image_cache.get_cached_image_status(
|
let cache_result = self.image_cache.get_cached_image_status(
|
||||||
url.clone(),
|
url.clone(),
|
||||||
|
@ -99,7 +121,9 @@ impl LayoutContext<'_> {
|
||||||
);
|
);
|
||||||
|
|
||||||
match cache_result {
|
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.
|
// 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.
|
// When the image loads it will trigger a reflow and/or repaint.
|
||||||
ImageCacheResult::Pending(id) => {
|
ImageCacheResult::Pending(id) => {
|
||||||
|
@ -110,7 +134,7 @@ impl LayoutContext<'_> {
|
||||||
origin: self.origin.clone(),
|
origin: self.origin.clone(),
|
||||||
};
|
};
|
||||||
self.pending_images.lock().push(image);
|
self.pending_images.lock().push(image);
|
||||||
Result::Err(ResolveImageError::ImagePending)
|
LayoutImageCacheResult::Pending
|
||||||
},
|
},
|
||||||
// Not yet requested - request image or metadata from the cache
|
// Not yet requested - request image or metadata from the cache
|
||||||
ImageCacheResult::ReadyForRequest(id) => {
|
ImageCacheResult::ReadyForRequest(id) => {
|
||||||
|
@ -121,14 +145,14 @@ impl LayoutContext<'_> {
|
||||||
origin: self.origin.clone(),
|
origin: self.origin.clone(),
|
||||||
};
|
};
|
||||||
self.pending_images.lock().push(image);
|
self.pending_images.lock().push(image);
|
||||||
Result::Err(ResolveImageError::ImageRequested)
|
LayoutImageCacheResult::Pending
|
||||||
},
|
},
|
||||||
// Image failed to load, so just return nothing
|
// Image failed to load, so just return the same error.
|
||||||
ImageCacheResult::LoadError => Result::Err(ResolveImageError::LoadError),
|
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();
|
let mut store = self.node_image_animation_map.write();
|
||||||
|
|
||||||
// 1. first check whether node previously being track for animated image.
|
// 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,
|
&self,
|
||||||
node: OpaqueNode,
|
node: OpaqueNode,
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
use_placeholder: UsePlaceholder,
|
use_placeholder: UsePlaceholder,
|
||||||
) -> Result<WebRenderImageInfo, ResolveImageError> {
|
) -> Result<CachedImage, ResolveImageError> {
|
||||||
if let Some(existing_webrender_image) = self
|
if let Some(cached_image) = self
|
||||||
.webrender_image_cache
|
.resolved_images_cache
|
||||||
.read()
|
.read()
|
||||||
.get(&(url.clone(), use_placeholder))
|
.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)?;
|
let result = self.get_or_request_image_or_meta(node, url.clone(), use_placeholder);
|
||||||
match image_or_meta {
|
match result {
|
||||||
|
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
|
||||||
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||||
|
if let Some(image) = image.as_raster_image() {
|
||||||
self.handle_animated_image(node, image.clone());
|
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 mut resolved_images_cache = self.resolved_images_cache.write();
|
||||||
|
resolved_images_cache.insert((url, use_placeholder), Ok(image.clone()));
|
||||||
|
Ok(image)
|
||||||
},
|
},
|
||||||
ImageOrMetadataAvailable::MetadataAvailable(..) => {
|
ImageOrMetadataAvailable::MetadataAvailable(..) => {
|
||||||
Result::Err(ResolveImageError::OnlyMetadata)
|
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>(
|
pub fn resolve_image<'a>(
|
||||||
&self,
|
&self,
|
||||||
node: Option<OpaqueNode>,
|
node: Option<OpaqueNode>,
|
||||||
|
@ -215,12 +263,14 @@ impl LayoutContext<'_> {
|
||||||
// element and not just the node.
|
// element and not just the node.
|
||||||
let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
|
let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
|
||||||
let node = node.ok_or(ResolveImageError::MissingNode)?;
|
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,
|
node,
|
||||||
image_url.clone().into(),
|
image_url.clone().into(),
|
||||||
UsePlaceholder::No,
|
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::ImageSet(image_set) => {
|
||||||
image_set
|
image_set
|
||||||
|
@ -230,17 +280,32 @@ impl LayoutContext<'_> {
|
||||||
.and_then(|image| {
|
.and_then(|image| {
|
||||||
self.resolve_image(node, &image.image)
|
self.resolve_image(node, &image.image)
|
||||||
.map(|info| match info {
|
.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>:
|
// From <https://drafts.csswg.org/css-images-4/#image-set-notation>:
|
||||||
// > A <resolution> (optional). This is used to help the UA decide
|
// > A <resolution> (optional). This is used to help the UA decide
|
||||||
// > which <image-set-option> to choose. If the image reference is
|
// > which <image-set-option> to choose. If the image reference is
|
||||||
// > for a raster image, it also specifies the image’s natural
|
// > for a raster image, it also specifies the image’s natural
|
||||||
// > resolution, overriding any other source of data that might
|
// > resolution, overriding any other source of data that might
|
||||||
// > supply a natural resolution.
|
// > supply a natural resolution.
|
||||||
image_info.size = (image_info.size.to_f32() /
|
let image_metadata = cached_image.metadata();
|
||||||
image.resolution.dppx())
|
let size = if cached_image.as_raster_image().is_some() {
|
||||||
.to_u32();
|
let scale_factor = image.resolution.dppx();
|
||||||
ResolvedImage::Image(image_info)
|
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,
|
_ => info,
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,6 +14,7 @@ use embedder_traits::Cursor;
|
||||||
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
|
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
|
||||||
use fonts::GlyphStore;
|
use fonts::GlyphStore;
|
||||||
use gradient::WebRenderGradient;
|
use gradient::WebRenderGradient;
|
||||||
|
use net_traits::image_cache::Image as CachedImage;
|
||||||
use range::Range as ServoRange;
|
use range::Range as ServoRange;
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
use servo_config::opts::DebugOptions;
|
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::text::TextDecorationLine;
|
||||||
use style::values::specified::ui::CursorKind;
|
use style::values::specified::ui::CursorKind;
|
||||||
use style_traits::CSSPixel;
|
use style_traits::CSSPixel;
|
||||||
use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
|
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
|
||||||
use webrender_api::{
|
use webrender_api::{
|
||||||
self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode,
|
self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode,
|
||||||
CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder,
|
CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder,
|
||||||
|
@ -68,12 +69,6 @@ mod stacking_context;
|
||||||
use background::BackgroundPainter;
|
use background::BackgroundPainter;
|
||||||
pub use stacking_context::*;
|
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.
|
// webrender's `ItemTag` is private.
|
||||||
type ItemTag = (u64, u16);
|
type ItemTag = (u64, u16);
|
||||||
type HitInfo = Option<ItemTag>;
|
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
|
// FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution
|
||||||
let dppx = 1.0;
|
let dppx = 1.0;
|
||||||
let intrinsic = NaturalSizes::from_width_and_height(
|
let intrinsic =
|
||||||
image_info.size.width as f32 / dppx,
|
NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
|
||||||
image_info.size.height as f32 / dppx,
|
let layer = background::layout_layer(self, painter, builder, index, intrinsic);
|
||||||
);
|
let image_wr_key = match image {
|
||||||
let Some(image_key) = image_info.key else {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(layer) =
|
if let Some(layer) = layer {
|
||||||
background::layout_layer(self, painter, builder, index, intrinsic)
|
|
||||||
{
|
|
||||||
if layer.repeat {
|
if layer.repeat {
|
||||||
builder.wr().push_repeating_image(
|
builder.wr().push_repeating_image(
|
||||||
&layer.common,
|
&layer.common,
|
||||||
|
@ -1469,13 +1485,17 @@ impl<'a> BuilderForBoxFragment<'a> {
|
||||||
.resolve_image(node, &border.border_image_source)
|
.resolve_image(node, &border.border_image_source)
|
||||||
{
|
{
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
Ok(ResolvedImage::Image(image_info)) => {
|
Ok(ResolvedImage::Image { image, size }) => {
|
||||||
let Some(key) = image_info.key else {
|
let Some(image) = image.as_raster_image() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
width = image_info.size.width as f32;
|
let Some(key) = image.id else {
|
||||||
height = image_info.size.height as f32;
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
width = size.width;
|
||||||
|
height = size.height;
|
||||||
NinePatchBorderSource::Image(key, ImageRendering::Auto)
|
NinePatchBorderSource::Image(key, ImageRendering::Auto)
|
||||||
},
|
},
|
||||||
Ok(ResolvedImage::Gradient(gradient)) => {
|
Ok(ResolvedImage::Gradient(gradient)) => {
|
||||||
|
|
|
@ -4,13 +4,12 @@
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
use base::id::{BrowsingContextId, PipelineId};
|
use base::id::{BrowsingContextId, PipelineId};
|
||||||
use html5ever::{local_name, ns};
|
use html5ever::{local_name, ns};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use pixels::Image;
|
use net_traits::image_cache::Image;
|
||||||
use script::layout_dom::ServoLayoutNode;
|
use script::layout_dom::ServoLayoutNode;
|
||||||
use script_layout_interface::wrapper_traits::{
|
use script_layout_interface::wrapper_traits::{
|
||||||
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||||
|
@ -197,7 +196,7 @@ impl Drop for BoxSlot<'_> {
|
||||||
pub(crate) trait NodeExt<'dom> {
|
pub(crate) trait NodeExt<'dom> {
|
||||||
/// Returns the image if it’s loaded, and its size in image pixels
|
/// Returns the image if it’s loaded, and its size in image pixels
|
||||||
/// adjusted for `image_density`.
|
/// 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_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
|
||||||
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
|
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
|
||||||
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
|
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> {
|
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 node = self.to_threadsafe();
|
||||||
let (resource, metadata) = node.image_data()?;
|
let (resource, metadata) = node.image_data()?;
|
||||||
let (width, height) = resource
|
let (width, height) = resource
|
||||||
.as_ref()
|
.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)))
|
.or_else(|| metadata.map(|metadata| (metadata.width, metadata.height)))
|
||||||
.unwrap_or((0, 0));
|
.unwrap_or((0, 0));
|
||||||
let (mut width, mut height) = (width as f64, height as f64);
|
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::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, LayoutSize};
|
||||||
use webrender_api::{ExternalScrollId, HitTestFlags};
|
use webrender_api::{ExternalScrollId, HitTestFlags};
|
||||||
|
|
||||||
use crate::context::LayoutContext;
|
use crate::context::{CachedImageOrError, LayoutContext};
|
||||||
use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo};
|
use crate::display_list::{DisplayListBuilder, StackingContextTree};
|
||||||
use crate::query::{
|
use crate::query::{
|
||||||
get_the_text_steps, process_client_rect_request, process_content_box_request,
|
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,
|
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 of nodes that scroll.
|
||||||
scroll_offsets: RefCell<HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>>,
|
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.
|
/// The executors for paint worklets.
|
||||||
registered_painters: RegisteredPaintersImpl,
|
registered_painters: RegisteredPaintersImpl,
|
||||||
|
@ -525,7 +528,7 @@ impl LayoutThread {
|
||||||
compositor_api: config.compositor_api,
|
compositor_api: config.compositor_api,
|
||||||
scroll_offsets: Default::default(),
|
scroll_offsets: Default::default(),
|
||||||
stylist: Stylist::new(device, QuirksMode::NoQuirks),
|
stylist: Stylist::new(device, QuirksMode::NoQuirks),
|
||||||
webrender_image_cache: Default::default(),
|
resolved_images_cache: Default::default(),
|
||||||
debug: opts::get().debug.clone(),
|
debug: opts::get().debug.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -635,8 +638,9 @@ impl LayoutThread {
|
||||||
),
|
),
|
||||||
image_cache: self.image_cache.clone(),
|
image_cache: self.image_cache.clone(),
|
||||||
font_context: self.font_context.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_images: Mutex::default(),
|
||||||
|
pending_rasterization_images: Mutex::default(),
|
||||||
node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
|
node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
|
||||||
&mut reflow_request.node_to_image_animation_map,
|
&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_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 iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
|
||||||
let node_to_image_animation_map =
|
let node_to_image_animation_map =
|
||||||
std::mem::take(&mut *layout_context.node_image_animation_map.write());
|
std::mem::take(&mut *layout_context.node_image_animation_map.write());
|
||||||
|
|
||||||
Some(ReflowResult {
|
Some(ReflowResult {
|
||||||
pending_images,
|
pending_images,
|
||||||
|
pending_rasterization_images,
|
||||||
iframe_sizes,
|
iframe_sizes,
|
||||||
node_to_image_animation_map,
|
node_to_image_animation_map,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::cell::LazyCell;
|
use std::cell::LazyCell;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use base::id::{BrowsingContextId, PipelineId};
|
use base::id::{BrowsingContextId, PipelineId};
|
||||||
|
@ -11,8 +10,7 @@ use data_url::DataUrl;
|
||||||
use embedder_traits::ViewportDetails;
|
use embedder_traits::ViewportDetails;
|
||||||
use euclid::{Scale, Size2D};
|
use euclid::{Scale, Size2D};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
|
use net_traits::image_cache::{Image, ImageOrMetadataAvailable, UsePlaceholder};
|
||||||
use pixels::Image;
|
|
||||||
use script::layout_dom::ServoLayoutNode;
|
use script::layout_dom::ServoLayoutNode;
|
||||||
use script_layout_interface::IFrameSize;
|
use script_layout_interface::IFrameSize;
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
|
@ -28,7 +26,7 @@ use url::Url;
|
||||||
use webrender_api::ImageKey;
|
use webrender_api::ImageKey;
|
||||||
|
|
||||||
use crate::cell::ArcRefCell;
|
use crate::cell::ArcRefCell;
|
||||||
use crate::context::LayoutContext;
|
use crate::context::{LayoutContext, LayoutImageCacheResult};
|
||||||
use crate::dom::NodeExt;
|
use crate::dom::NodeExt;
|
||||||
use crate::fragment_tree::{BaseFragmentInfo, Fragment, IFrameFragment, ImageFragment};
|
use crate::fragment_tree::{BaseFragmentInfo, Fragment, IFrameFragment, ImageFragment};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
|
@ -115,7 +113,7 @@ pub(crate) struct VideoInfo {
|
||||||
|
|
||||||
#[derive(Debug, MallocSizeOf)]
|
#[derive(Debug, MallocSizeOf)]
|
||||||
pub(crate) enum ReplacedContentKind {
|
pub(crate) enum ReplacedContentKind {
|
||||||
Image(#[conditional_malloc_size_of] Option<Arc<Image>>),
|
Image(Option<Image>),
|
||||||
IFrame(IFrameInfo),
|
IFrame(IFrameInfo),
|
||||||
Canvas(CanvasInfo),
|
Canvas(CanvasInfo),
|
||||||
Video(Option<VideoInfo>),
|
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());
|
context.handle_animated_image(element.opaque(), image.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,13 +195,20 @@ impl ReplacedContents {
|
||||||
image_url.clone().into(),
|
image_url.clone().into(),
|
||||||
UsePlaceholder::No,
|
UsePlaceholder::No,
|
||||||
) {
|
) {
|
||||||
Ok(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
|
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
|
||||||
(Some(image.clone()), image.width as f32, image.height as f32)
|
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||||
|
let metadata = image.metadata();
|
||||||
|
(
|
||||||
|
Some(image.clone()),
|
||||||
|
metadata.width as f32,
|
||||||
|
metadata.height as f32,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
Ok(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => {
|
ImageOrMetadataAvailable::MetadataAvailable(metadata, _id) => {
|
||||||
(None, metadata.width as f32, metadata.height as f32)
|
(None, metadata.width as f32, metadata.height as f32)
|
||||||
},
|
},
|
||||||
Err(_) => return None,
|
},
|
||||||
|
LayoutImageCacheResult::Pending | LayoutImageCacheResult::LoadError => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Some(Self {
|
return Some(Self {
|
||||||
|
@ -315,7 +320,19 @@ impl ReplacedContents {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
ReplacedContentKind::Image(image) => image
|
ReplacedContentKind::Image(image) => image
|
||||||
.as_ref()
|
.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| {
|
.map(|image_key| {
|
||||||
Fragment::Image(ArcRefCell::new(ImageFragment {
|
Fragment::Image(ArcRefCell::new(ImageFragment {
|
||||||
base: self.base_fragment_info.into(),
|
base: self.base_fragment_info.into(),
|
||||||
|
|
|
@ -22,6 +22,8 @@ indexmap = { workspace = true }
|
||||||
ipc-channel = { workspace = true }
|
ipc-channel = { workspace = true }
|
||||||
keyboard-types = { workspace = true }
|
keyboard-types = { workspace = true }
|
||||||
markup5ever = { workspace = true }
|
markup5ever = { workspace = true }
|
||||||
|
mime = { workspace = true }
|
||||||
|
resvg = { workspace = true }
|
||||||
servo_allocator = { path = "../allocator" }
|
servo_allocator = { path = "../allocator" }
|
||||||
servo_arc = { workspace = true }
|
servo_arc = { workspace = true }
|
||||||
smallvec = { 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!(http::StatusCode);
|
||||||
malloc_size_of_is_0!(app_units::Au);
|
malloc_size_of_is_0!(app_units::Au);
|
||||||
malloc_size_of_is_0!(keyboard_types::Modifiers);
|
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::NonZeroU64);
|
||||||
malloc_size_of_is_0!(std::num::NonZeroUsize);
|
malloc_size_of_is_0!(std::num::NonZeroUsize);
|
||||||
malloc_size_of_is_0!(std::sync::atomic::AtomicBool);
|
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::Duration);
|
||||||
malloc_size_of_is_0!(std::time::Instant);
|
malloc_size_of_is_0!(std::time::Instant);
|
||||||
malloc_size_of_is_0!(std::time::SystemTime);
|
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::data::ElementData);
|
||||||
malloc_size_of_is_0!(style::font_face::SourceList);
|
malloc_size_of_is_0!(style::font_face::SourceList);
|
||||||
malloc_size_of_is_0!(style::properties::ComputedValues);
|
malloc_size_of_is_0!(style::properties::ComputedValues);
|
||||||
|
|
|
@ -56,6 +56,7 @@ rayon = { workspace = true }
|
||||||
rustls = { workspace = true }
|
rustls = { workspace = true }
|
||||||
rustls-pemfile = { workspace = true }
|
rustls-pemfile = { workspace = true }
|
||||||
rustls-pki-types = { workspace = true }
|
rustls-pki-types = { workspace = true }
|
||||||
|
resvg = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
servo_arc = { 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::sync::{Arc, Mutex};
|
||||||
use std::{mem, thread};
|
use std::{mem, thread};
|
||||||
|
|
||||||
|
use base::id::PipelineId;
|
||||||
use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData};
|
use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData};
|
||||||
use imsz::imsz_from_reader;
|
use imsz::imsz_from_reader;
|
||||||
use ipc_channel::ipc::IpcSharedMemory;
|
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
|
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
|
use mime::Mime;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
Image, ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
|
||||||
PendingImageId, UsePlaceholder,
|
ImageOrMetadataAvailable, ImageResponse, PendingImageId, RasterizationCompleteResponse,
|
||||||
|
UsePlaceholder, VectorImage,
|
||||||
};
|
};
|
||||||
use net_traits::request::CorsSettings;
|
use net_traits::request::CorsSettings;
|
||||||
use net_traits::{FetchMetadata, FetchResponseMsg, FilteredMetadata, NetworkError};
|
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::mem::{Report, ReportKind};
|
||||||
use profile_traits::path;
|
use profile_traits::path;
|
||||||
|
use resvg::{tiny_skia, usvg};
|
||||||
use servo_config::pref;
|
use servo_config::pref;
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
use webrender_api::units::DeviceIntSize;
|
use webrender_api::units::DeviceIntSize;
|
||||||
|
@ -48,12 +52,53 @@ const FALLBACK_RIPPY: &[u8] = include_bytes!("../../resources/rippy.png");
|
||||||
// Helper functions.
|
// Helper functions.
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
fn decode_bytes_sync(key: LoadKey, bytes: &[u8], cors: CorsStatus) -> DecoderMsg {
|
fn parse_svg_document_in_memory(bytes: &[u8]) -> Result<usvg::Tree, &'static str> {
|
||||||
let image = load_from_memory(bytes, cors);
|
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 }
|
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)
|
let mut image = load_from_memory(data, CorsStatus::Unsafe)
|
||||||
.or_else(|| load_from_memory(FALLBACK_RIPPY, CorsStatus::Unsafe))
|
.or_else(|| load_from_memory(FALLBACK_RIPPY, CorsStatus::Unsafe))
|
||||||
.expect("load fallback image failed");
|
.expect("load fallback image failed");
|
||||||
|
@ -61,14 +106,14 @@ fn get_placeholder_image(compositor_api: &CrossProcessCompositorApi, data: &[u8]
|
||||||
Arc::new(image)
|
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() {
|
if image.id.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
let frame_bytes = image.first_frame().bytes;
|
let frame_bytes = image.first_frame().bytes;
|
||||||
let is_opaque = match image.format {
|
let is_opaque = match image.format {
|
||||||
PixelFormat::BGRA8 => {
|
PixelFormat::BGRA8 | PixelFormat::RGBA8 => {
|
||||||
bytes.extend_from_slice(frame_bytes);
|
bytes.extend_from_slice(frame_bytes);
|
||||||
pixels::rgba8_premultiply_inplace(bytes.as_mut_slice())
|
pixels::rgba8_premultiply_inplace(bytes.as_mut_slice())
|
||||||
},
|
},
|
||||||
|
@ -80,16 +125,24 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m
|
||||||
|
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
PixelFormat::K8 | PixelFormat::KA8 | PixelFormat::RGBA8 => {
|
PixelFormat::K8 | PixelFormat::KA8 => {
|
||||||
panic!("Not support by webrender yet");
|
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;
|
let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
|
||||||
flags.set(ImageDescriptorFlags::IS_OPAQUE, is_opaque);
|
flags.set(ImageDescriptorFlags::IS_OPAQUE, is_opaque);
|
||||||
|
|
||||||
|
let size = DeviceIntSize::new(image.metadata.width as i32, image.metadata.height as i32);
|
||||||
let descriptor = ImageDescriptor {
|
let descriptor = ImageDescriptor {
|
||||||
size: DeviceIntSize::new(image.width as i32, image.height as i32),
|
size,
|
||||||
stride: None,
|
stride: None,
|
||||||
format: ImageFormat::BGRA8,
|
format,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
flags,
|
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.
|
/// Message that the decoder worker threads send to the image cache.
|
||||||
struct DecoderMsg {
|
struct DecoderMsg {
|
||||||
key: LoadKey,
|
key: LoadKey,
|
||||||
image: Option<Image>,
|
image: Option<DecodedImage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(MallocSizeOf)]
|
#[derive(MallocSizeOf)]
|
||||||
|
@ -265,8 +330,9 @@ impl LoadKeyGenerator {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum LoadResult {
|
enum LoadResult {
|
||||||
Loaded(Image),
|
LoadedRasterImage(RasterImage),
|
||||||
PlaceholderLoaded(Arc<Image>),
|
LoadedVectorImage(VectorImageData),
|
||||||
|
PlaceholderLoaded(Arc<RasterImage>),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +351,7 @@ struct PendingLoad {
|
||||||
result: Option<Result<(), NetworkError>>,
|
result: Option<Result<(), NetworkError>>,
|
||||||
|
|
||||||
/// The listeners that are waiting for this response to complete.
|
/// 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
|
/// The url being loaded. Do not forget that this may be several Mb
|
||||||
/// if we are loading a data: url.
|
/// if we are loading a data: url.
|
||||||
|
@ -302,6 +368,9 @@ struct PendingLoad {
|
||||||
|
|
||||||
/// The URL of the final response that contains a body.
|
/// The URL of the final response that contains a body.
|
||||||
final_url: Option<ServoUrl>,
|
final_url: Option<ServoUrl>,
|
||||||
|
|
||||||
|
/// The MIME type from the `Content-type` header of the HTTP response, if any.
|
||||||
|
content_type: Option<Mime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingLoad {
|
impl PendingLoad {
|
||||||
|
@ -320,33 +389,48 @@ impl PendingLoad {
|
||||||
final_url: None,
|
final_url: None,
|
||||||
cors_setting,
|
cors_setting,
|
||||||
cors_status: CorsStatus::Unsafe,
|
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);
|
self.listeners.push(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
#[derive(Default, MallocSizeOf)]
|
||||||
// Image cache implementation.
|
struct RasterizationTask {
|
||||||
// ======================================================================
|
listeners: Vec<(PipelineId, IpcSender<ImageCacheResponseMessage>)>,
|
||||||
|
result: Option<RasterImage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Image cache implementation.
|
||||||
#[derive(MallocSizeOf)]
|
#[derive(MallocSizeOf)]
|
||||||
struct ImageCacheStore {
|
struct ImageCacheStore {
|
||||||
// Images that are loading over network, or decoding.
|
/// Images that are loading over network, or decoding.
|
||||||
pending_loads: AllPendingLoads,
|
pending_loads: AllPendingLoads,
|
||||||
|
|
||||||
// Images that have finished loading (successful or not)
|
/// Images that have finished loading (successful or not)
|
||||||
completed_loads: HashMap<ImageKey, CompletedLoad>,
|
completed_loads: HashMap<ImageKey, CompletedLoad>,
|
||||||
|
|
||||||
// The placeholder image used when an image fails to load
|
/// Vector (e.g. SVG) images that have been sucessfully loaded and parsed
|
||||||
#[conditional_malloc_size_of]
|
/// but are yet to be rasterized. Since the same SVG data can be used for
|
||||||
placeholder_image: Arc<Image>,
|
/// 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,
|
placeholder_url: ServoUrl,
|
||||||
|
|
||||||
// Cross-process compositor API instance.
|
/// Cross-process compositor API instance.
|
||||||
#[ignore_malloc_size_of = "Channel from another crate"]
|
#[ignore_malloc_size_of = "Channel from another crate"]
|
||||||
compositor_api: CrossProcessCompositorApi,
|
compositor_api: CrossProcessCompositorApi,
|
||||||
}
|
}
|
||||||
|
@ -361,15 +445,34 @@ impl ImageCacheStore {
|
||||||
};
|
};
|
||||||
|
|
||||||
match load_result {
|
match load_result {
|
||||||
LoadResult::Loaded(ref mut image) => {
|
LoadResult::LoadedRasterImage(ref mut raster_image) => {
|
||||||
set_webrender_image_key(&self.compositor_api, 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 => {},
|
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = pending_load.final_url.clone();
|
let url = pending_load.final_url.clone();
|
||||||
let image_response = match load_result {
|
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) => {
|
LoadResult::PlaceholderLoaded(image) => {
|
||||||
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone())
|
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone())
|
||||||
},
|
},
|
||||||
|
@ -399,19 +502,18 @@ impl ImageCacheStore {
|
||||||
origin: ImmutableOrigin,
|
origin: ImmutableOrigin,
|
||||||
cors_setting: Option<CorsSettings>,
|
cors_setting: Option<CorsSettings>,
|
||||||
placeholder: UsePlaceholder,
|
placeholder: UsePlaceholder,
|
||||||
) -> Option<Result<(Arc<Image>, ServoUrl), ()>> {
|
) -> Option<Result<(Image, ServoUrl), ()>> {
|
||||||
self.completed_loads
|
self.completed_loads
|
||||||
.get(&(url, origin, cors_setting))
|
.get(&(url, origin, cors_setting))
|
||||||
.map(
|
.map(
|
||||||
|completed_load| match (&completed_load.image_response, placeholder) {
|
|completed_load| match (&completed_load.image_response, placeholder) {
|
||||||
(&ImageResponse::Loaded(ref image, ref url), _) |
|
(ImageResponse::Loaded(image, url), _) => Ok((image.clone(), url.clone())),
|
||||||
(
|
(ImageResponse::PlaceholderLoaded(image, url), UsePlaceholder::Yes) => {
|
||||||
&ImageResponse::PlaceholderLoaded(ref image, ref url),
|
Ok((Image::Raster(image.clone()), url.clone()))
|
||||||
UsePlaceholder::Yes,
|
},
|
||||||
) => Ok((image.clone(), url.clone())),
|
(ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
|
||||||
(&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
|
(ImageResponse::None, _) |
|
||||||
(&ImageResponse::None, _) |
|
(ImageResponse::MetadataLoaded(_), _) => Err(()),
|
||||||
(&ImageResponse::MetadataLoaded(_), _) => Err(()),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -421,7 +523,10 @@ impl ImageCacheStore {
|
||||||
fn handle_decoder(&mut self, msg: DecoderMsg) {
|
fn handle_decoder(&mut self, msg: DecoderMsg) {
|
||||||
let image = match msg.image {
|
let image = match msg.image {
|
||||||
None => LoadResult::None,
|
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);
|
self.complete_load(msg.key, image);
|
||||||
}
|
}
|
||||||
|
@ -450,6 +555,8 @@ impl ImageCache for ImageCacheImpl {
|
||||||
store: Arc::new(Mutex::new(ImageCacheStore {
|
store: Arc::new(Mutex::new(ImageCacheStore {
|
||||||
pending_loads: AllPendingLoads::new(),
|
pending_loads: AllPendingLoads::new(),
|
||||||
completed_loads: HashMap::new(),
|
completed_loads: HashMap::new(),
|
||||||
|
vector_images: HashMap::new(),
|
||||||
|
rasterized_vector_images: HashMap::new(),
|
||||||
placeholder_image: get_placeholder_image(&compositor_api, &rippy_data),
|
placeholder_image: get_placeholder_image(&compositor_api, &rippy_data),
|
||||||
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
|
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
|
||||||
compositor_api,
|
compositor_api,
|
||||||
|
@ -475,7 +582,7 @@ impl ImageCache for ImageCacheImpl {
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
origin: ImmutableOrigin,
|
origin: ImmutableOrigin,
|
||||||
cors_setting: Option<CorsSettings>,
|
cors_setting: Option<CorsSettings>,
|
||||||
) -> Option<Arc<Image>> {
|
) -> Option<Image> {
|
||||||
let store = self.store.lock().unwrap();
|
let store = self.store.lock().unwrap();
|
||||||
let result =
|
let result =
|
||||||
store.get_completed_image_if_available(url, origin, cors_setting, UsePlaceholder::No);
|
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) {
|
CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) {
|
||||||
(&Some(Ok(_)), _) => {
|
(&Some(Ok(_)), _) => {
|
||||||
debug!("Sync decoding {} ({:?})", url, key);
|
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)) => {
|
(&None, Some(meta)) => {
|
||||||
debug!("Metadata available for {} ({:?})", url, key);
|
debug!("Metadata available for {} ({:?})", url, key);
|
||||||
return ImageCacheResult::Available(
|
return ImageCacheResult::Available(
|
||||||
ImageOrMetadataAvailable::MetadataAvailable(meta.clone(), key),
|
ImageOrMetadataAvailable::MetadataAvailable(*meta, key),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(&Some(Err(_)), _) | (&None, &None) => {
|
(&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,
|
/// Add a new listener for the given pending image id. If the image is already present,
|
||||||
/// the responder will still receive the expected response.
|
/// 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();
|
let mut store = self.store.lock().unwrap();
|
||||||
self.add_listener_with_store(&mut store, listener);
|
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());
|
let final_url = metadata.as_ref().map(|m| m.final_url.clone());
|
||||||
pending_load.final_url = final_url;
|
pending_load.final_url = final_url;
|
||||||
pending_load.cors_status = cors_status;
|
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), _) => {
|
(FetchResponseMsg::ProcessResponseChunk(_, data), _) => {
|
||||||
debug!("Got some data for {:?}", id);
|
debug!("Got some data for {:?}", id);
|
||||||
|
@ -619,7 +863,7 @@ impl ImageCache for ImageCacheImpl {
|
||||||
height: info.height as u32,
|
height: info.height as u32,
|
||||||
};
|
};
|
||||||
for listener in &pending_load.listeners {
|
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);
|
pending_load.metadata = Some(img_metadata);
|
||||||
}
|
}
|
||||||
|
@ -629,17 +873,21 @@ impl ImageCache for ImageCacheImpl {
|
||||||
debug!("Received EOF for {:?}", key);
|
debug!("Received EOF for {:?}", key);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let (bytes, cors_status) = {
|
let (bytes, cors_status, content_type) = {
|
||||||
let mut store = self.store.lock().unwrap();
|
let mut store = self.store.lock().unwrap();
|
||||||
let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap();
|
let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap();
|
||||||
pending_load.result = Some(Ok(()));
|
pending_load.result = Some(Ok(()));
|
||||||
debug!("Async decoding {} ({:?})", pending_load.url, key);
|
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();
|
let local_store = self.store.clone();
|
||||||
self.thread_pool.spawn(move || {
|
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");
|
debug!("Image decoded");
|
||||||
local_store.lock().unwrap().handle_decoder(msg);
|
local_store.lock().unwrap().handle_decoder(msg);
|
||||||
});
|
});
|
||||||
|
@ -669,6 +917,8 @@ impl ImageCache for ImageCacheImpl {
|
||||||
placeholder_image,
|
placeholder_image,
|
||||||
placeholder_url,
|
placeholder_url,
|
||||||
compositor_api,
|
compositor_api,
|
||||||
|
vector_images: HashMap::new(),
|
||||||
|
rasterized_vector_images: HashMap::new(),
|
||||||
})),
|
})),
|
||||||
thread_pool: self.thread_pool.clone(),
|
thread_pool: self.thread_pool.clone(),
|
||||||
})
|
})
|
||||||
|
@ -681,9 +931,16 @@ impl Drop for ImageCacheStore {
|
||||||
.completed_loads
|
.completed_loads
|
||||||
.values()
|
.values()
|
||||||
.filter_map(|load| match &load.image_response {
|
.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,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
.chain(
|
||||||
|
self.rasterized_vector_images
|
||||||
|
.values()
|
||||||
|
.filter_map(|task| task.result.as_ref()?.id.map(ImageUpdate::DeleteImage)),
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
self.compositor_api.update_images(image_updates);
|
self.compositor_api.update_images(image_updates);
|
||||||
}
|
}
|
||||||
|
@ -691,11 +948,11 @@ impl Drop for ImageCacheStore {
|
||||||
|
|
||||||
impl ImageCacheImpl {
|
impl ImageCacheImpl {
|
||||||
/// Require self.store.lock() before calling.
|
/// 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;
|
let id = listener.id;
|
||||||
if let Some(load) = store.pending_loads.get_by_key_mut(&id) {
|
if let Some(load) = store.pending_loads.get_by_key_mut(&id) {
|
||||||
if let Some(ref metadata) = load.metadata {
|
if let Some(ref metadata) = load.metadata {
|
||||||
listener.respond(ImageResponse::MetadataLoaded(metadata.clone()));
|
listener.respond(ImageResponse::MetadataLoaded(*metadata));
|
||||||
}
|
}
|
||||||
load.add_listener(listener);
|
load.add_listener(listener);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -121,9 +121,8 @@ pub enum CorsStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
|
#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
|
||||||
pub struct Image {
|
pub struct RasterImage {
|
||||||
pub width: u32,
|
pub metadata: ImageMetadata,
|
||||||
pub height: u32,
|
|
||||||
pub format: PixelFormat,
|
pub format: PixelFormat,
|
||||||
pub id: Option<ImageKey>,
|
pub id: Option<ImageKey>,
|
||||||
pub cors_status: CorsStatus,
|
pub cors_status: CorsStatus,
|
||||||
|
@ -149,7 +148,7 @@ pub struct ImageFrameView<'a> {
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl RasterImage {
|
||||||
pub fn should_animate(&self) -> bool {
|
pub fn should_animate(&self) -> bool {
|
||||||
self.frames.len() > 1
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
|
"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 struct ImageMetadata {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
|
@ -189,7 +188,7 @@ pub struct ImageMetadata {
|
||||||
// FIXME: Images must not be copied every frame. Instead we should atomically
|
// FIXME: Images must not be copied every frame. Instead we should atomically
|
||||||
// reference count them.
|
// 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() {
|
if buffer.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -212,9 +211,11 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image>
|
||||||
width: rgba.width(),
|
width: rgba.width(),
|
||||||
height: rgba.height(),
|
height: rgba.height(),
|
||||||
};
|
};
|
||||||
Some(Image {
|
Some(RasterImage {
|
||||||
|
metadata: ImageMetadata {
|
||||||
width: rgba.width(),
|
width: rgba.width(),
|
||||||
height: rgba.height(),
|
height: rgba.height(),
|
||||||
|
},
|
||||||
format: PixelFormat::BGRA8,
|
format: PixelFormat::BGRA8,
|
||||||
frames: vec![frame],
|
frames: vec![frame],
|
||||||
bytes: IpcSharedMemory::from_bytes(&rgba),
|
bytes: IpcSharedMemory::from_bytes(&rgba),
|
||||||
|
@ -374,7 +375,7 @@ fn is_webp(buffer: &[u8]) -> bool {
|
||||||
buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
|
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 {
|
let Ok(decoded_gif) = GifDecoder::new(Cursor::new(buffer)) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
@ -430,9 +431,8 @@ fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
|
||||||
bytes.extend_from_slice(frame.buffer());
|
bytes.extend_from_slice(frame.buffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Image {
|
Some(RasterImage {
|
||||||
width,
|
metadata: ImageMetadata { width, height },
|
||||||
height,
|
|
||||||
cors_status,
|
cors_status,
|
||||||
frames,
|
frames,
|
||||||
id: None,
|
id: None,
|
||||||
|
|
|
@ -328,7 +328,15 @@ impl CanvasState {
|
||||||
cors_setting: Option<CorsSettings>,
|
cors_setting: Option<CorsSettings>,
|
||||||
) -> Option<snapshot::Snapshot> {
|
) -> Option<snapshot::Snapshot> {
|
||||||
let img = match self.request_image_from_cache(url, cors_setting) {
|
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::PlaceholderLoaded(_, _) |
|
||||||
ImageResponse::None |
|
ImageResponse::None |
|
||||||
ImageResponse::MetadataLoaded(_) => {
|
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 {
|
let format = match img.format {
|
||||||
PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA,
|
PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA,
|
||||||
PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA,
|
PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA,
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use js::rust::{HandleObject, MutableHandleValue};
|
use js::rust::{HandleObject, MutableHandleValue};
|
||||||
|
use net_traits::image_cache::Image;
|
||||||
|
|
||||||
use crate::dom::bindings::cell::DomRefCell;
|
use crate::dom::bindings::cell::DomRefCell;
|
||||||
use crate::dom::bindings::codegen::Bindings::DataTransferBinding::DataTransferMethods;
|
use crate::dom::bindings::codegen::Bindings::DataTransferBinding::DataTransferMethods;
|
||||||
|
@ -155,7 +156,10 @@ impl DataTransferMethods<crate::DomTypeHolder> for DataTransfer {
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
if let Some(image) = image.downcast::<HTMLImageElement>() {
|
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 mime::{self, Mime};
|
||||||
use net_traits::http_status::HttpStatus;
|
use net_traits::http_status::HttpStatus;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
|
||||||
PendingImageId, UsePlaceholder,
|
ImageResponse, PendingImageId, UsePlaceholder,
|
||||||
};
|
};
|
||||||
use net_traits::request::{Destination, Initiator, RequestId};
|
use net_traits::request::{Destination, Initiator, RequestId};
|
||||||
use net_traits::{
|
use net_traits::{
|
||||||
|
@ -29,7 +29,7 @@ use net_traits::{
|
||||||
ResourceFetchTiming, ResourceTimingType,
|
ResourceFetchTiming, ResourceTimingType,
|
||||||
};
|
};
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
use pixels::{CorsStatus, Image, ImageMetadata};
|
use pixels::{CorsStatus, ImageMetadata};
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use servo_url::origin::MutableOrigin;
|
use servo_url::origin::MutableOrigin;
|
||||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_integer, parse_length};
|
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_integer, parse_length};
|
||||||
|
@ -146,9 +146,8 @@ struct ImageRequest {
|
||||||
parsed_url: Option<ServoUrl>,
|
parsed_url: Option<ServoUrl>,
|
||||||
source_url: Option<USVString>,
|
source_url: Option<USVString>,
|
||||||
blocker: DomRefCell<Option<LoadBlocker>>,
|
blocker: DomRefCell<Option<LoadBlocker>>,
|
||||||
#[conditional_malloc_size_of]
|
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
image: Option<Arc<Image>>,
|
image: Option<Image>,
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
metadata: Option<ImageMetadata>,
|
metadata: Option<ImageMetadata>,
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
|
@ -177,7 +176,8 @@ impl HTMLImageElement {
|
||||||
pub(crate) fn is_usable(&self) -> Fallible<bool> {
|
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 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 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);
|
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()
|
self.current_request.borrow().image.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,10 +341,12 @@ impl HTMLImageElement {
|
||||||
is_placeholder,
|
is_placeholder,
|
||||||
}) => {
|
}) => {
|
||||||
if is_placeholder {
|
if is_placeholder {
|
||||||
|
if let Some(raster_image) = image.as_raster_image() {
|
||||||
self.process_image_response(
|
self.process_image_response(
|
||||||
ImageResponse::PlaceholderLoaded(image, url),
|
ImageResponse::PlaceholderLoaded(raster_image, url),
|
||||||
can_gc,
|
can_gc,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.process_image_response(ImageResponse::Loaded(image, url), can_gc)
|
self.process_image_response(ImageResponse::Loaded(image, url), can_gc)
|
||||||
}
|
}
|
||||||
|
@ -403,7 +405,7 @@ impl HTMLImageElement {
|
||||||
|
|
||||||
window
|
window
|
||||||
.image_cache()
|
.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) {
|
fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
|
||||||
|
@ -448,11 +450,8 @@ impl HTMLImageElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Steps common to when an image has been loaded.
|
// Steps common to when an image has been loaded.
|
||||||
fn handle_loaded_image(&self, image: Arc<Image>, url: ServoUrl, can_gc: CanGc) {
|
fn handle_loaded_image(&self, image: Image, url: ServoUrl, can_gc: CanGc) {
|
||||||
self.current_request.borrow_mut().metadata = Some(ImageMetadata {
|
self.current_request.borrow_mut().metadata = Some(image.metadata());
|
||||||
height: image.height,
|
|
||||||
width: image.width,
|
|
||||||
});
|
|
||||||
self.current_request.borrow_mut().final_url = Some(url);
|
self.current_request.borrow_mut().final_url = Some(url);
|
||||||
self.current_request.borrow_mut().image = Some(image);
|
self.current_request.borrow_mut().image = Some(image);
|
||||||
self.current_request.borrow_mut().state = State::CompletelyAvailable;
|
self.current_request.borrow_mut().state = State::CompletelyAvailable;
|
||||||
|
@ -471,7 +470,7 @@ impl HTMLImageElement {
|
||||||
(true, false)
|
(true, false)
|
||||||
},
|
},
|
||||||
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
|
(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)
|
(false, true)
|
||||||
},
|
},
|
||||||
(ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
|
(ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
|
||||||
|
@ -483,7 +482,7 @@ impl HTMLImageElement {
|
||||||
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
|
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
|
||||||
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
|
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
|
||||||
self.image_request.set(ImageRequestPhase::Current);
|
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)
|
(false, true)
|
||||||
},
|
},
|
||||||
(ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
|
(ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
|
||||||
|
@ -536,11 +535,15 @@ impl HTMLImageElement {
|
||||||
can_gc: CanGc,
|
can_gc: CanGc,
|
||||||
) {
|
) {
|
||||||
match image {
|
match image {
|
||||||
ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
|
ImageResponse::Loaded(image, url) => {
|
||||||
self.pending_request.borrow_mut().metadata = Some(ImageMetadata {
|
self.pending_request.borrow_mut().metadata = Some(image.metadata());
|
||||||
height: image.height,
|
self.pending_request.borrow_mut().final_url = Some(url);
|
||||||
width: image.width,
|
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().final_url = Some(url);
|
||||||
self.pending_request.borrow_mut().image = Some(image);
|
self.pending_request.borrow_mut().image = Some(image);
|
||||||
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
|
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
|
||||||
|
@ -1020,10 +1023,7 @@ impl HTMLImageElement {
|
||||||
// set on this element.
|
// set on this element.
|
||||||
self.generation.set(self.generation.get() + 1);
|
self.generation.set(self.generation.get() + 1);
|
||||||
// Step 6.3
|
// Step 6.3
|
||||||
let metadata = ImageMetadata {
|
let metadata = image.metadata();
|
||||||
height: image.height,
|
|
||||||
width: image.width,
|
|
||||||
};
|
|
||||||
// Step 6.3.2 abort requests
|
// Step 6.3.2 abort requests
|
||||||
self.abort_request(
|
self.abort_request(
|
||||||
State::CompletelyAvailable,
|
State::CompletelyAvailable,
|
||||||
|
@ -1033,7 +1033,7 @@ impl HTMLImageElement {
|
||||||
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
|
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
|
||||||
let mut current_request = self.current_request.borrow_mut();
|
let mut current_request = self.current_request.borrow_mut();
|
||||||
current_request.final_url = Some(img_url.clone());
|
current_request.final_url = Some(img_url.clone());
|
||||||
current_request.image = Some(image.clone());
|
current_request.image = Some(image);
|
||||||
current_request.metadata = Some(metadata);
|
current_request.metadata = Some(metadata);
|
||||||
// Step 6.3.6
|
// Step 6.3.6
|
||||||
current_request.current_pixel_density = pixel_density;
|
current_request.current_pixel_density = pixel_density;
|
||||||
|
@ -1360,7 +1360,7 @@ impl HTMLImageElement {
|
||||||
|
|
||||||
pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
|
pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
|
||||||
if let Some(ref image) = self.current_request.borrow().image {
|
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
|
self.current_request
|
||||||
|
@ -1432,7 +1432,7 @@ impl MicrotaskRunnable for ImageElementMicrotask {
|
||||||
pub(crate) trait LayoutHTMLImageElementHelpers {
|
pub(crate) trait LayoutHTMLImageElementHelpers {
|
||||||
fn image_url(self) -> Option<ServoUrl>;
|
fn image_url(self) -> Option<ServoUrl>;
|
||||||
fn image_density(self) -> Option<f64>;
|
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_width(self) -> LengthOrPercentageOrAuto;
|
||||||
fn get_height(self) -> LengthOrPercentageOrAuto;
|
fn get_height(self) -> LengthOrPercentageOrAuto;
|
||||||
}
|
}
|
||||||
|
@ -1449,12 +1449,9 @@ impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
|
||||||
self.current_request().parsed_url.clone()
|
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();
|
let current_request = self.current_request();
|
||||||
(
|
(current_request.image.clone(), current_request.metadata)
|
||||||
current_request.image.clone(),
|
|
||||||
current_request.metadata.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image_density(self) -> Option<f64> {
|
fn image_density(self) -> Option<f64> {
|
||||||
|
|
|
@ -27,7 +27,7 @@ use net_traits::{
|
||||||
FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming,
|
FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming,
|
||||||
ResourceTimingType,
|
ResourceTimingType,
|
||||||
};
|
};
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use script_bindings::codegen::GenericBindings::TimeRangesBinding::TimeRangesMethods;
|
use script_bindings::codegen::GenericBindings::TimeRangesBinding::TimeRangesMethods;
|
||||||
use script_bindings::codegen::InheritTypes::{
|
use script_bindings::codegen::InheritTypes::{
|
||||||
ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
|
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 {
|
if let Some(image_key) = image.id {
|
||||||
self.current_frame = Some(MediaFrame {
|
self.current_frame = Some(MediaFrame {
|
||||||
image_key,
|
image_key,
|
||||||
width: image.width as i32,
|
width: image.metadata.width as i32,
|
||||||
height: image.height as i32,
|
height: image.metadata.height as i32,
|
||||||
});
|
});
|
||||||
self.show_poster = true;
|
self.show_poster = true;
|
||||||
}
|
}
|
||||||
|
@ -1358,13 +1358,13 @@ impl HTMLMediaElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
|
/// <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() {
|
if !self.show_poster.get() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6.
|
// 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
|
self.video_renderer
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::default::Default;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
|
|
||||||
use crate::dom::attr::Attr;
|
use crate::dom::attr::Attr;
|
||||||
|
@ -31,7 +31,7 @@ pub(crate) struct HTMLObjectElement {
|
||||||
htmlelement: HTMLElement,
|
htmlelement: HTMLElement,
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
#[ignore_malloc_size_of = "Arc"]
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
image: DomRefCell<Option<Arc<Image>>>,
|
image: DomRefCell<Option<Arc<RasterImage>>>,
|
||||||
form_owner: MutNullableDom<HTMLFormElement>,
|
form_owner: MutNullableDom<HTMLFormElement>,
|
||||||
validity_state: MutNullableDom<ValidityState>,
|
validity_state: MutNullableDom<ValidityState>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use html5ever::{LocalName, Prefix, local_name, ns};
|
||||||
use ipc_channel::ipc;
|
use ipc_channel::ipc;
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse,
|
||||||
PendingImageId, UsePlaceholder,
|
PendingImageId, UsePlaceholder,
|
||||||
};
|
};
|
||||||
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
|
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
|
||||||
|
@ -217,7 +217,7 @@ impl HTMLVideoElement {
|
||||||
element.process_image_response(response.response, CanGc::note());
|
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>
|
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
|
||||||
|
@ -262,7 +262,10 @@ impl HTMLVideoElement {
|
||||||
match response {
|
match response {
|
||||||
ImageResponse::Loaded(image, url) => {
|
ImageResponse::Loaded(image, url) => {
|
||||||
debug!("Loaded poster image for video element: {:?}", 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);
|
LoadBlocker::terminate(&self.load_blocker, can_gc);
|
||||||
},
|
},
|
||||||
ImageResponse::MetadataLoaded(..) => {},
|
ImageResponse::MetadataLoaded(..) => {},
|
||||||
|
|
|
@ -10,7 +10,6 @@ use std::default::Default;
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::slice::from_ref;
|
use std::slice::from_ref;
|
||||||
use std::sync::Arc as StdArc;
|
|
||||||
use std::{cmp, fmt, iter};
|
use std::{cmp, fmt, iter};
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
|
@ -26,7 +25,8 @@ use js::jsapi::JSObject;
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
use libc::{self, c_void, uintptr_t};
|
use libc::{self, c_void, uintptr_t};
|
||||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
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_bindings::codegen::InheritTypes::DocumentFragmentTypeId;
|
||||||
use script_layout_interface::{
|
use script_layout_interface::{
|
||||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
|
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
|
||||||
|
@ -1618,7 +1618,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
|
||||||
fn selection(self) -> Option<Range<usize>>;
|
fn selection(self) -> Option<Range<usize>>;
|
||||||
fn image_url(self) -> Option<ServoUrl>;
|
fn image_url(self) -> Option<ServoUrl>;
|
||||||
fn image_density(self) -> Option<f64>;
|
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 canvas_data(self) -> Option<HTMLCanvasData>;
|
||||||
fn media_data(self) -> Option<HTMLMediaData>;
|
fn media_data(self) -> Option<HTMLMediaData>;
|
||||||
fn svg_data(self) -> Option<SVGSVGData>;
|
fn svg_data(self) -> Option<SVGSVGData>;
|
||||||
|
@ -1831,7 +1831,7 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
||||||
.image_url()
|
.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())
|
self.downcast::<HTMLImageElement>().map(|e| e.image_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,15 @@ use js::jsval::JSVal;
|
||||||
use js::rust::{HandleObject, MutableHandleValue};
|
use js::rust::{HandleObject, MutableHandleValue};
|
||||||
use net_traits::http_status::HttpStatus;
|
use net_traits::http_status::HttpStatus;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
|
ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
|
||||||
PendingImageId, PendingImageResponse, UsePlaceholder,
|
ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder,
|
||||||
};
|
};
|
||||||
use net_traits::request::{RequestBuilder, RequestId};
|
use net_traits::request::{RequestBuilder, RequestId};
|
||||||
use net_traits::{
|
use net_traits::{
|
||||||
FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
|
FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
|
||||||
ResourceTimingType,
|
ResourceTimingType,
|
||||||
};
|
};
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -114,15 +114,15 @@ pub(crate) struct Notification {
|
||||||
/// <https://notifications.spec.whatwg.org/#image-resource>
|
/// <https://notifications.spec.whatwg.org/#image-resource>
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
#[ignore_malloc_size_of = "Arc"]
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
image_resource: DomRefCell<Option<Arc<Image>>>,
|
image_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||||
/// <https://notifications.spec.whatwg.org/#icon-resource>
|
/// <https://notifications.spec.whatwg.org/#icon-resource>
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
#[ignore_malloc_size_of = "Arc"]
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
icon_resource: DomRefCell<Option<Arc<Image>>>,
|
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||||
/// <https://notifications.spec.whatwg.org/#badge-resource>
|
/// <https://notifications.spec.whatwg.org/#badge-resource>
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
#[ignore_malloc_size_of = "Arc"]
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
badge_resource: DomRefCell<Option<Arc<Image>>>,
|
badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Notification {
|
impl Notification {
|
||||||
|
@ -561,7 +561,7 @@ struct Action {
|
||||||
/// <https://notifications.spec.whatwg.org/#action-icon-resource>
|
/// <https://notifications.spec.whatwg.org/#action-icon-resource>
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
#[ignore_malloc_size_of = "Arc"]
|
||||||
#[no_trace]
|
#[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>
|
/// <https://notifications.spec.whatwg.org/#create-a-notification-with-a-settings-object>
|
||||||
|
@ -886,7 +886,11 @@ impl Notification {
|
||||||
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
|
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
|
||||||
image, ..
|
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(
|
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
|
||||||
_,
|
_,
|
||||||
|
@ -925,8 +929,7 @@ impl Notification {
|
||||||
pending_image_id: PendingImageId,
|
pending_image_id: PendingImageId,
|
||||||
resource_type: ResourceType,
|
resource_type: ResourceType,
|
||||||
) {
|
) {
|
||||||
let (sender, receiver) =
|
let (sender, receiver) = ipc::channel().expect("ipc channel failure");
|
||||||
ipc::channel::<PendingImageResponse>().expect("ipc channel failure");
|
|
||||||
|
|
||||||
let global: &GlobalScope = &self.global();
|
let global: &GlobalScope = &self.global();
|
||||||
|
|
||||||
|
@ -942,7 +945,11 @@ impl Notification {
|
||||||
task_source.queue(task!(handle_response: move || {
|
task_source.queue(task!(handle_response: move || {
|
||||||
let this = trusted_this.root();
|
let this = trusted_this.root();
|
||||||
if let Ok(response) = response {
|
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 {
|
} else {
|
||||||
this.handle_image_cache_response(request_id, ImageResponse::None, resource_type);
|
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,
|
sender,
|
||||||
global.pipeline_id(),
|
global.pipeline_id(),
|
||||||
pending_image_id,
|
pending_image_id,
|
||||||
|
@ -965,7 +972,11 @@ impl Notification {
|
||||||
) {
|
) {
|
||||||
match response {
|
match response {
|
||||||
ImageResponse::Loaded(image, _) => {
|
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, _) => {
|
ImageResponse::PlaceholderLoaded(image, _) => {
|
||||||
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
|
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
|
||||||
|
@ -981,7 +992,7 @@ impl Notification {
|
||||||
&self,
|
&self,
|
||||||
request_id: RequestId,
|
request_id: RequestId,
|
||||||
resource_type: &ResourceType,
|
resource_type: &ResourceType,
|
||||||
image: Option<Arc<Image>>,
|
image: Option<Arc<RasterImage>>,
|
||||||
) {
|
) {
|
||||||
match resource_type {
|
match resource_type {
|
||||||
ResourceType::Image => {
|
ResourceType::Image => {
|
||||||
|
|
|
@ -609,15 +609,31 @@ impl WebGLRenderingContext {
|
||||||
};
|
};
|
||||||
let cors_setting = cors_setting_for_element(image.upcast());
|
let cors_setting = cors_setting_for_element(image.upcast());
|
||||||
|
|
||||||
let img =
|
let img = match canvas_utils::request_image_from_cache(
|
||||||
match canvas_utils::request_image_from_cache(&window, img_url, cors_setting) {
|
&window,
|
||||||
ImageResponse::Loaded(img, _) => img,
|
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::PlaceholderLoaded(_, _) |
|
||||||
ImageResponse::None |
|
ImageResponse::None |
|
||||||
ImageResponse::MetadataLoaded(_) => return Ok(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);
|
let data = IpcSharedMemory::from_bytes(img.first_frame().bytes);
|
||||||
TexPixels::new(data, size, img.format, false)
|
TexPixels::new(data, size, img.format, false)
|
||||||
|
|
|
@ -55,7 +55,8 @@ use malloc_size_of::MallocSizeOf;
|
||||||
use media::WindowGLContext;
|
use media::WindowGLContext;
|
||||||
use net_traits::ResourceThreads;
|
use net_traits::ResourceThreads;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageResponder, ImageResponse, PendingImageId, PendingImageResponse,
|
ImageCache, ImageCacheResponseMessage, ImageLoadListener, ImageResponse, PendingImageId,
|
||||||
|
PendingImageResponse, RasterizationCompleteResponse,
|
||||||
};
|
};
|
||||||
use net_traits::storage_thread::StorageType;
|
use net_traits::storage_thread::StorageType;
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
@ -87,7 +88,7 @@ use style_traits::CSSPixel;
|
||||||
use stylo_atoms::Atom;
|
use stylo_atoms::Atom;
|
||||||
use url::Position;
|
use url::Position;
|
||||||
use webrender_api::ExternalScrollId;
|
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::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
|
||||||
use super::bindings::trace::HashMapTracedValues;
|
use super::bindings::trace::HashMapTracedValues;
|
||||||
|
@ -218,6 +219,8 @@ impl LayoutBlocker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize);
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub(crate) struct Window {
|
pub(crate) struct Window {
|
||||||
globalscope: GlobalScope,
|
globalscope: GlobalScope,
|
||||||
|
@ -240,7 +243,7 @@ pub(crate) struct Window {
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
image_cache: Arc<dyn ImageCache>,
|
image_cache: Arc<dyn ImageCache>,
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
image_cache_sender: IpcSender<PendingImageResponse>,
|
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
|
||||||
window_proxy: MutNullableDom<WindowProxy>,
|
window_proxy: MutNullableDom<WindowProxy>,
|
||||||
document: MutNullableDom<Document>,
|
document: MutNullableDom<Document>,
|
||||||
location: MutNullableDom<Location>,
|
location: MutNullableDom<Location>,
|
||||||
|
@ -343,11 +346,17 @@ pub(crate) struct Window {
|
||||||
pending_image_callbacks: DomRefCell<HashMap<PendingImageId, Vec<PendingImageCallback>>>,
|
pending_image_callbacks: DomRefCell<HashMap<PendingImageId, Vec<PendingImageCallback>>>,
|
||||||
|
|
||||||
/// All of the elements that have an outstanding image request that was
|
/// 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
|
/// to ensure that the element can be marked dirty when the image data becomes
|
||||||
/// available at some point in the future.
|
/// available at some point in the future.
|
||||||
pending_layout_images: DomRefCell<HashMapTracedValues<PendingImageId, Vec<Dom<Node>>>>,
|
pending_layout_images: DomRefCell<HashMapTracedValues<PendingImageId, Vec<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
|
/// Directory to store unminified css for this window if unminify-css
|
||||||
/// opt is enabled.
|
/// opt is enabled.
|
||||||
unminified_css_dir: DomRefCell<Option<String>>,
|
unminified_css_dir: DomRefCell<Option<String>>,
|
||||||
|
@ -568,7 +577,7 @@ impl Window {
|
||||||
&self,
|
&self,
|
||||||
id: PendingImageId,
|
id: PendingImageId,
|
||||||
callback: impl Fn(PendingImageResponse) + 'static,
|
callback: impl Fn(PendingImageResponse) + 'static,
|
||||||
) -> IpcSender<PendingImageResponse> {
|
) -> IpcSender<ImageCacheResponseMessage> {
|
||||||
self.pending_image_callbacks
|
self.pending_image_callbacks
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.entry(id)
|
.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) {
|
pub(crate) fn pending_image_notification(&self, response: PendingImageResponse) {
|
||||||
// We take the images here, in order to prevent maintaining a mutable borrow when
|
// 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.
|
// image callbacks are called. These, in turn, can trigger garbage collection.
|
||||||
|
@ -2224,8 +2249,11 @@ impl Window {
|
||||||
.pending_layout_image_notification(response);
|
.pending_layout_image_notification(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.image_cache
|
self.image_cache.add_listener(ImageLoadListener::new(
|
||||||
.add_listener(ImageResponder::new(sender, self.pipeline_id(), id));
|
sender,
|
||||||
|
self.pipeline_id(),
|
||||||
|
id,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodes = images.entry(id).or_default();
|
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
|
let size_messages = self
|
||||||
.Document()
|
.Document()
|
||||||
.iframes_mut()
|
.iframes_mut()
|
||||||
|
@ -2325,12 +2372,13 @@ impl Window {
|
||||||
});
|
});
|
||||||
|
|
||||||
let has_sent_idle_message = self.has_sent_idle_message.get();
|
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 &&
|
if !has_sent_idle_message &&
|
||||||
is_ready_state_complete &&
|
is_ready_state_complete &&
|
||||||
!reftest_wait &&
|
!reftest_wait &&
|
||||||
!pending_images &&
|
no_pending_images &&
|
||||||
!waiting_for_web_fonts_to_load
|
!waiting_for_web_fonts_to_load
|
||||||
{
|
{
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -3005,7 +3053,7 @@ impl Window {
|
||||||
script_chan: Sender<MainThreadScriptMsg>,
|
script_chan: Sender<MainThreadScriptMsg>,
|
||||||
layout: Box<dyn Layout>,
|
layout: Box<dyn Layout>,
|
||||||
font_context: Arc<FontContext>,
|
font_context: Arc<FontContext>,
|
||||||
image_cache_sender: IpcSender<PendingImageResponse>,
|
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
|
||||||
image_cache: Arc<dyn ImageCache>,
|
image_cache: Arc<dyn ImageCache>,
|
||||||
resource_threads: ResourceThreads,
|
resource_threads: ResourceThreads,
|
||||||
#[cfg(feature = "bluetooth")] bluetooth_thread: IpcSender<BluetoothRequest>,
|
#[cfg(feature = "bluetooth")] bluetooth_thread: IpcSender<BluetoothRequest>,
|
||||||
|
@ -3104,6 +3152,7 @@ impl Window {
|
||||||
webxr_registry,
|
webxr_registry,
|
||||||
pending_image_callbacks: Default::default(),
|
pending_image_callbacks: Default::default(),
|
||||||
pending_layout_images: Default::default(),
|
pending_layout_images: Default::default(),
|
||||||
|
pending_images_for_rasterization: Default::default(),
|
||||||
unminified_css_dir: Default::default(),
|
unminified_css_dir: Default::default(),
|
||||||
local_script_source,
|
local_script_source,
|
||||||
test_worklet: Default::default(),
|
test_worklet: Default::default(),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use constellation_traits::BlobImpl;
|
use constellation_traits::BlobImpl;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
|
|
||||||
use crate::dom::bindings::error::{Error, Fallible};
|
use crate::dom::bindings::error::{Error, Fallible};
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
|
@ -70,7 +70,7 @@ impl Kind {
|
||||||
/// <https://html.spec.whatwg.org/multipage/#drag-data-store-bitmap>
|
/// <https://html.spec.whatwg.org/multipage/#drag-data-store-bitmap>
|
||||||
#[allow(dead_code)] // TODO this used by DragEvent.
|
#[allow(dead_code)] // TODO this used by DragEvent.
|
||||||
struct Bitmap {
|
struct Bitmap {
|
||||||
image: Option<Arc<Image>>,
|
image: Option<Arc<RasterImage>>,
|
||||||
x: i32,
|
x: i32,
|
||||||
y: i32,
|
y: i32,
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ impl DragDataStore {
|
||||||
self.mode = mode;
|
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 });
|
self.bitmap = Some(Bitmap { image, x, y });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,10 @@ impl ImageAnimationManager {
|
||||||
image.id.unwrap(),
|
image.id.unwrap(),
|
||||||
ImageDescriptor {
|
ImageDescriptor {
|
||||||
format: ImageFormat::BGRA8,
|
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,
|
stride: None,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
flags: ImageDescriptorFlags::ALLOW_MIPMAPS,
|
flags: ImageDescriptorFlags::ALLOW_MIPMAPS,
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc as StdArc;
|
|
||||||
|
|
||||||
use base::id::{BrowsingContextId, PipelineId};
|
use base::id::{BrowsingContextId, PipelineId};
|
||||||
use fonts_traits::ByteIndex;
|
use fonts_traits::ByteIndex;
|
||||||
use html5ever::{local_name, ns};
|
use html5ever::{local_name, ns};
|
||||||
use pixels::{Image, ImageMetadata};
|
use net_traits::image_cache::Image;
|
||||||
|
use pixels::ImageMetadata;
|
||||||
use range::Range;
|
use range::Range;
|
||||||
use script_layout_interface::wrapper_traits::{LayoutDataTrait, LayoutNode, ThreadSafeLayoutNode};
|
use script_layout_interface::wrapper_traits::{LayoutDataTrait, LayoutNode, ThreadSafeLayoutNode};
|
||||||
use script_layout_interface::{
|
use script_layout_interface::{
|
||||||
|
@ -371,7 +371,7 @@ impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
||||||
this.image_density()
|
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() };
|
let this = unsafe { self.get_jsmanaged() };
|
||||||
this.image_data()
|
this.image_data()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use crossbeam_channel::{Receiver, SendError, Sender, select};
|
||||||
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
|
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use net_traits::FetchResponseMsg;
|
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::mem::{self as profile_mem, OpaqueSender, ReportsChan};
|
||||||
use profile_traits::time::{self as profile_time};
|
use profile_traits::time::{self as profile_time};
|
||||||
use script_traits::{Painter, ScriptThreadMessage};
|
use script_traits::{Painter, ScriptThreadMessage};
|
||||||
|
@ -40,7 +40,7 @@ pub(crate) enum MixedMessage {
|
||||||
FromConstellation(ScriptThreadMessage),
|
FromConstellation(ScriptThreadMessage),
|
||||||
FromScript(MainThreadScriptMsg),
|
FromScript(MainThreadScriptMsg),
|
||||||
FromDevtools(DevtoolScriptControlMsg),
|
FromDevtools(DevtoolScriptControlMsg),
|
||||||
FromImageCache(PendingImageResponse),
|
FromImageCache(ImageCacheResponseMessage),
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
FromWebGPUServer(WebGPUMsg),
|
FromWebGPUServer(WebGPUMsg),
|
||||||
TimerFired,
|
TimerFired,
|
||||||
|
@ -104,7 +104,14 @@ impl MixedMessage {
|
||||||
MainThreadScriptMsg::Inactive => None,
|
MainThreadScriptMsg::Inactive => None,
|
||||||
MainThreadScriptMsg::WakeUp => 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,
|
MixedMessage::FromDevtools(_) | MixedMessage::TimerFired => None,
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
MixedMessage::FromWebGPUServer(..) => None,
|
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
|
/// messages on this channel are routed to crossbeam [`Sender`] on the router thread, which
|
||||||
/// in turn sends messages to [`ScriptThreadReceivers::image_cache_receiver`].
|
/// in turn sends messages to [`ScriptThreadReceivers::image_cache_receiver`].
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
pub(crate) image_cache_sender: IpcSender<PendingImageResponse>,
|
pub(crate) image_cache_sender: IpcSender<ImageCacheResponseMessage>,
|
||||||
|
|
||||||
/// For providing contact with the time profiler.
|
/// For providing contact with the time profiler.
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
|
@ -355,7 +362,7 @@ pub(crate) struct ScriptThreadReceivers {
|
||||||
|
|
||||||
/// The [`Receiver`] which receives incoming messages from the `ImageCache`.
|
/// The [`Receiver`] which receives incoming messages from the `ImageCache`.
|
||||||
#[no_trace]
|
#[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
|
/// 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()`].
|
/// 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 js::rust::ParentRuntime;
|
||||||
use media::WindowGLContext;
|
use media::WindowGLContext;
|
||||||
use metrics::MAX_TASK_NS;
|
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::request::{Referrer, RequestId};
|
||||||
use net_traits::response::ResponseInit;
|
use net_traits::response::ResponseInit;
|
||||||
use net_traits::storage_thread::StorageType;
|
use net_traits::storage_thread::StorageType;
|
||||||
|
@ -2095,11 +2095,24 @@ impl ScriptThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_msg_from_image_cache(&self, response: PendingImageResponse) {
|
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);
|
let window = self.documents.borrow().find_window(response.pipeline_id);
|
||||||
if let Some(ref window) = window {
|
if let Some(ref window) = window {
|
||||||
window.pending_image_notification(response);
|
window.handle_image_rasterization_complete_notification(response);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_webdriver_msg(
|
fn handle_webdriver_msg(
|
||||||
|
|
|
@ -16,7 +16,7 @@ use euclid::Rect;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use strum_macros::IntoStaticStr;
|
use strum_macros::IntoStaticStr;
|
||||||
use style_traits::CSSPixel;
|
use style_traits::CSSPixel;
|
||||||
use webrender_api::DocumentId;
|
use webrender_api::DocumentId;
|
||||||
|
@ -83,7 +83,7 @@ pub enum CompositorMsg {
|
||||||
CreatePng(
|
CreatePng(
|
||||||
WebViewId,
|
WebViewId,
|
||||||
Option<Rect<f32, CSSPixel>>,
|
Option<Rect<f32, CSSPixel>>,
|
||||||
IpcSender<Option<Image>>,
|
IpcSender<Option<RasterImage>>,
|
||||||
),
|
),
|
||||||
/// A reply to the compositor asking if the output image is stable.
|
/// A reply to the compositor asking if the output image is stable.
|
||||||
IsReadyToSaveImageReply(bool),
|
IsReadyToSaveImageReply(bool),
|
||||||
|
|
|
@ -30,7 +30,7 @@ use log::warn;
|
||||||
use malloc_size_of::malloc_size_of_is_0;
|
use malloc_size_of::malloc_size_of_is_0;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use strum_macros::IntoStaticStr;
|
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.
|
/// The URL of an icon. The icon will be displayed as part of the notification.
|
||||||
pub icon_url: Option<ServoUrl>,
|
pub icon_url: Option<ServoUrl>,
|
||||||
/// Icon's raw image data and metadata.
|
/// 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,
|
/// 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.
|
/// such as on a mobile device's notification bar.
|
||||||
pub badge_url: Option<ServoUrl>,
|
pub badge_url: Option<ServoUrl>,
|
||||||
/// Badge's raw image data and metadata.
|
/// 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.
|
/// The URL of an image. The image will be displayed as part of the notification.
|
||||||
pub image_url: Option<ServoUrl>,
|
pub image_url: Option<ServoUrl>,
|
||||||
/// Image's raw image data and metadata.
|
/// 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.
|
/// Actions available for users to choose from for interacting with the notification.
|
||||||
pub actions: Vec<NotificationAction>,
|
pub actions: Vec<NotificationAction>,
|
||||||
}
|
}
|
||||||
|
@ -698,7 +698,7 @@ pub struct NotificationAction {
|
||||||
/// The URL of an icon. The icon will be displayed with the action.
|
/// The URL of an icon. The icon will be displayed with the action.
|
||||||
pub icon_url: Option<ServoUrl>,
|
pub icon_url: Option<ServoUrl>,
|
||||||
/// Icon's raw image data and metadata.
|
/// 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
|
/// 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 ipc_channel::ipc::IpcSender;
|
||||||
use keyboard_types::KeyboardEvent;
|
use keyboard_types::KeyboardEvent;
|
||||||
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use style_traits::CSSPixel;
|
use style_traits::CSSPixel;
|
||||||
|
@ -69,7 +69,7 @@ pub enum WebDriverCommandMsg {
|
||||||
TakeScreenshot(
|
TakeScreenshot(
|
||||||
WebViewId,
|
WebViewId,
|
||||||
Option<Rect<f32, CSSPixel>>,
|
Option<Rect<f32, CSSPixel>>,
|
||||||
IpcSender<Option<Image>>,
|
IpcSender<Option<RasterImage>>,
|
||||||
),
|
),
|
||||||
/// Create a new webview that loads about:blank. The constellation will use
|
/// Create a new webview that loads about:blank. The constellation will use
|
||||||
/// the provided channels to return the top level browsing context id
|
/// the provided channels to return the top level browsing context id
|
||||||
|
|
|
@ -41,6 +41,7 @@ servo_rand = { path = "../../rand" }
|
||||||
servo_url = { path = "../../url" }
|
servo_url = { path = "../../url" }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
|
webrender_api = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
embedder_traits = { workspace = true, features = ["baked-default-resources"] }
|
embedder_traits = { workspace = true, features = ["baked-default-resources"] }
|
||||||
|
|
|
@ -10,10 +10,11 @@ use ipc_channel::ipc::IpcSender;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use malloc_size_of::MallocSizeOfOps;
|
use malloc_size_of::MallocSizeOfOps;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use pixels::{Image, ImageMetadata};
|
use pixels::{CorsStatus, ImageMetadata, RasterImage};
|
||||||
use profile_traits::mem::Report;
|
use profile_traits::mem::Report;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
|
use webrender_api::units::DeviceIntSize;
|
||||||
|
|
||||||
use crate::FetchResponseMsg;
|
use crate::FetchResponseMsg;
|
||||||
use crate::request::CorsSettings;
|
use crate::request::CorsSettings;
|
||||||
|
@ -22,12 +23,52 @@ use crate::request::CorsSettings;
|
||||||
// Aux structs and enums.
|
// 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
|
/// Indicating either entire image or just metadata availability
|
||||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||||
pub enum ImageOrMetadataAvailable {
|
pub enum ImageOrMetadataAvailable {
|
||||||
ImageAvailable {
|
ImageAvailable {
|
||||||
#[ignore_malloc_size_of = "Arc"]
|
image: Image,
|
||||||
image: Arc<Image>,
|
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
is_placeholder: bool,
|
is_placeholder: bool,
|
||||||
},
|
},
|
||||||
|
@ -39,19 +80,19 @@ pub enum ImageOrMetadataAvailable {
|
||||||
/// image load completes. It is typically used to trigger a reflow
|
/// image load completes. It is typically used to trigger a reflow
|
||||||
/// and/or repaint.
|
/// and/or repaint.
|
||||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||||
pub struct ImageResponder {
|
pub struct ImageLoadListener {
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
pub id: PendingImageId,
|
pub id: PendingImageId,
|
||||||
sender: IpcSender<PendingImageResponse>,
|
sender: IpcSender<ImageCacheResponseMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageResponder {
|
impl ImageLoadListener {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sender: IpcSender<PendingImageResponse>,
|
sender: IpcSender<ImageCacheResponseMessage>,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
id: PendingImageId,
|
id: PendingImageId,
|
||||||
) -> ImageResponder {
|
) -> ImageLoadListener {
|
||||||
ImageResponder {
|
ImageLoadListener {
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
sender,
|
sender,
|
||||||
id,
|
id,
|
||||||
|
@ -63,11 +104,15 @@ impl ImageResponder {
|
||||||
// This send can fail if thread waiting for this notification has panicked.
|
// This send can fail if thread waiting for this notification has panicked.
|
||||||
// That's not a case that's worth warning about.
|
// That's not a case that's worth warning about.
|
||||||
// TODO(#15501): are there cases in which we should perform cleanup?
|
// TODO(#15501): are there cases in which we should perform cleanup?
|
||||||
let _ = self.sender.send(PendingImageResponse {
|
let _ = self
|
||||||
|
.sender
|
||||||
|
.send(ImageCacheResponseMessage::NotifyPendingImageLoadStatus(
|
||||||
|
PendingImageResponse {
|
||||||
pipeline_id: self.pipeline_id,
|
pipeline_id: self.pipeline_id,
|
||||||
response,
|
response,
|
||||||
id: self.id,
|
id: self.id,
|
||||||
});
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +120,11 @@ impl ImageResponder {
|
||||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||||
pub enum ImageResponse {
|
pub enum ImageResponse {
|
||||||
/// The requested image was loaded.
|
/// The requested image was loaded.
|
||||||
Loaded(#[conditional_malloc_size_of] Arc<Image>, ServoUrl),
|
Loaded(Image, ServoUrl),
|
||||||
/// The request image metadata was loaded.
|
/// The request image metadata was loaded.
|
||||||
MetadataLoaded(ImageMetadata),
|
MetadataLoaded(ImageMetadata),
|
||||||
/// The requested image failed to load, so a placeholder was loaded instead.
|
/// 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.
|
/// Neither the requested image nor the placeholder could be loaded.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
@ -95,6 +140,19 @@ pub struct PendingImageResponse {
|
||||||
pub id: PendingImageId,
|
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)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub enum UsePlaceholder {
|
pub enum UsePlaceholder {
|
||||||
No,
|
No,
|
||||||
|
@ -125,7 +183,7 @@ pub trait ImageCache: Sync + Send {
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
origin: ImmutableOrigin,
|
origin: ImmutableOrigin,
|
||||||
cors_setting: Option<CorsSettings>,
|
cors_setting: Option<CorsSettings>,
|
||||||
) -> Option<Arc<Image>>;
|
) -> Option<Image>;
|
||||||
|
|
||||||
fn get_cached_image_status(
|
fn get_cached_image_status(
|
||||||
&self,
|
&self,
|
||||||
|
@ -135,9 +193,31 @@ pub trait ImageCache: Sync + Send {
|
||||||
use_placeholder: UsePlaceholder,
|
use_placeholder: UsePlaceholder,
|
||||||
) -> ImageCacheResult;
|
) -> 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,
|
/// Add a new listener for the given pending image id. If the image is already present,
|
||||||
/// the responder will still receive the expected response.
|
/// 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.
|
/// Inform the image cache about a response for a pending request.
|
||||||
fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg);
|
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::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use net_traits::image_cache::{ImageCache, PendingImageId};
|
use net_traits::image_cache::{ImageCache, PendingImageId};
|
||||||
use pixels::Image;
|
use pixels::RasterImage;
|
||||||
use profile_traits::mem::Report;
|
use profile_traits::mem::Report;
|
||||||
use profile_traits::time;
|
use profile_traits::time;
|
||||||
use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
|
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::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
|
||||||
use style::stylesheets::Stylesheet;
|
use style::stylesheets::Stylesheet;
|
||||||
use webrender_api::ImageKey;
|
use webrender_api::ImageKey;
|
||||||
|
use webrender_api::units::DeviceIntSize;
|
||||||
|
|
||||||
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
|
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
@ -153,6 +154,16 @@ pub struct PendingImage {
|
||||||
pub origin: ImmutableOrigin,
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MediaFrame {
|
pub struct MediaFrame {
|
||||||
pub image_key: webrender_api::ImageKey,
|
pub image_key: webrender_api::ImageKey,
|
||||||
|
@ -392,6 +403,8 @@ pub type IFrameSizes = FnvHashMap<BrowsingContextId, IFrameSize>;
|
||||||
pub struct ReflowResult {
|
pub struct ReflowResult {
|
||||||
/// The list of images that were encountered that are in progress.
|
/// The list of images that were encountered that are in progress.
|
||||||
pub pending_images: Vec<PendingImage>,
|
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
|
/// The list of iframes in this layout and their sizes, used in order
|
||||||
/// to communicate them with the Constellation and also the `Window`
|
/// to communicate them with the Constellation and also the `Window`
|
||||||
/// element of their content pages.
|
/// element of their content pages.
|
||||||
|
@ -507,13 +520,13 @@ pub fn node_id_from_scroll_id(id: usize) -> Option<usize> {
|
||||||
#[derive(Clone, Debug, MallocSizeOf)]
|
#[derive(Clone, Debug, MallocSizeOf)]
|
||||||
pub struct ImageAnimationState {
|
pub struct ImageAnimationState {
|
||||||
#[ignore_malloc_size_of = "Arc is hard"]
|
#[ignore_malloc_size_of = "Arc is hard"]
|
||||||
pub image: Arc<Image>,
|
pub image: Arc<RasterImage>,
|
||||||
pub active_frame: usize,
|
pub active_frame: usize,
|
||||||
last_update_time: f64,
|
last_update_time: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageAnimationState {
|
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 {
|
Self {
|
||||||
image,
|
image,
|
||||||
active_frame: 0,
|
active_frame: 0,
|
||||||
|
@ -579,7 +592,7 @@ mod test {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use ipc_channel::ipc::IpcSharedMemory;
|
use ipc_channel::ipc::IpcSharedMemory;
|
||||||
use pixels::{CorsStatus, Image, ImageFrame, PixelFormat};
|
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
|
||||||
|
|
||||||
use crate::ImageAnimationState;
|
use crate::ImageAnimationState;
|
||||||
|
|
||||||
|
@ -593,9 +606,11 @@ mod test {
|
||||||
})
|
})
|
||||||
.take(10)
|
.take(10)
|
||||||
.collect();
|
.collect();
|
||||||
let image = Image {
|
let image = RasterImage {
|
||||||
|
metadata: ImageMetadata {
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
|
},
|
||||||
format: PixelFormat::BGRA8,
|
format: PixelFormat::BGRA8,
|
||||||
id: None,
|
id: None,
|
||||||
bytes: IpcSharedMemory::from_byte(1, 1),
|
bytes: IpcSharedMemory::from_byte(1, 1),
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc as StdArc;
|
|
||||||
|
|
||||||
use atomic_refcell::AtomicRef;
|
use atomic_refcell::AtomicRef;
|
||||||
use base::id::{BrowsingContextId, PipelineId};
|
use base::id::{BrowsingContextId, PipelineId};
|
||||||
use fonts_traits::ByteIndex;
|
use fonts_traits::ByteIndex;
|
||||||
use html5ever::{LocalName, Namespace};
|
use html5ever::{LocalName, Namespace};
|
||||||
use pixels::{Image, ImageMetadata};
|
use net_traits::image_cache::Image;
|
||||||
|
use pixels::ImageMetadata;
|
||||||
use range::Range;
|
use range::Range;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
|
@ -222,7 +222,7 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE
|
||||||
fn image_density(&self) -> Option<f64>;
|
fn image_density(&self) -> Option<f64>;
|
||||||
|
|
||||||
/// If this is an image element, returns its image data. Otherwise, returns `None`.
|
/// 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>;
|
fn canvas_data(&self) -> Option<HTMLCanvasData>;
|
||||||
|
|
||||||
|
|
|
@ -1760,8 +1760,12 @@ impl Handler {
|
||||||
"Unexpected screenshot pixel format"
|
"Unexpected screenshot pixel format"
|
||||||
);
|
);
|
||||||
|
|
||||||
let rgb =
|
let rgb = RgbaImage::from_raw(
|
||||||
RgbaImage::from_raw(img.width, img.height, img.first_frame().bytes.to_vec()).unwrap();
|
img.metadata.width,
|
||||||
|
img.metadata.height,
|
||||||
|
img.first_frame().bytes.to_vec(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let mut png_data = Cursor::new(Vec::new());
|
let mut png_data = Cursor::new(Vec::new());
|
||||||
DynamicImage::ImageRgba8(rgb)
|
DynamicImage::ImageRgba8(rgb)
|
||||||
.write_to(&mut png_data, ImageFormat::Png)
|
.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