Merge branch 'main' into network_monitor_01

This commit is contained in:
Usman Yahaya Baba 2025-06-05 12:36:34 +01:00 committed by GitHub
commit e29f8eec2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2132 changed files with 47333 additions and 17366 deletions

18
.flake8
View file

@ -1,18 +0,0 @@
[flake8]
ignore =
# trailing whitespace; the standard tidy process will enforce no trailing whitespace
W291,
# linebreak before binary operator; replaced by W504 - linebreak after binary operator
W503,
# 80 character line length; the standard tidy process will enforce line length
E501
exclude =
# temporary local files
target
__pycache__
python/_venv*
# upstream
third_party
python/mach
components
tests

View file

@ -4,6 +4,10 @@ on:
types: ['opened', 'synchronize', 'reopened', 'edited', 'closed']
branches: ['main']
concurrency:
group: ${{ github.head_ref }}
cancel-in-progress: false
jobs:
upstream:
# Run job only on servo/servo

View file

@ -16,6 +16,8 @@
// IDL language support
"mythmon.idl",
// TOML files
"tamasfe.even-better-toml"
"tamasfe.even-better-toml",
// Python files
"charliermarsh.ruff"
]
}

423
Cargo.lock generated
View file

@ -737,6 +737,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.10.1"
@ -843,9 +849,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.23"
version = "1.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
dependencies = [
"jobserver",
"libc",
@ -963,18 +969,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.38"
version = "4.5.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.38"
version = "4.5.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
dependencies = [
"anstyle",
"clap_lex",
@ -1064,7 +1070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1103,6 +1109,7 @@ dependencies = [
"servo_geometry",
"stylo_traits",
"surfman",
"timers",
"tracing",
"webrender",
"webrender_api",
@ -1209,6 +1216,7 @@ dependencies = [
"serde",
"servo_malloc_size_of",
"servo_url",
"snapshot",
"strum",
"strum_macros",
"uuid",
@ -1220,7 +1228,7 @@ dependencies = [
[[package]]
name = "content-security-policy"
version = "0.5.4"
source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#58a09ee320fd6fbb828748ae04255e4c8d3f9c9e"
source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#dc1fd32b2b32b704a43f4ae170bb2cbb80a7cf59"
dependencies = [
"base64 0.22.1",
"bitflags 2.9.1",
@ -1482,9 +1490,9 @@ dependencies = [
[[package]]
name = "cursor-icon"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]]
name = "darling"
@ -1587,7 +1595,7 @@ dependencies = [
"crossbeam-channel",
"devtools_traits",
"embedder_traits",
"headers 0.4.0",
"headers 0.4.1",
"http 1.3.1",
"ipc-channel",
"log",
@ -1669,28 +1677,16 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
dependencies = [
"dirs-sys 0.5.0",
"dirs-sys",
]
[[package]]
name = "dirs"
version = "5.0.1"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys 0.4.1",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users 0.4.6",
"windows-sys 0.48.0",
"dirs-sys",
]
[[package]]
@ -1701,7 +1697,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users 0.5.0",
"redox_users",
"windows-sys 0.59.0",
]
@ -1925,6 +1921,7 @@ dependencies = [
"servo_malloc_size_of",
"servo_url",
"strum_macros",
"stylo",
"stylo_traits",
"url",
"webdriver",
@ -2028,7 +2025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2128,6 +2125,12 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "float-ord"
version = "0.3.2"
@ -2148,9 +2151,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "font-kit"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520"
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
dependencies = [
"bitflags 2.9.1",
"byteorder",
@ -2171,6 +2174,29 @@ dependencies = [
"yeslogic-fontconfig-sys",
]
[[package]]
name = "fontconfig-parser"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
dependencies = [
"roxmltree",
]
[[package]]
name = "fontdb"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
dependencies = [
"fontconfig-parser",
"log",
"memmap2",
"slotmap",
"tinyvec",
"ttf-parser",
]
[[package]]
name = "fonts"
version = "0.0.1"
@ -2561,7 +2587,7 @@ dependencies = [
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2708,9 +2734,9 @@ dependencies = [
[[package]]
name = "gpu-descriptor"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca"
checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
dependencies = [
"bitflags 2.9.1",
"gpu-descriptor-types",
@ -3132,11 +3158,11 @@ dependencies = [
[[package]]
name = "headers"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64 0.21.7",
"base64 0.22.1",
"bytes",
"headers-core 0.3.0",
"http 1.3.1",
@ -3169,12 +3195,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hermit-abi"
version = "0.4.0"
@ -3222,9 +3242,9 @@ dependencies = [
[[package]]
name = "hitrace"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92c0ae6f30b32eaeb811143fba3b56864f477b2e69458b13779a07b3aaf2f6e"
checksum = "4e3037245d690359ac992d8e3bb96e15ace6ad0a00305b680f4b1fd4ede684d9"
dependencies = [
"hitrace-sys",
]
@ -3390,12 +3410,13 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710"
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
@ -3413,7 +3434,7 @@ name = "hyper_serde"
version = "0.13.2"
dependencies = [
"cookie 0.18.1",
"headers 0.4.0",
"headers 0.4.1",
"http 1.3.1",
"hyper 1.6.0",
"mime",
@ -3908,6 +3929,22 @@ dependencies = [
"tiff",
]
[[package]]
name = "image-webp"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]]
name = "imagesize"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "imsz"
version = "0.2.2"
@ -4003,7 +4040,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.0",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -4147,6 +4184,16 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kurbo"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
dependencies = [
"arrayvec",
"smallvec",
]
[[package]]
name = "layout"
version = "0.0.1"
@ -4250,12 +4297,12 @@ dependencies = [
[[package]]
name = "libloading"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@ -4387,9 +4434,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
@ -4619,14 +4666,14 @@ dependencies = [
[[package]]
name = "mio"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -4812,7 +4859,7 @@ dependencies = [
"futures-core",
"futures-util",
"generic-array",
"headers 0.4.0",
"headers 0.4.1",
"http 1.3.1",
"http-body-util",
"hyper 1.6.0",
@ -4830,6 +4877,7 @@ dependencies = [
"pixels",
"profile_traits",
"rayon",
"resvg",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
@ -4864,7 +4912,7 @@ dependencies = [
"crossbeam-channel",
"data-url",
"embedder_traits",
"headers 0.4.0",
"headers 0.4.1",
"http 1.3.1",
"hyper-util",
"hyper_serde",
@ -4884,6 +4932,7 @@ dependencies = [
"servo_url",
"url",
"uuid",
"webrender_api",
]
[[package]]
@ -4999,11 +5048,11 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi 0.3.9",
"hermit-abi 0.5.0",
"libc",
]
@ -5341,9 +5390,9 @@ dependencies = [
[[package]]
name = "ohos-ime"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48107e68ed8451c17c2ff95938e1ba86003fb290a04f7a0213ce2d16ce4b3ee6"
checksum = "ee3ea454e31a3372cd9c4ed903db4fae861e92f57cf51852a3cd80f9d3945dcd"
dependencies = [
"log",
"ohos-ime-sys",
@ -5477,9 +5526,9 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.12.3"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core",
@ -5487,9 +5536,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.10"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
@ -5516,9 +5565,9 @@ dependencies = [
[[package]]
name = "pathfinder_simd"
version = "0.5.4"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2"
checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57"
dependencies = [
"rustc_version",
]
@ -5611,6 +5660,12 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.10"
@ -5787,9 +5842,9 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
[[package]]
name = "prettyplease"
version = "0.2.32"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d"
dependencies = [
"proc-macro2",
"syn",
@ -5898,6 +5953,12 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.37.5"
@ -6041,17 +6102,6 @@ dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror 1.0.69",
]
[[package]]
name = "redox_users"
version = "0.5.0"
@ -6116,6 +6166,32 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "resvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
dependencies = [
"gif",
"image-webp",
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia",
"usvg",
"zune-jpeg",
]
[[package]]
name = "rgb"
version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
dependencies = [
"bytemuck",
]
[[package]]
name = "ring"
version = "0.17.14"
@ -6143,6 +6219,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@ -6180,7 +6262,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -6234,6 +6316,24 @@ version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "rustybuzz"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
dependencies = [
"bitflags 2.9.1",
"bytemuck",
"core_maths",
"log",
"smallvec",
"ttf-parser",
"unicode-bidi-mirroring",
"unicode-ccc",
"unicode-properties",
"unicode-script",
]
[[package]]
name = "ryu"
version = "1.0.20"
@ -6304,7 +6404,7 @@ dependencies = [
"fonts_traits",
"fxhash",
"glow",
"headers 0.4.0",
"headers 0.4.1",
"html5ever",
"http 1.3.1",
"hyper_serde",
@ -6507,7 +6607,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.28.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"bitflags 2.9.1",
"cssparser",
@ -6802,7 +6902,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.4.1"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"serde",
"stable_deref_trait",
@ -6855,6 +6955,8 @@ dependencies = [
"ipc-channel",
"keyboard-types",
"markup5ever",
"mime",
"resvg",
"servo_allocator",
"servo_arc",
"smallvec",
@ -6919,7 +7021,7 @@ dependencies = [
"getopts",
"gilrs",
"glow",
"headers 0.4.0",
"headers 0.4.1",
"hilog",
"hitrace",
"image",
@ -7026,6 +7128,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "simplecss"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
dependencies = [
"log",
]
[[package]]
name = "siphasher"
version = "1.0.1"
@ -7116,15 +7227,17 @@ version = "0.0.1"
dependencies = [
"euclid",
"ipc-channel",
"malloc_size_of_derive",
"pixels",
"serde",
"servo_malloc_size_of",
]
[[package]]
name = "socket2"
version = "0.5.9"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
@ -7178,6 +7291,9 @@ name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "string_cache"
@ -7247,7 +7363,7 @@ dependencies = [
[[package]]
name = "stylo"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"app_units",
"arrayvec",
@ -7305,7 +7421,7 @@ dependencies = [
[[package]]
name = "stylo_atoms"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"string_cache",
"string_cache_codegen",
@ -7314,12 +7430,12 @@ dependencies = [
[[package]]
name = "stylo_config"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
[[package]]
name = "stylo_derive"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"darling",
"proc-macro2",
@ -7331,7 +7447,7 @@ dependencies = [
[[package]]
name = "stylo_dom"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"bitflags 2.9.1",
"stylo_malloc_size_of",
@ -7340,7 +7456,7 @@ dependencies = [
[[package]]
name = "stylo_malloc_size_of"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"app_units",
"cssparser",
@ -7357,12 +7473,12 @@ dependencies = [
[[package]]
name = "stylo_static_prefs"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
[[package]]
name = "stylo_traits"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"app_units",
"bitflags 2.9.1",
@ -7421,6 +7537,16 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
[[package]]
name = "svgtypes"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
dependencies = [
"kurbo",
"siphasher",
]
[[package]]
name = "sw-composite"
version = "0.7.16"
@ -7526,7 +7652,7 @@ dependencies = [
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -7708,6 +7834,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
"png",
"tiny-skia-path",
]
@ -7742,10 +7869,25 @@ dependencies = [
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "to_shmem"
version = "0.2.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"cssparser",
"servo_arc",
@ -7758,7 +7900,7 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
dependencies = [
"darling",
"proc-macro2",
@ -7769,9 +7911,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.45.0"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
"backtrace",
"bytes",
@ -7987,6 +8129,9 @@ name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
dependencies = [
"core_maths",
]
[[package]]
name = "tungstenite"
@ -8088,6 +8233,18 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-bidi-mirroring"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
[[package]]
name = "unicode-ccc"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@ -8112,6 +8269,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-vo"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
name = "unicode-width"
version = "0.1.14"
@ -8168,6 +8331,33 @@ dependencies = [
"url",
]
[[package]]
name = "usvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef"
dependencies = [
"base64 0.22.1",
"data-url",
"flate2",
"fontdb",
"imagesize",
"kurbo",
"log",
"pico-args",
"roxmltree",
"rustybuzz",
"simplecss",
"siphasher",
"strict-num",
"svgtypes",
"tiny-skia-path",
"unicode-bidi",
"unicode-script",
"unicode-vo",
"xmlwriter",
]
[[package]]
name = "utf-8"
version = "0.7.6"
@ -8724,9 +8914,9 @@ dependencies = [
[[package]]
name = "weezl"
version = "0.1.8"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
[[package]]
name = "wgpu-core"
@ -8879,7 +9069,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@ -9465,6 +9655,12 @@ dependencies = [
"markup5ever",
]
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "yeslogic-fontconfig-sys"
version = "6.0.0"
@ -9581,6 +9777,12 @@ dependencies = [
"syn",
]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-inflate"
version = "0.2.54"
@ -9589,3 +9791,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
[[package]]
name = "zune-jpeg"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63fd466826ec8fe25e1fc010c169213fec4e135ac39caccdba830eaa3895923"
dependencies = [
"zune-core",
]

View file

@ -70,7 +70,7 @@ gstreamer-sys = "0.23"
gstreamer-video = "0.23"
harfbuzz-sys = "0.6.1"
headers = "0.4"
hitrace = "0.1.4"
hitrace = "0.1.5"
html5ever = "0.31"
http = "1.3"
http-body-util = "0.1"
@ -100,7 +100,7 @@ mozangle = "0.5.3"
net_traits = { path = "components/shared/net" }
nix = "0.29"
num-traits = "0.2"
num_cpus = "1.1.0"
num_cpus = "1.17.0"
openxr = "0.19"
parking_lot = "0.12"
percent-encoding = "2.3"
@ -113,6 +113,7 @@ rand_isaac = "0.3"
raw-window-handle = "0.6"
rayon = "1"
regex = "1.11"
resvg = "0.45.0"
rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] }
rustls-pemfile = "2.0"
rustls-pki-types = "1.12"

View file

@ -4,7 +4,15 @@ Servo is a prototype web browser engine written in the
[Rust](https://github.com/rust-lang/rust) language. It is currently developed on
64-bit macOS, 64-bit Linux, 64-bit Windows, 64-bit OpenHarmony, and Android.
Servo welcomes contribution from everyone. Check out [The Servo Book](https://book.servo.org) to get started, or go to [servo.org](https://servo.org/) for news and guides.
Servo welcomes contribution from everyone. Check out:
- The [Servo Book](https://book.servo.org) for documentation
- [servo.org](https://servo.org/) for news and guides
Coordination of Servo development happens:
- Here in the Github Issues
- On the [Servo Zulip](https://servo.zulipchat.com/)
- In video calls advertised in the [Servo Project](https://github.com/servo/project/issues) repo.
## Getting started

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::Cow;
use canvas_traits::canvas::{
CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle,
};
@ -21,7 +23,6 @@ pub(crate) trait Backend: Clone + Sized {
type DrawTarget: GenericDrawTarget<Self>;
type PathBuilder: GenericPathBuilder<Self>;
type SourceSurface;
type Bytes<'a>: AsRef<[u8]>;
type Path: PathHelpers<Self> + Clone;
type GradientStop;
type GradientStops;
@ -122,7 +123,7 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
draw_options: &B::DrawOptions,
);
fn surface(&self) -> B::SourceSurface;
fn bytes(&'_ self) -> B::Bytes<'_>;
fn bytes(&self) -> Cow<[u8]>;
}
/// A generic PathBuilder that abstracts the interface for azure's and raqote's PathBuilder.

View file

@ -154,7 +154,18 @@ struct PathBuilderRef<'a, B: Backend> {
}
impl<B: Backend> PathBuilderRef<'_, B> {
/// <https://html.spec.whatwg.org/multipage#ensure-there-is-a-subpath>
fn ensure_there_is_a_subpath(&mut self, point: &Point2D<f32>) {
if self.builder.get_current_point().is_none() {
self.builder.move_to(*point);
}
}
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-lineto>
fn line_to(&mut self, pt: &Point2D<f32>) {
// 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
self.ensure_there_is_a_subpath(pt);
let pt = self.transform.transform_point(*pt);
self.builder.line_to(pt);
}
@ -182,14 +193,22 @@ impl<B: Backend> PathBuilderRef<'_, B> {
self.move_to(&first);
}
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-quadraticcurveto>
fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, endpoint: &Point2D<f32>) {
// 2. Ensure there is a subpath for (cpx, cpy).
self.ensure_there_is_a_subpath(cp);
self.builder.quadratic_curve_to(
&self.transform.transform_point(*cp),
&self.transform.transform_point(*endpoint),
)
}
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-beziercurveto>
fn bezier_curve_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, endpoint: &Point2D<f32>) {
// 2. Ensure there is a subpath for (cp1x, cp1y).
self.ensure_there_is_a_subpath(cp1);
self.builder.bezier_curve_to(
&self.transform.transform_point(*cp1),
&self.transform.transform_point(*cp2),
@ -210,6 +229,7 @@ impl<B: Backend> PathBuilderRef<'_, B> {
.arc(center, radius, start_angle, end_angle, ccw);
}
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-arcto>
fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) {
let cp0 = if let (Some(inverse), Some(point)) =
(self.transform.inverse(), self.builder.get_current_point())
@ -218,6 +238,9 @@ impl<B: Backend> PathBuilderRef<'_, B> {
} else {
*cp1
};
// 2. Ensure there is a subpath for (x1, y1) is done by one of self.line_to calls
if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 {
self.line_to(cp1);
return;
@ -1327,7 +1350,7 @@ impl<'a, B: Backend> CanvasData<'a, B> {
.to_vec()
}
} else {
self.drawtarget.bytes().as_ref().to_vec()
self.drawtarget.bytes().into_owned()
};
Snapshot::from_vec(

View file

@ -97,7 +97,10 @@ impl<'a> CanvasPaintThread<'a> {
let canvas_data = canvas_paint_thread.create_canvas(size);
creator.send(canvas_data).unwrap();
},
Ok(ConstellationCanvasMsg::Exit) => break,
Ok(ConstellationCanvasMsg::Exit(exit_sender)) => {
let _ = exit_sender.send(());
break;
},
Err(e) => {
warn!("Error on CanvasPaintThread receive ({})", e);
break;

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
@ -41,7 +42,6 @@ impl Backend for RaqoteBackend {
type DrawTarget = raqote::DrawTarget;
type PathBuilder = PathBuilder;
type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?)
type Bytes<'a> = &'a [u8];
type Path = raqote::Path;
type GradientStop = raqote::GradientStop;
type GradientStops = Vec<raqote::GradientStop>;
@ -656,9 +656,11 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
);
}
#[allow(unsafe_code)]
fn bytes(&self) -> &[u8] {
fn bytes(&self) -> Cow<[u8]> {
let v = self.get_data();
unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) }
Cow::Borrowed(unsafe {
std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v))
})
}
}
@ -708,7 +710,7 @@ impl GenericPathBuilder<RaqoteBackend> for PathBuilder {
PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)),
PathOp::CubicTo(_, _, point) => Some(Point2D::new(point.x, point.y)),
PathOp::QuadTo(_, point) => Some(Point2D::new(point.x, point.y)),
PathOp::Close => None,
PathOp::Close => path.ops.first().and_then(get_first_point),
})
}
@ -731,6 +733,15 @@ impl GenericPathBuilder<RaqoteBackend> for PathBuilder {
}
}
fn get_first_point(op: &PathOp) -> Option<euclid::Point2D<f32, euclid::UnknownUnit>> {
match op {
PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)),
PathOp::CubicTo(point, _, _) => Some(Point2D::new(point.x, point.y)),
PathOp::QuadTo(point, _) => Some(Point2D::new(point.x, point.y)),
PathOp::Close => None,
}
}
pub trait ToRaqoteStyle {
type Target;

View file

@ -38,6 +38,7 @@ servo_allocator = { path = "../allocator" }
servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" }
stylo_traits = { workspace = true }
timers = { path = "../timers" }
tracing = { workspace = true, optional = true }
webrender = { workspace = true }
webrender_api = { workspace = true }

View file

@ -9,7 +9,7 @@ use std::fs::create_dir_all;
use std::iter::once;
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use std::time::{SystemTime, UNIX_EPOCH};
use base::cross_process_instant::CrossProcessInstant;
use base::id::{PipelineId, WebViewId};
@ -32,7 +32,7 @@ use fnv::FnvHashMap;
use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void;
use log::{debug, info, trace, warn};
use pixels::{CorsStatus, Image, ImageFrame, PixelFormat};
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
use profile_traits::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind};
use profile_traits::time::{self as profile_time, ProfilerCategory};
use profile_traits::{path, time_profile};
@ -53,6 +53,7 @@ use webrender_api::{
};
use crate::InitialCompositorState;
use crate::refresh_driver::RefreshDriver;
use crate::webview_manager::WebViewManager;
use crate::webview_renderer::{PinchZoomResult, UnknownWebView, WebViewRenderer};
@ -86,6 +87,9 @@ pub enum WebRenderDebugOption {
}
/// Data that is shared by all WebView renderers.
pub struct ServoRenderer {
/// The [`RefreshDriver`] which manages the rythym of painting.
refresh_driver: RefreshDriver,
/// This is a temporary map between [`PipelineId`]s and their associated [`WebViewId`]. Once
/// all renderer operations become per-`WebView` this map can be removed, but we still sometimes
/// need to work backwards to figure out what `WebView` is associated with a `Pipeline`.
@ -151,18 +155,14 @@ pub struct IOCompositor {
/// The number of frames pending to receive from WebRender.
pending_frames: usize,
/// The [`Instant`] of the last animation tick, used to avoid flooding the Constellation and
/// ScriptThread with a deluge of animation ticks.
last_animation_tick: Instant,
/// A handle to the memory profiler which will automatically unregister
/// when it's dropped.
_mem_profiler_registration: ProfilerRegistration,
}
/// Why we need to be repainted. This is used for debugging.
#[derive(Clone, Copy, Default)]
struct RepaintReason(u8);
#[derive(Clone, Copy, Default, PartialEq)]
pub(crate) struct RepaintReason(u8);
bitflags! {
impl RepaintReason: u8 {
@ -281,6 +281,11 @@ impl PipelineDetails {
}
}
pub enum HitTestError {
EpochMismatch,
Others,
}
impl ServoRenderer {
pub fn shutdown_state(&self) -> ShutdownState {
self.shutdown_state.get()
@ -290,15 +295,19 @@ impl ServoRenderer {
&self,
point: DevicePoint,
details_for_pipeline: impl Fn(PipelineId) -> Option<&'a PipelineDetails>,
) -> Option<CompositorHitTestResult> {
self.hit_test_at_point_with_flags_and_pipeline(
) -> Result<CompositorHitTestResult, HitTestError> {
match self.hit_test_at_point_with_flags_and_pipeline(
point,
HitTestFlags::empty(),
None,
details_for_pipeline,
)
.first()
.cloned()
) {
Ok(hit_test_results) => hit_test_results
.first()
.cloned()
.ok_or(HitTestError::Others),
Err(error) => Err(error),
}
}
// TODO: split this into first half (global) and second half (one for whole compositor, one for webview)
@ -308,14 +317,15 @@ impl ServoRenderer {
flags: HitTestFlags,
pipeline_id: Option<WebRenderPipelineId>,
details_for_pipeline: impl Fn(PipelineId) -> Option<&'a PipelineDetails>,
) -> Vec<CompositorHitTestResult> {
) -> Result<Vec<CompositorHitTestResult>, HitTestError> {
// DevicePoint and WorldPoint are the same for us.
let world_point = WorldPoint::from_untyped(point.to_untyped());
let results =
self.webrender_api
.hit_test(self.webrender_document, pipeline_id, world_point, flags);
results
let mut epoch_mismatch = false;
let results = results
.items
.iter()
.filter_map(|item| {
@ -323,10 +333,16 @@ impl ServoRenderer {
let details = details_for_pipeline(pipeline_id)?;
// If the epoch in the tag does not match the current epoch of the pipeline,
// then the hit test is against an old version of the display list and we
// should ignore this hit test for now.
// then the hit test is against an old version of the display list.
match details.most_recent_display_list_epoch {
Some(epoch) if epoch.as_u16() == item.tag.1 => {},
Some(epoch) => {
if epoch.as_u16() != item.tag.1 {
// It's too early to hit test for now.
// New scene building is in progress.
epoch_mismatch = true;
return None;
}
},
_ => return None,
}
@ -340,7 +356,13 @@ impl ServoRenderer {
scroll_tree_node: info.scroll_tree_node,
})
})
.collect()
.collect();
if epoch_mismatch {
return Err(HitTestError::EpochMismatch);
}
Ok(results)
}
pub(crate) fn send_transaction(&mut self, transaction: Transaction) {
@ -386,6 +408,10 @@ impl IOCompositor {
);
let compositor = IOCompositor {
global: Rc::new(RefCell::new(ServoRenderer {
refresh_driver: RefreshDriver::new(
state.constellation_chan.clone(),
state.event_loop_waker,
),
shutdown_state: state.shutdown_state,
pipeline_to_webview_map: Default::default(),
compositor_receiver: state.receiver,
@ -406,7 +432,6 @@ impl IOCompositor {
webrender: Some(state.webrender),
rendering_context: state.rendering_context,
pending_frames: 0,
last_animation_tick: Instant::now(),
_mem_profiler_registration: registration,
};
@ -450,7 +475,16 @@ impl IOCompositor {
}
pub fn needs_repaint(&self) -> bool {
!self.needs_repaint.get().is_empty()
let repaint_reason = self.needs_repaint.get();
if repaint_reason.is_empty() {
return false;
}
!self
.global
.borrow()
.refresh_driver
.wait_to_paint(repaint_reason)
}
pub fn finish_shutting_down(&mut self) {
@ -519,15 +553,17 @@ impl IOCompositor {
pipeline_id,
animation_state,
) => {
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
if webview_renderer
.change_pipeline_running_animations_state(pipeline_id, animation_state) &&
webview_renderer.animating()
{
// These operations should eventually happen per-WebView, but they are
// global now as rendering is still global to all WebViews.
self.process_animations(true);
}
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
return;
};
if webview_renderer
.change_pipeline_running_animations_state(pipeline_id, animation_state)
{
self.global
.borrow()
.refresh_driver
.notify_animation_state_changed(webview_renderer);
}
},
@ -572,14 +608,15 @@ impl IOCompositor {
},
CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => {
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
if webview_renderer.set_throttled(pipeline_id, throttled) &&
webview_renderer.animating()
{
// These operations should eventually happen per-WebView, but they are
// global now as rendering is still global to all WebViews.
self.process_animations(true);
}
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
return;
};
if webview_renderer.set_throttled(pipeline_id, throttled) {
self.global
.borrow()
.refresh_driver
.notify_animation_state_changed(webview_renderer);
}
},
@ -604,7 +641,7 @@ impl IOCompositor {
.global
.borrow()
.hit_test_at_point(point, details_for_pipeline);
if let Some(result) = result {
if let Ok(result) = result {
self.global.borrow_mut().update_cursor(point, &result);
}
}
@ -634,7 +671,7 @@ impl IOCompositor {
};
let dppx = webview_renderer.device_pixels_per_page_pixel();
let point = dppx.transform_point(Point2D::new(x, y));
webview_renderer.dispatch_input_event(
webview_renderer.dispatch_point_input_event(
InputEvent::MouseButton(MouseButtonEvent::new(action, button, point))
.with_webdriver_message_id(Some(message_id)),
);
@ -647,7 +684,7 @@ impl IOCompositor {
};
let dppx = webview_renderer.device_pixels_per_page_pixel();
let point = dppx.transform_point(Point2D::new(x, y));
webview_renderer.dispatch_input_event(
webview_renderer.dispatch_point_input_event(
InputEvent::MouseMove(MouseMoveEvent::new(point))
.with_webdriver_message_id(Some(message_id)),
);
@ -669,7 +706,7 @@ impl IOCompositor {
let scroll_delta =
dppx.transform_vector(Vector2D::new(delta_x as f32, delta_y as f32));
webview_renderer
.dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point }));
.dispatch_point_input_event(InputEvent::Wheel(WheelEvent { delta, point }));
webview_renderer.on_webdriver_wheel_action(scroll_delta, point);
},
@ -777,6 +814,8 @@ impl IOCompositor {
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
return warn!("Could not find WebView for incoming display list");
};
// WebRender is not ready until we receive "NewWebRenderFrameReady"
webview_renderer.webrender_frame_ready.set(false);
let pipeline_id = display_list_info.pipeline_id;
let details = webview_renderer.ensure_pipeline_details(pipeline_id.into());
@ -826,7 +865,8 @@ impl IOCompositor {
flags,
pipeline,
details_for_pipeline,
);
)
.unwrap_or_default();
let _ = sender.send(result);
},
@ -1271,39 +1311,6 @@ impl IOCompositor {
self.set_needs_repaint(RepaintReason::Resize);
}
/// If there are any animations running, dispatches appropriate messages to the constellation.
fn process_animations(&mut self, force: bool) {
// When running animations in order to dump a screenshot (not after a full composite), don't send
// animation ticks faster than about 60Hz.
//
// TODO: This should be based on the refresh rate of the screen and also apply to all
// animation ticks, not just ones sent while waiting to dump screenshots. This requires
// something like a refresh driver concept though.
if !force && (Instant::now() - self.last_animation_tick) < Duration::from_millis(16) {
return;
}
self.last_animation_tick = Instant::now();
let animating_webviews: Vec<_> = self
.webview_renderers
.iter()
.filter_map(|webview_renderer| {
if webview_renderer.animating() {
Some(webview_renderer.id)
} else {
None
}
})
.collect();
if !animating_webviews.is_empty() {
if let Err(error) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::TickAnimation(animating_webviews),
) {
warn!("Sending tick to constellation failed ({error:?}).");
}
}
}
pub fn on_zoom_reset_window_event(&mut self, webview_id: WebViewId) {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return;
@ -1410,6 +1417,11 @@ impl IOCompositor {
/// Render the WebRender scene to the active `RenderingContext`. If successful, trigger
/// the next round of animations.
pub fn render(&mut self) -> bool {
self.global
.borrow()
.refresh_driver
.notify_will_paint(self.webview_renderers.iter());
if let Err(error) = self.render_inner() {
warn!("Unable to render: {error:?}");
return false;
@ -1419,9 +1431,6 @@ impl IOCompositor {
// the scene no longer needs to be repainted.
self.needs_repaint.set(RepaintReason::empty());
// Queue up any subsequent paints for animations.
self.process_animations(true);
true
}
@ -1431,7 +1440,7 @@ impl IOCompositor {
&mut self,
webview_id: WebViewId,
page_rect: Option<Rect<f32, CSSPixel>>,
) -> Result<Option<Image>, UnableToComposite> {
) -> Result<Option<RasterImage>, UnableToComposite> {
self.render_inner()?;
let size = self.rendering_context.size2d().to_i32();
@ -1458,9 +1467,11 @@ impl IOCompositor {
Ok(self
.rendering_context
.read_to_image(rect)
.map(|image| Image {
width: image.width(),
height: image.height(),
.map(|image| RasterImage {
metadata: ImageMetadata {
width: image.width(),
height: image.height(),
},
format: PixelFormat::RGBA8,
frames: vec![ImageFrame {
delay: None,
@ -1490,10 +1501,8 @@ impl IOCompositor {
if opts::get().wait_for_stable_image {
// The current image may be ready to output. However, if there are animations active,
// tick those instead and continue waiting for the image output to be stable AND
// all active animations to complete.
// continue waiting for the image output to be stable AND all active animations to complete.
if self.animations_or_animation_callbacks_running() {
self.process_animations(false);
return Err(UnableToComposite::NotReadyToPaintImage(
NotReadyToPaint::AnimationsActive,
));
@ -1645,11 +1654,20 @@ impl IOCompositor {
},
CompositorMsg::NewWebRenderFrameReady(..) => {
found_recomposite_msg = true;
compositor_messages.push(msg)
compositor_messages.push(msg);
},
_ => compositor_messages.push(msg),
}
}
if found_recomposite_msg {
// Process all pending events
self.webview_renderers.iter().for_each(|webview| {
webview.dispatch_pending_point_input_events();
webview.webrender_frame_ready.set(true);
});
}
for msg in compositor_messages {
self.handle_browser_message(msg);

View file

@ -11,7 +11,7 @@ use compositing_traits::rendering_context::RenderingContext;
use compositing_traits::{CompositorMsg, CompositorProxy};
use constellation_traits::EmbedderToConstellationMessage;
use crossbeam_channel::{Receiver, Sender};
use embedder_traits::ShutdownState;
use embedder_traits::{EventLoopWaker, ShutdownState};
use profile_traits::{mem, time};
use webrender::RenderApi;
use webrender_api::DocumentId;
@ -22,9 +22,10 @@ pub use crate::compositor::{IOCompositor, WebRenderDebugOption};
mod tracing;
mod compositor;
mod refresh_driver;
mod touch;
pub mod webview_manager;
pub mod webview_renderer;
mod webview_manager;
mod webview_renderer;
/// Data used to construct a compositor.
pub struct InitialCompositorState {
@ -49,4 +50,7 @@ pub struct InitialCompositorState {
pub webrender_gl: Rc<dyn gleam::gl::Gl>,
#[cfg(feature = "webxr")]
pub webxr_main_thread: webxr::MainThreadRegistry,
/// An [`EventLoopWaker`] used in order to wake up the embedder when it is
/// time to paint.
pub event_loop_waker: Box<dyn EventLoopWaker>,
}

View file

@ -0,0 +1,234 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::collections::hash_map::Values;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use base::id::WebViewId;
use constellation_traits::EmbedderToConstellationMessage;
use crossbeam_channel::{Sender, select};
use embedder_traits::EventLoopWaker;
use log::warn;
use timers::{BoxedTimerCallback, TimerEventId, TimerEventRequest, TimerScheduler, TimerSource};
use crate::compositor::RepaintReason;
use crate::webview_renderer::WebViewRenderer;
const FRAME_DURATION: Duration = Duration::from_millis(1000 / 120);
/// The [`RefreshDriver`] is responsible for controlling updates to aall `WebView`s
/// onscreen presentation. Currently, it only manages controlling animation update
/// requests.
///
/// The implementation is very basic at the moment, only requesting new animation
/// frames at a constant time after a repaint.
pub(crate) struct RefreshDriver {
/// The channel on which messages can be sent to the Constellation.
pub(crate) constellation_sender: Sender<EmbedderToConstellationMessage>,
/// Whether or not we are currently animating via a timer.
pub(crate) animating: Cell<bool>,
/// Whether or not we are waiting for our frame timeout to trigger
pub(crate) waiting_for_frame_timeout: Arc<AtomicBool>,
/// A [`TimerThread`] which is used to schedule frame timeouts in the future.
timer_thread: TimerThread,
/// An [`EventLoopWaker`] to be used to wake up the embedder when it is
/// time to paint a frame.
event_loop_waker: Box<dyn EventLoopWaker>,
}
impl RefreshDriver {
pub(crate) fn new(
constellation_sender: Sender<EmbedderToConstellationMessage>,
event_loop_waker: Box<dyn EventLoopWaker>,
) -> Self {
Self {
constellation_sender,
animating: Default::default(),
waiting_for_frame_timeout: Default::default(),
timer_thread: Default::default(),
event_loop_waker,
}
}
fn timer_callback(&self) -> BoxedTimerCallback {
let waiting_for_frame_timeout = self.waiting_for_frame_timeout.clone();
let event_loop_waker = self.event_loop_waker.clone_box();
Box::new(move |_| {
waiting_for_frame_timeout.store(false, Ordering::Relaxed);
event_loop_waker.wake();
})
}
/// Notify the [`RefreshDriver`] that a paint is about to happen. This will trigger
/// new animation frames for all active `WebView`s and schedule a new frame deadline.
pub(crate) fn notify_will_paint(
&self,
webview_renderers: Values<'_, WebViewId, WebViewRenderer>,
) {
// If we are still waiting for the frame to timeout this paint was caused for some
// non-animation related reason and we should wait until the frame timeout to trigger
// the next one.
if self.waiting_for_frame_timeout.load(Ordering::Relaxed) {
return;
}
// If any WebViews are animating ask them to paint again for another animation tick.
let animating_webviews: Vec<_> = webview_renderers
.filter_map(|webview_renderer| {
if webview_renderer.animating() {
Some(webview_renderer.id)
} else {
None
}
})
.collect();
// If nothing is animating any longer, update our state and exit early without requesting
// any noew frames nor triggering a new animation deadline.
if animating_webviews.is_empty() {
self.animating.set(false);
return;
}
if let Err(error) =
self.constellation_sender
.send(EmbedderToConstellationMessage::TickAnimation(
animating_webviews,
))
{
warn!("Sending tick to constellation failed ({error:?}).");
}
// Queue the next frame deadline.
self.animating.set(true);
self.waiting_for_frame_timeout
.store(true, Ordering::Relaxed);
self.timer_thread
.queue_timer(FRAME_DURATION, self.timer_callback());
}
/// Notify the [`RefreshDriver`] that the animation state of a particular `WebView`
/// via its associated [`WebViewRenderer`] has changed. In the case that a `WebView`
/// has started animating, the [`RefreshDriver`] will request a new frame from it
/// immediately, but only render that frame at the next frame deadline.
pub(crate) fn notify_animation_state_changed(&self, webview_renderer: &WebViewRenderer) {
if !webview_renderer.animating() {
// If no other WebView is animating we will officially stop animated once the
// next frame has been painted.
return;
}
if let Err(error) =
self.constellation_sender
.send(EmbedderToConstellationMessage::TickAnimation(vec![
webview_renderer.id,
]))
{
warn!("Sending tick to constellation failed ({error:?}).");
}
if self.animating.get() {
return;
}
self.animating.set(true);
self.waiting_for_frame_timeout
.store(true, Ordering::Relaxed);
self.timer_thread
.queue_timer(FRAME_DURATION, self.timer_callback());
}
/// Whether or not the renderer should trigger a message to the embedder to request a
/// repaint. This might be false if: we are animating and the repaint reason is just
/// for a new frame. In that case, the renderer should wait until the frame timeout to
/// ask the embedder to repaint.
pub(crate) fn wait_to_paint(&self, repaint_reason: RepaintReason) -> bool {
if !self.animating.get() || repaint_reason != RepaintReason::NewWebRenderFrame {
return false;
}
self.waiting_for_frame_timeout.load(Ordering::Relaxed)
}
}
enum TimerThreadMessage {
Request(TimerEventRequest),
Quit,
}
/// A thread that manages a [`TimerScheduler`] running in the background of the
/// [`RefreshDriver`]. This is necessary because we need a reliable way of waking up the
/// embedder's main thread, which may just be sleeping until the `EventLoopWaker` asks it
/// to wake up.
///
/// It would be nice to integrate this somehow into the embedder thread, but it would
/// require both some communication with the embedder and for all embedders to be well
/// behave respecting wakeup timeouts -- a bit too much to ask at the moment.
struct TimerThread {
sender: Sender<TimerThreadMessage>,
join_handle: Option<JoinHandle<()>>,
}
impl Drop for TimerThread {
fn drop(&mut self) {
let _ = self.sender.send(TimerThreadMessage::Quit);
if let Some(join_handle) = self.join_handle.take() {
let _ = join_handle.join();
}
}
}
impl Default for TimerThread {
fn default() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded::<TimerThreadMessage>();
let join_handle = thread::Builder::new()
.name(String::from("CompositorTimerThread"))
.spawn(move || {
let mut scheduler = TimerScheduler::default();
loop {
select! {
recv(receiver) -> message => {
match message {
Ok(TimerThreadMessage::Request(request)) => {
scheduler.schedule_timer(request);
},
_ => return,
}
},
recv(scheduler.wait_channel()) -> _message => {
scheduler.dispatch_completed_timers();
},
};
}
})
.expect("Could not create RefreshDriver timer thread.");
Self {
sender,
join_handle: Some(join_handle),
}
}
}
impl TimerThread {
fn queue_timer(&self, duration: Duration, callback: BoxedTimerCallback) {
let _ = self
.sender
.send(TimerThreadMessage::Request(TimerEventRequest {
callback,
source: TimerSource::FromWorker,
id: TimerEventId(0),
duration,
}));
}
}

View file

@ -2,9 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::RefCell;
use std::collections::HashMap;
use std::cell::{Cell, RefCell};
use std::collections::hash_map::Keys;
use std::collections::{HashMap, VecDeque};
use std::rc::Rc;
use base::id::{PipelineId, WebViewId};
@ -25,7 +25,7 @@ use webrender_api::units::{
};
use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation};
use crate::compositor::{PipelineDetails, ServoRenderer};
use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer};
use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
// Default viewport constraints
@ -98,6 +98,10 @@ pub(crate) struct WebViewRenderer {
/// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with
/// active animations or animation frame callbacks.
animating: bool,
/// Pending input events queue. Priavte and only this thread pushes events to it.
pending_point_input_events: RefCell<VecDeque<InputEvent>>,
/// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages.
pub webrender_frame_ready: Cell<bool>,
}
impl Drop for WebViewRenderer {
@ -132,6 +136,8 @@ impl WebViewRenderer {
max_viewport_zoom: None,
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
animating: false,
pending_point_input_events: Default::default(),
webrender_frame_ready: Cell::default(),
}
}
@ -309,29 +315,89 @@ impl WebViewRenderer {
}
}
pub(crate) fn dispatch_input_event(&mut self, event: InputEvent) {
pub(crate) fn dispatch_point_input_event(&mut self, mut event: InputEvent) -> bool {
// Events that do not need to do hit testing are sent directly to the
// constellation to filter down.
let Some(point) = event.point() else {
return;
return false;
};
// Delay the event if the epoch is not synchronized yet (new frame is not ready),
// or hit test result would fail and the event is rejected anyway.
if !self.webrender_frame_ready.get() || !self.pending_point_input_events.borrow().is_empty()
{
self.pending_point_input_events
.borrow_mut()
.push_back(event);
return false;
}
// If we can't find a pipeline to send this event to, we cannot continue.
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
let Some(result) = self
let result = match self
.global
.borrow()
.hit_test_at_point(point, get_pipeline_details)
else {
return;
{
Ok(hit_test_results) => hit_test_results,
Err(HitTestError::EpochMismatch) => {
self.pending_point_input_events
.borrow_mut()
.push_back(event);
return false;
},
_ => {
return false;
},
};
self.global.borrow_mut().update_cursor(point, &result);
match event {
InputEvent::Touch(ref mut touch_event) => {
touch_event.init_sequence_id(self.touch_handler.current_sequence_id);
},
InputEvent::MouseButton(_) | InputEvent::MouseMove(_) | InputEvent::Wheel(_) => {
self.global.borrow_mut().update_cursor(point, &result);
},
_ => unreachable!("Unexpected input event type: {event:?}"),
}
if let Err(error) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)),
) {
warn!("Sending event to constellation failed ({error:?}).");
false
} else {
true
}
}
pub(crate) fn dispatch_pending_point_input_events(&self) {
while let Some(event) = self.pending_point_input_events.borrow_mut().pop_front() {
// Events that do not need to do hit testing are sent directly to the
// constellation to filter down.
let Some(point) = event.point() else {
continue;
};
// If we can't find a pipeline to send this event to, we cannot continue.
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
let Ok(result) = self
.global
.borrow()
.hit_test_at_point(point, get_pipeline_details)
else {
// Don't need to process pending input events in this frame any more.
// TODO: Add multiple retry later if needed.
return;
};
self.global.borrow_mut().update_cursor(point, &result);
if let Err(error) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)),
) {
warn!("Sending event to constellation failed ({error:?}).");
}
}
}
@ -401,29 +467,11 @@ impl WebViewRenderer {
}
}
self.dispatch_input_event(event);
self.dispatch_point_input_event(event);
}
fn send_touch_event(&self, mut event: TouchEvent) -> bool {
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
let Some(result) = self
.global
.borrow()
.hit_test_at_point(event.point, get_pipeline_details)
else {
return false;
};
event.init_sequence_id(self.touch_handler.current_sequence_id);
let event = InputEvent::Touch(event);
if let Err(e) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)),
) {
warn!("Sending event to constellation failed ({:?}).", e);
false
} else {
true
}
fn send_touch_event(&mut self, event: TouchEvent) -> bool {
self.dispatch_point_input_event(InputEvent::Touch(event))
}
pub(crate) fn on_touch_event(&mut self, event: TouchEvent) {
@ -687,13 +735,13 @@ impl WebViewRenderer {
/// <http://w3c.github.io/touch-events/#mouse-events>
fn simulate_mouse_click(&mut self, point: DevicePoint) {
let button = MouseButton::Left;
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
self.dispatch_point_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
self.dispatch_point_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Down,
button,
point,
)));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
self.dispatch_point_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Up,
button,
point,
@ -858,7 +906,8 @@ impl WebViewRenderer {
HitTestFlags::FIND_ALL,
None,
get_pipeline_details,
);
)
.unwrap_or_default();
// Iterate through all hit test results, processing only the first node of each pipeline.
// This is needed to propagate the scroll events from a pipeline representing an iframe to

View file

@ -116,10 +116,6 @@ pub struct Preferences {
// https://testutils.spec.whatwg.org#availability
pub dom_testutils_enabled: bool,
pub dom_trusted_types_enabled: bool,
/// Enable the [URLPattern] API.
///
/// [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
pub dom_urlpattern_enabled: bool,
pub dom_xpath_enabled: bool,
/// Enable WebGL2 APIs.
pub dom_webgl2_enabled: bool,
@ -292,7 +288,6 @@ impl Preferences {
dom_testperf_enabled: false,
dom_testutils_enabled: false,
dom_trusted_types_enabled: false,
dom_urlpattern_enabled: false,
dom_webgl2_enabled: false,
dom_webgpu_enabled: false,
dom_webgpu_wgpu_backend: String::new(),

View file

@ -148,6 +148,7 @@ use net_traits::pub_domains::reg_host;
use net_traits::request::Referrer;
use net_traits::storage_thread::{StorageThreadMsg, StorageType};
use net_traits::{self, IpcSend, ReferrerPolicy, ResourceThreads};
use profile_traits::mem::ProfilerMsg;
use profile_traits::{mem, time};
use script_layout_interface::{LayoutFactory, ScriptThreadFactory};
use script_traits::{
@ -172,6 +173,7 @@ use crate::browsingcontext::{
AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator,
NewBrowsingContextInfo,
};
use crate::constellation_webview::ConstellationWebView;
use crate::event_loop::EventLoop;
use crate::pipeline::{InitialPipelineState, Pipeline};
use crate::process_manager::ProcessManager;
@ -229,18 +231,6 @@ struct WebrenderWGPU {
wgpu_image_map: WGPUImageMap,
}
/// Servo supports multiple top-level browsing contexts or “webviews”, so `Constellation` needs to
/// store webview-specific data for bookkeeping.
struct WebView {
/// The currently focused browsing context in this webview for key events.
/// The focused pipeline is the current entry of the focused browsing
/// context.
focused_browsing_context_id: BrowsingContextId,
/// The joint session history for this webview.
session_history: JointSessionHistory,
}
/// A browsing context group.
///
/// <https://html.spec.whatwg.org/multipage/#browsing-context-group>
@ -324,7 +314,7 @@ pub struct Constellation<STF, SWF> {
compositor_proxy: CompositorProxy,
/// Bookkeeping data for all webviews in the constellation.
webviews: WebViewManager<WebView>,
webviews: WebViewManager<ConstellationWebView>,
/// Channels for the constellation to send messages to the public
/// resource-related threads. There are two groups of resource threads: one
@ -895,6 +885,16 @@ where
if self.shutting_down {
return;
}
let Some(theme) = self
.webviews
.get(webview_id)
.map(ConstellationWebView::theme)
else {
warn!("Tried to create Pipeline for uknown WebViewId: {webview_id:?}");
return;
};
debug!(
"{}: Creating new pipeline in {}",
pipeline_id, browsing_context_id
@ -973,6 +973,7 @@ where
time_profiler_chan: self.time_profiler_chan.clone(),
mem_profiler_chan: self.mem_profiler_chan.clone(),
viewport_details: initial_viewport_details,
theme,
event_loop,
load_data,
prev_throttled: throttled,
@ -1436,8 +1437,8 @@ where
size_type,
);
},
EmbedderToConstellationMessage::ThemeChange(theme) => {
self.handle_theme_change(theme);
EmbedderToConstellationMessage::ThemeChange(webview_id, theme) => {
self.handle_theme_change(webview_id, theme);
},
EmbedderToConstellationMessage::TickAnimation(webview_ids) => {
self.handle_tick_animation(webview_ids)
@ -1488,6 +1489,9 @@ where
) => {
self.handle_evaluate_javascript(webview_id, evaluation_id, script);
},
EmbedderToConstellationMessage::CreateMemoryReport(sender) => {
self.mem_profiler_chan.send(ProfilerMsg::Report(sender));
},
}
}
@ -2758,7 +2762,11 @@ where
}
debug!("Exiting Canvas Paint thread.");
if let Err(e) = self.canvas_sender.send(ConstellationCanvasMsg::Exit) {
let (canvas_exit_sender, canvas_exit_receiver) = unbounded();
if let Err(e) = self
.canvas_sender
.send(ConstellationCanvasMsg::Exit(canvas_exit_sender))
{
warn!("Exit Canvas Paint thread failed ({})", e);
}
@ -2800,6 +2808,10 @@ where
debug!("Exiting GLPlayer thread.");
WindowGLContext::get().exit();
// Wait for the canvas thread to exit before shutting down the font service, as
// canvas might still be using the system font service before shutting down.
let _ = canvas_exit_receiver.recv();
debug!("Exiting the system font service thread.");
self.system_font_service.exit();
@ -3142,13 +3154,8 @@ where
// Register this new top-level browsing context id as a webview and set
// its focused browsing context to be itself.
self.webviews.add(
webview_id,
WebView {
focused_browsing_context_id: browsing_context_id,
session_history: JointSessionHistory::new(),
},
);
self.webviews
.add(webview_id, ConstellationWebView::new(browsing_context_id));
// https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context-group
let mut new_bc_group: BrowsingContextGroup = Default::default();
@ -3554,10 +3561,7 @@ where
self.pipelines.insert(new_pipeline_id, pipeline);
self.webviews.add(
new_webview_id,
WebView {
focused_browsing_context_id: new_browsing_context_id,
session_history: JointSessionHistory::new(),
},
ConstellationWebView::new(new_browsing_context_id),
);
// https://html.spec.whatwg.org/multipage/#bcg-append
@ -5623,18 +5627,31 @@ where
}
}
/// Handle theme change events from the embedder and forward them to the script thread
/// Handle theme change events from the embedder and forward them to all appropriate `ScriptThread`s.
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_theme_change(&mut self, theme: Theme) {
fn handle_theme_change(&mut self, webview_id: WebViewId, theme: Theme) {
let Some(webview) = self.webviews.get_mut(webview_id) else {
warn!("Received theme change request for uknown WebViewId: {webview_id:?}");
return;
};
if !webview.set_theme(theme) {
return;
}
for pipeline in self.pipelines.values() {
let msg = ScriptThreadMessage::ThemeChange(pipeline.id, theme);
if let Err(err) = pipeline.event_loop.send(msg) {
if pipeline.webview_id != webview_id {
continue;
}
if let Err(error) = pipeline
.event_loop
.send(ScriptThreadMessage::ThemeChange(pipeline.id, theme))
{
warn!(
"{}: Failed to send theme change event to pipeline ({:?}).",
pipeline.id, err
"{}: Failed to send theme change event to pipeline ({error:?}).",
pipeline.id,
);
}
}

View file

@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use base::id::BrowsingContextId;
use embedder_traits::Theme;
use crate::session_history::JointSessionHistory;
/// The `Constellation`'s view of a `WebView` in the embedding layer. This tracks all of the
/// `Constellation` state for this `WebView`.
pub(crate) struct ConstellationWebView {
/// The currently focused browsing context in this webview for key events.
/// The focused pipeline is the current entry of the focused browsing
/// context.
pub focused_browsing_context_id: BrowsingContextId,
/// The joint session history for this webview.
pub session_history: JointSessionHistory,
/// The [`Theme`] that this [`ConstellationWebView`] uses. This is communicated to all
/// `ScriptThread`s so that they know how to render the contents of a particular `WebView.
theme: Theme,
}
impl ConstellationWebView {
pub(crate) fn new(focused_browsing_context_id: BrowsingContextId) -> Self {
Self {
focused_browsing_context_id,
session_history: JointSessionHistory::new(),
theme: Theme::Light,
}
}
/// Set the [`Theme`] on this [`ConstellationWebView`] returning true if the theme changed.
pub(crate) fn set_theme(&mut self, new_theme: Theme) -> bool {
let old_theme = std::mem::replace(&mut self.theme, new_theme);
old_theme != self.theme
}
/// Get the [`Theme`] of this [`ConstellationWebView`].
pub(crate) fn theme(&self) -> Theme {
self.theme
}
}

View file

@ -9,6 +9,7 @@ mod tracing;
mod browsingcontext;
mod constellation;
mod constellation_webview;
mod event_loop;
mod logging;
mod pipeline;

View file

@ -25,7 +25,7 @@ use constellation_traits::{LoadData, SWManagerMsg, ScriptToConstellationChan};
use crossbeam_channel::{Sender, unbounded};
use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{AnimationState, FocusSequenceNumber, ViewportDetails};
use embedder_traits::{AnimationState, FocusSequenceNumber, Theme, ViewportDetails};
use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender};
use ipc_channel::Error;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@ -61,7 +61,7 @@ pub struct Pipeline {
/// The ID of the browsing context that contains this Pipeline.
pub browsing_context_id: BrowsingContextId,
/// The ID of the top-level browsing context that contains this Pipeline.
/// The [`WebViewId`] of the `WebView` that contains this Pipeline.
pub webview_id: WebViewId,
pub opener: Option<BrowsingContextId>,
@ -170,6 +170,9 @@ pub struct InitialPipelineState {
/// The initial [`ViewportDetails`] to use when starting this new [`Pipeline`].
pub viewport_details: ViewportDetails,
/// The initial [`Theme`] to use when starting this new [`Pipeline`].
pub theme: Theme,
/// The ID of the pipeline namespace for this script thread.
pub pipeline_namespace_id: PipelineNamespaceId,
@ -224,6 +227,7 @@ impl Pipeline {
opener: state.opener,
load_data: state.load_data.clone(),
viewport_details: state.viewport_details,
theme: state.theme,
};
if let Err(e) = script_chan.send(ScriptThreadMessage::AttachLayout(new_layout_info))
@ -280,6 +284,7 @@ impl Pipeline {
time_profiler_chan: state.time_profiler_chan,
mem_profiler_chan: state.mem_profiler_chan,
viewport_details: state.viewport_details,
theme: state.theme,
script_chan: script_chan.clone(),
load_data: state.load_data.clone(),
script_port,
@ -494,6 +499,7 @@ pub struct UnprivilegedPipelineContent {
time_profiler_chan: time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
viewport_details: ViewportDetails,
theme: Theme,
script_chan: IpcSender<ScriptThreadMessage>,
load_data: LoadData,
script_port: IpcReceiver<ScriptThreadMessage>,
@ -544,6 +550,7 @@ impl UnprivilegedPipelineContent {
memory_profiler_sender: self.mem_profiler_chan.clone(),
devtools_server_sender: self.devtools_ipc_sender,
viewport_details: self.viewport_details,
theme: self.theme,
pipeline_namespace_id: self.pipeline_namespace_id,
content_process_shutdown_sender: content_process_shutdown_chan,
webgl_chan: self.webgl_chan,

View file

@ -78,6 +78,7 @@ mod from_compositor {
Self::SetScrollStates(..) => target!("SetScrollStates"),
Self::PaintMetric(..) => target!("PaintMetric"),
Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"),
Self::CreateMemoryReport(..) => target!("CreateMemoryReport"),
}
}
}

View file

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use serde::Serialize;
use crate::EmptyReplyMsg;
use crate::actor::{Actor, ActorMessageStatus};
use crate::protocol::JsonPacketStream;
#[derive(Serialize)]
pub struct BreakpointListActorMsg {
actor: String,
}
pub struct BreakpointListActor {
name: String,
}
impl Actor for BreakpointListActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &crate::actor::ActorRegistry,
msg_type: &str,
_msg: &serde_json::Map<String, serde_json::Value>,
stream: &mut std::net::TcpStream,
_stream_id: crate::StreamId,
) -> Result<crate::actor::ActorMessageStatus, ()> {
Ok(match msg_type {
"setBreakpoint" => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"setActiveEventBreakpoints" => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
}
impl BreakpointListActor {
pub fn new(name: String) -> Self {
Self { name }
}
}

View file

@ -81,6 +81,13 @@ struct BrowsingContextTraits {
watchpoints: bool,
}
#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
enum TargetType {
Frame,
// Other target types not implemented yet.
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BrowsingContextActorMsg {
@ -104,6 +111,7 @@ pub struct BrowsingContextActorMsg {
reflow_actor: String,
style_sheets_actor: String,
thread_actor: String,
target_type: TargetType,
// Part of the official protocol, but not yet implemented.
// animations_actor: String,
// changes_actor: String,
@ -302,6 +310,7 @@ impl BrowsingContextActor {
reflow_actor: self.reflow.clone(),
style_sheets_actor: self.style_sheets.clone(),
thread_actor: self.thread.clone(),
target_type: TargetType::Frame,
}
}

View file

@ -19,6 +19,7 @@ use serde::Serialize;
use serde_json::{Map, Value};
use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
use super::breakpoint::BreakpointListActor;
use super::thread::ThreadActor;
use super::worker::WorkerMsg;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
@ -362,10 +363,14 @@ impl Actor for WatcherActor {
ActorMessageStatus::Processed
},
"getBreakpointListActor" => {
let breakpoint_list_name = registry.new_name("breakpoint-list");
let breakpoint_list = BreakpointListActor::new(breakpoint_list_name.clone());
registry.register_later(Box::new(breakpoint_list));
let _ = stream.write_json_packet(&GetBreakpointListActorReply {
from: self.name(),
breakpoint_list: GetBreakpointListActorReplyInner {
actor: registry.new_name("breakpoint-list"),
actor: breakpoint_list_name,
},
});
ActorMessageStatus::Processed

View file

@ -53,6 +53,7 @@ use crate::protocol::JsonPacketStream;
mod actor;
/// <https://searchfox.org/mozilla-central/source/devtools/server/actors>
mod actors {
pub mod breakpoint;
pub mod browsing_context;
pub mod console;
pub mod device;

View file

@ -10,17 +10,21 @@ use fnv::FnvHashMap;
use fonts::FontContext;
use fxhash::FxHashMap;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
UsePlaceholder,
};
use parking_lot::{Mutex, RwLock};
use pixels::Image as PixelImage;
use script_layout_interface::{IFrameSizes, ImageAnimationState, PendingImage, PendingImageState};
use pixels::RasterImage;
use script_layout_interface::{
IFrameSizes, ImageAnimationState, PendingImage, PendingImageState, PendingRasterizationImage,
};
use servo_url::{ImmutableOrigin, ServoUrl};
use style::context::SharedStyleContext;
use style::dom::OpaqueNode;
use style::values::computed::image::{Gradient, Image};
use webrender_api::units::{DeviceIntSize, DeviceSize};
use crate::display_list::WebRenderImageInfo;
pub(crate) type CachedImageOrError = Result<CachedImage, ResolveImageError>;
pub struct LayoutContext<'a> {
pub id: PipelineId,
@ -39,11 +43,17 @@ pub struct LayoutContext<'a> {
/// A list of in-progress image loads to be shared with the script thread.
pub pending_images: Mutex<Vec<PendingImage>>,
/// A list of fully loaded vector images that need to be rasterized to a specific
/// size determined by layout. This will be shared with the script thread.
pub pending_rasterization_images: Mutex<Vec<PendingRasterizationImage>>,
/// A collection of `<iframe>` sizes to send back to script.
pub iframe_sizes: Mutex<IFrameSizes>,
pub webrender_image_cache:
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
// A cache that maps image resources used in CSS (e.g as the `url()` value
// for `background-image` or `content` property) to the final resolved image data.
pub resolved_images_cache:
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
@ -53,18 +63,24 @@ pub struct LayoutContext<'a> {
pub enum ResolvedImage<'a> {
Gradient(&'a Gradient),
Image(WebRenderImageInfo),
// The size is tracked explicitly as image-set images can specify their
// natural resolution which affects the final size for raster images.
Image {
image: CachedImage,
size: DeviceSize,
},
}
impl Drop for LayoutContext<'_> {
fn drop(&mut self) {
if !std::thread::panicking() {
assert!(self.pending_images.lock().is_empty());
assert!(self.pending_rasterization_images.lock().is_empty());
}
}
}
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub enum ResolveImageError {
LoadError,
ImagePending,
@ -78,18 +94,24 @@ pub enum ResolveImageError {
None,
}
pub(crate) enum LayoutImageCacheResult {
Pending,
DataAvailable(ImageOrMetadataAvailable),
LoadError,
}
impl LayoutContext<'_> {
#[inline(always)]
pub fn shared_context(&self) -> &SharedStyleContext {
&self.style_context
}
pub fn get_or_request_image_or_meta(
pub(crate) fn get_or_request_image_or_meta(
&self,
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Result<ImageOrMetadataAvailable, ResolveImageError> {
) -> LayoutImageCacheResult {
// Check for available image or start tracking.
let cache_result = self.image_cache.get_cached_image_status(
url.clone(),
@ -99,7 +121,9 @@ impl LayoutContext<'_> {
);
match cache_result {
ImageCacheResult::Available(img_or_meta) => Ok(img_or_meta),
ImageCacheResult::Available(img_or_meta) => {
LayoutImageCacheResult::DataAvailable(img_or_meta)
},
// Image has been requested, is still pending. Return no image for this paint loop.
// When the image loads it will trigger a reflow and/or repaint.
ImageCacheResult::Pending(id) => {
@ -110,7 +134,7 @@ impl LayoutContext<'_> {
origin: self.origin.clone(),
};
self.pending_images.lock().push(image);
Result::Err(ResolveImageError::ImagePending)
LayoutImageCacheResult::Pending
},
// Not yet requested - request image or metadata from the cache
ImageCacheResult::ReadyForRequest(id) => {
@ -121,14 +145,14 @@ impl LayoutContext<'_> {
origin: self.origin.clone(),
};
self.pending_images.lock().push(image);
Result::Err(ResolveImageError::ImageRequested)
LayoutImageCacheResult::Pending
},
// Image failed to load, so just return nothing
ImageCacheResult::LoadError => Result::Err(ResolveImageError::LoadError),
// Image failed to load, so just return the same error.
ImageCacheResult::LoadError => LayoutImageCacheResult::LoadError,
}
}
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<PixelImage>) {
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<RasterImage>) {
let mut store = self.node_image_animation_map.write();
// 1. first check whether node previously being track for animated image.
@ -157,42 +181,66 @@ impl LayoutContext<'_> {
}
}
fn get_webrender_image_for_url(
fn get_cached_image_for_url(
&self,
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Result<WebRenderImageInfo, ResolveImageError> {
if let Some(existing_webrender_image) = self
.webrender_image_cache
) -> Result<CachedImage, ResolveImageError> {
if let Some(cached_image) = self
.resolved_images_cache
.read()
.get(&(url.clone(), use_placeholder))
{
return Ok(*existing_webrender_image);
return cached_image.clone();
}
let image_or_meta =
self.get_or_request_image_or_meta(node, url.clone(), use_placeholder)?;
match image_or_meta {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
self.handle_animated_image(node, image.clone());
let image_info = WebRenderImageInfo {
size: Size2D::new(image.width, image.height),
key: image.id,
};
if image_info.key.is_none() {
Ok(image_info)
} else {
let mut webrender_image_cache = self.webrender_image_cache.write();
webrender_image_cache.insert((url, use_placeholder), image_info);
Ok(image_info)
}
let result = self.get_or_request_image_or_meta(node, url.clone(), use_placeholder);
match result {
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
if let Some(image) = image.as_raster_image() {
self.handle_animated_image(node, image.clone());
}
let mut resolved_images_cache = self.resolved_images_cache.write();
resolved_images_cache.insert((url, use_placeholder), Ok(image.clone()));
Ok(image)
},
ImageOrMetadataAvailable::MetadataAvailable(..) => {
Result::Err(ResolveImageError::OnlyMetadata)
},
},
ImageOrMetadataAvailable::MetadataAvailable(..) => {
Result::Err(ResolveImageError::OnlyMetadata)
LayoutImageCacheResult::Pending => Result::Err(ResolveImageError::ImagePending),
LayoutImageCacheResult::LoadError => {
let error = Err(ResolveImageError::LoadError);
self.resolved_images_cache
.write()
.insert((url, use_placeholder), error.clone());
error
},
}
}
pub fn rasterize_vector_image(
&self,
image_id: PendingImageId,
size: DeviceIntSize,
node: OpaqueNode,
) -> Option<RasterImage> {
let result = self.image_cache.rasterize_vector_image(image_id, size);
if result.is_none() {
self.pending_rasterization_images
.lock()
.push(PendingRasterizationImage {
id: image_id,
node: node.into(),
size,
});
}
result
}
pub fn resolve_image<'a>(
&self,
node: Option<OpaqueNode>,
@ -215,12 +263,14 @@ impl LayoutContext<'_> {
// element and not just the node.
let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
let node = node.ok_or(ResolveImageError::MissingNode)?;
let webrender_info = self.get_webrender_image_for_url(
let image = self.get_cached_image_for_url(
node,
image_url.clone().into(),
UsePlaceholder::No,
)?;
Ok(ResolvedImage::Image(webrender_info))
let metadata = image.metadata();
let size = Size2D::new(metadata.width, metadata.height).to_f32();
Ok(ResolvedImage::Image { image, size })
},
Image::ImageSet(image_set) => {
image_set
@ -230,17 +280,32 @@ impl LayoutContext<'_> {
.and_then(|image| {
self.resolve_image(node, &image.image)
.map(|info| match info {
ResolvedImage::Image(mut image_info) => {
ResolvedImage::Image {
image: cached_image,
..
} => {
// From <https://drafts.csswg.org/css-images-4/#image-set-notation>:
// > A <resolution> (optional). This is used to help the UA decide
// > which <image-set-option> to choose. If the image reference is
// > for a raster image, it also specifies the images natural
// > resolution, overriding any other source of data that might
// > supply a natural resolution.
image_info.size = (image_info.size.to_f32() /
image.resolution.dppx())
.to_u32();
ResolvedImage::Image(image_info)
let image_metadata = cached_image.metadata();
let size = if cached_image.as_raster_image().is_some() {
let scale_factor = image.resolution.dppx();
Size2D::new(
image_metadata.width as f32 / scale_factor,
image_metadata.height as f32 / scale_factor,
)
} else {
Size2D::new(image_metadata.width, image_metadata.height)
.to_f32()
};
ResolvedImage::Image {
image: cached_image,
size,
}
},
_ => info,
})

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use app_units::Au;
use euclid::{Point2D, Size2D, Vector2D};
use euclid::{Size2D, Vector2D};
use style::computed_values::background_attachment::SingleComputedValue as BackgroundAttachment;
use style::computed_values::background_clip::single_value::T as Clip;
use style::computed_values::background_origin::single_value::T as Origin;
@ -15,7 +15,6 @@ use style::values::specified::background::{
};
use webrender_api::{self as wr, units};
use wr::ClipChainId;
use wr::units::LayoutSize;
use crate::replaced::NaturalSizes;
@ -66,8 +65,7 @@ impl<'a> BackgroundPainter<'a> {
if &BackgroundAttachment::Fixed ==
get_cyclic(&background.background_attachment.0, layer_index)
{
let viewport_size = builder.compositor_info.viewport_size;
return units::LayoutRect::from_origin_and_size(Point2D::origin(), viewport_size);
return builder.compositor_info.viewport_size.into();
}
match get_cyclic(&background.background_clip.0, layer_index) {
@ -132,6 +130,7 @@ impl<'a> BackgroundPainter<'a> {
pub(super) fn positioning_area(
&self,
fragment_builder: &'a super::BuilderForBoxFragment,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
) -> units::LayoutRect {
if let Some(positioning_area_override) = self.positioning_area_override {
@ -150,14 +149,7 @@ impl<'a> BackgroundPainter<'a> {
Origin::PaddingBox => *fragment_builder.padding_rect(),
Origin::BorderBox => fragment_builder.border_rect,
},
BackgroundAttachment::Fixed => {
// This isn't the viewport size because that rects larger than the viewport might be
// transformed down into areas smaller than the viewport.
units::LayoutRect::from_origin_and_size(
Point2D::origin(),
LayoutSize::new(f32::MAX, f32::MAX),
)
},
BackgroundAttachment::Fixed => builder.compositor_info.viewport_size.into(),
}
}
}
@ -170,7 +162,7 @@ pub(super) fn layout_layer(
natural_sizes: NaturalSizes,
) -> Option<BackgroundLayer> {
let painting_area = painter.painting_area(fragment_builder, builder, layer_index);
let positioning_area = painter.positioning_area(fragment_builder, layer_index);
let positioning_area = painter.positioning_area(fragment_builder, builder, layer_index);
let common = painter.common_properties(fragment_builder, builder, layer_index, painting_area);
// https://drafts.csswg.org/css-backgrounds/#background-size

View file

@ -14,6 +14,7 @@ use embedder_traits::Cursor;
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
use fonts::GlyphStore;
use gradient::WebRenderGradient;
use net_traits::image_cache::Image as CachedImage;
use range::Range as ServoRange;
use servo_arc::Arc as ServoArc;
use servo_config::opts::DebugOptions;
@ -37,7 +38,7 @@ use style::values::generics::rect::Rect;
use style::values::specified::text::TextDecorationLine;
use style::values::specified::ui::CursorKind;
use style_traits::CSSPixel;
use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
use webrender_api::{
self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode,
CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder,
@ -68,12 +69,6 @@ mod stacking_context;
use background::BackgroundPainter;
pub use stacking_context::*;
#[derive(Clone, Copy)]
pub struct WebRenderImageInfo {
pub size: Size2D<u32, UnknownUnit>,
pub key: Option<wr::ImageKey>,
}
// webrender's `ItemTag` is private.
type ItemTag = (u64, u16);
type HitInfo = Option<ItemTag>;
@ -1280,20 +1275,41 @@ impl<'a> BuilderForBoxFragment<'a> {
},
}
},
Ok(ResolvedImage::Image(image_info)) => {
Ok(ResolvedImage::Image { image, size }) => {
// FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution
let dppx = 1.0;
let intrinsic = NaturalSizes::from_width_and_height(
image_info.size.width as f32 / dppx,
image_info.size.height as f32 / dppx,
);
let Some(image_key) = image_info.key else {
let intrinsic =
NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
let layer = background::layout_layer(self, painter, builder, index, intrinsic);
let image_wr_key = match image {
CachedImage::Raster(raster_image) => raster_image.id,
CachedImage::Vector(vector_image) => {
let scale = builder.context.shared_context().device_pixel_ratio().0;
let default_size: DeviceIntSize =
Size2D::new(size.width * scale, size.height * scale).to_i32();
let layer_size = layer.as_ref().map(|layer| {
Size2D::new(
layer.tile_size.width * scale,
layer.tile_size.height * scale,
)
.to_i32()
});
node.and_then(|node| {
let size = layer_size.unwrap_or(default_size);
builder
.context
.rasterize_vector_image(vector_image.id, size, node)
})
.and_then(|rasterized_image| rasterized_image.id)
},
};
let Some(image_key) = image_wr_key else {
continue;
};
if let Some(layer) =
background::layout_layer(self, painter, builder, index, intrinsic)
{
if let Some(layer) = layer {
if layer.repeat {
builder.wr().push_repeating_image(
&layer.common,
@ -1469,13 +1485,17 @@ impl<'a> BuilderForBoxFragment<'a> {
.resolve_image(node, &border.border_image_source)
{
Err(_) => return false,
Ok(ResolvedImage::Image(image_info)) => {
let Some(key) = image_info.key else {
Ok(ResolvedImage::Image { image, size }) => {
let Some(image) = image.as_raster_image() else {
return false;
};
width = image_info.size.width as f32;
height = image_info.size.height as f32;
let Some(key) = image.id else {
return false;
};
width = size.width;
height = size.height;
NinePatchBorderSource::Image(key, ImageRendering::Auto)
},
Ok(ResolvedImage::Gradient(gradient)) => {

View file

@ -618,7 +618,7 @@ impl StackingContext {
// If its larger, we also want to paint areas reachable after scrolling.
let painting_area = fragment_tree
.initial_containing_block
.union(&fragment_tree.scrollable_overflow)
.union(&fragment_tree.scrollable_overflow())
.to_webrender();
let background_color =
@ -834,16 +834,6 @@ impl Fragment {
_ => text_decorations,
};
// If this fragment has a transform applied that makes it take up no space
// then we don't need to create any stacking contexts for it.
let has_non_invertible_transform = fragment
.has_non_invertible_transform_or_zero_scale(
&containing_block.rect.to_untyped(),
);
if has_non_invertible_transform {
return;
}
fragment.build_stacking_context_tree(
fragment_clone,
stacking_context_tree,
@ -991,6 +981,13 @@ impl BoxFragment {
},
};
// <https://drafts.csswg.org/css-transforms/#transform-function-lists>
// > If a transform function causes the current transformation matrix of an object
// > to be non-invertible, the object and its content do not get displayed.
if !reference_frame_data.transform.is_invertible() {
return;
}
let new_spatial_id = stacking_context_tree.push_reference_frame(
reference_frame_data.origin.to_webrender(),
&containing_block.scroll_node_id,
@ -1349,7 +1346,7 @@ impl BoxFragment {
let position = self.style.get_box().position;
// https://drafts.csswg.org/css2/#clipping
// The clip property applies only to absolutely positioned elements
if position != ComputedPosition::Absolute && position != ComputedPosition::Fixed {
if !position.is_absolutely_positioned() {
return None;
}
@ -1622,15 +1619,6 @@ impl BoxFragment {
})
}
/// Returns true if the given style contains a transform that is not invertible.
fn has_non_invertible_transform_or_zero_scale(&self, containing_block: &Rect<Au>) -> bool {
let list = &self.style.get_box().transform;
match list.to_transform_3d_matrix(Some(&au_rect_to_length_rect(containing_block))) {
Ok(t) => !t.0.is_invertible() || t.0.m11 == 0. || t.0.m22 == 0.,
Err(_) => false,
}
}
/// Returns the 4D matrix representing this fragment's transform.
pub fn calculate_transform_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> {
let list = &self.style.get_box().transform;
@ -1660,11 +1648,6 @@ impl BoxFragment {
.then_rotate(rotate.0, rotate.1, rotate.2, angle)
.then_scale(scale.0, scale.1, scale.2)
.then(&translation);
// WebRender will end up dividing by the scale value of this transform, so we
// want to ensure we don't feed it a divisor of 0.
if transform.m11 == 0. || transform.m22 == 0. {
return Some(LayoutTransform::identity());
}
let transform_origin = &self.style.get_box().transform_origin;
let transform_origin_x = transform_origin

View file

@ -4,13 +4,12 @@
use std::any::Any;
use std::marker::PhantomData;
use std::sync::Arc;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use base::id::{BrowsingContextId, PipelineId};
use html5ever::{local_name, ns};
use malloc_size_of_derive::MallocSizeOf;
use pixels::Image;
use net_traits::image_cache::Image;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
@ -197,7 +196,7 @@ impl Drop for BoxSlot<'_> {
pub(crate) trait NodeExt<'dom> {
/// Returns the image if its loaded, and its size in image pixels
/// adjusted for `image_density`.
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)>;
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)>;
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
@ -220,12 +219,15 @@ pub(crate) trait NodeExt<'dom> {
}
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)> {
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)> {
let node = self.to_threadsafe();
let (resource, metadata) = node.image_data()?;
let (width, height) = resource
.as_ref()
.map(|image| (image.width, image.height))
.map(|image| {
let image_metadata = image.metadata();
(image_metadata.width, image_metadata.height)
})
.or_else(|| metadata.map(|metadata| (metadata.width, metadata.height)))
.unwrap_or((0, 0));
let (mut width, mut height) = (width as f64, height as f64);

View file

@ -59,8 +59,14 @@ impl<'dom> NodeAndStyleInfo<'dom> {
}
}
/// Whether this is a container for the editable text within a single-line text input.
/// This is used to solve the special case of line height for a text editor.
/// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
// FIXME(stevennovaryo): Now, this would also refer to HTMLInputElement, to handle input
// elements without shadow DOM.
pub(crate) fn is_single_line_text_input(&self) -> bool {
self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement)
self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) ||
self.node.is_text_control_inner_editor()
}
pub(crate) fn pseudo(

View file

@ -29,7 +29,7 @@ use crate::flow::inline::InlineItem;
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::FragmentTree;
use crate::geom::{LogicalVec2, PhysicalRect, PhysicalSize};
use crate::geom::{LogicalVec2, PhysicalSize};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::replaced::ReplacedContents;
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
@ -392,31 +392,9 @@ impl BoxTree {
&mut root_fragments,
);
let scrollable_overflow = root_fragments
.iter()
.fold(PhysicalRect::zero(), |acc, child| {
let child_overflow = child.scrollable_overflow_for_parent();
// https://drafts.csswg.org/css-overflow/#scrolling-direction
// We want to clip scrollable overflow on box-start and inline-start
// sides of the scroll container.
//
// FIXME(mrobinson, bug 25564): This should take into account writing
// mode.
let child_overflow = PhysicalRect::new(
euclid::Point2D::zero(),
euclid::Size2D::new(
child_overflow.size.width + child_overflow.origin.x,
child_overflow.size.height + child_overflow.origin.y,
),
);
acc.union(&child_overflow)
});
FragmentTree::new(
layout_context,
root_fragments,
scrollable_overflow,
physical_containing_block,
self.viewport_scroll_sensitivity,
)

View file

@ -89,7 +89,7 @@ pub(crate) struct BoxFragment {
block_margins_collapsed_with_children: Option<Box<CollapsedBlockMargins>>,
/// The scrollable overflow of this box fragment.
pub scrollable_overflow_from_children: PhysicalRect<Au>,
scrollable_overflow: Option<PhysicalRect<Au>>,
/// The resolved box insets if this box is `position: sticky`. These are calculated
/// during `StackingContextTree` construction because they rely on the size of the
@ -114,11 +114,6 @@ impl BoxFragment {
margin: PhysicalSides<Au>,
clearance: Option<Au>,
) -> BoxFragment {
let scrollable_overflow_from_children =
children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(&child.scrollable_overflow_for_parent())
});
BoxFragment {
base: base_fragment_info.into(),
style,
@ -131,7 +126,7 @@ impl BoxFragment {
clearance,
baselines: Baselines::default(),
block_margins_collapsed_with_children: None,
scrollable_overflow_from_children,
scrollable_overflow: None,
resolved_sticky_insets: AtomicRefCell::default(),
background_mode: BackgroundMode::Normal,
specific_layout_info: None,
@ -203,13 +198,23 @@ impl BoxFragment {
/// Get the scrollable overflow for this [`BoxFragment`] relative to its
/// containing block.
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
self.scrollable_overflow
.expect("Should only call `scrollable_overflow()` after calculating overflow")
}
pub(crate) fn calculate_scrollable_overflow(&mut self) {
let scrollable_overflow_from_children = self
.children
.iter()
.fold(PhysicalRect::zero(), |acc, child| {
acc.union(&child.calculate_scrollable_overflow_for_parent())
});
let physical_padding_rect = self.padding_rect();
let content_origin = self.content_rect.origin.to_vector();
physical_padding_rect.union(
&self
.scrollable_overflow_from_children
.translate(content_origin),
)
self.scrollable_overflow = Some(
physical_padding_rect
.union(&scrollable_overflow_from_children.translate(content_origin)),
);
}
pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
@ -275,7 +280,12 @@ impl BoxFragment {
tree.end_level();
}
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
// TODO: Properly handle absolutely positioned fragments.
if self.style.get_box().position.is_absolutely_positioned() {
return PhysicalRect::zero();
}
let mut overflow = self.border_rect();
if !self.style.establishes_scroll_container(self.base.flags) {
// https://www.w3.org/TR/css-overflow-3/#scrollable
@ -328,7 +338,7 @@ impl BoxFragment {
///
/// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction.
/// For an element, the clip rect is the padding rect and for viewport, it is the initial containing block.
pub fn clip_unreachable_scrollable_overflow_region(
pub(crate) fn clip_unreachable_scrollable_overflow_region(
&self,
scrollable_overflow: PhysicalRect<Au>,
clipping_rect: PhysicalRect<Au>,
@ -362,7 +372,7 @@ impl BoxFragment {
///
/// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction.
/// This will coincides with the scrollport if the fragment is a scroll container.
pub fn reachable_scrollable_overflow_region(&self) -> PhysicalRect<Au> {
pub(crate) fn reachable_scrollable_overflow_region(&self) -> PhysicalRect<Au> {
self.clip_unreachable_scrollable_overflow_region(
self.scrollable_overflow(),
self.padding_rect(),
@ -421,9 +431,7 @@ impl BoxFragment {
return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left));
}
debug_assert!(
position == ComputedPosition::Fixed || position == ComputedPosition::Absolute
);
debug_assert!(position.is_absolutely_positioned());
let margin_rect = self.margin_rect();
let (top, bottom) = match (&insets.top, &insets.bottom) {

View file

@ -183,19 +183,36 @@ impl Fragment {
}
}
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow().scrollable_overflow_for_parent()
return fragment.borrow().scrollable_overflow_for_parent();
},
Fragment::AbsoluteOrFixedPositioned(_) => PhysicalRect::zero(),
Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow,
Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow_for_parent(),
Fragment::Text(fragment) => fragment.borrow().rect,
Fragment::Image(fragment) => fragment.borrow().rect,
Fragment::IFrame(fragment) => fragment.borrow().rect,
}
}
pub(crate) fn calculate_scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
self.calculate_scrollable_overflow();
self.scrollable_overflow_for_parent()
}
pub(crate) fn calculate_scrollable_overflow(&self) {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow_mut().calculate_scrollable_overflow()
},
Fragment::Positioning(fragment) => {
fragment.borrow_mut().calculate_scrollable_overflow()
},
_ => {},
}
}
pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {

View file

@ -2,14 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use app_units::Au;
use base::print_tree::PrintTree;
use compositing_traits::display_list::AxesScrollSensitivity;
use euclid::default::Size2D;
use fxhash::FxHashSet;
use malloc_size_of_derive::MallocSizeOf;
use style::animation::AnimationSetKey;
use webrender_api::units;
use super::{BoxFragment, ContainingBlockManager, Fragment};
use crate::ArcRefCell;
@ -30,7 +30,7 @@ pub struct FragmentTree {
/// The scrollable overflow rectangle for the entire tree
/// <https://drafts.csswg.org/css-overflow/#scrollable>
pub(crate) scrollable_overflow: PhysicalRect<Au>,
scrollable_overflow: Cell<Option<PhysicalRect<Au>>>,
/// The containing block used in the layout of this fragment tree.
pub(crate) initial_containing_block: PhysicalRect<Au>,
@ -43,13 +43,12 @@ impl FragmentTree {
pub(crate) fn new(
layout_context: &LayoutContext,
root_fragments: Vec<Fragment>,
scrollable_overflow: PhysicalRect<Au>,
initial_containing_block: PhysicalRect<Au>,
viewport_scroll_sensitivity: AxesScrollSensitivity,
) -> Self {
let fragment_tree = Self {
root_fragments,
scrollable_overflow,
scrollable_overflow: Cell::default(),
initial_containing_block,
viewport_scroll_sensitivity,
};
@ -97,11 +96,35 @@ impl FragmentTree {
}
}
pub fn scrollable_overflow(&self) -> units::LayoutSize {
units::LayoutSize::from_untyped(Size2D::new(
self.scrollable_overflow.size.width.to_f32_px(),
self.scrollable_overflow.size.height.to_f32_px(),
))
pub(crate) fn scrollable_overflow(&self) -> PhysicalRect<Au> {
self.scrollable_overflow
.get()
.expect("Should only call `scrollable_overflow()` after calculating overflow")
}
pub(crate) fn calculate_scrollable_overflow(&self) {
self.scrollable_overflow
.set(Some(self.root_fragments.iter().fold(
PhysicalRect::zero(),
|acc, child| {
let child_overflow = child.calculate_scrollable_overflow_for_parent();
// https://drafts.csswg.org/css-overflow/#scrolling-direction
// We want to clip scrollable overflow on box-start and inline-start
// sides of the scroll container.
//
// FIXME(mrobinson, bug 25564): This should take into account writing
// mode.
let child_overflow = PhysicalRect::new(
euclid::Point2D::zero(),
euclid::Size2D::new(
child_overflow.size.width + child_overflow.origin.x,
child_overflow.size.height + child_overflow.origin.y,
),
);
acc.union(&child_overflow)
},
)));
}
pub(crate) fn find<T>(

View file

@ -22,7 +22,7 @@ pub(crate) struct PositioningFragment {
pub children: Vec<Fragment>,
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Au>,
scrollable_overflow: Option<PhysicalRect<Au>>,
/// The style of the fragment.
pub style: ServoArc<ComputedValues>,
@ -55,20 +55,12 @@ impl PositioningFragment {
rect: PhysicalRect<Au>,
children: Vec<Fragment>,
) -> ArcRefCell<Self> {
let content_origin = rect.origin;
let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(
&child
.scrollable_overflow_for_parent()
.translate(content_origin.to_vector()),
)
});
ArcRefCell::new(PositioningFragment {
base,
style,
rect,
children,
scrollable_overflow,
scrollable_overflow: None,
cumulative_containing_block_rect: PhysicalRect::zero(),
})
}
@ -81,6 +73,25 @@ impl PositioningFragment {
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
}
pub(crate) fn calculate_scrollable_overflow(&mut self) {
self.scrollable_overflow = Some(self.children.iter().fold(
PhysicalRect::zero(),
|acc, child| {
acc.union(
&child
.calculate_scrollable_overflow_for_parent()
.translate(self.rect.origin.to_vector()),
)
},
));
}
pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
self.scrollable_overflow.expect(
"Should only call `scrollable_overflow_for_parent()` after calculating overflow",
)
}
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"PositioningFragment\

View file

@ -8,6 +8,7 @@ use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::fmt::Debug;
use std::process;
use std::rc::Rc;
use std::sync::{Arc, LazyLock};
use app_units::Au;
@ -15,7 +16,7 @@ use base::Epoch;
use base::id::{PipelineId, WebViewId};
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::ScrollState;
use embedder_traits::{UntrustedNodeAddress, ViewportDetails};
use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
use euclid::{Point2D, Scale, Size2D, Vector2D};
use fnv::FnvHashMap;
@ -76,8 +77,8 @@ use url::Url;
use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, LayoutSize};
use webrender_api::{ExternalScrollId, HitTestFlags};
use crate::context::LayoutContext;
use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo};
use crate::context::{CachedImageOrError, LayoutContext};
use crate::display_list::{DisplayListBuilder, StackingContextTree};
use crate::query::{
get_the_text_steps, process_client_rect_request, process_content_box_request,
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
@ -142,7 +143,7 @@ pub struct LayoutThread {
box_tree: RefCell<Option<Arc<BoxTree>>>,
/// The fragment tree.
fragment_tree: RefCell<Option<Arc<FragmentTree>>>,
fragment_tree: RefCell<Option<Rc<FragmentTree>>>,
/// The [`StackingContextTree`] cached from previous layouts.
stacking_context_tree: RefCell<Option<StackingContextTree>>,
@ -153,7 +154,10 @@ pub struct LayoutThread {
/// Scroll offsets of nodes that scroll.
scroll_offsets: RefCell<HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>>,
webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
// A cache that maps image resources specified in CSS (e.g as the `url()` value
// for `background-image` or `content` properties) to either the final resolved
// image data, or an error if the image cache failed to load/decode the image.
resolved_images_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
/// The executors for paint worklets.
registered_painters: RegisteredPaintersImpl,
@ -503,8 +507,7 @@ impl LayoutThread {
Scale::new(config.viewport_details.hidpi_scale_factor.get()),
Box::new(LayoutFontMetricsProvider(config.font_context.clone())),
ComputedValues::initial_values_with_font_override(font),
// TODO: obtain preferred color scheme from embedder
PrefersColorScheme::Light,
config.theme.into(),
);
LayoutThread {
@ -526,7 +529,7 @@ impl LayoutThread {
compositor_api: config.compositor_api,
scroll_offsets: Default::default(),
stylist: Stylist::new(device, QuirksMode::NoQuirks),
webrender_image_cache: Default::default(),
resolved_images_cache: Default::default(),
debug: opts::get().debug.clone(),
}
}
@ -636,8 +639,9 @@ impl LayoutThread {
),
image_cache: self.image_cache.clone(),
font_context: self.font_context.clone(),
webrender_image_cache: self.webrender_image_cache.clone(),
resolved_images_cache: self.resolved_images_cache.clone(),
pending_images: Mutex::default(),
pending_rasterization_images: Mutex::default(),
node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
&mut reflow_request.node_to_image_animation_map,
))),
@ -646,15 +650,15 @@ impl LayoutThread {
highlighted_dom_node: reflow_request.highlighted_dom_node,
};
let did_reflow = self.restyle_and_build_trees(
let damage = self.restyle_and_build_trees(
&reflow_request,
root_element,
rayon_pool,
&mut layout_context,
viewport_changed,
);
self.build_stacking_context_tree(&reflow_request, did_reflow);
self.calculate_overflow(damage);
self.build_stacking_context_tree(&reflow_request, damage);
self.build_display_list(&reflow_request, &mut layout_context);
self.first_reflow.set(false);
@ -663,12 +667,15 @@ impl LayoutThread {
}
let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
let pending_rasterization_images =
std::mem::take(&mut *layout_context.pending_rasterization_images.lock());
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
let node_to_image_animation_map =
std::mem::take(&mut *layout_context.node_image_animation_map.write());
Some(ReflowResult {
pending_images,
pending_rasterization_images,
iframe_sizes,
node_to_image_animation_map,
})
@ -744,7 +751,7 @@ impl LayoutThread {
rayon_pool: Option<&ThreadPool>,
layout_context: &mut LayoutContext<'_>,
viewport_changed: bool,
) -> bool {
) -> RestyleDamage {
let dirty_root = unsafe {
ServoLayoutNode::new(&reflow_request.dirty_root.unwrap())
.as_element()
@ -760,17 +767,20 @@ impl LayoutThread {
if !token.should_traverse() {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return false;
return RestyleDamage::empty();
}
let dirty_root: ServoLayoutNode =
driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
let root_node = root_element.as_node();
let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if !viewport_changed && (damage.is_empty() || damage == RestyleDamage::REPAINT) {
let mut damage =
compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if viewport_changed {
damage = RestyleDamage::REBUILD_BOX;
} else if !damage.contains(RestyleDamage::REBUILD_BOX) {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return false;
return damage;
}
let mut box_tree = self.box_tree.borrow_mut();
@ -796,15 +806,12 @@ impl LayoutThread {
.unwrap()
.layout(recalc_style_traversal.context(), viewport_size)
};
let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
let fragment_tree = Rc::new(if let Some(pool) = rayon_pool {
pool.install(run_layout)
} else {
run_layout()
});
if self.debug.dump_flow_tree {
fragment_tree.print();
}
*self.fragment_tree.borrow_mut() = Some(fragment_tree);
// The FragmentTree has been updated, so any existing StackingContext tree that layout
@ -828,10 +835,23 @@ impl LayoutThread {
// GC the rule tree if some heuristics are met.
layout_context.style_context.stylist.rule_tree().maybe_gc();
true
damage
}
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, did_reflow: bool) {
fn calculate_overflow(&self, damage: RestyleDamage) {
if !damage.contains(RestyleDamage::RECALCULATE_OVERFLOW) {
return;
}
if let Some(fragment_tree) = &*self.fragment_tree.borrow() {
fragment_tree.calculate_scrollable_overflow();
if self.debug.dump_flow_tree {
fragment_tree.print();
}
}
}
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, damage: RestyleDamage) {
if !reflow_request.reflow_goal.needs_display_list() &&
!reflow_request.reflow_goal.needs_display()
{
@ -840,7 +860,9 @@ impl LayoutThread {
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
return;
};
if !did_reflow && self.stacking_context_tree.borrow().is_some() {
if !damage.contains(RestyleDamage::REBUILD_STACKING_CONTEXT) &&
self.stacking_context_tree.borrow().is_some()
{
return;
}
@ -850,13 +872,19 @@ impl LayoutThread {
viewport_size.height.to_f32_px(),
);
let scrollable_overflow = fragment_tree.scrollable_overflow();
let scrollable_overflow = LayoutSize::from_untyped(Size2D::new(
scrollable_overflow.size.width.to_f32_px(),
scrollable_overflow.size.height.to_f32_px(),
));
// Build the StackingContextTree. This turns the `FragmentTree` into a
// tree of fragments in CSS painting order and also creates all
// applicable spatial and clip nodes.
*self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new(
fragment_tree,
viewport_size,
fragment_tree.scrollable_overflow(),
scrollable_overflow,
self.id.into(),
fragment_tree.viewport_scroll_sensitivity,
self.first_reflow.get(),
@ -949,7 +977,8 @@ impl LayoutThread {
size_did_change || pixel_ratio_did_change
}
fn theme_did_change(&self, theme: PrefersColorScheme) -> bool {
fn theme_did_change(&self, theme: Theme) -> bool {
let theme: PrefersColorScheme = theme.into();
theme != self.device().color_scheme()
}
@ -957,7 +986,7 @@ impl LayoutThread {
fn update_device(
&mut self,
viewport_details: ViewportDetails,
theme: PrefersColorScheme,
theme: Theme,
guards: &StylesheetGuards,
) {
let device = Device::new(
@ -967,7 +996,7 @@ impl LayoutThread {
Scale::new(viewport_details.hidpi_scale_factor.get()),
Box::new(LayoutFontMetricsProvider(self.font_context.clone())),
self.stylist.device().default_computed_values().to_arc(),
theme,
theme.into(),
);
// Preserve any previously computed root font size.

View file

@ -305,10 +305,7 @@ impl PositioningContext {
}
pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
debug_assert!(matches!(
hoisted_box.position(),
Position::Absolute | Position::Fixed
));
debug_assert!(hoisted_box.position().is_absolutely_positioned());
self.absolutes.push(hoisted_box);
}
@ -380,7 +377,7 @@ impl HoistedAbsolutelyPositionedBox {
.context
.style()
.clone_position();
assert!(position == Position::Fixed || position == Position::Absolute);
assert!(position.is_absolutely_positioned());
position
}

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Utilities for querying the layout, as needed by layout.
use std::sync::Arc;
use std::rc::Rc;
use app_units::Au;
use euclid::default::{Point2D, Rect};
@ -80,7 +80,7 @@ pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32> {
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
pub fn process_node_scroll_area_request(
requested_node: Option<ServoLayoutNode<'_>>,
fragment_tree: Option<Arc<FragmentTree>>,
fragment_tree: Option<Rc<FragmentTree>>,
) -> Rect<i32> {
let Some(tree) = fragment_tree else {
return Rect::zero();

View file

@ -3,7 +3,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::LazyCell;
use std::sync::Arc;
use app_units::Au;
use base::id::{BrowsingContextId, PipelineId};
@ -11,8 +10,7 @@ use data_url::DataUrl;
use embedder_traits::ViewportDetails;
use euclid::{Scale, Size2D};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use pixels::Image;
use net_traits::image_cache::{Image, ImageOrMetadataAvailable, UsePlaceholder};
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::IFrameSize;
use servo_arc::Arc as ServoArc;
@ -28,7 +26,7 @@ use url::Url;
use webrender_api::ImageKey;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::context::{LayoutContext, LayoutImageCacheResult};
use crate::dom::NodeExt;
use crate::fragment_tree::{BaseFragmentInfo, Fragment, IFrameFragment, ImageFragment};
use crate::geom::{
@ -115,7 +113,7 @@ pub(crate) struct VideoInfo {
#[derive(Debug, MallocSizeOf)]
pub(crate) enum ReplacedContentKind {
Image(#[conditional_malloc_size_of] Option<Arc<Image>>),
Image(Option<Image>),
IFrame(IFrameInfo),
Canvas(CanvasInfo),
Video(Option<VideoInfo>),
@ -162,7 +160,7 @@ impl ReplacedContents {
}
};
if let ReplacedContentKind::Image(Some(ref image)) = kind {
if let ReplacedContentKind::Image(Some(Image::Raster(ref image))) = kind {
context.handle_animated_image(element.opaque(), image.clone());
}
@ -197,13 +195,20 @@ impl ReplacedContents {
image_url.clone().into(),
UsePlaceholder::No,
) {
Ok(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
(Some(image.clone()), image.width as f32, image.height as f32)
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
let metadata = image.metadata();
(
Some(image.clone()),
metadata.width as f32,
metadata.height as f32,
)
},
ImageOrMetadataAvailable::MetadataAvailable(metadata, _id) => {
(None, metadata.width as f32, metadata.height as f32)
},
},
Ok(ImageOrMetadataAvailable::MetadataAvailable(metadata, _id)) => {
(None, metadata.width as f32, metadata.height as f32)
},
Err(_) => return None,
LayoutImageCacheResult::Pending | LayoutImageCacheResult::LoadError => return None,
};
return Some(Self {
@ -315,7 +320,19 @@ impl ReplacedContents {
match &self.kind {
ReplacedContentKind::Image(image) => image
.as_ref()
.and_then(|image| image.id)
.and_then(|image| match image {
Image::Raster(raster_image) => raster_image.id,
Image::Vector(vector_image) => {
let scale = layout_context.shared_context().device_pixel_ratio();
let width = object_fit_size.width.scale_by(scale.0).to_px();
let height = object_fit_size.height.scale_by(scale.0).to_px();
let size = Size2D::new(width, height);
let tag = self.base_fragment_info.tag?;
layout_context
.rasterize_vector_image(vector_image.id, size, tag.node)
.and_then(|i| i.id)
},
})
.map(|image_key| {
Fragment::Image(ArcRefCell::new(ImageFragment {
base: self.base_fragment_info.into(),

View file

@ -265,4 +265,8 @@ select {
padding: 0 0.25em;
/* Don't show a text cursor when hovering selected option */
cursor: default;
}
slot {
display: contents;
}

View file

@ -131,7 +131,9 @@ pub(crate) fn compute_damage_and_repair_style_inner(
}
}
if propagated_damage == RestyleDamage::REPAINT && original_damage == RestyleDamage::REPAINT {
if !propagated_damage.contains(RestyleDamage::REBUILD_BOX) &&
!original_damage.contains(RestyleDamage::REBUILD_BOX)
{
node.repair_style(context);
}

View file

@ -22,6 +22,8 @@ indexmap = { workspace = true }
ipc-channel = { workspace = true }
keyboard-types = { workspace = true }
markup5ever = { workspace = true }
mime = { workspace = true }
resvg = { workspace = true }
servo_allocator = { path = "../allocator" }
servo_arc = { workspace = true }
smallvec = { workspace = true }

View file

@ -774,6 +774,7 @@ malloc_size_of_is_0!(content_security_policy::Destination);
malloc_size_of_is_0!(http::StatusCode);
malloc_size_of_is_0!(app_units::Au);
malloc_size_of_is_0!(keyboard_types::Modifiers);
malloc_size_of_is_0!(mime::Mime);
malloc_size_of_is_0!(std::num::NonZeroU64);
malloc_size_of_is_0!(std::num::NonZeroUsize);
malloc_size_of_is_0!(std::sync::atomic::AtomicBool);
@ -782,6 +783,7 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize);
malloc_size_of_is_0!(std::time::Duration);
malloc_size_of_is_0!(std::time::Instant);
malloc_size_of_is_0!(std::time::SystemTime);
malloc_size_of_is_0!(resvg::usvg::Tree);
malloc_size_of_is_0!(style::data::ElementData);
malloc_size_of_is_0!(style::font_face::SourceList);
malloc_size_of_is_0!(style::properties::ComputedValues);

View file

@ -56,6 +56,7 @@ rayon = { workspace = true }
rustls = { workspace = true }
rustls-pemfile = { workspace = true }
rustls-pki-types = { workspace = true }
resvg = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
servo_arc = { workspace = true }

View file

@ -170,8 +170,12 @@ pub async fn fetch_with_cors_cache(
// TODO: We don't implement fetchParams as defined in the spec
}
fn convert_request_to_csp_request(request: &Request, origin: &ImmutableOrigin) -> csp::Request {
csp::Request {
pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option<csp::Request> {
let origin = match &request.origin {
Origin::Client => return None,
Origin::Origin(origin) => origin,
};
let csp_request = csp::Request {
url: request.url().into_url(),
origin: origin.clone().into_url_origin(),
redirect_count: request.redirect_count,
@ -190,45 +194,58 @@ fn convert_request_to_csp_request(request: &Request, origin: &ImmutableOrigin) -
ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
ParserMetadata::Default => csp::ParserMetadata::None,
},
}
};
Some(csp_request)
}
/// <https://www.w3.org/TR/CSP/#should-block-request>
pub fn should_request_be_blocked_by_csp(
request: &Request,
csp_request: &csp::Request,
policy_container: &PolicyContainer,
) -> (csp::CheckResult, Vec<csp::Violation>) {
let origin = match &request.origin {
Origin::Client => return (csp::CheckResult::Allowed, Vec::new()),
Origin::Origin(origin) => origin,
};
let csp_request = convert_request_to_csp_request(request, origin);
policy_container
.csp_list
.as_ref()
.map(|c| c.should_request_be_blocked(&csp_request))
.map(|c| c.should_request_be_blocked(csp_request))
.unwrap_or((csp::CheckResult::Allowed, Vec::new()))
}
/// <https://www.w3.org/TR/CSP/#report-for-request>
pub fn report_violations_for_request_by_csp(
request: &Request,
csp_request: &csp::Request,
policy_container: &PolicyContainer,
) -> Vec<csp::Violation> {
let origin = match &request.origin {
Origin::Client => return Vec::new(),
Origin::Origin(origin) => origin,
};
let csp_request = convert_request_to_csp_request(request, origin);
policy_container
.csp_list
.as_ref()
.map(|c| c.report_violations_for_request(&csp_request))
.map(|c| c.report_violations_for_request(csp_request))
.unwrap_or_default()
}
fn should_response_be_blocked_by_csp(
csp_request: &csp::Request,
response: &Response,
policy_container: &PolicyContainer,
) -> (csp::CheckResult, Vec<csp::Violation>) {
if response.is_network_error() {
return (csp::CheckResult::Allowed, Vec::new());
}
let csp_response = csp::Response {
url: response
.actual_response()
.url()
.cloned()
.expect("response must have a url")
.into_url(),
redirect_count: csp_request.redirect_count,
};
policy_container
.csp_list
.as_ref()
.map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response))
.unwrap_or((csp::CheckResult::Allowed, Vec::new()))
}
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
pub async fn main_fetch(
fetch_params: &mut FetchParams,
@ -270,13 +287,15 @@ pub async fn main_fetch(
RequestPolicyContainer::Client => PolicyContainer::default(),
RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
};
let csp_request = convert_request_to_csp_request(request);
if let Some(csp_request) = csp_request.as_ref() {
// Step 2.2.
let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
// Step 2.2.
let violations = report_violations_for_request_by_csp(request, &policy_container);
if !violations.is_empty() {
target.process_csp_violations(request, violations);
}
if !violations.is_empty() {
target.process_csp_violations(request, violations);
}
};
// Step 3.
// TODO: handle request abort.
@ -309,22 +328,24 @@ pub async fn main_fetch(
request.insecure_requests_policy
);
}
if let Some(csp_request) = csp_request.as_ref() {
// Step 7. If should request be blocked due to a bad port, should fetching request be blocked
// as mixed content, or should request be blocked by Content Security Policy returns blocked,
// then set response to a network error.
let (check_result, violations) =
should_request_be_blocked_by_csp(csp_request, &policy_container);
// Step 7. If should request be blocked due to a bad port, should fetching request be blocked
// as mixed content, or should request be blocked by Content Security Policy returns blocked,
// then set response to a network error.
let (check_result, violations) = should_request_be_blocked_by_csp(request, &policy_container);
if !violations.is_empty() {
target.process_csp_violations(request, violations);
}
if !violations.is_empty() {
target.process_csp_violations(request, violations);
}
if check_result == csp::CheckResult::Blocked {
warn!("Request blocked by CSP");
response = Some(Response::network_error(NetworkError::Internal(
"Blocked by Content-Security-Policy".into(),
)))
}
if check_result == csp::CheckResult::Blocked {
warn!("Request blocked by CSP");
response = Some(Response::network_error(NetworkError::Internal(
"Blocked by Content-Security-Policy".into(),
)))
}
};
if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
response = Some(Response::network_error(NetworkError::Internal(
"Request attempted on bad port".into(),
@ -530,6 +551,14 @@ pub async fn main_fetch(
should_be_blocked_due_to_mime_type(request.destination, &response.headers);
let should_replace_with_mixed_content = !response_is_network_error &&
should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| {
let (check_result, violations) =
should_response_be_blocked_by_csp(&csp_request, &response, &policy_container);
if !violations.is_empty() {
target.process_csp_violations(request, violations);
}
check_result == csp::CheckResult::Blocked
});
// Step 15.
let mut network_error_response = response
@ -553,7 +582,7 @@ pub async fn main_fetch(
// Step 19. If response is not a network error and any of the following returns blocked
// * should internalResponse to request be blocked as mixed content
// TODO: * should internalResponse to request be blocked by Content Security Policy
// * should internalResponse to request be blocked by Content Security Policy
// * should internalResponse to request be blocked due to its MIME type
// * should internalResponse to request be blocked due to nosniff
let mut blocked_error_response;
@ -572,6 +601,10 @@ pub async fn main_fetch(
blocked_error_response =
Response::network_error(NetworkError::Internal("Blocked as mixed content".into()));
&blocked_error_response
} else if should_replace_with_csp_error {
blocked_error_response =
Response::network_error(NetworkError::Internal("Blocked due to CSP".into()));
&blocked_error_response
} else {
internal_response
};

View file

@ -7,21 +7,25 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::sync::{Arc, Mutex};
use std::{mem, thread};
use base::id::PipelineId;
use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData};
use imsz::imsz_from_reader;
use ipc_channel::ipc::IpcSharedMemory;
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
use log::{debug, warn};
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
use mime::Mime;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
PendingImageId, UsePlaceholder,
Image, ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
ImageOrMetadataAvailable, ImageResponse, PendingImageId, RasterizationCompleteResponse,
UsePlaceholder, VectorImage,
};
use net_traits::request::CorsSettings;
use net_traits::{FetchMetadata, FetchResponseMsg, FilteredMetadata, NetworkError};
use pixels::{CorsStatus, Image, ImageMetadata, PixelFormat, load_from_memory};
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, load_from_memory};
use profile_traits::mem::{Report, ReportKind};
use profile_traits::path;
use resvg::{tiny_skia, usvg};
use servo_config::pref;
use servo_url::{ImmutableOrigin, ServoUrl};
use webrender_api::units::DeviceIntSize;
@ -48,12 +52,53 @@ const FALLBACK_RIPPY: &[u8] = include_bytes!("../../resources/rippy.png");
// Helper functions.
// ======================================================================
fn decode_bytes_sync(key: LoadKey, bytes: &[u8], cors: CorsStatus) -> DecoderMsg {
let image = load_from_memory(bytes, cors);
fn parse_svg_document_in_memory(bytes: &[u8]) -> Result<usvg::Tree, &'static str> {
let image_string_href_resolver = Box::new(move |_: &str, _: &usvg::Options| {
// Do not try to load `href` in <image> as local file path.
None
});
let mut opt = usvg::Options {
image_href_resolver: usvg::ImageHrefResolver {
resolve_data: usvg::ImageHrefResolver::default_data_resolver(),
resolve_string: image_string_href_resolver,
},
..usvg::Options::default()
};
opt.fontdb_mut().load_system_fonts();
usvg::Tree::from_data(bytes, &opt)
.inspect_err(|error| {
warn!("Error when parsing SVG data: {error}");
})
.map_err(|_| "Not a valid SVG document")
}
fn decode_bytes_sync(
key: LoadKey,
bytes: &[u8],
cors: CorsStatus,
content_type: Option<Mime>,
) -> DecoderMsg {
let image = if content_type == Some(mime::IMAGE_SVG) {
parse_svg_document_in_memory(bytes).ok().map(|svg_tree| {
DecodedImage::Vector(VectorImageData {
svg_tree: Arc::new(svg_tree),
cors_status: cors,
})
})
} else {
load_from_memory(bytes, cors).map(DecodedImage::Raster)
};
DecoderMsg { key, image }
}
fn get_placeholder_image(compositor_api: &CrossProcessCompositorApi, data: &[u8]) -> Arc<Image> {
fn get_placeholder_image(
compositor_api: &CrossProcessCompositorApi,
data: &[u8],
) -> Arc<RasterImage> {
let mut image = load_from_memory(data, CorsStatus::Unsafe)
.or_else(|| load_from_memory(FALLBACK_RIPPY, CorsStatus::Unsafe))
.expect("load fallback image failed");
@ -61,14 +106,14 @@ fn get_placeholder_image(compositor_api: &CrossProcessCompositorApi, data: &[u8]
Arc::new(image)
}
fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &mut Image) {
fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &mut RasterImage) {
if image.id.is_some() {
return;
}
let mut bytes = Vec::new();
let frame_bytes = image.first_frame().bytes;
let is_opaque = match image.format {
PixelFormat::BGRA8 => {
PixelFormat::BGRA8 | PixelFormat::RGBA8 => {
bytes.extend_from_slice(frame_bytes);
pixels::rgba8_premultiply_inplace(bytes.as_mut_slice())
},
@ -80,16 +125,24 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m
true
},
PixelFormat::K8 | PixelFormat::KA8 | PixelFormat::RGBA8 => {
PixelFormat::K8 | PixelFormat::KA8 => {
panic!("Not support by webrender yet");
},
};
let format = if matches!(image.format, PixelFormat::RGBA8) {
ImageFormat::RGBA8
} else {
ImageFormat::BGRA8
};
let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
flags.set(ImageDescriptorFlags::IS_OPAQUE, is_opaque);
let size = DeviceIntSize::new(image.metadata.width as i32, image.metadata.height as i32);
let descriptor = ImageDescriptor {
size: DeviceIntSize::new(image.width as i32, image.height as i32),
size,
stride: None,
format: ImageFormat::BGRA8,
format,
offset: 0,
flags,
};
@ -204,10 +257,22 @@ impl CompletedLoad {
}
}
#[derive(Clone, Debug, MallocSizeOf)]
struct VectorImageData {
#[conditional_malloc_size_of]
svg_tree: Arc<usvg::Tree>,
cors_status: CorsStatus,
}
enum DecodedImage {
Raster(RasterImage),
Vector(VectorImageData),
}
/// Message that the decoder worker threads send to the image cache.
struct DecoderMsg {
key: LoadKey,
image: Option<Image>,
image: Option<DecodedImage>,
}
#[derive(MallocSizeOf)]
@ -265,8 +330,9 @@ impl LoadKeyGenerator {
#[derive(Debug)]
enum LoadResult {
Loaded(Image),
PlaceholderLoaded(Arc<Image>),
LoadedRasterImage(RasterImage),
LoadedVectorImage(VectorImageData),
PlaceholderLoaded(Arc<RasterImage>),
None,
}
@ -285,7 +351,7 @@ struct PendingLoad {
result: Option<Result<(), NetworkError>>,
/// The listeners that are waiting for this response to complete.
listeners: Vec<ImageResponder>,
listeners: Vec<ImageLoadListener>,
/// The url being loaded. Do not forget that this may be several Mb
/// if we are loading a data: url.
@ -302,6 +368,9 @@ struct PendingLoad {
/// The URL of the final response that contains a body.
final_url: Option<ServoUrl>,
/// The MIME type from the `Content-type` header of the HTTP response, if any.
content_type: Option<Mime>,
}
impl PendingLoad {
@ -320,33 +389,48 @@ impl PendingLoad {
final_url: None,
cors_setting,
cors_status: CorsStatus::Unsafe,
content_type: None,
}
}
fn add_listener(&mut self, listener: ImageResponder) {
fn add_listener(&mut self, listener: ImageLoadListener) {
self.listeners.push(listener);
}
}
// ======================================================================
// Image cache implementation.
// ======================================================================
#[derive(Default, MallocSizeOf)]
struct RasterizationTask {
listeners: Vec<(PipelineId, IpcSender<ImageCacheResponseMessage>)>,
result: Option<RasterImage>,
}
/// ## Image cache implementation.
#[derive(MallocSizeOf)]
struct ImageCacheStore {
// Images that are loading over network, or decoding.
/// Images that are loading over network, or decoding.
pending_loads: AllPendingLoads,
// Images that have finished loading (successful or not)
/// Images that have finished loading (successful or not)
completed_loads: HashMap<ImageKey, CompletedLoad>,
// The placeholder image used when an image fails to load
#[conditional_malloc_size_of]
placeholder_image: Arc<Image>,
/// Vector (e.g. SVG) images that have been sucessfully loaded and parsed
/// but are yet to be rasterized. Since the same SVG data can be used for
/// rasterizing at different sizes, we use this hasmap to share the data.
vector_images: HashMap<PendingImageId, VectorImageData>,
// The URL used for the placeholder image
/// Vector images for which rasterization at a particular size has started
/// or completed. If completed, the `result` member of `RasterizationTask`
/// contains the rasterized image.
rasterized_vector_images: HashMap<(PendingImageId, DeviceIntSize), RasterizationTask>,
/// The placeholder image used when an image fails to load
#[conditional_malloc_size_of]
placeholder_image: Arc<RasterImage>,
/// The URL used for the placeholder image
placeholder_url: ServoUrl,
// Cross-process compositor API instance.
/// Cross-process compositor API instance.
#[ignore_malloc_size_of = "Channel from another crate"]
compositor_api: CrossProcessCompositorApi,
}
@ -361,15 +445,34 @@ impl ImageCacheStore {
};
match load_result {
LoadResult::Loaded(ref mut image) => {
set_webrender_image_key(&self.compositor_api, image)
LoadResult::LoadedRasterImage(ref mut raster_image) => {
set_webrender_image_key(&self.compositor_api, raster_image)
},
LoadResult::LoadedVectorImage(ref vector_image) => {
self.vector_images.insert(key, vector_image.clone());
},
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {},
}
let url = pending_load.final_url.clone();
let image_response = match load_result {
LoadResult::Loaded(image) => ImageResponse::Loaded(Arc::new(image), url.unwrap()),
LoadResult::LoadedRasterImage(raster_image) => {
ImageResponse::Loaded(Image::Raster(Arc::new(raster_image)), url.unwrap())
},
LoadResult::LoadedVectorImage(vector_image) => {
let natural_dimensions = vector_image.svg_tree.size().to_int_size();
let metadata = ImageMetadata {
width: natural_dimensions.width(),
height: natural_dimensions.height(),
};
let vector_image = VectorImage {
id: key,
metadata,
cors_status: vector_image.cors_status,
};
ImageResponse::Loaded(Image::Vector(vector_image), url.unwrap())
},
LoadResult::PlaceholderLoaded(image) => {
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone())
},
@ -399,19 +502,18 @@ impl ImageCacheStore {
origin: ImmutableOrigin,
cors_setting: Option<CorsSettings>,
placeholder: UsePlaceholder,
) -> Option<Result<(Arc<Image>, ServoUrl), ()>> {
) -> Option<Result<(Image, ServoUrl), ()>> {
self.completed_loads
.get(&(url, origin, cors_setting))
.map(
|completed_load| match (&completed_load.image_response, placeholder) {
(&ImageResponse::Loaded(ref image, ref url), _) |
(
&ImageResponse::PlaceholderLoaded(ref image, ref url),
UsePlaceholder::Yes,
) => Ok((image.clone(), url.clone())),
(&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
(&ImageResponse::None, _) |
(&ImageResponse::MetadataLoaded(_), _) => Err(()),
(ImageResponse::Loaded(image, url), _) => Ok((image.clone(), url.clone())),
(ImageResponse::PlaceholderLoaded(image, url), UsePlaceholder::Yes) => {
Ok((Image::Raster(image.clone()), url.clone()))
},
(ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
(ImageResponse::None, _) |
(ImageResponse::MetadataLoaded(_), _) => Err(()),
},
)
}
@ -421,7 +523,10 @@ impl ImageCacheStore {
fn handle_decoder(&mut self, msg: DecoderMsg) {
let image = match msg.image {
None => LoadResult::None,
Some(image) => LoadResult::Loaded(image),
Some(DecodedImage::Raster(raster_image)) => LoadResult::LoadedRasterImage(raster_image),
Some(DecodedImage::Vector(vector_image_data)) => {
LoadResult::LoadedVectorImage(vector_image_data)
},
};
self.complete_load(msg.key, image);
}
@ -450,6 +555,8 @@ impl ImageCache for ImageCacheImpl {
store: Arc::new(Mutex::new(ImageCacheStore {
pending_loads: AllPendingLoads::new(),
completed_loads: HashMap::new(),
vector_images: HashMap::new(),
rasterized_vector_images: HashMap::new(),
placeholder_image: get_placeholder_image(&compositor_api, &rippy_data),
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
compositor_api,
@ -475,7 +582,7 @@ impl ImageCache for ImageCacheImpl {
url: ServoUrl,
origin: ImmutableOrigin,
cors_setting: Option<CorsSettings>,
) -> Option<Arc<Image>> {
) -> Option<Image> {
let store = self.store.lock().unwrap();
let result =
store.get_completed_image_if_available(url, origin, cors_setting, UsePlaceholder::No);
@ -524,12 +631,17 @@ impl ImageCache for ImageCacheImpl {
CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) {
(&Some(Ok(_)), _) => {
debug!("Sync decoding {} ({:?})", url, key);
decode_bytes_sync(key, pl.bytes.as_slice(), pl.cors_status)
decode_bytes_sync(
key,
pl.bytes.as_slice(),
pl.cors_status,
pl.content_type.clone(),
)
},
(&None, Some(meta)) => {
debug!("Metadata available for {} ({:?})", url, key);
return ImageCacheResult::Available(
ImageOrMetadataAvailable::MetadataAvailable(meta.clone(), key),
ImageOrMetadataAvailable::MetadataAvailable(*meta, key),
);
},
(&Some(Err(_)), _) | (&None, &None) => {
@ -566,9 +678,137 @@ impl ImageCache for ImageCacheImpl {
}
}
fn add_rasterization_complete_listener(
&self,
pipeline_id: PipelineId,
image_id: PendingImageId,
requested_size: DeviceIntSize,
sender: IpcSender<ImageCacheResponseMessage>,
) {
let completed = {
let mut store = self.store.lock().unwrap();
let key = (image_id, requested_size);
if !store.vector_images.contains_key(&image_id) {
warn!("Unknown image requested for rasterization for key {key:?}");
return;
};
let Some(task) = store.rasterized_vector_images.get_mut(&key) else {
warn!("Image rasterization task not found in the cache for key {key:?}");
return;
};
match task.result {
Some(_) => true,
None => {
task.listeners.push((pipeline_id, sender.clone()));
false
},
}
};
if completed {
let _ = sender.send(ImageCacheResponseMessage::VectorImageRasterizationComplete(
RasterizationCompleteResponse {
pipeline_id,
image_id,
requested_size,
},
));
}
}
fn rasterize_vector_image(
&self,
image_id: PendingImageId,
requested_size: DeviceIntSize,
) -> Option<RasterImage> {
let mut store = self.store.lock().unwrap();
let Some(vector_image) = store.vector_images.get(&image_id).cloned() else {
warn!("Unknown image id {image_id:?} requested for rasterization");
return None;
};
// This early return relies on the fact that the result of image rasterization cannot
// ever be `None`. If that were the case we would need to check whether the entry
// in the `HashMap` was `Occupied` or not.
let entry = store
.rasterized_vector_images
.entry((image_id, requested_size))
.or_default();
if let Some(result) = entry.result.as_ref() {
return Some(result.clone());
}
let store = self.store.clone();
self.thread_pool.spawn(move || {
let natural_size = vector_image.svg_tree.size().to_int_size();
let tinyskia_requested_size = {
let width = requested_size.width.try_into().unwrap_or(0);
let height = requested_size.height.try_into().unwrap_or(0);
tiny_skia::IntSize::from_wh(width, height).unwrap_or(natural_size)
};
let transform = tiny_skia::Transform::from_scale(
tinyskia_requested_size.width() as f32 / natural_size.width() as f32,
tinyskia_requested_size.height() as f32 / natural_size.height() as f32,
);
let mut pixmap = tiny_skia::Pixmap::new(
tinyskia_requested_size.width(),
tinyskia_requested_size.height(),
)
.unwrap();
resvg::render(&vector_image.svg_tree, transform, &mut pixmap.as_mut());
let bytes = pixmap.take();
let frame = ImageFrame {
delay: None,
byte_range: 0..bytes.len(),
width: tinyskia_requested_size.width(),
height: tinyskia_requested_size.height(),
};
let mut rasterized_image = RasterImage {
metadata: ImageMetadata {
width: tinyskia_requested_size.width(),
height: tinyskia_requested_size.height(),
},
format: PixelFormat::RGBA8,
frames: vec![frame],
bytes: IpcSharedMemory::from_bytes(&bytes),
id: None,
cors_status: vector_image.cors_status,
};
let listeners = {
let mut store = store.lock().unwrap();
set_webrender_image_key(&store.compositor_api, &mut rasterized_image);
store
.rasterized_vector_images
.get_mut(&(image_id, requested_size))
.map(|task| {
task.result = Some(rasterized_image);
std::mem::take(&mut task.listeners)
})
.unwrap_or_default()
};
for (pipeline_id, sender) in listeners {
let _ = sender.send(ImageCacheResponseMessage::VectorImageRasterizationComplete(
RasterizationCompleteResponse {
pipeline_id,
image_id,
requested_size,
},
));
}
});
None
}
/// Add a new listener for the given pending image id. If the image is already present,
/// the responder will still receive the expected response.
fn add_listener(&self, listener: ImageResponder) {
fn add_listener(&self, listener: ImageLoadListener) {
let mut store = self.store.lock().unwrap();
self.add_listener_with_store(&mut store, listener);
}
@ -603,6 +843,10 @@ impl ImageCache for ImageCacheImpl {
let final_url = metadata.as_ref().map(|m| m.final_url.clone());
pending_load.final_url = final_url;
pending_load.cors_status = cors_status;
pending_load.content_type = metadata
.as_ref()
.and_then(|metadata| metadata.content_type.clone())
.map(|content_type| content_type.into_inner().into());
},
(FetchResponseMsg::ProcessResponseChunk(_, data), _) => {
debug!("Got some data for {:?}", id);
@ -619,7 +863,7 @@ impl ImageCache for ImageCacheImpl {
height: info.height as u32,
};
for listener in &pending_load.listeners {
listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone()));
listener.respond(ImageResponse::MetadataLoaded(img_metadata));
}
pending_load.metadata = Some(img_metadata);
}
@ -629,17 +873,21 @@ impl ImageCache for ImageCacheImpl {
debug!("Received EOF for {:?}", key);
match result {
Ok(_) => {
let (bytes, cors_status) = {
let (bytes, cors_status, content_type) = {
let mut store = self.store.lock().unwrap();
let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap();
pending_load.result = Some(Ok(()));
debug!("Async decoding {} ({:?})", pending_load.url, key);
(pending_load.bytes.mark_complete(), pending_load.cors_status)
(
pending_load.bytes.mark_complete(),
pending_load.cors_status,
pending_load.content_type.clone(),
)
};
let local_store = self.store.clone();
self.thread_pool.spawn(move || {
let msg = decode_bytes_sync(key, &bytes, cors_status);
let msg = decode_bytes_sync(key, &bytes, cors_status, content_type);
debug!("Image decoded");
local_store.lock().unwrap().handle_decoder(msg);
});
@ -669,6 +917,8 @@ impl ImageCache for ImageCacheImpl {
placeholder_image,
placeholder_url,
compositor_api,
vector_images: HashMap::new(),
rasterized_vector_images: HashMap::new(),
})),
thread_pool: self.thread_pool.clone(),
})
@ -681,9 +931,16 @@ impl Drop for ImageCacheStore {
.completed_loads
.values()
.filter_map(|load| match &load.image_response {
ImageResponse::Loaded(image, _) => image.id.map(ImageUpdate::DeleteImage),
ImageResponse::Loaded(Image::Raster(image), _) => {
image.id.map(ImageUpdate::DeleteImage)
},
_ => None,
})
.chain(
self.rasterized_vector_images
.values()
.filter_map(|task| task.result.as_ref()?.id.map(ImageUpdate::DeleteImage)),
)
.collect();
self.compositor_api.update_images(image_updates);
}
@ -691,11 +948,11 @@ impl Drop for ImageCacheStore {
impl ImageCacheImpl {
/// Require self.store.lock() before calling.
fn add_listener_with_store(&self, store: &mut ImageCacheStore, listener: ImageResponder) {
fn add_listener_with_store(&self, store: &mut ImageCacheStore, listener: ImageLoadListener) {
let id = listener.id;
if let Some(load) = store.pending_loads.get_by_key_mut(&id) {
if let Some(ref metadata) = load.metadata {
listener.respond(ImageResponse::MetadataLoaded(metadata.clone()));
listener.respond(ImageResponse::MetadataLoaded(*metadata));
}
load.add_listener(listener);
return;

View file

@ -16,7 +16,6 @@ pub mod http_cache;
pub mod http_loader;
pub mod image_cache;
pub mod local_directory_listing;
pub mod mime_classifier;
pub mod protocols;
pub mod request_interceptor;
pub mod resource_thread;

View file

@ -14,7 +14,6 @@ mod filemanager_thread;
mod hsts;
mod http_cache;
mod http_loader;
mod mime_classifier;
mod resource_thread;
mod subresource_integrity;

View file

@ -43,7 +43,8 @@ use crate::async_runtime::HANDLE;
use crate::connector::{CACertificates, TlsConfig, create_tls_config};
use crate::cookie::ServoCookie;
use crate::fetch::methods::{
should_request_be_blocked_by_csp, should_request_be_blocked_due_to_a_bad_port,
convert_request_to_csp_request, should_request_be_blocked_by_csp,
should_request_be_blocked_due_to_a_bad_port,
};
use crate::hosts::replace_host;
use crate::http_loader::HttpState;
@ -390,14 +391,18 @@ fn connect(
RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
};
let (check_result, violations) = should_request_be_blocked_by_csp(&request, &policy_container);
if let Some(csp_request) = convert_request_to_csp_request(&request) {
let (check_result, violations) =
should_request_be_blocked_by_csp(&csp_request, &policy_container);
if !violations.is_empty() {
let _ = resource_event_sender.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
}
if !violations.is_empty() {
let _ =
resource_event_sender.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
}
if check_result == csp::CheckResult::Blocked {
return Err("Blocked by Content-Security-Policy".to_string());
if check_result == csp::CheckResult::Blocked {
return Err("Blocked by Content-Security-Policy".to_string());
}
}
let client = match create_request(

View file

@ -31,6 +31,20 @@ pub enum PixelFormat {
BGRA8,
}
// Computes image byte length, returning None if overflow occurred or the total length exceeds
// the maximum image allocation size.
pub fn compute_rgba8_byte_length_if_within_limit(width: usize, height: usize) -> Option<usize> {
// Maximum allowed image allocation size (2^31-1 ~ 2GB).
const MAX_IMAGE_BYTE_LENGTH: usize = 2147483647;
// The color components of each pixel must be stored in four sequential
// elements in the order of red, green, blue, and then alpha.
4usize
.checked_mul(width)
.and_then(|v| v.checked_mul(height))
.filter(|v| *v <= MAX_IMAGE_BYTE_LENGTH)
}
pub fn rgba8_get_rect(pixels: &[u8], size: Size2D<u64>, rect: Rect<u64>) -> Cow<[u8]> {
assert!(!rect.is_empty());
assert!(Rect::from_size(size).contains_rect(&rect));
@ -121,9 +135,8 @@ pub enum CorsStatus {
}
#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
pub struct Image {
pub width: u32,
pub height: u32,
pub struct RasterImage {
pub metadata: ImageMetadata,
pub format: PixelFormat,
pub id: Option<ImageKey>,
pub cors_status: CorsStatus,
@ -149,7 +162,7 @@ pub struct ImageFrameView<'a> {
pub height: u32,
}
impl Image {
impl RasterImage {
pub fn should_animate(&self) -> bool {
self.frames.len() > 1
}
@ -170,17 +183,17 @@ impl Image {
}
}
impl fmt::Debug for Image {
impl fmt::Debug for RasterImage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
self.width, self.height, self.format, self.id
self.metadata.width, self.metadata.height, self.format, self.id
)
}
}
#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
pub struct ImageMetadata {
pub width: u32,
pub height: u32,
@ -189,7 +202,7 @@ pub struct ImageMetadata {
// FIXME: Images must not be copied every frame. Instead we should atomically
// reference count them.
pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
if buffer.is_empty() {
return None;
}
@ -212,9 +225,11 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image>
width: rgba.width(),
height: rgba.height(),
};
Some(Image {
width: rgba.width(),
height: rgba.height(),
Some(RasterImage {
metadata: ImageMetadata {
width: rgba.width(),
height: rgba.height(),
},
format: PixelFormat::BGRA8,
frames: vec![frame],
bytes: IpcSharedMemory::from_bytes(&rgba),
@ -374,7 +389,7 @@ fn is_webp(buffer: &[u8]) -> bool {
buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
}
fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
let Ok(decoded_gif) = GifDecoder::new(Cursor::new(buffer)) else {
return None;
};
@ -430,9 +445,8 @@ fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> {
bytes.extend_from_slice(frame.buffer());
}
Some(Image {
width,
height,
Some(RasterImage {
metadata: ImageMetadata { width, height },
cors_status,
frames,
id: None,

View file

@ -12,9 +12,9 @@ use ipc_channel::ipc::{self, IpcReceiver};
use ipc_channel::router::ROUTER;
use log::debug;
use profile_traits::mem::{
MemoryReportResult, ProfilerChan, ProfilerMsg, Report, Reporter, ReporterRequest, ReportsChan,
MemoryReport, MemoryReportResult, ProfilerChan, ProfilerMsg, Report, Reporter, ReporterRequest,
ReportsChan,
};
use serde::Serialize;
use crate::system_reporter;
@ -100,31 +100,20 @@ impl Profiler {
ProfilerMsg::Report(sender) => {
let main_pid = std::process::id();
#[derive(Serialize)]
struct JsonReport {
pid: u32,
#[serde(rename = "isMainProcess")]
is_main_process: bool,
reports: Vec<Report>,
}
let reports = self.collect_reports();
// Turn the pid -> reports map into a vector and add the
// hint to find the main process.
let json_reports: Vec<JsonReport> = reports
let results: Vec<MemoryReport> = reports
.into_iter()
.map(|(pid, reports)| JsonReport {
.map(|(pid, reports)| MemoryReport {
pid,
reports,
is_main_process: pid == main_pid,
})
.collect();
let content = serde_json::to_string(&json_reports)
.unwrap_or_else(|_| "{ error: \"failed to create memory report\"}".to_owned());
let _ = sender.send(MemoryReportResult { content });
let _ = sender.send(MemoryReportResult { results });
true
},
ProfilerMsg::Exit => false,
}
}

View file

@ -249,7 +249,7 @@ impl TransmitBodyConnectHandler {
let rejection_handler = Box::new(TransmitBodyPromiseRejectionHandler {
bytes_sender,
stream: rooted_stream,
stream: Dom::from_ref(&rooted_stream.clone()),
control_sender,
});
@ -321,11 +321,12 @@ impl Callback for TransmitBodyPromiseHandler {
/// The handler of read promises rejection of body streams used in
/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct TransmitBodyPromiseRejectionHandler {
#[ignore_malloc_size_of = "Channels are hard"]
#[no_trace]
bytes_sender: IpcSender<BodyChunkResponse>,
stream: DomRoot<ReadableStream>,
stream: Dom<ReadableStream>,
#[ignore_malloc_size_of = "Channels are hard"]
#[no_trace]
control_sender: IpcSender<BodyChunkRequest>,

View file

@ -54,6 +54,7 @@ use crate::dom::dommatrix::DOMMatrix;
use crate::dom::element::{Element, cors_setting_for_element};
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::HTMLCanvasElement;
use crate::dom::htmlvideoelement::HTMLVideoElement;
use crate::dom::imagedata::ImageData;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::offscreencanvas::OffscreenCanvas;
@ -310,14 +311,15 @@ impl CanvasState {
self.origin_clean.set(false)
}
// https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean
fn is_origin_clean(&self, image: CanvasImageSource) -> bool {
match image {
CanvasImageSource::HTMLCanvasElement(canvas) => canvas.origin_is_clean(),
CanvasImageSource::OffscreenCanvas(canvas) => canvas.origin_is_clean(),
/// <https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean>
fn is_origin_clean(&self, source: CanvasImageSource) -> bool {
match source {
CanvasImageSource::HTMLImageElement(image) => {
image.same_origin(GlobalScope::entry().origin())
},
CanvasImageSource::HTMLVideoElement(video) => video.origin_is_clean(),
CanvasImageSource::HTMLCanvasElement(canvas) => canvas.origin_is_clean(),
CanvasImageSource::OffscreenCanvas(canvas) => canvas.origin_is_clean(),
CanvasImageSource::CSSStyleValue(_) => true,
}
}
@ -328,7 +330,15 @@ impl CanvasState {
cors_setting: Option<CorsSettings>,
) -> Option<snapshot::Snapshot> {
let img = match self.request_image_from_cache(url, cors_setting) {
ImageResponse::Loaded(img, _) => img,
ImageResponse::Loaded(image, _) => {
if let Some(image) = image.as_raster_image() {
image
} else {
// TODO: https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
warn!("Vector images are not supported as image source in canvas2d");
return None;
}
},
ImageResponse::PlaceholderLoaded(_, _) |
ImageResponse::None |
ImageResponse::MetadataLoaded(_) => {
@ -336,7 +346,7 @@ impl CanvasState {
},
};
let size = Size2D::new(img.width, img.height);
let size = Size2D::new(img.metadata.width, img.metadata.height);
let format = match img.format {
PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA,
PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA,
@ -430,6 +440,17 @@ impl CanvasState {
}
let result = match image {
CanvasImageSource::HTMLVideoElement(ref video) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
// Step 2. Let usability be the result of checking the usability of image.
// Step 3. If usability is bad, then return (without drawing anything).
if !video.is_usable() {
return Ok(());
}
self.draw_html_video_element(video, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
Ok(())
},
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
@ -490,6 +511,52 @@ impl CanvasState {
result
}
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
#[allow(clippy::too_many_arguments)]
fn draw_html_video_element(
&self,
video: &HTMLVideoElement,
canvas: Option<&HTMLCanvasElement>,
sx: f64,
sy: f64,
sw: Option<f64>,
sh: Option<f64>,
dx: f64,
dy: f64,
dw: Option<f64>,
dh: Option<f64>,
) {
let Some(snapshot) = video.get_current_frame_data() else {
return;
};
// Step 4. Establish the source and destination rectangles.
let video_size = snapshot.size().to_f64();
let dw = dw.unwrap_or(video_size.width);
let dh = dh.unwrap_or(video_size.height);
let sw = sw.unwrap_or(video_size.width);
let sh = sh.unwrap_or(video_size.height);
let (source_rect, dest_rect) =
self.adjust_source_dest_rects(video_size, sx, sy, sw, sh, dx, dy, dw, dh);
// Step 5. If one of the sw or sh arguments is zero, then return. Nothing is painted.
if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
return;
}
let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
snapshot.as_ipc(),
dest_rect,
source_rect,
smoothing_enabled,
));
self.mark_as_dirty(canvas);
}
#[allow(clippy::too_many_arguments)]
fn draw_offscreen_canvas(
&self,
@ -958,7 +1025,7 @@ impl CanvasState {
))
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern>
pub(crate) fn create_pattern(
&self,
global: &GlobalScope,
@ -968,7 +1035,7 @@ impl CanvasState {
) -> Fallible<Option<DomRoot<CanvasPattern>>> {
let snapshot = match image {
CanvasImageSource::HTMLImageElement(ref image) => {
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if !image.is_usable()? {
return Ok(None);
}
@ -980,10 +1047,28 @@ impl CanvasState {
})
.ok_or(Error::InvalidState)?
},
CanvasImageSource::HTMLVideoElement(ref video) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if !video.is_usable() {
return Ok(None);
}
video.get_current_frame_data().ok_or(Error::InvalidState)?
},
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
return Err(Error::InvalidState);
}
canvas.get_image_data().ok_or(Error::InvalidState)?
},
CanvasImageSource::OffscreenCanvas(ref canvas) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
return Err(Error::InvalidState);
}
canvas.get_image_data().ok_or(Error::InvalidState)?
},
CanvasImageSource::CSSStyleValue(ref value) => value

View file

@ -132,7 +132,7 @@ fn find_node_by_unique_id(
document
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
.find(|candidate| candidate.unique_id() == node_id)
.find(|candidate| candidate.unique_id(pipeline) == node_id)
})
}

View file

@ -3,24 +3,33 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::jsapi::Value;
use js::rust::{Handle, HandleObject};
use js::rust::{HandleObject, HandleValue};
use crate::dom::abortsignal::AbortSignal;
use crate::dom::bindings::codegen::Bindings::AbortControllerBinding::AbortControllerMethods;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::{CanGc, JSContext};
/// <https://dom.spec.whatwg.org/#abortcontroller>
#[dom_struct]
pub(crate) struct AbortController {
reflector_: Reflector,
/// An AbortController object has an associated signal (an AbortSignal object).
signal: Dom<AbortSignal>,
}
impl AbortController {
/// <https://dom.spec.whatwg.org/#dom-abortcontroller-abortcontroller>
fn new_inherited() -> AbortController {
// The new AbortController() constructor steps are:
// Let signal be a new AbortSignal object.
// Set thiss signal to signal.
AbortController {
reflector_: Reflector::new(),
signal: Dom::from_ref(&AbortSignal::new_inherited()),
}
}
@ -36,6 +45,12 @@ impl AbortController {
can_gc,
)
}
/// <https://dom.spec.whatwg.org/#abortcontroller-signal-abort>
fn signal_abort(&self, cx: JSContext, reason: HandleValue, can_gc: CanGc) {
// signal abort on controllers signal with reason if it is given.
self.signal.signal_abort(cx, reason, can_gc);
}
}
impl AbortControllerMethods<crate::DomTypeHolder> for AbortController {
@ -49,5 +64,15 @@ impl AbortControllerMethods<crate::DomTypeHolder> for AbortController {
}
/// <https://dom.spec.whatwg.org/#dom-abortcontroller-abort>
fn Abort(&self, _cx: JSContext, _reason: Handle<'_, Value>) {}
fn Abort(&self, cx: JSContext, reason: HandleValue, can_gc: CanGc) {
// The abort(reason) method steps are
// to signal abort on this with reason if it is given.
self.signal_abort(cx, reason, can_gc);
}
/// <https://dom.spec.whatwg.org/#dom-abortcontroller-signal>
fn Signal(&self) -> DomRoot<AbortSignal> {
// The signal getter steps are to return thiss signal.
self.signal.as_rooted()
}
}

View file

@ -0,0 +1,157 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::RefCell;
use std::mem;
use dom_struct::dom_struct;
use js::jsapi::{ExceptionStackBehavior, Heap, JS_SetPendingException};
use js::jsval::{JSVal, UndefinedValue};
use js::rust::{HandleObject, HandleValue, MutableHandleValue};
use script_bindings::inheritance::Castable;
use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
use crate::dom::bindings::error::{Error, ErrorToJsval};
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::{CanGc, JSContext};
/// <https://dom.spec.whatwg.org/#abortcontroller-api-integration>
/// TODO: implement algorithms at call point,
/// in order to integrate the abort signal with its various use cases.
#[derive(JSTraceable, MallocSizeOf)]
#[allow(dead_code)]
enum AbortAlgorithm {
/// <https://dom.spec.whatwg.org/#add-an-event-listener>
DomEventLister,
/// <https://streams.spec.whatwg.org/#readable-stream-pipe-to>
StreamPiping,
/// <https://fetch.spec.whatwg.org/#dom-global-fetch>
Fetch,
}
/// <https://dom.spec.whatwg.org/#abortsignal>
#[dom_struct]
pub(crate) struct AbortSignal {
eventtarget: EventTarget,
/// <https://dom.spec.whatwg.org/#abortsignal-abort-reason>
#[ignore_malloc_size_of = "mozjs"]
abort_reason: Heap<JSVal>,
/// <https://dom.spec.whatwg.org/#abortsignal-abort-algorithms>
abort_algorithms: RefCell<Vec<AbortAlgorithm>>,
}
impl AbortSignal {
pub(crate) fn new_inherited() -> AbortSignal {
AbortSignal {
eventtarget: EventTarget::new_inherited(),
abort_reason: Default::default(),
abort_algorithms: Default::default(),
}
}
#[allow(dead_code)]
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<AbortSignal> {
reflect_dom_object_with_proto(
Box::new(AbortSignal::new_inherited()),
global,
proto,
can_gc,
)
}
/// <https://dom.spec.whatwg.org/#abortsignal-signal-abort>
pub(crate) fn signal_abort(&self, cx: JSContext, reason: HandleValue, can_gc: CanGc) {
// If signal is aborted, then return.
if self.Aborted() {
return;
}
let abort_reason = reason.get();
// Set signals abort reason to reason if it is given;
if !abort_reason.is_undefined() {
self.abort_reason.set(abort_reason);
} else {
// otherwise to a new "AbortError" DOMException.
rooted!(in(*cx) let mut rooted_error = UndefinedValue());
Error::Abort.to_jsval(cx, &self.global(), rooted_error.handle_mut(), can_gc);
self.abort_reason.set(rooted_error.get())
}
// Let dependentSignalsToAbort be a new list.
// For each dependentSignal of signals dependent signals:
// TODO: #36936
// Run the abort steps for signal.
self.run_the_abort_steps(can_gc);
// For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal.
// TODO: #36936
}
/// <https://dom.spec.whatwg.org/#run-the-abort-steps>
fn run_the_abort_steps(&self, can_gc: CanGc) {
// For each algorithm of signals abort algorithms: run algorithm.
let algos = mem::take(&mut *self.abort_algorithms.borrow_mut());
for _algo in algos {
// TODO: match on variant and implement algo steps.
// See the various items of #34866
}
// Empty signals abort algorithms.
// Done above with `take`.
// Fire an event named abort at signal.
self.upcast::<EventTarget>()
.fire_event(atom!("abort"), can_gc);
}
/// <https://dom.spec.whatwg.org/#abortsignal-aborted>
fn aborted(&self) -> bool {
// An AbortSignal object is aborted when its abort reason is not undefined.
!self.abort_reason.get().is_undefined()
}
}
impl AbortSignalMethods<crate::DomTypeHolder> for AbortSignal {
/// <https://dom.spec.whatwg.org/#dom-abortsignal-aborted>
fn Aborted(&self) -> bool {
// The aborted getter steps are to return true if this is aborted; otherwise false.
self.aborted()
}
/// <https://dom.spec.whatwg.org/#dom-abortsignal-reason>
fn Reason(&self, _cx: JSContext, mut rval: MutableHandleValue) {
// The reason getter steps are to return thiss abort reason.
rval.set(self.abort_reason.get());
}
/// <https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted>
#[allow(unsafe_code)]
fn ThrowIfAborted(&self) {
// The throwIfAborted() method steps are to throw thiss abort reason, if this is aborted.
if self.aborted() {
let cx = GlobalScope::get_cx();
unsafe {
JS_SetPendingException(
*cx,
self.abort_reason.handle(),
ExceptionStackBehavior::Capture,
)
};
}
}
// <https://dom.spec.whatwg.org/#dom-abortsignal-onabort>
event_handler!(abort, GetOnabort, SetOnabort);
}

View file

@ -11,7 +11,7 @@ use crate::DomTypes;
use crate::dom::bindings::conversions::DerivedFrom;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::realms::InRealm;
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::CanGc;
/// Create the reflector for a new DOM object and yield ownership to the
@ -42,7 +42,16 @@ where
}
pub(crate) trait DomGlobal {
/// Returns the [relevant global] in whatever realm is currently active.
///
/// [relevant global]: https://html.spec.whatwg.org/multipage/#concept-relevant-global
fn global_(&self, realm: InRealm) -> DomRoot<GlobalScope>;
/// Returns the [relevant global] in the same realm as the callee object.
/// If you know the callee's realm is already the current realm, it is
/// more efficient to call [DomGlobal::global_] instead.
///
/// [relevant global]: https://html.spec.whatwg.org/multipage/#concept-relevant-global
fn global(&self) -> DomRoot<GlobalScope>;
}
@ -51,7 +60,8 @@ impl<T: DomGlobalGeneric<crate::DomTypeHolder>> DomGlobal for T {
<Self as DomGlobalGeneric<crate::DomTypeHolder>>::global_(self, realm)
}
fn global(&self) -> DomRoot<GlobalScope> {
<Self as DomGlobalGeneric<crate::DomTypeHolder>>::global(self)
let realm = enter_realm(self);
<Self as DomGlobalGeneric<crate::DomTypeHolder>>::global_(self, InRealm::entered(&realm))
}
}

View file

@ -10,11 +10,13 @@ use std::os::raw;
use std::ptr;
use base::id::{
BlobId, DomExceptionId, DomPointId, Index, MessagePortId, NamespaceIndex, PipelineNamespaceId,
BlobId, DomExceptionId, DomPointId, ImageBitmapId, Index, MessagePortId, NamespaceIndex,
PipelineNamespaceId,
};
use constellation_traits::{
BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface,
StructuredSerializedData, Transferrable as TransferrableInterface, TransformStreamData,
SerializableImageBitmap, StructuredSerializedData, Transferrable as TransferrableInterface,
TransformStreamData,
};
use js::gc::RootedVec;
use js::glue::{
@ -42,6 +44,7 @@ use crate::dom::blob::Blob;
use crate::dom::dompoint::DOMPoint;
use crate::dom::dompointreadonly::DOMPointReadOnly;
use crate::dom::globalscope::GlobalScope;
use crate::dom::imagebitmap::ImageBitmap;
use crate::dom::messageport::MessagePort;
use crate::dom::readablestream::ReadableStream;
use crate::dom::types::{DOMException, TransformStream};
@ -66,6 +69,7 @@ pub(super) enum StructuredCloneTags {
DomException = 0xFFFF8007,
WritableStream = 0xFFFF8008,
TransformStream = 0xFFFF8009,
ImageBitmap = 0xFFFF800A,
Max = 0xFFFFFFFF,
}
@ -76,6 +80,7 @@ impl From<SerializableInterface> for StructuredCloneTags {
SerializableInterface::DomPointReadOnly => StructuredCloneTags::DomPointReadOnly,
SerializableInterface::DomPoint => StructuredCloneTags::DomPoint,
SerializableInterface::DomException => StructuredCloneTags::DomException,
SerializableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap,
}
}
}
@ -83,6 +88,7 @@ impl From<SerializableInterface> for StructuredCloneTags {
impl From<TransferrableInterface> for StructuredCloneTags {
fn from(v: TransferrableInterface) -> Self {
match v {
TransferrableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap,
TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort,
TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream,
TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream,
@ -104,6 +110,7 @@ fn reader_for_type(
SerializableInterface::DomPointReadOnly => read_object::<DOMPointReadOnly>,
SerializableInterface::DomPoint => read_object::<DOMPoint>,
SerializableInterface::DomException => read_object::<DOMException>,
SerializableInterface::ImageBitmap => read_object::<ImageBitmap>,
}
}
@ -237,6 +244,7 @@ fn serialize_for_type(val: SerializableInterface) -> SerializeOperation {
SerializableInterface::DomPointReadOnly => try_serialize::<DOMPointReadOnly>,
SerializableInterface::DomPoint => try_serialize::<DOMPoint>,
SerializableInterface::DomException => try_serialize::<DOMException>,
SerializableInterface::ImageBitmap => try_serialize::<ImageBitmap>,
}
}
@ -264,6 +272,7 @@ fn receiver_for_type(
) -> fn(&GlobalScope, &mut StructuredDataReader<'_>, u64, RawMutableHandleObject) -> Result<(), ()>
{
match val {
TransferrableInterface::ImageBitmap => receive_object::<ImageBitmap>,
TransferrableInterface::MessagePort => receive_object::<MessagePort>,
TransferrableInterface::ReadableStream => receive_object::<ReadableStream>,
TransferrableInterface::WritableStream => receive_object::<WritableStream>,
@ -390,6 +399,7 @@ type TransferOperation = unsafe fn(
fn transfer_for_type(val: TransferrableInterface) -> TransferOperation {
match val {
TransferrableInterface::ImageBitmap => try_transfer::<ImageBitmap>,
TransferrableInterface::MessagePort => try_transfer::<MessagePort>,
TransferrableInterface::ReadableStream => try_transfer::<ReadableStream>,
TransferrableInterface::WritableStream => try_transfer::<WritableStream>,
@ -439,6 +449,7 @@ unsafe fn can_transfer_for_type(
root_from_object::<T>(*obj, cx).map(|o| Transferable::can_transfer(&*o))
}
match transferable {
TransferrableInterface::ImageBitmap => can_transfer::<ImageBitmap>(obj, cx),
TransferrableInterface::MessagePort => can_transfer::<MessagePort>(obj, cx),
TransferrableInterface::ReadableStream => can_transfer::<ReadableStream>(obj, cx),
TransferrableInterface::WritableStream => can_transfer::<WritableStream>(obj, cx),
@ -527,6 +538,10 @@ pub(crate) struct StructuredDataReader<'a> {
pub(crate) points: Option<HashMap<DomPointId, DomPoint>>,
/// A map of serialized exceptions.
pub(crate) exceptions: Option<HashMap<DomExceptionId, DomException>>,
// A map of serialized image bitmaps.
pub(crate) image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
/// A map of transferred image bitmaps.
pub(crate) transferred_image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
}
/// A data holder for transferred and serialized objects.
@ -545,6 +560,10 @@ pub(crate) struct StructuredDataWriter {
pub(crate) exceptions: Option<HashMap<DomExceptionId, DomException>>,
/// Serialized blobs.
pub(crate) blobs: Option<HashMap<BlobId, BlobImpl>>,
/// Serialized image bitmaps.
pub(crate) image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
/// Transferred image bitmaps.
pub(crate) transferred_image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
}
/// Writes a structured clone. Returns a `DataClone` error if that fails.
@ -599,6 +618,8 @@ pub(crate) fn write(
points: sc_writer.points.take(),
exceptions: sc_writer.exceptions.take(),
blobs: sc_writer.blobs.take(),
image_bitmaps: sc_writer.image_bitmaps.take(),
transferred_image_bitmaps: sc_writer.transferred_image_bitmaps.take(),
};
Ok(data)
@ -623,6 +644,8 @@ pub(crate) fn read(
points: data.points.take(),
exceptions: data.exceptions.take(),
errors: DOMErrorRecord { message: None },
image_bitmaps: data.image_bitmaps.take(),
transferred_image_bitmaps: data.transferred_image_bitmaps.take(),
};
let sc_reader_ptr = &mut sc_reader as *mut _;
unsafe {

View file

@ -5,6 +5,7 @@
use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
use dom_struct::dom_struct;
use euclid::default::Size2D;
use ipc_channel::ipc::IpcSender;
use profile_traits::ipc;
use script_bindings::inheritance::Castable;
use servo_url::ServoUrl;
@ -36,27 +37,50 @@ use crate::dom::path2d::Path2D;
use crate::dom::textmetrics::TextMetrics;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
struct DroppableCanvasRenderingContext2D {
#[no_trace]
ipc_sender: IpcSender<CanvasMsg>,
#[no_trace]
canvas_id: CanvasId,
}
impl Drop for DroppableCanvasRenderingContext2D {
fn drop(&mut self) {
if let Err(err) = self.ipc_sender.send(CanvasMsg::Close(self.canvas_id)) {
warn!("Could not close canvas: {}", err)
}
}
}
// https://html.spec.whatwg.org/multipage/#canvasrenderingcontext2d
#[dom_struct]
pub(crate) struct CanvasRenderingContext2D {
reflector_: Reflector,
canvas: HTMLCanvasElementOrOffscreenCanvas,
canvas_state: CanvasState,
droppable: DroppableCanvasRenderingContext2D,
}
impl CanvasRenderingContext2D {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new_inherited(
global: &GlobalScope,
canvas: HTMLCanvasElementOrOffscreenCanvas,
size: Size2D<u32>,
) -> CanvasRenderingContext2D {
let canvas_state =
CanvasState::new(global, Size2D::new(size.width as u64, size.height as u64));
let ipc_sender = canvas_state.get_ipc_renderer().clone();
let canvas_id = canvas_state.get_canvas_id();
CanvasRenderingContext2D {
reflector_: Reflector::new(),
canvas,
canvas_state: CanvasState::new(
global,
Size2D::new(size.width as u64, size.height as u64),
),
canvas_state,
droppable: DroppableCanvasRenderingContext2D {
ipc_sender,
canvas_id,
},
}
}
@ -689,15 +713,3 @@ impl CanvasRenderingContext2DMethods<crate::DomTypeHolder> for CanvasRenderingCo
.set_shadow_color(self.canvas.canvas(), value, can_gc)
}
}
impl Drop for CanvasRenderingContext2D {
fn drop(&mut self) {
if let Err(err) = self
.canvas_state
.get_ipc_renderer()
.send(CanvasMsg::Close(self.canvas_state.get_canvas_id()))
{
warn!("Could not close canvas: {}", err)
}
}
}

View file

@ -7,6 +7,7 @@ use std::rc::Rc;
use dom_struct::dom_struct;
use js::rust::{HandleObject, MutableHandleValue};
use net_traits::image_cache::Image;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::DataTransferBinding::DataTransferMethods;
@ -155,7 +156,10 @@ impl DataTransferMethods<crate::DomTypeHolder> for DataTransfer {
// Step 3
if let Some(image) = image.downcast::<HTMLImageElement>() {
data_store.set_bitmap(image.image_data(), x, y);
match image.image_data().as_ref().and_then(Image::as_raster_image) {
Some(image) => data_store.set_bitmap(Some(image), x, y),
None => warn!("Vector images are not yet supported in setDragImage"),
}
}
}

View file

@ -1556,11 +1556,14 @@ impl Document {
return;
}
// For a node within a text input UA shadow DOM, delegate the focus target into its shadow host.
// TODO: This focus delegation should be done with shadow DOM delegateFocus attribute.
let target_el = el.find_focusable_shadow_host_if_necessary();
self.begin_focus_transaction();
// Try to focus `el`. If it's not focusable, focus the document
// instead.
// Try to focus `el`. If it's not focusable, focus the document instead.
self.request_focus(None, FocusInitiator::Local, can_gc);
self.request_focus(Some(&*el), FocusInitiator::Local, can_gc);
self.request_focus(target_el.as_deref(), FocusInitiator::Local, can_gc);
}
let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
@ -2374,6 +2377,9 @@ impl Document {
let mut cancel_state = event.get_cancel_state();
// https://w3c.github.io/uievents/#keys-cancelable-keys
// it MUST prevent the respective beforeinput and input
// (and keypress if supported) events from being generated
// TODO: keypress should be deprecated and superceded by beforeinput
if keyboard_event.state == KeyState::Down &&
is_character_value_key(&(keyboard_event.key)) &&
!keyboard_event.is_composing &&

View file

@ -13,7 +13,7 @@ use std::str::FromStr;
use std::{fmt, mem};
use content_security_policy as csp;
use cssparser::match_ignore_ascii_case;
use cssparser::{Parser as CssParser, ParserInput as CssParserInput, match_ignore_ascii_case};
use devtools_traits::AttrInfo;
use dom_struct::dom_struct;
use embedder_traits::InputMethodType;
@ -36,6 +36,8 @@ use style::applicable_declarations::ApplicableDeclarationBlock;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::context::QuirksMode;
use style::invalidation::element::restyle_hints::RestyleHint;
use style::media_queries::MediaList;
use style::parser::ParserContext as CssParserContext;
use style::properties::longhands::{
self, background_image, border_spacing, font_family, font_size,
};
@ -50,13 +52,14 @@ use style::selector_parser::{
};
use style::shared_lock::{Locked, SharedRwLock};
use style::stylesheets::layer_rule::LayerOrder;
use style::stylesheets::{CssRuleType, UrlExtraData};
use style::stylesheets::{CssRuleType, Origin as CssOrigin, UrlExtraData};
use style::values::computed::Overflow;
use style::values::generics::NonNegative;
use style::values::generics::position::PreferredRatio;
use style::values::generics::ratio::Ratio;
use style::values::{AtomIdent, AtomString, CSSFloat, computed, specified};
use style::{ArcSlice, CaseSensitivityExt, dom_apis, thread_state};
use style_traits::ParsingMode as CssParsingMode;
use stylo_atoms::Atom;
use stylo_dom::ElementState;
use xml5ever::serialize::TraversalScope::{
@ -118,7 +121,7 @@ use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers};
use crate::dom::htmlformelement::FormControlElementHelpers;
use crate::dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers};
use crate::dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers, SizePresentationalHint};
use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
use crate::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
@ -781,6 +784,33 @@ impl Element {
.registered_intersection_observers
.retain(|reg_obs| *reg_obs.observer != *observer)
}
/// <https://html.spec.whatwg.org/multipage/#matches-the-environment>
pub(crate) fn matches_environment(&self, media_query: &str) -> bool {
let document = self.owner_document();
let quirks_mode = document.quirks_mode();
let document_url_data = UrlExtraData(document.url().get_arc());
// FIXME(emilio): This should do the same that we do for other media
// lists regarding the rule type and such, though it doesn't really
// matter right now...
//
// Also, ParsingMode::all() is wrong, and should be DEFAULT.
let context = CssParserContext::new(
CssOrigin::Author,
&document_url_data,
Some(CssRuleType::Style),
CssParsingMode::all(),
quirks_mode,
/* namespaces = */ Default::default(),
None,
None,
);
let mut parser_input = CssParserInput::new(media_query);
let mut parser = CssParser::new(&mut parser_input);
let media_list = MediaList::parse(&context, &mut parser);
let result = media_list.evaluate(document.window().layout().device(), quirks_mode);
result
}
}
/// <https://dom.spec.whatwg.org/#valid-shadow-host-name>
@ -1276,6 +1306,47 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> {
PropertyDeclaration::PaddingRight(cellpadding),
));
}
// https://html.spec.whatwg.org/multipage/#the-hr-element-2
if let Some(size_info) = self
.downcast::<HTMLHRElement>()
.and_then(|hr_element| hr_element.get_size_info())
{
match size_info {
SizePresentationalHint::SetHeightTo(height) => {
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::Height(height),
));
},
SizePresentationalHint::SetAllBorderWidthValuesTo(border_width) => {
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::BorderLeftWidth(border_width.clone()),
));
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::BorderRightWidth(border_width.clone()),
));
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::BorderTopWidth(border_width.clone()),
));
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::BorderBottomWidth(border_width),
));
},
SizePresentationalHint::SetBottomBorderWidthToZero => {
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::BorderBottomWidth(
specified::border::BorderSideWidth::from_px(0.),
),
));
},
}
}
}
fn get_span(self) -> Option<u32> {
@ -1632,6 +1703,27 @@ impl Element {
)
}
/// Returns the focusable shadow host if this is a text control inner editor.
/// This is a workaround for the focus delegation of shadow DOM and should be
/// used only to delegate focusable inner editor of [HTMLInputElement] and
/// [HTMLTextAreaElement].
pub(crate) fn find_focusable_shadow_host_if_necessary(&self) -> Option<DomRoot<Element>> {
if self.is_focusable_area() {
Some(DomRoot::from_ref(self))
} else if self.upcast::<Node>().is_text_control_inner_editor() {
let containing_shadow_host = self.containing_shadow_root().map(|root| root.Host());
assert!(
containing_shadow_host
.as_ref()
.is_some_and(|e| e.is_focusable_area()),
"Containing shadow host is not focusable"
);
containing_shadow_host
} else {
None
}
}
pub(crate) fn is_actually_disabled(&self) -> bool {
let node = self.upcast::<Node>();
match node.type_id() {
@ -4430,7 +4522,9 @@ impl SelectorsElement for SelectorWrapper<'_> {
// a string containing commas (separating each language tag in
// a list) but the pseudo-class instead should be parsing and
// storing separate <ident> or <string>s for each language tag.
NonTSPseudoClass::Lang(ref lang) => extended_filtering(&self.get_lang(), lang),
NonTSPseudoClass::Lang(ref lang) => {
extended_filtering(&self.upcast::<Node>().get_lang().unwrap_or_default(), lang)
},
NonTSPseudoClass::ReadOnly => {
!Element::state(self).contains(NonTSPseudoClass::ReadWrite.state_flag())
@ -4770,24 +4864,7 @@ impl Element {
}
}
// https://html.spec.whatwg.org/multipage/#language
pub(crate) fn get_lang(&self) -> String {
self.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::Yes)
.filter_map(|node| {
node.downcast::<Element>().and_then(|el| {
el.get_attribute(&ns!(xml), &local_name!("lang"))
.or_else(|| el.get_attribute(&ns!(), &local_name!("lang")))
.map(|attr| String::from(attr.Value()))
})
// TODO: Check meta tags for a pragma-set default language
// TODO: Check HTTP Content-Language header
})
.next()
.unwrap_or(String::new())
}
pub(crate) fn state(&self) -> ElementState {
pub fn state(&self) -> ElementState {
self.state.get()
}

View file

@ -74,6 +74,7 @@ use super::bindings::codegen::Bindings::WebGPUBinding::GPUDeviceLostReason;
use super::bindings::error::Fallible;
use super::bindings::trace::{HashMapTracedValues, RootedTraceableBox};
use super::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use super::transformstream::CrossRealmTransform;
use crate::dom::bindings::cell::{DomRefCell, RefMut};
use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSource_Binding::EventSourceMethods;
@ -458,13 +459,9 @@ pub(crate) struct ManagedMessagePort {
/// Whether the port has been closed by script in this global,
/// so it can be removed.
explicitly_closed: bool,
/// Note: it may seem strange to use a pair of options, versus for example an enum.
/// But it looks like tranform streams will require both of those in their transfer.
/// This will be resolved when we reach that point of the implementation.
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
cross_realm_transform_readable: Option<CrossRealmTransformReadable>,
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
cross_realm_transform_writable: Option<CrossRealmTransformWritable>,
/// The handler for `message` or `messageerror` used in the cross realm transform,
/// if any was setup with this port.
cross_realm_transform: Option<CrossRealmTransform>,
}
/// State representing whether this global is currently managing broadcast channels.
@ -1345,7 +1342,9 @@ impl GlobalScope {
unreachable!("Cross realm transform readable must match a managed port");
};
managed_port.cross_realm_transform_readable = Some(cross_realm_transform_readable.clone());
managed_port.cross_realm_transform = Some(CrossRealmTransform::Readable(
cross_realm_transform_readable.clone(),
));
}
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
@ -1368,7 +1367,9 @@ impl GlobalScope {
unreachable!("Cross realm transform writable must match a managed port");
};
managed_port.cross_realm_transform_writable = Some(cross_realm_transform_writable.clone());
managed_port.cross_realm_transform = Some(CrossRealmTransform::Writable(
cross_realm_transform_writable.clone(),
));
}
/// Custom routing logic, followed by the task steps of
@ -1380,8 +1381,7 @@ impl GlobalScope {
can_gc: CanGc,
) {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut cross_realm_transform_readable = None);
rooted!(in(*cx) let mut cross_realm_transform_writable = None);
rooted!(in(*cx) let mut cross_realm_transform = None);
let should_dispatch = if let MessagePortState::Managed(_id, message_ports) =
&mut *self.message_port_state.borrow_mut()
@ -1399,10 +1399,7 @@ impl GlobalScope {
let to_dispatch = port_impl.handle_incoming(task).map(|to_dispatch| {
(DomRoot::from_ref(&*managed_port.dom_port), to_dispatch)
});
cross_realm_transform_readable
.set(managed_port.cross_realm_transform_readable.clone());
cross_realm_transform_writable
.set(managed_port.cross_realm_transform_writable.clone());
cross_realm_transform.set(managed_port.cross_realm_transform.clone());
to_dispatch
} else {
panic!("managed-port has no port-impl.");
@ -1429,10 +1426,6 @@ impl GlobalScope {
// Re-ordered because we need to pass it to `structuredclone::read`.
rooted!(in(*cx) let mut message_clone = UndefinedValue());
// Note: if this port is used to transfer a stream, we handle the events in Rust.
let has_cross_realm_tansform = cross_realm_transform_readable.is_some() ||
cross_realm_transform_writable.is_some();
let realm = enter_realm(self);
let comp = InRealm::Entered(&realm);
@ -1447,26 +1440,28 @@ impl GlobalScope {
// if any, maintaining their relative order.
// Note: both done in `structuredclone::read`.
if let Ok(ports) = structuredclone::read(self, data, message_clone.handle_mut()) {
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
if let Some(transform) = cross_realm_transform_readable.as_ref() {
transform.handle_message(
cx,
self,
&dom_port,
message_clone.handle(),
comp,
can_gc,
);
}
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
if let Some(transform) = cross_realm_transform_writable.as_ref() {
transform.handle_message(cx, self, message_clone.handle(), comp, can_gc);
}
if !has_cross_realm_tansform {
// Note: if this port is used to transfer a stream, we handle the events in Rust.
if let Some(transform) = cross_realm_transform.as_ref() {
match transform {
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
CrossRealmTransform::Readable(readable) => {
readable.handle_message(
cx,
self,
&dom_port,
message_clone.handle(),
comp,
can_gc,
);
},
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
CrossRealmTransform::Writable(writable) => {
writable.handle_message(cx, self, message_clone.handle(), comp, can_gc);
},
}
} else {
// Fire an event named message at messageEventTarget,
// using MessageEvent,
// with the data attribute initialized to messageClone
@ -1481,25 +1476,24 @@ impl GlobalScope {
can_gc,
);
}
} else if let Some(transform) = cross_realm_transform.as_ref() {
match transform {
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
CrossRealmTransform::Readable(readable) => {
readable.handle_error(cx, self, &dom_port, comp, can_gc);
},
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
CrossRealmTransform::Writable(writable) => {
writable.handle_error(cx, self, &dom_port, comp, can_gc);
},
}
} else {
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
if let Some(transform) = cross_realm_transform_readable.as_ref() {
transform.handle_error(cx, self, &dom_port, comp, can_gc);
}
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
if let Some(transform) = cross_realm_transform_writable.as_ref() {
transform.handle_error(cx, self, &dom_port, comp, can_gc);
}
if !has_cross_realm_tansform {
// If this throws an exception, catch it,
// fire an event named messageerror at messageEventTarget,
// using MessageEvent, and then return.
MessageEvent::dispatch_error(message_event_target, self, can_gc);
}
// If this throws an exception, catch it,
// fire an event named messageerror at messageEventTarget,
// using MessageEvent, and then return.
MessageEvent::dispatch_error(message_event_target, self, can_gc);
}
}
}
@ -1689,8 +1683,7 @@ impl GlobalScope {
dom_port: Dom::from_ref(dom_port),
pending: true,
explicitly_closed: false,
cross_realm_transform_readable: None,
cross_realm_transform_writable: None,
cross_realm_transform: None,
},
);
@ -1713,8 +1706,7 @@ impl GlobalScope {
dom_port: Dom::from_ref(dom_port),
pending: false,
explicitly_closed: false,
cross_realm_transform_readable: None,
cross_realm_transform_writable: None,
cross_realm_transform: None,
},
);
let _ = self.script_to_constellation_chan().send(
@ -2990,13 +2982,13 @@ impl GlobalScope {
return p;
}
if let Some(snapshot) = canvas.get_image_data() {
let size = snapshot.size().cast();
let image_bitmap =
ImageBitmap::new(self, size.width, size.height, can_gc).unwrap();
image_bitmap.set_bitmap_data(snapshot.to_vec());
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
match canvas.get_image_data() {
Some(snapshot) => {
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
},
None => p.reject_error(Error::InvalidState, can_gc),
}
p
},
@ -3007,13 +2999,13 @@ impl GlobalScope {
return p;
}
if let Some(snapshot) = canvas.get_image_data() {
let size = snapshot.size().cast();
let image_bitmap =
ImageBitmap::new(self, size.width, size.height, can_gc).unwrap();
image_bitmap.set_bitmap_data(snapshot.to_vec());
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
match canvas.get_image_data() {
Some(snapshot) => {
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
},
None => p.reject_error(Error::InvalidState, can_gc),
}
p
},

View file

@ -46,6 +46,8 @@ pub enum Area {
bottom_right: (f32, f32),
},
Polygon {
/// Stored as a flat array of coordinates
/// e.g. [x1, y1, x2, y2, x3, y3] for a triangle
points: Vec<f32>,
},
}
@ -203,8 +205,28 @@ impl Area {
p.y >= top_left.1
},
//TODO polygon hit_test
_ => false,
Area::Polygon { ref points } => {
// Ray-casting algorithm to determine if point is inside polygon
// https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
let mut inside = false;
debug_assert!(points.len() % 2 == 0);
let vertices = points.len() / 2;
for i in 0..vertices {
let next_i = if i + 1 == vertices { 0 } else { i + 1 };
let xi = points[2 * i];
let yi = points[2 * i + 1];
let xj = points[2 * next_i];
let yj = points[2 * next_i + 1];
if (yi > p.y) != (yj > p.y) && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi {
inside = !inside;
}
}
inside
},
}
}

View file

@ -16,7 +16,7 @@ use html5ever::{LocalName, Prefix, local_name, ns};
use image::codecs::jpeg::JpegEncoder;
use image::codecs::png::PngEncoder;
use image::codecs::webp::WebPEncoder;
use image::{ColorType, ImageEncoder};
use image::{ColorType, ImageEncoder, ImageError};
#[cfg(feature = "webgpu")]
use ipc_channel::ipc::{self as ipcchan};
use js::error::throw_type_error;
@ -362,7 +362,13 @@ impl HTMLCanvasElement {
Some(context) => context.get_image_data(),
None => {
let size = self.get_size();
if size.width == 0 || size.height == 0 {
if size.is_empty() ||
pixels::compute_rgba8_byte_length_if_within_limit(
size.width as usize,
size.height as usize,
)
.is_none()
{
None
} else {
Some(Snapshot::cleared(size.cast()))
@ -385,7 +391,7 @@ impl HTMLCanvasElement {
quality: Option<f64>,
snapshot: &Snapshot,
encoder: &mut W,
) {
) -> Result<(), ImageError> {
// We can't use self.Width() or self.Height() here, since the size of the canvas
// may have changed since the snapshot was created. Truncating the dimensions to a
// u32 can't panic, since the data comes from a canvas which is always smaller than
@ -398,9 +404,7 @@ impl HTMLCanvasElement {
EncodedImageType::Png => {
// FIXME(nox): https://github.com/image-rs/image-png/issues/86
// FIXME(nox): https://github.com/image-rs/image-png/issues/87
PngEncoder::new(encoder)
.write_image(canvas_data, width, height, ColorType::Rgba8)
.unwrap();
PngEncoder::new(encoder).write_image(canvas_data, width, height, ColorType::Rgba8)
},
EncodedImageType::Jpeg => {
let jpeg_encoder = if let Some(quality) = quality {
@ -418,16 +422,16 @@ impl HTMLCanvasElement {
JpegEncoder::new(encoder)
};
jpeg_encoder
.write_image(canvas_data, width, height, ColorType::Rgba8)
.unwrap();
jpeg_encoder.write_image(canvas_data, width, height, ColorType::Rgba8)
},
EncodedImageType::Webp => {
// No quality support because of https://github.com/image-rs/image/issues/1984
WebPEncoder::new_lossless(encoder)
.write_image(canvas_data, width, height, ColorType::Rgba8)
.unwrap();
WebPEncoder::new_lossless(encoder).write_image(
canvas_data,
width,
height,
ColorType::Rgba8,
)
},
}
}
@ -516,17 +520,22 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
mime_type: DOMString,
quality: HandleValue,
) -> Fallible<USVString> {
// Step 1.
// Step 1: If this canvas element's bitmap's origin-clean flag is set to false,
// then throw a "SecurityError" DOMException.
if !self.origin_is_clean() {
return Err(Error::Security);
}
// Step 2.
// Step 2: If this canvas element's bitmap has no pixels (i.e. either its
// horizontal dimension or its vertical dimension is zero), then return the string
// "data:,". (This is the shortest data: URL; it represents the empty string in a
// text/plain resource.)
if self.Width() == 0 || self.Height() == 0 {
return Ok(USVString("data:,".into()));
}
// Step 3.
// Step 3: Let file be a serialization of this canvas element's bitmap as a file,
// passing type and quality if given.
let Some(mut snapshot) = self.get_image_data() else {
return Ok(USVString("data:,".into()));
};
@ -551,12 +560,20 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
&base64::engine::general_purpose::STANDARD,
);
self.encode_for_mime_type(
&image_type,
Self::maybe_quality(quality),
&snapshot,
&mut encoder,
);
if self
.encode_for_mime_type(
&image_type,
Self::maybe_quality(quality),
&snapshot,
&mut encoder,
)
.is_err()
{
// Step 4. If file is null, then return "data:,".
return Ok(USVString("data:,".into()));
}
// Step 5. Return a data: URL representing file. [RFC2397]
encoder.into_inner();
Ok(USVString(url))
}
@ -604,26 +621,37 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
return error!("Expected blob callback, but found none!");
};
if let Some(mut snapshot) = result {
snapshot.transform(
snapshot::AlphaMode::Transparent{ premultiplied: false },
snapshot::PixelFormat::RGBA
);
// Step 4.1
// If result is non-null, then set result to a serialization of result as a file with
// type and quality if given.
let mut encoded: Vec<u8> = vec![];
this.encode_for_mime_type(&image_type, quality, &snapshot, &mut encoded);
let blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
// Step 4.2.1 Set result to a new Blob object, created in the relevant realm of this canvas element
let blob = Blob::new(&this.global(), blob_impl, CanGc::note());
// Step 4.2.2 Invoke callback with « result » and "report".
let _ = callback.Call__(Some(&blob), ExceptionHandling::Report, CanGc::note());
} else {
let Some(mut snapshot) = result else {
let _ = callback.Call__(None, ExceptionHandling::Report, CanGc::note());
}
return;
};
snapshot.transform(
snapshot::AlphaMode::Transparent{ premultiplied: false },
snapshot::PixelFormat::RGBA
);
// Step 4.1: If result is non-null, then set result to a serialization of
// result as a file with type and quality if given.
// Step 4.2: Queue an element task on the canvas blob serialization task
// source given the canvas element to run these steps:
let mut encoded: Vec<u8> = vec![];
let blob_impl;
let blob;
let result = match this.encode_for_mime_type(&image_type, quality, &snapshot, &mut encoded) {
Ok(..) => {
// Step 4.2.1: If result is non-null, then set result to a new Blob
// object, created in the relevant realm of this canvas element,
// representing result. [FILEAPI]
blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
blob = Blob::new(&this.global(), blob_impl, CanGc::note());
Some(&*blob)
}
Err(..) => None,
};
// Step 4.2.2: Invoke callback with « result » and "report".
let _ = callback.Call__(result, ExceptionHandling::Report, CanGc::note());
}));
Ok(())

View file

@ -2,11 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::str::FromStr;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor;
use style::values::generics::NonNegative;
use style::values::specified::border::BorderSideWidth;
use style::values::specified::length::Size;
use style::values::specified::{LengthPercentage, NoCalcLength};
use crate::dom::bindings::codegen::Bindings::HTMLHRElementBinding::HTMLHRElementMethods;
use crate::dom::bindings::inheritance::Castable;
@ -65,6 +71,18 @@ impl HTMLHRElementMethods<crate::DomTypeHolder> for HTMLHRElement {
// https://html.spec.whatwg.org/multipage/#dom-hr-color
make_legacy_color_setter!(SetColor, "color");
// https://html.spec.whatwg.org/multipage/#dom-hr-noshade
make_bool_getter!(NoShade, "noshade");
// https://html.spec.whatwg.org/multipage/#dom-hr-noshade
make_bool_setter!(SetNoShade, "noshade");
// https://html.spec.whatwg.org/multipage/#dom-hr-size
make_getter!(Size, "size");
// https://html.spec.whatwg.org/multipage/#dom-hr-size
make_dimension_setter!(SetSize, "size");
// https://html.spec.whatwg.org/multipage/#dom-hr-width
make_getter!(Width, "width");
@ -72,9 +90,20 @@ impl HTMLHRElementMethods<crate::DomTypeHolder> for HTMLHRElement {
make_dimension_setter!(SetWidth, "width");
}
/// The result of applying the the presentational hint for the `size` attribute.
///
/// (This attribute can mean different things depending on its value and other attributes)
#[allow(clippy::enum_variant_names)]
pub(crate) enum SizePresentationalHint {
SetHeightTo(Size),
SetAllBorderWidthValuesTo(BorderSideWidth),
SetBottomBorderWidthToZero,
}
pub(crate) trait HTMLHRLayoutHelpers {
fn get_color(self) -> Option<AbsoluteColor>;
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_size_info(self) -> Option<SizePresentationalHint>;
}
impl HTMLHRLayoutHelpers for LayoutDom<'_, HTMLHRElement> {
@ -92,6 +121,35 @@ impl HTMLHRLayoutHelpers for LayoutDom<'_, HTMLHRElement> {
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
fn get_size_info(self) -> Option<SizePresentationalHint> {
// https://html.spec.whatwg.org/multipage/#the-hr-element-2
let element = self.upcast::<Element>();
let size_value = element
.get_attr_val_for_layout(&ns!(), &local_name!("size"))
.and_then(|value| usize::from_str(value).ok())
.filter(|value| *value != 0)?;
let hint = if element
.get_attr_for_layout(&ns!(), &local_name!("color"))
.is_some() ||
element
.get_attr_for_layout(&ns!(), &local_name!("noshade"))
.is_some()
{
SizePresentationalHint::SetAllBorderWidthValuesTo(BorderSideWidth::from_px(
size_value as f32 / 2.0,
))
} else if size_value == 1 {
SizePresentationalHint::SetBottomBorderWidthToZero
} else {
SizePresentationalHint::SetHeightTo(Size::LengthPercentage(NonNegative(
LengthPercentage::Length(NoCalcLength::from_px((size_value - 2) as f32)),
)))
};
Some(hint)
}
}
impl VirtualMethods for HTMLHRElement {

View file

@ -223,6 +223,7 @@ impl HTMLIFrameElement {
old_pipeline_id,
sandbox: sandboxed,
viewport_details,
theme: window.theme(),
};
window
.as_global_scope()
@ -238,6 +239,7 @@ impl HTMLIFrameElement {
opener: None,
load_data,
viewport_details,
theme: window.theme(),
};
self.pipeline_id.set(Some(new_pipeline_id));
@ -250,6 +252,7 @@ impl HTMLIFrameElement {
old_pipeline_id,
sandbox: sandboxed,
viewport_details,
theme: window.theme(),
};
window
.as_global_scope()

View file

@ -20,8 +20,8 @@ use js::rust::HandleObject;
use mime::{self, Mime};
use net_traits::http_status::HttpStatus;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
PendingImageId, UsePlaceholder,
Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
ImageResponse, PendingImageId, UsePlaceholder,
};
use net_traits::request::{Destination, Initiator, RequestId};
use net_traits::{
@ -29,14 +29,13 @@ use net_traits::{
ResourceFetchTiming, ResourceTimingType,
};
use num_traits::ToPrimitive;
use pixels::{CorsStatus, Image, ImageMetadata};
use pixels::{CorsStatus, ImageMetadata};
use servo_url::ServoUrl;
use servo_url::origin::MutableOrigin;
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_integer, parse_length};
use style::context::QuirksMode;
use style::media_queries::MediaList;
use style::parser::ParserContext;
use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
use style::stylesheets::{CssRuleType, Origin};
use style::values::specified::AbsoluteLength;
use style::values::specified::length::{Length, NoCalcLength};
use style::values::specified::source_size_list::SourceSizeList;
@ -146,9 +145,8 @@ struct ImageRequest {
parsed_url: Option<ServoUrl>,
source_url: Option<USVString>,
blocker: DomRefCell<Option<LoadBlocker>>,
#[conditional_malloc_size_of]
#[no_trace]
image: Option<Arc<Image>>,
image: Option<Image>,
#[no_trace]
metadata: Option<ImageMetadata>,
#[no_trace]
@ -177,7 +175,8 @@ impl HTMLImageElement {
pub(crate) fn is_usable(&self) -> Fallible<bool> {
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
if let Some(image) = &self.current_request.borrow().image {
if image.width == 0 || image.height == 0 {
let intrinsic_size = image.metadata();
if intrinsic_size.width == 0 || intrinsic_size.height == 0 {
return Ok(false);
}
}
@ -191,7 +190,7 @@ impl HTMLImageElement {
}
}
pub(crate) fn image_data(&self) -> Option<Arc<Image>> {
pub(crate) fn image_data(&self) -> Option<Image> {
self.current_request.borrow().image.clone()
}
}
@ -341,10 +340,12 @@ impl HTMLImageElement {
is_placeholder,
}) => {
if is_placeholder {
self.process_image_response(
ImageResponse::PlaceholderLoaded(image, url),
can_gc,
)
if let Some(raster_image) = image.as_raster_image() {
self.process_image_response(
ImageResponse::PlaceholderLoaded(raster_image, url),
can_gc,
)
}
} else {
self.process_image_response(ImageResponse::Loaded(image, url), can_gc)
}
@ -403,7 +404,7 @@ impl HTMLImageElement {
window
.image_cache()
.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
}
fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
@ -448,11 +449,8 @@ impl HTMLImageElement {
}
// Steps common to when an image has been loaded.
fn handle_loaded_image(&self, image: Arc<Image>, url: ServoUrl, can_gc: CanGc) {
self.current_request.borrow_mut().metadata = Some(ImageMetadata {
height: image.height,
width: image.width,
});
fn handle_loaded_image(&self, image: Image, url: ServoUrl, can_gc: CanGc) {
self.current_request.borrow_mut().metadata = Some(image.metadata());
self.current_request.borrow_mut().final_url = Some(url);
self.current_request.borrow_mut().image = Some(image);
self.current_request.borrow_mut().state = State::CompletelyAvailable;
@ -471,7 +469,7 @@ impl HTMLImageElement {
(true, false)
},
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
self.handle_loaded_image(image, url, can_gc);
self.handle_loaded_image(Image::Raster(image), url, can_gc);
(false, true)
},
(ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
@ -483,7 +481,7 @@ impl HTMLImageElement {
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
self.image_request.set(ImageRequestPhase::Current);
self.handle_loaded_image(image, url, can_gc);
self.handle_loaded_image(Image::Raster(image), url, can_gc);
(false, true)
},
(ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
@ -536,11 +534,15 @@ impl HTMLImageElement {
can_gc: CanGc,
) {
match image {
ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
self.pending_request.borrow_mut().metadata = Some(ImageMetadata {
height: image.height,
width: image.width,
});
ImageResponse::Loaded(image, url) => {
self.pending_request.borrow_mut().metadata = Some(image.metadata());
self.pending_request.borrow_mut().final_url = Some(url);
self.pending_request.borrow_mut().image = Some(image);
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
},
ImageResponse::PlaceholderLoaded(image, url) => {
let image = Image::Raster(image);
self.pending_request.borrow_mut().metadata = Some(image.metadata());
self.pending_request.borrow_mut().final_url = Some(url);
self.pending_request.borrow_mut().image = Some(image);
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
@ -675,7 +677,7 @@ impl HTMLImageElement {
// Step 4.6
if let Some(x) = element.get_attribute(&ns!(), &local_name!("media")) {
if !self.matches_environment(x.value().to_string()) {
if !elem.matches_environment(&x.value()) {
continue;
}
}
@ -719,33 +721,6 @@ impl HTMLImageElement {
result
}
/// <https://html.spec.whatwg.org/multipage/#matches-the-environment>
fn matches_environment(&self, media_query: String) -> bool {
let document = self.owner_document();
let quirks_mode = document.quirks_mode();
let document_url_data = UrlExtraData(document.url().get_arc());
// FIXME(emilio): This should do the same that we do for other media
// lists regarding the rule type and such, though it doesn't really
// matter right now...
//
// Also, ParsingMode::all() is wrong, and should be DEFAULT.
let context = ParserContext::new(
Origin::Author,
&document_url_data,
Some(CssRuleType::Style),
ParsingMode::all(),
quirks_mode,
/* namespaces = */ Default::default(),
None,
None,
);
let mut parserInput = ParserInput::new(&media_query);
let mut parser = Parser::new(&mut parserInput);
let media_list = MediaList::parse(&context, &mut parser);
let result = media_list.evaluate(document.window().layout().device(), quirks_mode);
result
}
/// <https://html.spec.whatwg.org/multipage/#normalise-the-source-densities>
fn normalise_source_densities(&self, source_set: &mut SourceSet, width: Option<Length>) {
// Step 1
@ -1020,10 +995,7 @@ impl HTMLImageElement {
// set on this element.
self.generation.set(self.generation.get() + 1);
// Step 6.3
let metadata = ImageMetadata {
height: image.height,
width: image.width,
};
let metadata = image.metadata();
// Step 6.3.2 abort requests
self.abort_request(
State::CompletelyAvailable,
@ -1033,7 +1005,7 @@ impl HTMLImageElement {
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
let mut current_request = self.current_request.borrow_mut();
current_request.final_url = Some(img_url.clone());
current_request.image = Some(image.clone());
current_request.image = Some(image);
current_request.metadata = Some(metadata);
// Step 6.3.6
current_request.current_pixel_density = pixel_density;
@ -1360,7 +1332,7 @@ impl HTMLImageElement {
pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
if let Some(ref image) = self.current_request.borrow().image {
return image.cors_status == CorsStatus::Safe;
return image.cors_status() == CorsStatus::Safe;
}
self.current_request
@ -1432,7 +1404,7 @@ impl MicrotaskRunnable for ImageElementMicrotask {
pub(crate) trait LayoutHTMLImageElementHelpers {
fn image_url(self) -> Option<ServoUrl>;
fn image_density(self) -> Option<f64>;
fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>);
fn image_data(self) -> (Option<Image>, Option<ImageMetadata>);
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
@ -1449,12 +1421,9 @@ impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
self.current_request().parsed_url.clone()
}
fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>) {
fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
let current_request = self.current_request();
(
current_request.image.clone(),
current_request.metadata.clone(),
)
(current_request.image.clone(), current_request.metadata)
}
fn image_density(self) -> Option<f64> {

View file

@ -101,6 +101,29 @@ const DEFAULT_RESET_VALUE: &str = "Reset";
const PASSWORD_REPLACEMENT_CHAR: char = '●';
const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
/// Contains reference to text control inner editor and placeholder container element in the UA
/// shadow tree for `<input type=text>`. The following is the structure of the shadow tree.
///
/// ```
/// <input type="text">
/// #shadow-root
/// <div id="inner-container">
/// <div id="input-editor"></div>
/// <div id="input-placeholder"></div>
/// </div>
/// </input>
/// ```
// TODO(stevennovaryo): We are trying to use CSS to mimic Chrome and Firefox's layout for the <input> element.
// But, this could be slower in performance and does have some discrepancies. For example,
// they would try to vertically align <input> text baseline with the baseline of other
// TextNode within an inline flow. Another example is the horizontal scroll.
struct InputTypeTextShadowTree {
text_container: Dom<HTMLDivElement>,
placeholder_container: Dom<HTMLDivElement>,
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
/// Contains references to the elements in the shadow tree for `<input type=range>`.
@ -111,10 +134,49 @@ struct InputTypeColorShadowTree {
color_value: Dom<HTMLDivElement>,
}
// FIXME: These styles should be inside UA stylesheet, but it is not possible without internal pseudo element support.
const TEXT_TREE_STYLE: &str = "
#input-editor::selection {
background: rgba(176, 214, 255, 1.0);
color: black;
}
:host:not(:placeholder-shown) #input-placeholder {
visibility: hidden !important
}
#input-editor {
overflow-wrap: normal;
pointer-events: auto;
}
#input-container {
position: relative;
height: 100%;
pointer-events: none;
display: flex;
}
#input-editor, #input-placeholder {
white-space: pre;
margin-block: auto !important;
inset-block: 0 !important;
block-size: fit-content !important;
}
#input-placeholder {
overflow: hidden !important;
position: absolute !important;
color: grey;
pointer-events: none !important;
}
";
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[non_exhaustive]
enum ShadowTree {
Text(InputTypeTextShadowTree),
Color(InputTypeColorShadowTree),
// TODO: Add shadow trees for other input types (range etc) here
}
@ -1071,7 +1133,7 @@ impl HTMLInputElement {
ShadowRootMode::Closed,
false,
false,
false,
true,
SlotAssignmentMode::Manual,
can_gc,
)
@ -1079,6 +1141,92 @@ impl HTMLInputElement {
})
}
fn create_text_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let shadow_root = self.shadow_root(can_gc);
Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
let inner_container =
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
inner_container
.upcast::<Element>()
.SetId(DOMString::from("input-container"), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(inner_container.upcast::<Node>(), can_gc)
.unwrap();
let placeholder_container =
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
placeholder_container
.upcast::<Element>()
.SetId(DOMString::from("input-placeholder"), can_gc);
inner_container
.upcast::<Node>()
.AppendChild(placeholder_container.upcast::<Node>(), can_gc)
.unwrap();
let text_container = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
text_container
.upcast::<Element>()
.SetId(DOMString::from("input-editor"), can_gc);
text_container
.upcast::<Node>()
.set_text_control_inner_editor();
inner_container
.upcast::<Node>()
.AppendChild(text_container.upcast::<Node>(), can_gc)
.unwrap();
let style = HTMLStyleElement::new(
local_name!("style"),
None,
&document,
None,
ElementCreator::ScriptCreated,
can_gc,
);
// TODO(stevennovaryo): Either use UA stylesheet with internal pseudo element or preemptively parse
// the stylesheet to reduce the costly operation and avoid CSP related error.
style
.upcast::<Node>()
.SetTextContent(Some(DOMString::from(TEXT_TREE_STYLE)), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(style.upcast::<Node>(), can_gc)
.unwrap();
let _ = self
.shadow_tree
.borrow_mut()
.insert(ShadowTree::Text(InputTypeTextShadowTree {
text_container: text_container.as_traced(),
placeholder_container: placeholder_container.as_traced(),
}));
}
fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<InputTypeTextShadowTree> {
let has_text_shadow_tree = self
.shadow_tree
.borrow()
.as_ref()
.is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
if !has_text_shadow_tree {
self.create_text_shadow_tree(can_gc);
}
let shadow_tree = self.shadow_tree.borrow();
Ref::filter_map(shadow_tree, |shadow_tree| {
let shadow_tree = shadow_tree.as_ref()?;
match shadow_tree {
ShadowTree::Text(text_tree) => Some(text_tree),
_ => None,
}
})
.ok()
.expect("UA shadow tree was not created")
}
fn create_color_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let shadow_root = self.shadow_root(can_gc);
@ -1136,27 +1284,53 @@ impl HTMLInputElement {
let shadow_tree = self.shadow_tree.borrow();
Ref::filter_map(shadow_tree, |shadow_tree| {
let shadow_tree = shadow_tree.as_ref()?;
let ShadowTree::Color(color_tree) = shadow_tree;
Some(color_tree)
match shadow_tree {
ShadowTree::Color(color_tree) => Some(color_tree),
_ => None,
}
})
.ok()
.expect("UA shadow tree was not created")
}
fn update_shadow_tree_if_needed(&self, can_gc: CanGc) {
if self.input_type() == InputType::Color {
let color_shadow_tree = self.color_shadow_tree(can_gc);
let mut value = self.Value();
if value.str().is_valid_simple_color_string() {
value.make_ascii_lowercase();
} else {
value = DOMString::from("#000000");
}
let style = format!("background-color: {value}");
color_shadow_tree
.color_value
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
match self.input_type() {
InputType::Text => {
let text_shadow_tree = self.text_shadow_tree(can_gc);
let value = self.Value();
// The addition of zero-width space here forces the text input to have an inline formatting
// context that might otherwise be trimmed if there's no text. This is important to ensure
// that the input element is at least as tall as the line gap of the caret:
// <https://drafts.csswg.org/css-ui/#element-with-default-preferred-size>.
//
// This is also used to ensure that the caret will still be rendered when the input is empty.
// TODO: Is there a less hacky way to do this?
let value_text = match value.is_empty() {
false => value,
true => "\u{200B}".into(),
};
text_shadow_tree
.text_container
.upcast::<Node>()
.SetTextContent(Some(value_text), can_gc);
},
InputType::Color => {
let color_shadow_tree = self.color_shadow_tree(can_gc);
let mut value = self.Value();
if value.str().is_valid_simple_color_string() {
value.make_ascii_lowercase();
} else {
value = DOMString::from("#000000");
}
let style = format!("background-color: {value}");
color_shadow_tree
.color_value
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
},
_ => {},
}
}
}
@ -1465,22 +1639,29 @@ impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
match self.value_mode() {
ValueMode::Value => {
// Step 3.
self.value_dirty.set(true);
{
// Step 3.
self.value_dirty.set(true);
// Step 4.
self.sanitize_value(&mut value);
// Step 4.
self.sanitize_value(&mut value);
let mut textinput = self.textinput.borrow_mut();
// Step 5.
if *textinput.single_line_content() != value {
// Steps 1-2
textinput.set_content(value);
let mut textinput = self.textinput.borrow_mut();
// Step 5.
textinput.clear_selection_to_limit(Direction::Forward);
if *textinput.single_line_content() != value {
// Steps 1-2
textinput.set_content(value);
// Step 5.
textinput.clear_selection_to_limit(Direction::Forward);
}
}
// Additionaly, update the placeholder shown state. This is
// normally being done in the attributed mutated. And, being
// done in another scope to prevent borrow checker issues.
self.update_placeholder_shown_state();
},
ValueMode::Default | ValueMode::DefaultOn => {
self.upcast::<Element>()
@ -2063,6 +2244,19 @@ impl HTMLInputElement {
el.set_placeholder_shown_state(has_placeholder && !has_value);
}
// Update the placeholder text in the text shadow tree.
// To increase the performance, we would only do this when it is necessary.
fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
if self.input_type() != InputType::Text {
return;
}
self.text_shadow_tree(can_gc)
.placeholder_container
.upcast::<Node>()
.SetTextContent(Some(self.placeholder.borrow().clone()), can_gc);
}
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)
// Select files by invoking UI or by passed in argument
fn select_files(&self, opt_test_paths: Option<Vec<DOMString>>, can_gc: CanGc) {
@ -2688,8 +2882,11 @@ impl VirtualMethods for HTMLInputElement {
},
}
self.update_text_shadow_tree_placeholder(can_gc);
self.update_placeholder_shown_state();
},
// FIXME(stevennovaryo): This is only reachable by Default and DefaultOn value mode. While others
// are being handled in [Self::SetValue]. Should we merge this two together?
local_name!("value") if !self.value_dirty.get() => {
let value = mutation.new_value(attr).map(|value| (**value).to_owned());
let mut value = value.map_or(DOMString::new(), DOMString::from);
@ -2738,6 +2935,7 @@ impl VirtualMethods for HTMLInputElement {
.extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
}
}
self.update_text_shadow_tree_placeholder(can_gc);
self.update_placeholder_shown_state();
},
local_name!("readonly") => {
@ -2883,6 +3081,17 @@ impl VirtualMethods for HTMLInputElement {
self.implicit_submission(can_gc);
},
DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@ -2899,17 +3108,9 @@ impl VirtualMethods for HTMLInputElement {
!event.DefaultPrevented() &&
self.input_type().is_textual_or_password()
{
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if (event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")) &&

View file

@ -5,6 +5,7 @@
use std::borrow::{Borrow, ToOwned};
use std::cell::Cell;
use std::default::Default;
use std::str::FromStr;
use base::id::WebViewId;
use content_security_policy as csp;
@ -12,6 +13,8 @@ use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use mime::Mime;
use net_traits::mime_classifier::{MediaType, MimeClassifier};
use net_traits::policy_container::PolicyContainer;
use net_traits::request::{
CorsSettings, Destination, Initiator, InsecureRequestsPolicy, Referrer, RequestBuilder,
@ -22,7 +25,7 @@ use net_traits::{
ResourceTimingType,
};
use servo_arc::Arc;
use servo_url::ServoUrl;
use servo_url::{ImmutableOrigin, ServoUrl};
use style::attr::AttrValue;
use style::stylesheets::Stylesheet;
use stylo_atoms::Atom;
@ -78,6 +81,7 @@ struct LinkProcessingOptions {
policy_container: PolicyContainer,
source_set: Option<()>,
base_url: ServoUrl,
origin: ImmutableOrigin,
insecure_requests_policy: InsecureRequestsPolicy,
has_trustworthy_ancestor_origin: bool,
// Some fields that we don't need yet are missing
@ -113,6 +117,10 @@ pub(crate) struct HTMLLinkElement {
request_generation_id: Cell<RequestGenerationId>,
/// <https://html.spec.whatwg.org/multipage/#explicitly-enabled>
is_explicitly_enabled: Cell<bool>,
/// Whether the previous type matched with the destination
previous_type_matched: Cell<bool>,
/// Whether the previous media environment matched with the media query
previous_media_environment_matched: Cell<bool>,
}
impl HTMLLinkElement {
@ -133,6 +141,8 @@ impl HTMLLinkElement {
any_failed_load: Cell::new(false),
request_generation_id: Cell::new(RequestGenerationId(0)),
is_explicitly_enabled: Cell::new(false),
previous_type_matched: Cell::new(true),
previous_media_environment_matched: Cell::new(true),
}
}
@ -236,7 +246,7 @@ impl VirtualMethods for HTMLLinkElement {
return;
}
if !self.upcast::<Node>().is_connected() || is_removal {
if !self.upcast::<Node>().is_connected() {
return;
}
match *local_name {
@ -245,6 +255,12 @@ impl VirtualMethods for HTMLLinkElement {
.set(LinkRelations::for_element(self.upcast()));
},
local_name!("href") => {
if is_removal {
return;
}
// https://html.spec.whatwg.org/multipage/#link-type-stylesheet
// When the href attribute of the link element of an external resource link
// that is already browsing-context connected is changed.
if self.relations.get().contains(LinkRelations::STYLESHEET) {
self.handle_stylesheet_url(&attr.value());
}
@ -254,9 +270,19 @@ impl VirtualMethods for HTMLLinkElement {
self.handle_favicon_url(&attr.value(), &sizes);
}
// https://html.spec.whatwg.org/multipage/#link-type-prefetch
// When the href attribute of the link element of an external resource link
// that is already browsing-context connected is changed.
if self.relations.get().contains(LinkRelations::PREFETCH) {
self.fetch_and_process_prefetch_link(&attr.value());
}
// https://html.spec.whatwg.org/multipage/#link-type-preload
// When the href attribute of the link element of an external resource link
// that is already browsing-context connected is changed.
if self.relations.get().contains(LinkRelations::PRELOAD) {
self.handle_preload_url();
}
},
local_name!("sizes") if self.relations.get().contains(LinkRelations::ICON) => {
if let Some(ref href) = get_attr(self.upcast(), &local_name!("href")) {
@ -264,9 +290,73 @@ impl VirtualMethods for HTMLLinkElement {
}
},
local_name!("crossorigin") => {
// https://html.spec.whatwg.org/multipage/#link-type-prefetch
// When the crossorigin attribute of the link element of an external resource link
// that is already browsing-context connected is set, changed, or removed.
if self.relations.get().contains(LinkRelations::PREFETCH) {
self.fetch_and_process_prefetch_link(&attr.value());
}
// https://html.spec.whatwg.org/multipage/#link-type-stylesheet
// When the crossorigin attribute of the link element of an external resource link
// that is already browsing-context connected is set, changed, or removed.
if self.relations.get().contains(LinkRelations::STYLESHEET) {
self.handle_stylesheet_url(&attr.value());
}
},
local_name!("as") => {
// https://html.spec.whatwg.org/multipage/#link-type-preload
// When the as attribute of the link element of an external resource link
// that is already browsing-context connected is changed.
if self.relations.get().contains(LinkRelations::PRELOAD) {
if let AttributeMutation::Set(Some(_)) = mutation {
self.handle_preload_url();
}
}
},
local_name!("type") => {
// https://html.spec.whatwg.org/multipage/#link-type-stylesheet
// When the type attribute of the link element of an external resource link that
// is already browsing-context connected is set or changed to a value that does
// not or no longer matches the Content-Type metadata of the previous obtained
// external resource, if any.
//
// TODO: Match Content-Type metadata to check if it needs to be updated
if self.relations.get().contains(LinkRelations::STYLESHEET) {
self.handle_stylesheet_url(&attr.value());
}
// https://html.spec.whatwg.org/multipage/#link-type-preload
// When the type attribute of the link element of an external resource link that
// is already browsing-context connected, but was previously not obtained due to
// the type attribute specifying an unsupported type for the request destination,
// is set, removed, or changed.
if self.relations.get().contains(LinkRelations::PRELOAD) &&
!self.previous_type_matched.get()
{
self.handle_preload_url();
}
},
local_name!("media") => {
// https://html.spec.whatwg.org/multipage/#link-type-preload
// When the media attribute of the link element of an external resource link that
// is already browsing-context connected, but was previously not obtained due to
// the media attribute not matching the environment, is changed or removed.
if self.relations.get().contains(LinkRelations::PRELOAD) &&
!self.previous_media_environment_matched.get()
{
match mutation {
AttributeMutation::Removed | AttributeMutation::Set(Some(_)) => {
self.handle_preload_url()
},
_ => {},
};
}
let matches_media_environment =
self.upcast::<Element>().matches_environment(&attr.value());
self.previous_media_environment_matched
.set(matches_media_environment);
},
_ => {},
}
@ -307,6 +397,10 @@ impl VirtualMethods for HTMLLinkElement {
if relations.contains(LinkRelations::PREFETCH) {
self.fetch_and_process_prefetch_link(&href);
}
if relations.contains(LinkRelations::PRELOAD) {
self.handle_preload_url();
}
}
}
}
@ -325,6 +419,14 @@ impl VirtualMethods for HTMLLinkElement {
}
impl HTMLLinkElement {
fn compute_destination_for_attribute(&self) -> Destination {
let element = self.upcast::<Element>();
element
.get_attribute(&ns!(), &local_name!("as"))
.map(|attr| translate_a_preload_destination(&attr.value()))
.unwrap_or(Destination::None)
}
/// <https://html.spec.whatwg.org/multipage/#create-link-options-from-element>
fn processing_options(&self) -> LinkProcessingOptions {
let element = self.upcast::<Element>();
@ -333,10 +435,7 @@ impl HTMLLinkElement {
let document = self.upcast::<Node>().owner_doc();
// Step 2. Let options be a new link processing options
let destination = element
.get_attribute(&ns!(), &local_name!("as"))
.map(|attr| translate_a_preload_destination(&attr.value()))
.unwrap_or(Destination::None);
let destination = self.compute_destination_for_attribute();
let mut options = LinkProcessingOptions {
href: String::new(),
@ -348,6 +447,7 @@ impl HTMLLinkElement {
referrer_policy: referrer_policy_for_element(element),
policy_container: document.policy_container().to_owned(),
source_set: None, // FIXME
origin: document.borrow().origin().immutable().to_owned(),
base_url: document.borrow().base_url(),
insecure_requests_policy: document.insecure_requests_policy(),
has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
@ -446,6 +546,10 @@ impl HTMLLinkElement {
None => "",
};
if !element.matches_environment(mq_str) {
return;
}
let media = MediaList::parse_media_list(mq_str, document.window());
let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
@ -458,8 +562,6 @@ impl HTMLLinkElement {
self.request_generation_id
.set(self.request_generation_id.get().increment());
// TODO: #8085 - Don't load external stylesheets if the node's mq
// doesn't match.
let loader = StylesheetLoader::for_element(self.upcast());
loader.load(
StylesheetContextSource::LinkElement { media: Some(media) },
@ -494,6 +596,133 @@ impl HTMLLinkElement {
Err(e) => debug!("Parsing url {} failed: {}", href, e),
}
}
/// <https://html.spec.whatwg.org/multipage/#link-type-preload:fetch-and-process-the-linked-resource-2>
fn handle_preload_url(&self) {
// Step 1. Update the source set for el.
// TODO
// Step 2. Let options be the result of creating link options from el.
let options = self.processing_options();
// Step 3. Preload options, with the following steps given a response response:
// Step 3.1 If response is a network error, fire an event named error at el.
// Otherwise, fire an event named load at el.
self.preload(options);
}
/// <https://html.spec.whatwg.org/multipage/#preload>
fn preload(&self, options: LinkProcessingOptions) {
// Step 1. If options's type doesn't match options's destination, then return.
let type_matches_destination: bool =
HTMLLinkElement::type_matches_destination(&options.link_type, options.destination);
self.previous_type_matched.set(type_matches_destination);
if !type_matches_destination {
return;
}
// Step 2. If options's destination is "image" and options's source set is not null,
// then set options's href to the result of selecting an image source from options's source set.
// TODO
// Step 3. Let request be the result of creating a link request given options.
let url = options.base_url.clone();
let Some(request) = options.create_link_request(self.owner_window().webview_id()) else {
// Step 4. If request is null, then return.
return;
};
let document = self.upcast::<Node>().owner_doc();
// Step 5. Let unsafeEndTime be 0.
// TODO
// Step 6. Let entry be a new preload entry whose integrity metadata is options's integrity.
// TODO
// Step 7. Let key be the result of creating a preload key given request.
// TODO
// Step 8. If options's document is "pending", then set request's initiator type to "early hint".
// TODO
// Step 9. Let controller be null.
// Step 10. Let reportTiming given a Document document be to report timing for controller
// given document's relevant global object.
// Step 11. Set controller to the result of fetching request, with processResponseConsumeBody
// set to the following steps given a response response and null, failure, or a byte sequence bodyBytes:
let fetch_context = PreloadContext {
url,
link: Trusted::new(self),
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
};
document.fetch_background(request.clone(), fetch_context);
}
/// <https://html.spec.whatwg.org/multipage/#match-preload-type>
fn type_matches_destination(mime_type: &str, destination: Option<Destination>) -> bool {
// Step 1. If type is an empty string, then return true.
if mime_type.is_empty() {
return true;
}
// Step 2. If destination is "fetch", then return true.
//
// Fetch is handled as an empty string destination in the spec:
// https://fetch.spec.whatwg.org/#concept-potential-destination-translate
let Some(destination) = destination else {
return false;
};
if destination == Destination::None {
return true;
}
// Step 3. Let mimeTypeRecord be the result of parsing type.
let Ok(mime_type_record) = Mime::from_str(mime_type) else {
// Step 4. If mimeTypeRecord is failure, then return false.
return false;
};
// Step 5. If mimeTypeRecord is not supported by the user agent, then return false.
//
// We currently don't check if we actually support the mime type. Only if we can classify
// it according to the spec.
let Some(mime_type) = MimeClassifier::get_media_type(&mime_type_record) else {
return false;
};
// Step 6. If any of the following are true:
if
// destination is "audio" or "video", and mimeTypeRecord is an audio or video MIME type;
((destination == Destination::Audio || destination == Destination::Video) &&
mime_type == MediaType::AudioVideo)
// destination is a script-like destination and mimeTypeRecord is a JavaScript MIME type;
|| (destination.is_script_like() && mime_type == MediaType::JavaScript)
// destination is "image" and mimeTypeRecord is an image MIME type;
|| (destination == Destination::Image && mime_type == MediaType::Image)
// destination is "font" and mimeTypeRecord is a font MIME type;
|| (destination == Destination::Font && mime_type == MediaType::Font)
// destination is "json" and mimeTypeRecord is a JSON MIME type;
|| (destination == Destination::Json && mime_type == MediaType::Json)
// destination is "style" and mimeTypeRecord's essence is text/css; or
|| (destination == Destination::Style && mime_type_record == mime::TEXT_CSS)
// destination is "track" and mimeTypeRecord's essence is text/vtt,
|| (destination == Destination::Track && mime_type_record.essence_str() == "text/vtt")
{
// then return true.
return true;
}
// Step 7. Return false.
false
}
fn fire_event_after_response(&self, response: Result<ResourceFetchTiming, NetworkError>) {
if response.is_err() {
self.upcast::<EventTarget>()
.fire_event(atom!("error"), CanGc::note());
} else {
// TODO(35035): Figure out why we need to queue a task for the load event. Otherwise
// the performance timing data hasn't been saved yet, which fails several preload
// WPT tests that assume that performance timing information is available when
// the load event is fired.
let this = Trusted::new(self);
self.owner_global()
.task_manager()
.performance_timeline_task_source()
.queue(task!(preload_load_event: move || {
let this = this.root();
this
.upcast::<EventTarget>()
.fire_event(atom!("load"), CanGc::note());
}));
}
}
}
impl StylesheetOwner for HTMLLinkElement {
@ -552,6 +781,21 @@ impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
.set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
}
// https://html.spec.whatwg.org/multipage/#dom-link-as
make_enumerated_getter!(
As,
"as",
"fetch" | "audio" | "audioworklet" | "document" | "embed" | "font" | "frame"
| "iframe" | "image" | "json" | "manifest" | "object" | "paintworklet"
| "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track"
| "video" | "webidentity" | "worker" | "xslt",
missing => "",
invalid => ""
);
// https://html.spec.whatwg.org/multipage/#dom-link-as
make_setter!(SetAs, "as");
// https://html.spec.whatwg.org/multipage/#dom-link-media
make_getter!(Media, "media");
@ -689,6 +933,8 @@ impl LinkProcessingOptions {
self.has_trustworthy_ancestor_origin,
self.policy_container,
)
.initiator(Initiator::Link)
.origin(self.origin)
.integrity_metadata(self.integrity)
.cryptographic_nonce_metadata(self.cryptographic_nonce_metadata)
.referrer_policy(self.referrer_policy);
@ -795,3 +1041,77 @@ impl PreInvoke for PrefetchContext {
true
}
}
struct PreloadContext {
/// The `<link>` element that caused this preload operation
link: Trusted<HTMLLinkElement>,
resource_timing: ResourceFetchTiming,
/// The url being preloaded
url: ServoUrl,
}
impl FetchResponseListener for PreloadContext {
fn process_request_body(&mut self, _: RequestId) {}
fn process_request_eof(&mut self, _: RequestId) {}
fn process_response(
&mut self,
_: RequestId,
fetch_metadata: Result<FetchMetadata, NetworkError>,
) {
_ = fetch_metadata;
}
fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
_ = chunk;
}
/// Step 3.1 of <https://html.spec.whatwg.org/multipage/#link-type-preload:fetch-and-process-the-linked-resource-2>
fn process_response_eof(
&mut self,
_: RequestId,
response: Result<ResourceFetchTiming, NetworkError>,
) {
self.link.root().fire_event_after_response(response);
}
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
&mut self.resource_timing
}
fn resource_timing(&self) -> &ResourceFetchTiming {
&self.resource_timing
}
fn submit_resource_timing(&mut self) {
submit_timing(self, CanGc::note())
}
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations, None);
}
}
impl ResourceTimingListener for PreloadContext {
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
(
InitiatorType::LocalName(self.url.clone().into_string()),
self.url.clone(),
)
}
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
self.link.root().upcast::<Node>().owner_doc().global()
}
}
impl PreInvoke for PreloadContext {
fn should_invoke(&self) -> bool {
// Preload requests are never aborted.
true
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use pixels::Image;
use pixels::RasterImage;
use servo_arc::Arc;
use crate::dom::attr::Attr;
@ -31,7 +31,7 @@ pub(crate) struct HTMLObjectElement {
htmlelement: HTMLElement,
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
image: DomRefCell<Option<Arc<Image>>>,
image: DomRefCell<Option<Arc<RasterImage>>>,
form_owner: MutNullableDom<HTMLFormElement>,
validity_state: MutNullableDom<ValidityState>,
}

View file

@ -152,25 +152,6 @@ impl HTMLOptionElement {
}
}
// FIXME(ajeffrey): Provide a way of buffering DOMStrings other than using Strings
fn collect_text(element: &Element, value: &mut String) {
let svg_script =
*element.namespace() == ns!(svg) && element.local_name() == &local_name!("script");
let html_script = element.is::<HTMLScriptElement>();
if svg_script || html_script {
return;
}
for child in element.upcast::<Node>().children() {
if child.is::<Text>() {
let characterdata = child.downcast::<CharacterData>().unwrap();
value.push_str(&characterdata.Data());
} else if let Some(element_child) = child.downcast() {
collect_text(element_child, value);
}
}
}
impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
/// <https://html.spec.whatwg.org/multipage/#dom-option>
fn Option(
@ -216,8 +197,28 @@ impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
/// <https://html.spec.whatwg.org/multipage/#dom-option-text>
fn Text(&self) -> DOMString {
let mut content = String::new();
collect_text(self.upcast(), &mut content);
let mut content = DOMString::new();
let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
while let Some(node) = iterator.peek() {
if let Some(element) = node.downcast::<Element>() {
let html_script = element.is::<HTMLScriptElement>();
let svg_script = *element.namespace() == ns!(svg) &&
element.local_name() == &local_name!("script");
if html_script || svg_script {
iterator.next_skipping_children();
continue;
}
}
if node.is::<Text>() {
let characterdata = node.downcast::<CharacterData>().unwrap();
content.push_str(&characterdata.Data());
}
iterator.next();
}
DOMString::from(str_join(split_html_space_chars(&content), " "))
}

View file

@ -643,6 +643,17 @@ impl VirtualMethods for HTMLTextAreaElement {
match action {
KeyReaction::TriggerDefaultAction => (),
KeyReaction::DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@ -656,17 +667,9 @@ impl VirtualMethods for HTMLTextAreaElement {
}
}
} else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")

View file

@ -9,10 +9,9 @@ use content_security_policy as csp;
use dom_struct::dom_struct;
use euclid::default::Size2D;
use html5ever::{LocalName, Prefix, local_name, ns};
use ipc_channel::ipc;
use js::rust::HandleObject;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse,
PendingImageId, UsePlaceholder,
};
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
@ -23,6 +22,7 @@ use net_traits::{
use script_layout_interface::{HTMLMediaData, MediaMetadata};
use servo_media::player::video::VideoFrame;
use servo_url::ServoUrl;
use snapshot::Snapshot;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use crate::document_loader::{LoadBlocker, LoadType};
@ -133,9 +133,7 @@ impl HTMLVideoElement {
sent_resize
}
pub(crate) fn get_current_frame_data(
&self,
) -> Option<(Option<ipc::IpcSharedMemory>, Size2D<u32>)> {
pub(crate) fn get_current_frame_data(&self) -> Option<Snapshot> {
let frame = self.htmlmediaelement.get_current_frame();
if frame.is_some() {
*self.last_frame.borrow_mut() = frame;
@ -145,11 +143,19 @@ impl HTMLVideoElement {
Some(frame) => {
let size = Size2D::new(frame.get_width() as u32, frame.get_height() as u32);
if !frame.is_gl_texture() {
let data = Some(ipc::IpcSharedMemory::from_bytes(&frame.get_data()));
Some((data, size))
let alpha_mode = snapshot::AlphaMode::Transparent {
premultiplied: false,
};
Some(Snapshot::from_vec(
size.cast(),
snapshot::PixelFormat::BGRA,
alpha_mode,
frame.get_data().to_vec(),
))
} else {
// XXX(victor): here we only have the GL texture ID.
Some((None, size))
Some(Snapshot::cleared(size.cast()))
}
},
None => None,
@ -217,7 +223,7 @@ impl HTMLVideoElement {
element.process_image_response(response.response, CanGc::note());
});
image_cache.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
image_cache.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
}
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
@ -262,7 +268,10 @@ impl HTMLVideoElement {
match response {
ImageResponse::Loaded(image, url) => {
debug!("Loaded poster image for video element: {:?}", url);
self.htmlmediaelement.process_poster_image_loaded(image);
match image.as_raster_image() {
Some(image) => self.htmlmediaelement.process_poster_image_loaded(image),
None => warn!("Vector images are not yet supported in video poster"),
}
LoadBlocker::terminate(&self.load_blocker, can_gc);
},
ImageResponse::MetadataLoaded(..) => {},
@ -273,6 +282,18 @@ impl HTMLVideoElement {
},
}
}
/// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
pub(crate) fn is_usable(&self) -> bool {
!matches!(
self.htmlmediaelement.get_ready_state(),
ReadyState::HaveNothing | ReadyState::HaveMetadata
)
}
pub(crate) fn origin_is_clean(&self) -> bool {
self.htmlmediaelement.origin_is_clean()
}
}
impl HTMLVideoElementMethods<crate::DomTypeHolder> for HTMLVideoElement {

View file

@ -2,58 +2,64 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::vec::Vec;
use std::cell::{Cell, Ref};
use std::collections::HashMap;
use base::id::{ImageBitmapId, ImageBitmapIndex};
use constellation_traits::SerializableImageBitmap;
use dom_struct::dom_struct;
use snapshot::Snapshot;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::ImageBitmapMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::serializable::Serializable;
use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::bindings::transferable::Transferable;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct ImageBitmap {
reflector_: Reflector,
width: u32,
height: u32,
/// The actual pixel data of the bitmap
///
/// If this is `None`, then the bitmap data has been released by calling
/// [`close`](https://html.spec.whatwg.org/multipage/#dom-imagebitmap-close)
bitmap_data: DomRefCell<Option<Vec<u8>>>,
#[no_trace]
bitmap_data: DomRefCell<Option<Snapshot>>,
origin_clean: Cell<bool>,
}
impl ImageBitmap {
fn new_inherited(width_arg: u32, height_arg: u32) -> ImageBitmap {
fn new_inherited(bitmap_data: Snapshot) -> ImageBitmap {
ImageBitmap {
reflector_: Reflector::new(),
width: width_arg,
height: height_arg,
bitmap_data: DomRefCell::new(Some(vec![])),
bitmap_data: DomRefCell::new(Some(bitmap_data)),
origin_clean: Cell::new(true),
}
}
#[allow(dead_code)]
pub(crate) fn new(
global: &GlobalScope,
width: u32,
height: u32,
bitmap_data: Snapshot,
can_gc: CanGc,
) -> Fallible<DomRoot<ImageBitmap>> {
//assigning to a variable the return object of new_inherited
let imagebitmap = Box::new(ImageBitmap::new_inherited(width, height));
Ok(reflect_dom_object(imagebitmap, global, can_gc))
) -> DomRoot<ImageBitmap> {
reflect_dom_object(
Box::new(ImageBitmap::new_inherited(bitmap_data)),
global,
can_gc,
)
}
pub(crate) fn set_bitmap_data(&self, data: Vec<u8>) {
*self.bitmap_data.borrow_mut() = Some(data);
#[allow(dead_code)]
pub(crate) fn bitmap_data(&self) -> Ref<Option<Snapshot>> {
self.bitmap_data.borrow()
}
pub(crate) fn origin_is_clean(&self) -> bool {
self.origin_clean.get()
}
pub(crate) fn set_origin_clean(&self, origin_is_clean: bool) {
@ -67,6 +73,108 @@ impl ImageBitmap {
}
}
impl Serializable for ImageBitmap {
type Index = ImageBitmapIndex;
type Data = SerializableImageBitmap;
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:serialization-steps>
fn serialize(&self) -> Result<(ImageBitmapId, Self::Data), ()> {
// Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
if !self.origin_is_clean() {
return Err(());
}
// If value has a [[Detached]] internal slot whose value is true,
// then throw a "DataCloneError" DOMException.
if self.is_detached() {
return Err(());
}
// Step 2. Set serialized.[[BitmapData]] to a copy of value's bitmap data.
let serialized = SerializableImageBitmap {
bitmap_data: self.bitmap_data.borrow().clone().unwrap(),
};
Ok((ImageBitmapId::new(), serialized))
}
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:deserialization-steps>
fn deserialize(
owner: &GlobalScope,
serialized: Self::Data,
can_gc: CanGc,
) -> Result<DomRoot<Self>, ()> {
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
Ok(ImageBitmap::new(owner, serialized.bitmap_data, can_gc))
}
fn serialized_storage<'a>(
reader: StructuredData<'a, '_>,
) -> &'a mut Option<HashMap<ImageBitmapId, Self::Data>> {
match reader {
StructuredData::Reader(r) => &mut r.image_bitmaps,
StructuredData::Writer(w) => &mut w.image_bitmaps,
}
}
}
impl Transferable for ImageBitmap {
type Index = ImageBitmapIndex;
type Data = SerializableImageBitmap;
fn can_transfer(&self) -> bool {
if !self.origin_is_clean() || self.is_detached() {
return false;
}
true
}
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-steps>
fn transfer(&self) -> Result<(ImageBitmapId, SerializableImageBitmap), ()> {
// Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
if !self.origin_is_clean() {
return Err(());
}
// If value has a [[Detached]] internal slot whose value is true,
// then throw a "DataCloneError" DOMException.
if self.is_detached() {
return Err(());
}
// Step 2. Set dataHolder.[[BitmapData]] to value's bitmap data.
// Step 3. Unset value's bitmap data.
let serialized = SerializableImageBitmap {
bitmap_data: self.bitmap_data.borrow_mut().take().unwrap(),
};
Ok((ImageBitmapId::new(), serialized))
}
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-receiving-steps>
fn transfer_receive(
owner: &GlobalScope,
_: ImageBitmapId,
serialized: SerializableImageBitmap,
) -> Result<DomRoot<Self>, ()> {
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
Ok(ImageBitmap::new(
owner,
serialized.bitmap_data,
CanGc::note(),
))
}
fn serialized_storage<'a>(
data: StructuredData<'a, '_>,
) -> &'a mut Option<HashMap<ImageBitmapId, Self::Data>> {
match data {
StructuredData::Reader(r) => &mut r.transferred_image_bitmaps,
StructuredData::Writer(w) => &mut w.transferred_image_bitmaps,
}
}
}
impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-height>
fn Height(&self) -> u32 {
@ -76,7 +184,13 @@ impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
}
// Step 2. Return this's height, in CSS pixels.
self.height
self.bitmap_data
.borrow()
.as_ref()
.unwrap()
.size()
.cast()
.height
}
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-width>
@ -87,7 +201,13 @@ impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
}
// Step 2. Return this's width, in CSS pixels.
self.width
self.bitmap_data
.borrow()
.as_ref()
.unwrap()
.size()
.cast()
.width
}
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-close>

View file

@ -41,14 +41,11 @@ impl ImageData {
mut data: Option<Vec<u8>>,
can_gc: CanGc,
) -> Fallible<DomRoot<ImageData>> {
// The color components of each pixel must be stored in four sequential
// elements in the order of red, green, blue, and then alpha.
let len = 4u32
.checked_mul(width)
.and_then(|v| v.checked_mul(height))
.ok_or(Error::Range(
"The requested image size exceeds the supported range".to_owned(),
))?;
let len =
pixels::compute_rgba8_byte_length_if_within_limit(width as usize, height as usize)
.ok_or(Error::Range(
"The requested image size exceeds the supported range".to_owned(),
))?;
unsafe {
let cx = GlobalScope::get_cx();
@ -129,18 +126,16 @@ impl ImageData {
return Err(Error::IndexSize);
}
// The color components of each pixel must be stored in four sequential
// elements in the order of red, green, blue, and then alpha.
// Please note when a too-large ImageData is created using a constructor
// historically throwns an IndexSizeError, instead of RangeError.
let len = 4u32
.checked_mul(width)
.and_then(|v| v.checked_mul(height))
.ok_or(Error::IndexSize)?;
// When a constructor is called for an ImageData that is too large, other browsers throw
// IndexSizeError rather than RangeError here, so we do the same.
let len =
pixels::compute_rgba8_byte_length_if_within_limit(width as usize, height as usize)
.ok_or(Error::IndexSize)?;
let cx = GlobalScope::get_cx();
let heap_typed_array = create_heap_buffer_source_with_length::<ClampedU8>(cx, len, can_gc)?;
let heap_typed_array =
create_heap_buffer_source_with_length::<ClampedU8>(cx, len as u32, can_gc)?;
let imagedata = Box::new(ImageData {
reflector_: Reflector::new(),

View file

@ -211,6 +211,7 @@ pub(crate) mod types {
}
pub(crate) mod abortcontroller;
pub(crate) mod abortsignal;
#[allow(dead_code)]
pub(crate) mod abstractrange;
pub(crate) mod abstractworker;

View file

@ -10,7 +10,6 @@ use std::default::Default;
use std::f64::consts::PI;
use std::ops::Range;
use std::slice::from_ref;
use std::sync::Arc as StdArc;
use std::{cmp, fmt, iter};
use app_units::Au;
@ -26,7 +25,8 @@ use js::jsapi::JSObject;
use js::rust::HandleObject;
use libc::{self, c_void, uintptr_t};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use pixels::{Image, ImageMetadata};
use net_traits::image_cache::Image;
use pixels::ImageMetadata;
use script_bindings::codegen::InheritTypes::DocumentFragmentTypeId;
use script_layout_interface::{
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
@ -48,7 +48,7 @@ use style::properties::ComputedValues;
use style::selector_parser::{SelectorImpl, SelectorParser};
use style::stylesheets::{Stylesheet, UrlExtraData};
use uuid::Uuid;
use xml5ever::serialize as xml_serialize;
use xml5ever::{local_name, serialize as xml_serialize};
use crate::conversions::Convert;
use crate::document_loader::DocumentLoader;
@ -230,6 +230,10 @@ bitflags! {
/// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML
/// needs extra work or not
const HAS_WEIRD_PARSER_INSERTION_MODE = 1 << 11;
/// Whether this node serves as the text container for editable content of
/// <input> or <textarea> element.
const IS_TEXT_CONTROL_INNER_EDITOR = 1 << 12;
}
}
@ -697,6 +701,16 @@ impl Node {
self.flags.get().contains(NodeFlags::IS_CONNECTED)
}
pub(crate) fn set_text_control_inner_editor(&self) {
self.set_flag(NodeFlags::IS_TEXT_CONTROL_INNER_EDITOR, true)
}
pub(crate) fn is_text_control_inner_editor(&self) -> bool {
self.flags
.get()
.contains(NodeFlags::IS_TEXT_CONTROL_INNER_EDITOR)
}
/// Returns the type ID of this node.
pub(crate) fn type_id(&self) -> NodeTypeId {
match *self.eventtarget.type_id() {
@ -1247,13 +1261,13 @@ impl Node {
}
}
pub(crate) fn unique_id(&self) -> String {
pub(crate) fn unique_id(&self, pipeline: PipelineId) -> String {
let mut rare_data = self.ensure_rare_data();
if rare_data.unique_id.is_none() {
let id = UniqueId::new();
ScriptThread::save_node_id(id.borrow().simple().to_string());
rare_data.unique_id = Some(id);
let node_id = UniqueId::new();
ScriptThread::save_node_id(pipeline, node_id.borrow().simple().to_string());
rare_data.unique_id = Some(node_id);
}
rare_data
.unique_id
@ -1267,6 +1281,7 @@ impl Node {
pub(crate) fn summarize(&self, can_gc: CanGc) -> NodeInfo {
let USVString(base_uri) = self.BaseURI();
let node_type = self.NodeType();
let pipeline = self.owner_document().window().pipeline_id();
let maybe_shadow_root = self.downcast::<ShadowRoot>();
let shadow_root_mode = maybe_shadow_root
@ -1274,7 +1289,7 @@ impl Node {
.map(ShadowRootMode::convert);
let host = maybe_shadow_root
.map(ShadowRoot::Host)
.map(|host| host.upcast::<Node>().unique_id());
.map(|host| host.upcast::<Node>().unique_id(pipeline));
let is_shadow_host = self.downcast::<Element>().is_some_and(|potential_host| {
let Some(root) = potential_host.shadow_root() else {
return false;
@ -1296,12 +1311,12 @@ impl Node {
.map(|style| style.Display().into());
NodeInfo {
unique_id: self.unique_id(),
unique_id: self.unique_id(pipeline),
host,
base_uri,
parent: self
.GetParentNode()
.map_or("".to_owned(), |node| node.unique_id()),
.map_or("".to_owned(), |node| node.unique_id(pipeline)),
node_type,
is_top_level_document: node_type == NodeConstants::DOCUMENT_NODE,
node_name: String::from(self.NodeName()),
@ -1455,6 +1470,21 @@ impl Node {
.map(|data| data.element_data.borrow().styles.primary().clone())
}
/// <https://html.spec.whatwg.org/multipage/#language>
pub(crate) fn get_lang(&self) -> Option<String> {
self.inclusive_ancestors(ShadowIncluding::Yes)
.filter_map(|node| {
node.downcast::<Element>().and_then(|el| {
el.get_attribute(&ns!(xml), &local_name!("lang"))
.or_else(|| el.get_attribute(&ns!(), &local_name!("lang")))
.map(|attr| String::from(attr.Value()))
})
// TODO: Check meta tags for a pragma-set default language
// TODO: Check HTTP Content-Language header
})
.next()
}
/// <https://dom.spec.whatwg.org/#assign-slotables-for-a-tree>
pub(crate) fn assign_slottables_for_a_tree(&self) {
// NOTE: This method traverses all descendants of the node and is potentially very
@ -1579,6 +1609,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
fn assigned_slot_for_layout(self) -> Option<LayoutDom<'dom, HTMLSlotElement>>;
fn is_element_for_layout(&self) -> bool;
fn is_text_node_for_layout(&self) -> bool;
unsafe fn get_flag(self, flag: NodeFlags) -> bool;
unsafe fn set_flag(self, flag: NodeFlags, value: bool);
@ -1614,11 +1645,14 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
/// Whether this element is a `<input>` rendered as text or a `<textarea>`.
fn is_text_input(&self) -> bool;
/// Whether this element serve as a container of editable text for a text input.
fn is_text_control_inner_editor(&self) -> bool;
fn text_content(self) -> Cow<'dom, str>;
fn selection(self) -> Option<Range<usize>>;
fn image_url(self) -> Option<ServoUrl>;
fn image_density(self) -> Option<f64>;
fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>;
fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
fn canvas_data(self) -> Option<HTMLCanvasData>;
fn media_data(self) -> Option<HTMLMediaData>;
fn svg_data(self) -> Option<SVGSVGData>;
@ -1646,6 +1680,11 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
(*self).is::<Element>()
}
fn is_text_node_for_layout(&self) -> bool {
self.type_id_for_layout() ==
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text))
}
#[inline]
fn composed_parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> {
let parent = self.parent_node_ref();
@ -1787,8 +1826,8 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
{
let input = self.unsafe_get().downcast::<HTMLInputElement>().unwrap();
// FIXME: All the non-color input types currently render as text
input.input_type() != InputType::Color
// FIXME: All the non-color and non-text input types currently render as text
!matches!(input.input_type(), InputType::Color | InputType::Text)
} else {
type_id ==
NodeTypeId::Element(ElementTypeId::HTMLElement(
@ -1797,6 +1836,10 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
}
}
fn is_text_control_inner_editor(&self) -> bool {
self.unsafe_get().is_text_control_inner_editor()
}
fn text_content(self) -> Cow<'dom, str> {
if let Some(text) = self.downcast::<Text>() {
return text.upcast().data_for_layout().into();
@ -1814,6 +1857,25 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
}
fn selection(self) -> Option<Range<usize>> {
// This node is a text node of a text control inner editor in a <input> or <textarea> element.
// So we should find those corresponding element, and get its selection.
if self.is_text_node_for_layout() &&
self.parent_node_ref()
.is_some_and(|parent| parent.is_text_control_inner_editor())
{
let shadow_root = self.containing_shadow_root_for_layout();
if let Some(containing_shadow_host) = shadow_root.map(|root| root.get_host_for_layout())
{
if let Some(area) = containing_shadow_host.downcast::<HTMLTextAreaElement>() {
return area.selection_for_layout();
}
if let Some(input) = containing_shadow_host.downcast::<HTMLInputElement>() {
return input.selection_for_layout();
}
}
}
if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
return area.selection_for_layout();
}
@ -1831,7 +1893,7 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
.image_url()
}
fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> {
fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
self.downcast::<HTMLImageElement>().map(|e| e.image_data())
}
@ -2035,6 +2097,10 @@ impl TreeIterator {
self.current = None;
Some(current)
}
pub(crate) fn peek(&self) -> Option<&DomRoot<Node>> {
self.current.as_ref()
}
}
impl Iterator for TreeIterator {

View file

@ -21,15 +21,15 @@ use js::jsval::JSVal;
use js::rust::{HandleObject, MutableHandleValue};
use net_traits::http_status::HttpStatus;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
PendingImageId, PendingImageResponse, UsePlaceholder,
ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder,
};
use net_traits::request::{RequestBuilder, RequestId};
use net_traits::{
FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
ResourceTimingType,
};
use pixels::Image;
use pixels::RasterImage;
use servo_url::{ImmutableOrigin, ServoUrl};
use uuid::Uuid;
@ -114,15 +114,15 @@ pub(crate) struct Notification {
/// <https://notifications.spec.whatwg.org/#image-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
image_resource: DomRefCell<Option<Arc<Image>>>,
image_resource: DomRefCell<Option<Arc<RasterImage>>>,
/// <https://notifications.spec.whatwg.org/#icon-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
icon_resource: DomRefCell<Option<Arc<Image>>>,
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
/// <https://notifications.spec.whatwg.org/#badge-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
badge_resource: DomRefCell<Option<Arc<Image>>>,
badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
}
impl Notification {
@ -561,7 +561,7 @@ struct Action {
/// <https://notifications.spec.whatwg.org/#action-icon-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
icon_resource: DomRefCell<Option<Arc<Image>>>,
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
}
/// <https://notifications.spec.whatwg.org/#create-a-notification-with-a-settings-object>
@ -886,7 +886,11 @@ impl Notification {
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
image, ..
}) => {
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
let image = image.as_raster_image();
if image.is_none() {
warn!("Vector images are not supported in notifications yet");
};
self.set_resource_and_show_when_ready(request_id, &resource_type, image);
},
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
_,
@ -925,8 +929,7 @@ impl Notification {
pending_image_id: PendingImageId,
resource_type: ResourceType,
) {
let (sender, receiver) =
ipc::channel::<PendingImageResponse>().expect("ipc channel failure");
let (sender, receiver) = ipc::channel().expect("ipc channel failure");
let global: &GlobalScope = &self.global();
@ -942,7 +945,11 @@ impl Notification {
task_source.queue(task!(handle_response: move || {
let this = trusted_this.root();
if let Ok(response) = response {
this.handle_image_cache_response(request_id, response.response, resource_type);
let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
warn!("Received unexpected message from image cache: {response:?}");
return;
};
this.handle_image_cache_response(request_id, status.response, resource_type);
} else {
this.handle_image_cache_response(request_id, ImageResponse::None, resource_type);
}
@ -950,7 +957,7 @@ impl Notification {
}),
);
global.image_cache().add_listener(ImageResponder::new(
global.image_cache().add_listener(ImageLoadListener::new(
sender,
global.pipeline_id(),
pending_image_id,
@ -965,7 +972,11 @@ impl Notification {
) {
match response {
ImageResponse::Loaded(image, _) => {
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
let image = image.as_raster_image();
if image.is_none() {
warn!("Vector images are not yet supported in notification attribute");
};
self.set_resource_and_show_when_ready(request_id, &resource_type, image);
},
ImageResponse::PlaceholderLoaded(image, _) => {
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
@ -981,7 +992,7 @@ impl Notification {
&self,
request_id: RequestId,
resource_type: &ResourceType,
image: Option<Arc<Image>>,
image: Option<Arc<RasterImage>>,
) {
match resource_type {
ResourceType::Image => {

View file

@ -92,7 +92,13 @@ impl OffscreenCanvas {
Some(context) => context.get_image_data(),
None => {
let size = self.get_size();
if size.width == 0 || size.height == 0 {
if size.is_empty() ||
pixels::compute_rgba8_byte_length_if_within_limit(
size.width as usize,
size.height as usize,
)
.is_none()
{
None
} else {
Some(Snapshot::cleared(size))

View file

@ -59,7 +59,9 @@ impl ServoInternalsMethods<crate::DomTypeHolder> for ServoInternals {
impl RoutedPromiseListener<MemoryReportResult> for ServoInternals {
fn handle_response(&self, response: MemoryReportResult, promise: &Rc<Promise>, can_gc: CanGc) {
promise.resolve_native(&response.content, can_gc);
let stringified = serde_json::to_string(&response.results)
.unwrap_or_else(|_| "{ error: \"failed to create memory report\"}".to_owned());
promise.resolve_native(&stringified, can_gc);
}
}

View file

@ -39,8 +39,8 @@ impl StyleSheet {
}
impl StyleSheetMethods<crate::DomTypeHolder> for StyleSheet {
// https://drafts.csswg.org/cssom/#dom-stylesheet-type
fn Type_(&self) -> DOMString {
/// <https://drafts.csswg.org/cssom/#dom-stylesheet-type>
fn Type(&self) -> DOMString {
self.type_.clone()
}

View file

@ -21,7 +21,9 @@ use super::bindings::structuredclone::StructuredData;
use super::bindings::transferable::Transferable;
use super::messageport::MessagePort;
use super::promisenativehandler::Callback;
use super::readablestream::CrossRealmTransformReadable;
use super::types::{TransformStreamDefaultController, WritableStream};
use super::writablestream::CrossRealmTransformWritable;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy;
use crate::dom::bindings::codegen::Bindings::TransformStreamBinding::TransformStreamMethods;
@ -368,6 +370,19 @@ impl Callback for FlushPromiseRejection {
}
}
impl js::gc::Rootable for CrossRealmTransform {}
/// A wrapper to handle `message` and `messageerror` events
/// for the message port used by the transfered stream.
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum CrossRealmTransform {
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
Readable(CrossRealmTransformReadable),
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
Writable(CrossRealmTransformWritable),
}
/// <https://streams.spec.whatwg.org/#ts-class>
#[dom_struct]
pub struct TransformStream {

View file

@ -4,6 +4,7 @@
use dom_struct::dom_struct;
use js::rust::HandleObject;
use script_bindings::codegen::GenericBindings::URLPatternBinding::URLPatternResult;
use script_bindings::codegen::GenericUnionTypes::USVStringOrURLPatternInit;
use script_bindings::error::{Error, Fallible};
use script_bindings::reflector::Reflector;
@ -46,7 +47,7 @@ impl URLPattern {
) -> Fallible<DomRoot<URLPattern>> {
// The section below converts from servos types to the types used in the urlpattern crate
let base_url = base_url.map(|usv_string| usv_string.0);
let input = bindings_to_third_party::map_urlpattern_input(input, base_url.clone());
let input = bindings_to_third_party::map_urlpattern_input(input);
let options = urlpattern::UrlPatternOptions {
ignore_case: options.ignoreCase,
};
@ -94,6 +95,50 @@ impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
URLPattern::initialize(global, proto, input, None, options, can_gc)
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-test>
fn Test(
&self,
input: USVStringOrURLPatternInit,
base_url: Option<USVString>,
) -> Fallible<bool> {
let input = bindings_to_third_party::map_urlpattern_input(input);
let inputs = urlpattern::quirks::process_match_input(input, base_url.as_deref())
.map_err(|error| Error::Type(format!("{error}")))?;
let Some((match_input, _)) = inputs else {
return Ok(false);
};
self.associated_url_pattern
.test(match_input)
.map_err(|error| Error::Type(format!("{error}")))
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec>
fn Exec(
&self,
input: USVStringOrURLPatternInit,
base_url: Option<USVString>,
) -> Fallible<Option<URLPatternResult>> {
let input = bindings_to_third_party::map_urlpattern_input(input);
let inputs = urlpattern::quirks::process_match_input(input, base_url.as_deref())
.map_err(|error| Error::Type(format!("{error}")))?;
let Some((match_input, inputs)) = inputs else {
return Ok(None);
};
let result = self
.associated_url_pattern
.exec(match_input)
.map_err(|error| Error::Type(format!("{error}")))?;
let Some(result) = result else {
return Ok(None);
};
Ok(Some(third_party_to_bindings::map_urlpattern_result(
result, inputs,
)))
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol>
fn Protocol(&self) -> USVString {
// Step 1. Return thiss associated URL patterns protocol components pattern string.
@ -151,54 +196,115 @@ impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
}
mod bindings_to_third_party {
use script_bindings::codegen::GenericBindings::URLPatternBinding::URLPatternInit;
use crate::dom::urlpattern::USVStringOrURLPatternInit;
fn map_urlpatterninit(pattern_init: URLPatternInit) -> urlpattern::quirks::UrlPatternInit {
urlpattern::quirks::UrlPatternInit {
protocol: pattern_init.protocol.map(|protocol| protocol.0),
username: pattern_init.username.map(|username| username.0),
password: pattern_init.password.map(|password| password.0),
hostname: pattern_init.hostname.map(|hostname| hostname.0),
port: pattern_init.port.map(|hash| hash.0),
pathname: pattern_init
.pathname
.as_ref()
.map(|usv_string| usv_string.to_string()),
search: pattern_init.search.map(|search| search.0),
hash: pattern_init.hash.map(|hash| hash.0),
base_url: pattern_init.baseURL.map(|base_url| base_url.0),
}
}
pub(super) fn map_urlpattern_input(
input: USVStringOrURLPatternInit,
base_url: Option<String>,
) -> urlpattern::quirks::StringOrInit {
match input {
USVStringOrURLPatternInit::USVString(usv_string) => {
urlpattern::quirks::StringOrInit::String(usv_string.0)
},
USVStringOrURLPatternInit::URLPatternInit(pattern_init) => {
let pattern_init = urlpattern::quirks::UrlPatternInit {
protocol: pattern_init
.protocol
.as_ref()
.map(|usv_string| usv_string.to_string()),
username: pattern_init
.username
.as_ref()
.map(|usv_string| usv_string.to_string()),
password: pattern_init
.password
.as_ref()
.map(|usv_string| usv_string.to_string()),
hostname: pattern_init
.hostname
.as_ref()
.map(|usv_string| usv_string.to_string()),
port: pattern_init
.port
.as_ref()
.map(|usv_string| usv_string.to_string()),
pathname: pattern_init
.pathname
.as_ref()
.map(|usv_string| usv_string.to_string()),
search: pattern_init
.search
.as_ref()
.map(|usv_string| usv_string.to_string()),
hash: pattern_init
.hash
.as_ref()
.map(|usv_string| usv_string.to_string()),
base_url,
};
urlpattern::quirks::StringOrInit::Init(pattern_init)
urlpattern::quirks::StringOrInit::Init(map_urlpatterninit(pattern_init))
},
}
}
}
mod third_party_to_bindings {
use script_bindings::codegen::GenericBindings::URLPatternBinding::{
URLPatternComponentResult, URLPatternInit, URLPatternResult,
};
use script_bindings::codegen::GenericUnionTypes::USVStringOrUndefined;
use script_bindings::record::Record;
use script_bindings::str::USVString;
use crate::dom::bindings::codegen::UnionTypes::USVStringOrURLPatternInit;
// FIXME: For some reason codegen puts a lot of options into these types that don't make sense
fn map_component_result(
component_result: urlpattern::UrlPatternComponentResult,
) -> URLPatternComponentResult {
let mut groups = Record::new();
for (key, value) in component_result.groups.iter() {
let value = match value {
Some(value) => USVStringOrUndefined::USVString(USVString(value.to_owned())),
None => USVStringOrUndefined::Undefined(()),
};
groups.insert(USVString(key.to_owned()), value);
}
URLPatternComponentResult {
input: Some(component_result.input.into()),
groups: Some(groups),
}
}
fn map_urlpatterninit(pattern_init: urlpattern::quirks::UrlPatternInit) -> URLPatternInit {
URLPatternInit {
baseURL: pattern_init.base_url.map(USVString),
protocol: pattern_init.protocol.map(USVString),
username: pattern_init.username.map(USVString),
password: pattern_init.password.map(USVString),
hostname: pattern_init.hostname.map(USVString),
port: pattern_init.port.map(USVString),
pathname: pattern_init.pathname.map(USVString),
search: pattern_init.search.map(USVString),
hash: pattern_init.hash.map(USVString),
}
}
pub(super) fn map_urlpattern_result(
result: urlpattern::UrlPatternResult,
(string_or_init, base_url): urlpattern::quirks::Inputs,
) -> URLPatternResult {
let string_or_init = match string_or_init {
urlpattern::quirks::StringOrInit::String(string) => {
USVStringOrURLPatternInit::USVString(USVString(string))
},
urlpattern::quirks::StringOrInit::Init(pattern_init) => {
USVStringOrURLPatternInit::URLPatternInit(map_urlpatterninit(pattern_init))
},
};
let mut inputs = vec![string_or_init];
if let Some(base_url) = base_url {
inputs.push(USVStringOrURLPatternInit::USVString(USVString(base_url)));
}
URLPatternResult {
inputs: Some(inputs),
protocol: Some(map_component_result(result.protocol)),
username: Some(map_component_result(result.username)),
password: Some(map_component_result(result.password)),
hostname: Some(map_component_result(result.hostname)),
port: Some(map_component_result(result.port)),
pathname: Some(map_component_result(result.pathname)),
search: Some(map_component_result(result.search)),
hash: Some(map_component_result(result.hash)),
}
}
}

View file

@ -32,12 +32,11 @@ use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::{
WebGL2RenderingContextConstants as constants, WebGL2RenderingContextMethods,
};
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{
WebGLContextAttributes, WebGLRenderingContextMethods,
TexImageSource, WebGLContextAttributes, WebGLRenderingContextMethods,
};
use crate::dom::bindings::codegen::UnionTypes::{
ArrayBufferViewOrArrayBuffer, Float32ArrayOrUnrestrictedFloatSequence,
HTMLCanvasElementOrOffscreenCanvas,
ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, Int32ArrayOrLongSequence,
HTMLCanvasElementOrOffscreenCanvas, Int32ArrayOrLongSequence,
Uint32ArrayOrUnsignedLongSequence,
};
use crate::dom::bindings::error::{ErrorResult, Fallible};
@ -3023,7 +3022,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
internal_format: i32,
format: u32,
data_type: u32,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
source: TexImageSource,
) -> ErrorResult {
self.base
.TexImage2D_(target, level, internal_format, format, data_type, source)
@ -3118,7 +3117,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
border: i32,
format: u32,
type_: u32,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
source: TexImageSource,
) -> Fallible<()> {
if self.bound_pixel_unpack_buffer.get().is_some() {
self.base.webgl_error(InvalidOperation);
@ -3301,7 +3300,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
yoffset: i32,
format: u32,
data_type: u32,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
source: TexImageSource,
) -> ErrorResult {
self.base
.TexSubImage2D_(target, level, xoffset, yoffset, format, data_type, source)

View file

@ -609,15 +609,31 @@ impl WebGLRenderingContext {
};
let cors_setting = cors_setting_for_element(image.upcast());
let img =
match canvas_utils::request_image_from_cache(&window, img_url, cors_setting) {
ImageResponse::Loaded(img, _) => img,
ImageResponse::PlaceholderLoaded(_, _) |
ImageResponse::None |
ImageResponse::MetadataLoaded(_) => return Ok(None),
};
let img = match canvas_utils::request_image_from_cache(
&window,
img_url,
cors_setting,
) {
ImageResponse::Loaded(image, _) => {
match image.as_raster_image() {
Some(image) => image,
None => {
// Vector images are not currently supported here and there are some open questions
// in the specification about how to handle them:
// See https://github.com/KhronosGroup/WebGL/issues/1503.
warn!(
"Vector images as are not yet supported as WebGL texture source"
);
return Ok(None);
},
}
},
ImageResponse::PlaceholderLoaded(_, _) |
ImageResponse::None |
ImageResponse::MetadataLoaded(_) => return Ok(None),
};
let size = Size2D::new(img.width, img.height);
let size = Size2D::new(img.metadata.width, img.metadata.height);
let data = IpcSharedMemory::from_bytes(img.first_frame().bytes);
TexPixels::new(data, size, img.format, false)
@ -642,14 +658,23 @@ impl WebGLRenderingContext {
return Ok(None);
}
},
TexImageSource::HTMLVideoElement(video) => match video.get_current_frame_data() {
Some((data, size)) => {
let data = data.unwrap_or_else(|| {
IpcSharedMemory::from_bytes(&vec![0; size.area() as usize * 4])
});
TexPixels::new(data, size, PixelFormat::BGRA8, false)
},
None => return Ok(None),
TexImageSource::HTMLVideoElement(video) => {
if !video.origin_is_clean() {
return Err(Error::Security);
}
let Some(snapshot) = video.get_current_frame_data() else {
return Ok(None);
};
let snapshot = snapshot.as_ipc();
let size = snapshot.size().cast();
let format: PixelFormat = match snapshot.format() {
snapshot::PixelFormat::RGBA => PixelFormat::RGBA8,
snapshot::PixelFormat::BGRA => PixelFormat::BGRA8,
};
let premultiply = snapshot.alpha_mode().is_premultiplied();
TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply)
},
}))
}

View file

@ -55,7 +55,8 @@ use malloc_size_of::MallocSizeOf;
use media::WindowGLContext;
use net_traits::ResourceThreads;
use net_traits::image_cache::{
ImageCache, ImageResponder, ImageResponse, PendingImageId, PendingImageResponse,
ImageCache, ImageCacheResponseMessage, ImageLoadListener, ImageResponse, PendingImageId,
PendingImageResponse, RasterizationCompleteResponse,
};
use net_traits::storage_thread::StorageType;
use num_traits::ToPrimitive;
@ -80,7 +81,6 @@ use style::dom::OpaqueNode;
use style::error_reporting::{ContextualParseError, ParseErrorReporter};
use style::properties::PropertyId;
use style::properties::style_structs::Font;
use style::queries::values::PrefersColorScheme;
use style::selector_parser::PseudoElement;
use style::str::HTML_SPACE_CHARACTERS;
use style::stylesheets::UrlExtraData;
@ -88,7 +88,7 @@ use style_traits::CSSPixel;
use stylo_atoms::Atom;
use url::Position;
use webrender_api::ExternalScrollId;
use webrender_api::units::{DevicePixel, LayoutPixel};
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel};
use super::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
use super::bindings::trace::HashMapTracedValues;
@ -219,6 +219,8 @@ impl LayoutBlocker {
}
}
type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize);
#[dom_struct]
pub(crate) struct Window {
globalscope: GlobalScope,
@ -241,7 +243,7 @@ pub(crate) struct Window {
#[no_trace]
image_cache: Arc<dyn ImageCache>,
#[no_trace]
image_cache_sender: IpcSender<PendingImageResponse>,
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
window_proxy: MutNullableDom<WindowProxy>,
document: MutNullableDom<Document>,
location: MutNullableDom<Location>,
@ -269,7 +271,7 @@ pub(crate) struct Window {
/// Platform theme.
#[no_trace]
theme: Cell<PrefersColorScheme>,
theme: Cell<Theme>,
/// Parent id associated with this page, if any.
#[no_trace]
@ -344,11 +346,17 @@ pub(crate) struct Window {
pending_image_callbacks: DomRefCell<HashMap<PendingImageId, Vec<PendingImageCallback>>>,
/// All of the elements that have an outstanding image request that was
/// initiated by layout during a reflow. They are stored in the script thread
/// initiated by layout during a reflow. They are stored in the [`ScriptThread`]
/// to ensure that the element can be marked dirty when the image data becomes
/// available at some point in the future.
pending_layout_images: DomRefCell<HashMapTracedValues<PendingImageId, Vec<Dom<Node>>>>,
/// Vector images for which layout has intiated rasterization at a specific size
/// and whose results are not yet available. They are stored in the [`ScriptThread`]
/// so that the element can be marked dirty once the rasterization is completed.
pending_images_for_rasterization:
DomRefCell<HashMapTracedValues<PendingImageRasterizationKey, Vec<Dom<Node>>>>,
/// Directory to store unminified css for this window if unminify-css
/// opt is enabled.
unminified_css_dir: DomRefCell<Option<String>>,
@ -569,7 +577,7 @@ impl Window {
&self,
id: PendingImageId,
callback: impl Fn(PendingImageResponse) + 'static,
) -> IpcSender<PendingImageResponse> {
) -> IpcSender<ImageCacheResponseMessage> {
self.pending_image_callbacks
.borrow_mut()
.entry(id)
@ -598,6 +606,22 @@ impl Window {
}
}
pub(crate) fn handle_image_rasterization_complete_notification(
&self,
response: RasterizationCompleteResponse,
) {
let mut images = self.pending_images_for_rasterization.borrow_mut();
let nodes = images.entry((response.image_id, response.requested_size));
let nodes = match nodes {
Entry::Occupied(nodes) => nodes,
Entry::Vacant(_) => return,
};
for node in nodes.get() {
node.dirty(NodeDamage::OtherNodeDamage);
}
nodes.remove();
}
pub(crate) fn pending_image_notification(&self, response: PendingImageResponse) {
// We take the images here, in order to prevent maintaining a mutable borrow when
// image callbacks are called. These, in turn, can trigger garbage collection.
@ -2225,8 +2249,11 @@ impl Window {
.pending_layout_image_notification(response);
});
self.image_cache
.add_listener(ImageResponder::new(sender, self.pipeline_id(), id));
self.image_cache.add_listener(ImageLoadListener::new(
sender,
self.pipeline_id(),
id,
));
}
let nodes = images.entry(id).or_default();
@ -2235,6 +2262,25 @@ impl Window {
}
}
for image in results.pending_rasterization_images {
let node = unsafe { from_untrusted_node_address(image.node) };
let mut images = self.pending_images_for_rasterization.borrow_mut();
if !images.contains_key(&(image.id, image.size)) {
self.image_cache.add_rasterization_complete_listener(
pipeline_id,
image.id,
image.size,
self.image_cache_sender.clone(),
);
}
let nodes = images.entry((image.id, image.size)).or_default();
if !nodes.iter().any(|n| std::ptr::eq(&**n, &*node)) {
nodes.push(Dom::from_ref(&*node));
}
}
let size_messages = self
.Document()
.iframes_mut()
@ -2326,12 +2372,13 @@ impl Window {
});
let has_sent_idle_message = self.has_sent_idle_message.get();
let pending_images = !self.pending_layout_images.borrow().is_empty();
let no_pending_images = self.pending_layout_images.borrow().is_empty() &&
self.pending_images_for_rasterization.borrow().is_empty();
if !has_sent_idle_message &&
is_ready_state_complete &&
!reftest_wait &&
!pending_images &&
no_pending_images &&
!waiting_for_web_fonts_to_load
{
debug!(
@ -2739,13 +2786,13 @@ impl Window {
self.viewport_details.get()
}
/// Get the theme of this [`Window`].
pub(crate) fn theme(&self) -> Theme {
self.theme.get()
}
/// Handle a theme change request, triggering a reflow is any actual change occured.
pub(crate) fn handle_theme_change(&self, new_theme: Theme) {
let new_theme = match new_theme {
Theme::Light => PrefersColorScheme::Light,
Theme::Dark => PrefersColorScheme::Dark,
};
if self.theme.get() == new_theme {
return;
}
@ -3006,7 +3053,7 @@ impl Window {
script_chan: Sender<MainThreadScriptMsg>,
layout: Box<dyn Layout>,
font_context: Arc<FontContext>,
image_cache_sender: IpcSender<PendingImageResponse>,
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
image_cache: Arc<dyn ImageCache>,
resource_threads: ResourceThreads,
#[cfg(feature = "bluetooth")] bluetooth_thread: IpcSender<BluetoothRequest>,
@ -3033,6 +3080,7 @@ impl Window {
player_context: WindowGLContext,
#[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
inherited_secure_context: Option<bool>,
theme: Theme,
) -> DomRoot<Self> {
let error_reporter = CSSErrorReporter {
pipelineid: pipeline_id,
@ -3104,6 +3152,7 @@ impl Window {
webxr_registry,
pending_image_callbacks: Default::default(),
pending_layout_images: Default::default(),
pending_images_for_rasterization: Default::default(),
unminified_css_dir: Default::default(),
local_script_source,
test_worklet: Default::default(),
@ -3118,7 +3167,7 @@ impl Window {
throttled: Cell::new(false),
layout_marker: DomRefCell::new(Rc::new(Cell::new(true))),
current_event: DomRefCell::new(None),
theme: Cell::new(PrefersColorScheme::Light),
theme: Cell::new(theme),
trusted_types: Default::default(),
});

View file

@ -329,6 +329,9 @@ impl WindowProxy {
opener: Some(self.browsing_context_id),
load_data,
viewport_details: window.viewport_details(),
// Use the current `WebView`'s theme initially, but the embedder may
// change this later.
theme: window.theme(),
};
ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
// TODO: if noopener is false, copy the sessionStorage storage area of the creator origin.

View file

@ -797,6 +797,7 @@ impl XMLHttpRequestMethods<crate::DomTypeHolder> for XMLHttpRequest {
if self.ready_state.get() == XMLHttpRequestState::Done {
self.change_ready_state(XMLHttpRequestState::Unsent, can_gc);
self.response_status.set(Err(()));
*self.status.borrow_mut() = HttpStatus::new_error();
self.response.borrow_mut().clear();
self.response_headers.borrow_mut().clear();
}
@ -1188,6 +1189,8 @@ impl XMLHttpRequest {
self.discard_subsequent_responses();
self.send_flag.set(false);
*self.status.borrow_mut() = HttpStatus::new_error();
self.response_headers.borrow_mut().clear();
// XXXManishearth set response to NetworkError
self.change_ready_state(XMLHttpRequestState::Done, can_gc);
return_if_fetch_was_terminated!();

View file

@ -54,7 +54,7 @@ impl TryFrom<u16> for XPathResultType {
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[derive(Debug, JSTraceable, MallocSizeOf)]
pub(crate) enum XPathResultValue {
Boolean(bool),
/// A IEEE-754 double-precision floating point number

View file

@ -6,7 +6,7 @@ use std::sync::Arc;
use constellation_traits::BlobImpl;
use indexmap::IndexMap;
use pixels::Image;
use pixels::RasterImage;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::root::DomRoot;
@ -70,7 +70,7 @@ impl Kind {
/// <https://html.spec.whatwg.org/multipage/#drag-data-store-bitmap>
#[allow(dead_code)] // TODO this used by DragEvent.
struct Bitmap {
image: Option<Arc<Image>>,
image: Option<Arc<RasterImage>>,
x: i32,
y: i32,
}
@ -126,7 +126,7 @@ impl DragDataStore {
self.mode = mode;
}
pub(crate) fn set_bitmap(&mut self, image: Option<Arc<Image>>, x: i32, y: i32) {
pub(crate) fn set_bitmap(&mut self, image: Option<Arc<RasterImage>>, x: i32, y: i32) {
self.bitmap = Some(Bitmap { image, x, y });
}

View file

@ -77,7 +77,10 @@ impl ImageAnimationManager {
image.id.unwrap(),
ImageDescriptor {
format: ImageFormat::BGRA8,
size: DeviceIntSize::new(image.width as i32, image.height as i32),
size: DeviceIntSize::new(
image.metadata.width as i32,
image.metadata.height as i32,
),
stride: None,
offset: 0,
flags: ImageDescriptorFlags::ALLOW_MIPMAPS,

View file

@ -6,12 +6,12 @@
use std::borrow::Cow;
use std::fmt;
use std::sync::Arc as StdArc;
use base::id::{BrowsingContextId, PipelineId};
use fonts_traits::ByteIndex;
use html5ever::{local_name, ns};
use pixels::{Image, ImageMetadata};
use net_traits::image_cache::Image;
use pixels::ImageMetadata;
use range::Range;
use script_layout_interface::wrapper_traits::{LayoutDataTrait, LayoutNode, ThreadSafeLayoutNode};
use script_layout_interface::{
@ -28,7 +28,7 @@ use style::selector_parser::PseudoElement;
use super::{
ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement,
};
use crate::dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId, TextTypeId};
use crate::dom::bindings::inheritance::NodeTypeId;
use crate::dom::bindings::root::LayoutDom;
use crate::dom::element::{Element, LayoutElementHelpers};
use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags, NodeTypeIdWrapper};
@ -100,6 +100,10 @@ impl<'dom> ServoLayoutNode<'dom> {
pub fn is_text_input(&self) -> bool {
self.node.is_text_input()
}
pub fn is_text_control_inner_editor(&self) -> bool {
self.node.is_text_control_inner_editor()
}
}
impl style::dom::NodeInfo for ServoLayoutNode<'_> {
@ -108,8 +112,7 @@ impl style::dom::NodeInfo for ServoLayoutNode<'_> {
}
fn is_text_node(&self) -> bool {
self.script_type_id() ==
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text))
self.node.is_text_node_for_layout()
}
}
@ -371,7 +374,7 @@ impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
this.image_density()
}
fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> {
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
let this = unsafe { self.get_jsmanaged() };
this.image_data()
}

View file

@ -16,7 +16,7 @@ use crossbeam_channel::{Receiver, SendError, Sender, select};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
use ipc_channel::ipc::IpcSender;
use net_traits::FetchResponseMsg;
use net_traits::image_cache::PendingImageResponse;
use net_traits::image_cache::ImageCacheResponseMessage;
use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan};
use profile_traits::time::{self as profile_time};
use script_traits::{Painter, ScriptThreadMessage};
@ -40,7 +40,7 @@ pub(crate) enum MixedMessage {
FromConstellation(ScriptThreadMessage),
FromScript(MainThreadScriptMsg),
FromDevtools(DevtoolScriptControlMsg),
FromImageCache(PendingImageResponse),
FromImageCache(ImageCacheResponseMessage),
#[cfg(feature = "webgpu")]
FromWebGPUServer(WebGPUMsg),
TimerFired,
@ -104,7 +104,14 @@ impl MixedMessage {
MainThreadScriptMsg::Inactive => None,
MainThreadScriptMsg::WakeUp => None,
},
MixedMessage::FromImageCache(response) => Some(response.pipeline_id),
MixedMessage::FromImageCache(response) => match response {
ImageCacheResponseMessage::NotifyPendingImageLoadStatus(response) => {
Some(response.pipeline_id)
},
ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
Some(response.pipeline_id)
},
},
MixedMessage::FromDevtools(_) | MixedMessage::TimerFired => None,
#[cfg(feature = "webgpu")]
MixedMessage::FromWebGPUServer(..) => None,
@ -326,7 +333,7 @@ pub(crate) struct ScriptThreadSenders {
/// messages on this channel are routed to crossbeam [`Sender`] on the router thread, which
/// in turn sends messages to [`ScriptThreadReceivers::image_cache_receiver`].
#[no_trace]
pub(crate) image_cache_sender: IpcSender<PendingImageResponse>,
pub(crate) image_cache_sender: IpcSender<ImageCacheResponseMessage>,
/// For providing contact with the time profiler.
#[no_trace]
@ -355,7 +362,7 @@ pub(crate) struct ScriptThreadReceivers {
/// The [`Receiver`] which receives incoming messages from the `ImageCache`.
#[no_trace]
pub(crate) image_cache_receiver: Receiver<PendingImageResponse>,
pub(crate) image_cache_receiver: Receiver<ImageCacheResponseMessage>,
/// For receiving commands from an optional devtools server. Will be ignored if no such server
/// exists. When devtools are not active this will be [`crossbeam_channel::never()`].

View file

@ -12,7 +12,7 @@ use base::cross_process_instant::CrossProcessInstant;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use constellation_traits::LoadData;
use crossbeam_channel::Sender;
use embedder_traits::ViewportDetails;
use embedder_traits::{Theme, ViewportDetails};
use http::header;
use net_traits::request::{
CredentialsMode, InsecureRequestsPolicy, RedirectMode, RequestBuilder, RequestMode,
@ -159,6 +159,9 @@ pub(crate) struct InProgressLoad {
/// this load.
#[no_trace]
pub(crate) url_list: Vec<ServoUrl>,
/// The [`Theme`] to use for this page, once it loads.
#[no_trace]
pub(crate) theme: Theme,
}
impl InProgressLoad {
@ -171,6 +174,7 @@ impl InProgressLoad {
parent_info: Option<PipelineId>,
opener: Option<BrowsingContextId>,
viewport_details: ViewportDetails,
theme: Theme,
origin: MutableOrigin,
load_data: LoadData,
) -> InProgressLoad {
@ -189,6 +193,7 @@ impl InProgressLoad {
canceller: Default::default(),
load_data,
url_list: vec![url],
theme,
}
}

View file

@ -71,7 +71,7 @@ use js::jsval::UndefinedValue;
use js::rust::ParentRuntime;
use media::WindowGLContext;
use metrics::MAX_TASK_NS;
use net_traits::image_cache::{ImageCache, PendingImageResponse};
use net_traits::image_cache::{ImageCache, ImageCacheResponseMessage};
use net_traits::request::{Referrer, RequestId};
use net_traits::response::ResponseInit;
use net_traits::storage_thread::StorageType;
@ -194,6 +194,8 @@ pub(crate) struct IncompleteParserContexts(RefCell<Vec<(PipelineId, ParserContex
unsafe_no_jsmanaged_fields!(TaskQueue<MainThreadScriptMsg>);
type NodeIdSet = HashSet<String>;
#[derive(JSTraceable)]
// ScriptThread instances are rooted on creation, so this is okay
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
@ -315,8 +317,9 @@ pub struct ScriptThread {
#[no_trace]
player_context: WindowGLContext,
/// A set of all nodes ever created in this script thread
node_ids: DomRefCell<HashSet<String>>,
/// A map from pipelines to all owned nodes ever created in this script thread
#[no_trace]
pipeline_to_node_ids: DomRefCell<HashMap<PipelineId, NodeIdSet>>,
/// Code is running as a consequence of a user interaction
is_user_interacting: Cell<bool>,
@ -403,14 +406,20 @@ impl ScriptThreadFactory for ScriptThread {
WebViewId::install(state.webview_id);
let roots = RootCollection::new();
let _stack_roots = ThreadLocalStackRoots::new(&roots);
let id = state.id;
let browsing_context_id = state.browsing_context_id;
let webview_id = state.webview_id;
let parent_info = state.parent_info;
let opener = state.opener;
let memory_profiler_sender = state.memory_profiler_sender.clone();
let viewport_details = state.viewport_details;
let in_progress_load = InProgressLoad::new(
state.id,
state.browsing_context_id,
state.webview_id,
state.parent_info,
state.opener,
state.viewport_details,
state.theme,
MutableOrigin::new(load_data.url.origin()),
load_data,
);
let reporter_name = format!("script-reporter-{:?}", state.id);
let script_thread = ScriptThread::new(state, layout_factory, system_font_service);
SCRIPT_THREAD_ROOT.with(|root| {
@ -419,19 +428,8 @@ impl ScriptThreadFactory for ScriptThread {
let mut failsafe = ScriptMemoryFailsafe::new(&script_thread);
let origin = MutableOrigin::new(load_data.url.origin());
script_thread.pre_page_load(InProgressLoad::new(
id,
browsing_context_id,
webview_id,
parent_info,
opener,
viewport_details,
origin,
load_data,
));
script_thread.pre_page_load(in_progress_load);
let reporter_name = format!("script-reporter-{:?}", id);
memory_profiler_sender.run_with_memory_reporting(
|| {
script_thread.start(CanGc::note());
@ -823,14 +821,25 @@ impl ScriptThread {
})
}
pub(crate) fn save_node_id(node_id: String) {
pub(crate) fn save_node_id(pipeline: PipelineId, node_id: String) {
with_script_thread(|script_thread| {
script_thread.node_ids.borrow_mut().insert(node_id);
script_thread
.pipeline_to_node_ids
.borrow_mut()
.entry(pipeline)
.or_default()
.insert(node_id);
})
}
pub(crate) fn has_node_id(node_id: &str) -> bool {
with_script_thread(|script_thread| script_thread.node_ids.borrow().contains(node_id))
pub(crate) fn has_node_id(pipeline: PipelineId, node_id: &str) -> bool {
with_script_thread(|script_thread| {
script_thread
.pipeline_to_node_ids
.borrow()
.get(&pipeline)
.is_some_and(|node_ids| node_ids.contains(node_id))
})
}
/// Creates a new script thread.
@ -950,7 +959,7 @@ impl ScriptThread {
unminify_css: opts.unminify_css,
user_content_manager: state.user_content_manager,
player_context: state.player_context,
node_ids: Default::default(),
pipeline_to_node_ids: Default::default(),
is_user_interacting: Cell::new(false),
#[cfg(feature = "webgpu")]
gpu_id_hub: Arc::new(IdentityHub::default()),
@ -2100,11 +2109,24 @@ impl ScriptThread {
}
}
fn handle_msg_from_image_cache(&self, response: PendingImageResponse) {
let window = self.documents.borrow().find_window(response.pipeline_id);
if let Some(ref window) = window {
window.pending_image_notification(response);
}
fn handle_msg_from_image_cache(&self, response: ImageCacheResponseMessage) {
match response {
ImageCacheResponseMessage::NotifyPendingImageLoadStatus(pending_image_response) => {
let window = self
.documents
.borrow()
.find_window(pending_image_response.pipeline_id);
if let Some(ref window) = window {
window.pending_image_notification(pending_image_response);
}
},
ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
let window = self.documents.borrow().find_window(response.pipeline_id);
if let Some(ref window) = window {
window.handle_image_rasterization_complete_notification(response);
}
},
};
}
fn handle_webdriver_msg(
@ -2258,15 +2280,6 @@ impl ScriptThread {
can_gc,
)
},
WebDriverScriptCommand::FocusElement(element_id, reply) => {
webdriver_handlers::handle_focus_element(
&documents,
pipeline_id,
element_id,
reply,
can_gc,
)
},
WebDriverScriptCommand::ElementClick(element_id, reply) => {
webdriver_handlers::handle_element_click(
&documents,
@ -2372,6 +2385,20 @@ impl ScriptThread {
WebDriverScriptCommand::GetTitle(reply) => {
webdriver_handlers::handle_get_title(&documents, pipeline_id, reply)
},
WebDriverScriptCommand::WillSendKeys(
element_id,
text,
strict_file_interactability,
reply,
) => webdriver_handlers::handle_will_send_keys(
&documents,
pipeline_id,
element_id,
text,
strict_file_interactability,
reply,
can_gc,
),
_ => (),
}
}
@ -2435,6 +2462,7 @@ impl ScriptThread {
opener,
load_data,
viewport_details,
theme,
} = new_layout_info;
// Kick off the fetch for the new resource.
@ -2446,6 +2474,7 @@ impl ScriptThread {
parent_info,
opener,
viewport_details,
theme,
origin,
load_data,
);
@ -3189,6 +3218,7 @@ impl ScriptThread {
time_profiler_chan: self.senders.time_profiler_sender.clone(),
compositor_api: self.compositor_api.clone(),
viewport_details: incomplete.viewport_details,
theme: incomplete.theme,
};
// Create the window and document objects.
@ -3228,6 +3258,7 @@ impl ScriptThread {
#[cfg(feature = "webgpu")]
self.gpu_id_hub.clone(),
incomplete.load_data.inherited_secure_context,
incomplete.theme,
);
let _realm = enter_realm(&*window);

View file

@ -319,11 +319,18 @@ impl<T: ClipboardProvider> TextInput<T> {
}
/// Remove a character at the current editing point
pub fn delete_char(&mut self, dir: Direction) {
///
/// Returns true if any character was deleted
pub fn delete_char(&mut self, dir: Direction) -> bool {
if self.selection_origin.is_none() || self.selection_origin == Some(self.edit_point) {
self.adjust_horizontal_by_one(dir, Selection::Selected);
}
self.replace_selection(DOMString::new());
if self.selection_start() == self.selection_end() {
false
} else {
self.replace_selection(DOMString::new());
true
}
}
/// Insert a character at the current editing point
@ -895,6 +902,7 @@ impl<T: ClipboardProvider> TextInput<T> {
KeyReaction::RedrawSelection
})
.shortcut(CMD_OR_CONTROL, 'X', || {
// FIXME: this is unreachable because ClipboardEvent is fired instead of keydown
if let Some(text) = self.get_selection_text() {
self.clipboard_provider.set_text(text);
self.delete_char(Direction::Backward);
@ -914,12 +922,18 @@ impl<T: ClipboardProvider> TextInput<T> {
KeyReaction::DispatchInput
})
.shortcut(Modifiers::empty(), Key::Delete, || {
self.delete_char(Direction::Forward);
KeyReaction::DispatchInput
if self.delete_char(Direction::Forward) {
KeyReaction::DispatchInput
} else {
KeyReaction::Nothing
}
})
.shortcut(Modifiers::empty(), Key::Backspace, || {
self.delete_char(Direction::Backward);
KeyReaction::DispatchInput
if self.delete_char(Direction::Backward) {
KeyReaction::DispatchInput
} else {
KeyReaction::Nothing
}
})
.optional_shortcut(macos, Modifiers::META, Key::ArrowLeft, || {
self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
@ -984,6 +998,9 @@ impl<T: ClipboardProvider> TextInput<T> {
self.insert_string(c.as_str());
return KeyReaction::DispatchInput;
}
if matches!(key, Key::Process) {
return KeyReaction::DispatchInput;
}
KeyReaction::Nothing
})
.unwrap()

View file

@ -4,7 +4,7 @@
use std::cell::Cell;
use std::cmp::{Ord, Ordering};
use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};
use std::default::Default;
use std::rc::Rc;
use std::time::{Duration, Instant};
@ -47,7 +47,7 @@ pub(crate) struct OneshotTimers {
global_scope: Dom<GlobalScope>,
js_timers: JsTimers,
next_timer_handle: Cell<OneshotTimerHandle>,
timers: DomRefCell<Vec<OneshotTimer>>,
timers: DomRefCell<VecDeque<OneshotTimer>>,
suspended_since: Cell<Option<Instant>>,
/// Initially 0, increased whenever the associated document is reactivated
/// by the amount of ms the document was inactive. The current time can be
@ -131,7 +131,7 @@ impl OneshotTimers {
global_scope: Dom::from_ref(global_scope),
js_timers: JsTimers::default(),
next_timer_handle: Cell::new(OneshotTimerHandle(1)),
timers: DomRefCell::new(Vec::new()),
timers: DomRefCell::new(VecDeque::new()),
suspended_since: Cell::new(None),
suspension_offset: Cell::new(Duration::ZERO),
expected_event_id: Cell::new(TimerEventId(0)),
@ -180,7 +180,7 @@ impl OneshotTimers {
}
fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
match self.timers.borrow().last() {
match self.timers.borrow().back() {
None => false,
Some(max_timer) => max_timer.handle == handle,
}
@ -201,7 +201,7 @@ impl OneshotTimers {
let base_time = self.base_time();
// Since the event id was the expected one, at least one timer should be due.
if base_time < self.timers.borrow().last().unwrap().scheduled_for {
if base_time < self.timers.borrow().back().unwrap().scheduled_for {
warn!("Unexpected timing!");
return;
}
@ -213,11 +213,11 @@ impl OneshotTimers {
loop {
let mut timers = self.timers.borrow_mut();
if timers.is_empty() || timers.last().unwrap().scheduled_for > base_time {
if timers.is_empty() || timers.back().unwrap().scheduled_for > base_time {
break;
}
timers_to_run.push(timers.pop().unwrap());
timers_to_run.push(timers.pop_back().unwrap());
}
for timer in timers_to_run {
@ -286,7 +286,7 @@ impl OneshotTimers {
}
let timers = self.timers.borrow();
let Some(timer) = timers.last() else {
let Some(timer) = timers.back() else {
return;
};

Some files were not shown because too many files have changed in this diff Show more