mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Merge branch 'main' into network_monitor_01
This commit is contained in:
commit
e29f8eec2f
2132 changed files with 47333 additions and 17366 deletions
18
.flake8
18
.flake8
|
@ -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
|
|
@ -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
|
||||
|
|
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
|
@ -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
423
Cargo.lock
generated
|
@ -737,6 +737,12 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
10
README.md
10
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
234
components/compositing/refresh_driver.rs
Normal file
234
components/compositing/refresh_driver.rs
Normal 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,
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
45
components/constellation/constellation_webview.rs
Normal file
45
components/constellation/constellation_webview.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ mod tracing;
|
|||
|
||||
mod browsingcontext;
|
||||
mod constellation;
|
||||
mod constellation_webview;
|
||||
mod event_loop;
|
||||
mod logging;
|
||||
mod pipeline;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -78,6 +78,7 @@ mod from_compositor {
|
|||
Self::SetScrollStates(..) => target!("SetScrollStates"),
|
||||
Self::PaintMetric(..) => target!("PaintMetric"),
|
||||
Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"),
|
||||
Self::CreateMemoryReport(..) => target!("CreateMemoryReport"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
55
components/devtools/actors/breakpoint.rs
Normal file
55
components/devtools/actors/breakpoint.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -10,17 +10,21 @@ use fnv::FnvHashMap;
|
|||
use fonts::FontContext;
|
||||
use fxhash::FxHashMap;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
||||
Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
|
||||
UsePlaceholder,
|
||||
};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use pixels::Image as PixelImage;
|
||||
use script_layout_interface::{IFrameSizes, ImageAnimationState, PendingImage, PendingImageState};
|
||||
use pixels::RasterImage;
|
||||
use script_layout_interface::{
|
||||
IFrameSizes, ImageAnimationState, PendingImage, PendingImageState, PendingRasterizationImage,
|
||||
};
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use style::context::SharedStyleContext;
|
||||
use style::dom::OpaqueNode;
|
||||
use style::values::computed::image::{Gradient, Image};
|
||||
use webrender_api::units::{DeviceIntSize, DeviceSize};
|
||||
|
||||
use crate::display_list::WebRenderImageInfo;
|
||||
pub(crate) type CachedImageOrError = Result<CachedImage, ResolveImageError>;
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
pub id: PipelineId,
|
||||
|
@ -39,11 +43,17 @@ pub struct LayoutContext<'a> {
|
|||
/// A list of in-progress image loads to be shared with the script thread.
|
||||
pub pending_images: Mutex<Vec<PendingImage>>,
|
||||
|
||||
/// A list of fully loaded vector images that need to be rasterized to a specific
|
||||
/// size determined by layout. This will be shared with the script thread.
|
||||
pub pending_rasterization_images: Mutex<Vec<PendingRasterizationImage>>,
|
||||
|
||||
/// A collection of `<iframe>` sizes to send back to script.
|
||||
pub iframe_sizes: Mutex<IFrameSizes>,
|
||||
|
||||
pub webrender_image_cache:
|
||||
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
|
||||
// A cache that maps image resources used in CSS (e.g as the `url()` value
|
||||
// for `background-image` or `content` property) to the final resolved image data.
|
||||
pub resolved_images_cache:
|
||||
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
|
||||
|
||||
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
|
||||
|
||||
|
@ -53,18 +63,24 @@ pub struct LayoutContext<'a> {
|
|||
|
||||
pub enum ResolvedImage<'a> {
|
||||
Gradient(&'a Gradient),
|
||||
Image(WebRenderImageInfo),
|
||||
// The size is tracked explicitly as image-set images can specify their
|
||||
// natural resolution which affects the final size for raster images.
|
||||
Image {
|
||||
image: CachedImage,
|
||||
size: DeviceSize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Drop for LayoutContext<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !std::thread::panicking() {
|
||||
assert!(self.pending_images.lock().is_empty());
|
||||
assert!(self.pending_rasterization_images.lock().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ResolveImageError {
|
||||
LoadError,
|
||||
ImagePending,
|
||||
|
@ -78,18 +94,24 @@ pub enum ResolveImageError {
|
|||
None,
|
||||
}
|
||||
|
||||
pub(crate) enum LayoutImageCacheResult {
|
||||
Pending,
|
||||
DataAvailable(ImageOrMetadataAvailable),
|
||||
LoadError,
|
||||
}
|
||||
|
||||
impl LayoutContext<'_> {
|
||||
#[inline(always)]
|
||||
pub fn shared_context(&self) -> &SharedStyleContext {
|
||||
&self.style_context
|
||||
}
|
||||
|
||||
pub fn get_or_request_image_or_meta(
|
||||
pub(crate) fn get_or_request_image_or_meta(
|
||||
&self,
|
||||
node: OpaqueNode,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
) -> Result<ImageOrMetadataAvailable, ResolveImageError> {
|
||||
) -> LayoutImageCacheResult {
|
||||
// Check for available image or start tracking.
|
||||
let cache_result = self.image_cache.get_cached_image_status(
|
||||
url.clone(),
|
||||
|
@ -99,7 +121,9 @@ impl LayoutContext<'_> {
|
|||
);
|
||||
|
||||
match cache_result {
|
||||
ImageCacheResult::Available(img_or_meta) => Ok(img_or_meta),
|
||||
ImageCacheResult::Available(img_or_meta) => {
|
||||
LayoutImageCacheResult::DataAvailable(img_or_meta)
|
||||
},
|
||||
// Image has been requested, is still pending. Return no image for this paint loop.
|
||||
// When the image loads it will trigger a reflow and/or repaint.
|
||||
ImageCacheResult::Pending(id) => {
|
||||
|
@ -110,7 +134,7 @@ impl LayoutContext<'_> {
|
|||
origin: self.origin.clone(),
|
||||
};
|
||||
self.pending_images.lock().push(image);
|
||||
Result::Err(ResolveImageError::ImagePending)
|
||||
LayoutImageCacheResult::Pending
|
||||
},
|
||||
// Not yet requested - request image or metadata from the cache
|
||||
ImageCacheResult::ReadyForRequest(id) => {
|
||||
|
@ -121,14 +145,14 @@ impl LayoutContext<'_> {
|
|||
origin: self.origin.clone(),
|
||||
};
|
||||
self.pending_images.lock().push(image);
|
||||
Result::Err(ResolveImageError::ImageRequested)
|
||||
LayoutImageCacheResult::Pending
|
||||
},
|
||||
// Image failed to load, so just return nothing
|
||||
ImageCacheResult::LoadError => Result::Err(ResolveImageError::LoadError),
|
||||
// Image failed to load, so just return the same error.
|
||||
ImageCacheResult::LoadError => LayoutImageCacheResult::LoadError,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<PixelImage>) {
|
||||
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<RasterImage>) {
|
||||
let mut store = self.node_image_animation_map.write();
|
||||
|
||||
// 1. first check whether node previously being track for animated image.
|
||||
|
@ -157,42 +181,66 @@ impl LayoutContext<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_webrender_image_for_url(
|
||||
fn get_cached_image_for_url(
|
||||
&self,
|
||||
node: OpaqueNode,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
) -> Result<WebRenderImageInfo, ResolveImageError> {
|
||||
if let Some(existing_webrender_image) = self
|
||||
.webrender_image_cache
|
||||
) -> Result<CachedImage, ResolveImageError> {
|
||||
if let Some(cached_image) = self
|
||||
.resolved_images_cache
|
||||
.read()
|
||||
.get(&(url.clone(), use_placeholder))
|
||||
{
|
||||
return Ok(*existing_webrender_image);
|
||||
return cached_image.clone();
|
||||
}
|
||||
let image_or_meta =
|
||||
self.get_or_request_image_or_meta(node, url.clone(), use_placeholder)?;
|
||||
match image_or_meta {
|
||||
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||
self.handle_animated_image(node, image.clone());
|
||||
let image_info = WebRenderImageInfo {
|
||||
size: Size2D::new(image.width, image.height),
|
||||
key: image.id,
|
||||
};
|
||||
if image_info.key.is_none() {
|
||||
Ok(image_info)
|
||||
} else {
|
||||
let mut webrender_image_cache = self.webrender_image_cache.write();
|
||||
webrender_image_cache.insert((url, use_placeholder), image_info);
|
||||
Ok(image_info)
|
||||
}
|
||||
|
||||
let result = self.get_or_request_image_or_meta(node, url.clone(), use_placeholder);
|
||||
match result {
|
||||
LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
|
||||
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
|
||||
if let Some(image) = image.as_raster_image() {
|
||||
self.handle_animated_image(node, image.clone());
|
||||
}
|
||||
|
||||
let mut resolved_images_cache = self.resolved_images_cache.write();
|
||||
resolved_images_cache.insert((url, use_placeholder), Ok(image.clone()));
|
||||
Ok(image)
|
||||
},
|
||||
ImageOrMetadataAvailable::MetadataAvailable(..) => {
|
||||
Result::Err(ResolveImageError::OnlyMetadata)
|
||||
},
|
||||
},
|
||||
ImageOrMetadataAvailable::MetadataAvailable(..) => {
|
||||
Result::Err(ResolveImageError::OnlyMetadata)
|
||||
LayoutImageCacheResult::Pending => Result::Err(ResolveImageError::ImagePending),
|
||||
LayoutImageCacheResult::LoadError => {
|
||||
let error = Err(ResolveImageError::LoadError);
|
||||
self.resolved_images_cache
|
||||
.write()
|
||||
.insert((url, use_placeholder), error.clone());
|
||||
error
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rasterize_vector_image(
|
||||
&self,
|
||||
image_id: PendingImageId,
|
||||
size: DeviceIntSize,
|
||||
node: OpaqueNode,
|
||||
) -> Option<RasterImage> {
|
||||
let result = self.image_cache.rasterize_vector_image(image_id, size);
|
||||
if result.is_none() {
|
||||
self.pending_rasterization_images
|
||||
.lock()
|
||||
.push(PendingRasterizationImage {
|
||||
id: image_id,
|
||||
node: node.into(),
|
||||
size,
|
||||
});
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn resolve_image<'a>(
|
||||
&self,
|
||||
node: Option<OpaqueNode>,
|
||||
|
@ -215,12 +263,14 @@ impl LayoutContext<'_> {
|
|||
// element and not just the node.
|
||||
let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
|
||||
let node = node.ok_or(ResolveImageError::MissingNode)?;
|
||||
let webrender_info = self.get_webrender_image_for_url(
|
||||
let image = self.get_cached_image_for_url(
|
||||
node,
|
||||
image_url.clone().into(),
|
||||
UsePlaceholder::No,
|
||||
)?;
|
||||
Ok(ResolvedImage::Image(webrender_info))
|
||||
let metadata = image.metadata();
|
||||
let size = Size2D::new(metadata.width, metadata.height).to_f32();
|
||||
Ok(ResolvedImage::Image { image, size })
|
||||
},
|
||||
Image::ImageSet(image_set) => {
|
||||
image_set
|
||||
|
@ -230,17 +280,32 @@ impl LayoutContext<'_> {
|
|||
.and_then(|image| {
|
||||
self.resolve_image(node, &image.image)
|
||||
.map(|info| match info {
|
||||
ResolvedImage::Image(mut image_info) => {
|
||||
ResolvedImage::Image {
|
||||
image: cached_image,
|
||||
..
|
||||
} => {
|
||||
// From <https://drafts.csswg.org/css-images-4/#image-set-notation>:
|
||||
// > A <resolution> (optional). This is used to help the UA decide
|
||||
// > which <image-set-option> to choose. If the image reference is
|
||||
// > for a raster image, it also specifies the image’s natural
|
||||
// > resolution, overriding any other source of data that might
|
||||
// > supply a natural resolution.
|
||||
image_info.size = (image_info.size.to_f32() /
|
||||
image.resolution.dppx())
|
||||
.to_u32();
|
||||
ResolvedImage::Image(image_info)
|
||||
let image_metadata = cached_image.metadata();
|
||||
let size = if cached_image.as_raster_image().is_some() {
|
||||
let scale_factor = image.resolution.dppx();
|
||||
Size2D::new(
|
||||
image_metadata.width as f32 / scale_factor,
|
||||
image_metadata.height as f32 / scale_factor,
|
||||
)
|
||||
} else {
|
||||
Size2D::new(image_metadata.width, image_metadata.height)
|
||||
.to_f32()
|
||||
};
|
||||
|
||||
ResolvedImage::Image {
|
||||
image: cached_image,
|
||||
size,
|
||||
}
|
||||
},
|
||||
_ => info,
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) => {
|
||||
|
|
|
@ -618,7 +618,7 @@ impl StackingContext {
|
|||
// If it’s 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
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
|
||||
use std::any::Any;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use html5ever::{local_name, ns};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use pixels::Image;
|
||||
use net_traits::image_cache::Image;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use script_layout_interface::wrapper_traits::{
|
||||
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
|
@ -197,7 +196,7 @@ impl Drop for BoxSlot<'_> {
|
|||
pub(crate) trait NodeExt<'dom> {
|
||||
/// Returns the image if it’s loaded, and its size in image pixels
|
||||
/// adjusted for `image_density`.
|
||||
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)>;
|
||||
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)>;
|
||||
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
|
||||
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
|
||||
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
|
||||
|
@ -220,12 +219,15 @@ pub(crate) trait NodeExt<'dom> {
|
|||
}
|
||||
|
||||
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
|
||||
fn as_image(&self) -> Option<(Option<Arc<Image>>, PhysicalSize<f64>)> {
|
||||
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)> {
|
||||
let node = self.to_threadsafe();
|
||||
let (resource, metadata) = node.image_data()?;
|
||||
let (width, height) = resource
|
||||
.as_ref()
|
||||
.map(|image| (image.width, image.height))
|
||||
.map(|image| {
|
||||
let image_metadata = image.metadata();
|
||||
(image_metadata.width, image_metadata.height)
|
||||
})
|
||||
.or_else(|| metadata.map(|metadata| (metadata.width, metadata.height)))
|
||||
.unwrap_or((0, 0));
|
||||
let (mut width, mut height) = (width as f64, height as f64);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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\
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -265,4 +265,8 @@ select {
|
|||
padding: 0 0.25em;
|
||||
/* Don't show a text cursor when hovering selected option */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
slot {
|
||||
display: contents;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ indexmap = { workspace = true }
|
|||
ipc-channel = { workspace = true }
|
||||
keyboard-types = { workspace = true }
|
||||
markup5ever = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
servo_allocator = { path = "../allocator" }
|
||||
servo_arc = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
|
|
|
@ -774,6 +774,7 @@ malloc_size_of_is_0!(content_security_policy::Destination);
|
|||
malloc_size_of_is_0!(http::StatusCode);
|
||||
malloc_size_of_is_0!(app_units::Au);
|
||||
malloc_size_of_is_0!(keyboard_types::Modifiers);
|
||||
malloc_size_of_is_0!(mime::Mime);
|
||||
malloc_size_of_is_0!(std::num::NonZeroU64);
|
||||
malloc_size_of_is_0!(std::num::NonZeroUsize);
|
||||
malloc_size_of_is_0!(std::sync::atomic::AtomicBool);
|
||||
|
@ -782,6 +783,7 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize);
|
|||
malloc_size_of_is_0!(std::time::Duration);
|
||||
malloc_size_of_is_0!(std::time::Instant);
|
||||
malloc_size_of_is_0!(std::time::SystemTime);
|
||||
malloc_size_of_is_0!(resvg::usvg::Tree);
|
||||
malloc_size_of_is_0!(style::data::ElementData);
|
||||
malloc_size_of_is_0!(style::font_face::SourceList);
|
||||
malloc_size_of_is_0!(style::properties::ComputedValues);
|
||||
|
|
|
@ -56,6 +56,7 @@ rayon = { workspace = true }
|
|||
rustls = { workspace = true }
|
||||
rustls-pemfile = { workspace = true }
|
||||
rustls-pki-types = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
servo_arc = { workspace = true }
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -14,7 +14,6 @@ mod filemanager_thread;
|
|||
mod hsts;
|
||||
mod http_cache;
|
||||
mod http_loader;
|
||||
mod mime_classifier;
|
||||
mod resource_thread;
|
||||
mod subresource_integrity;
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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 this’s 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 controller’s 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 this’s signal.
|
||||
self.signal.as_rooted()
|
||||
}
|
||||
}
|
||||
|
|
157
components/script/dom/abortsignal.rs
Normal file
157
components/script/dom/abortsignal.rs
Normal 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 signal’s 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 signal’s 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 signal’s 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 signal’s 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 this’s 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 this’s 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);
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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")) &&
|
||||
|
|
|
@ -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
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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), " "))
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 this’s associated URL pattern’s protocol component’s 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||
|
||||
use constellation_traits::BlobImpl;
|
||||
use indexmap::IndexMap;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
|
@ -70,7 +70,7 @@ impl Kind {
|
|||
/// <https://html.spec.whatwg.org/multipage/#drag-data-store-bitmap>
|
||||
#[allow(dead_code)] // TODO this used by DragEvent.
|
||||
struct Bitmap {
|
||||
image: Option<Arc<Image>>,
|
||||
image: Option<Arc<RasterImage>>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ impl DragDataStore {
|
|||
self.mode = mode;
|
||||
}
|
||||
|
||||
pub(crate) fn set_bitmap(&mut self, image: Option<Arc<Image>>, x: i32, y: i32) {
|
||||
pub(crate) fn set_bitmap(&mut self, image: Option<Arc<RasterImage>>, x: i32, y: i32) {
|
||||
self.bitmap = Some(Bitmap { image, x, y });
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,10 @@ impl ImageAnimationManager {
|
|||
image.id.unwrap(),
|
||||
ImageDescriptor {
|
||||
format: ImageFormat::BGRA8,
|
||||
size: DeviceIntSize::new(image.width as i32, image.height as i32),
|
||||
size: DeviceIntSize::new(
|
||||
image.metadata.width as i32,
|
||||
image.metadata.height as i32,
|
||||
),
|
||||
stride: None,
|
||||
offset: 0,
|
||||
flags: ImageDescriptorFlags::ALLOW_MIPMAPS,
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::sync::Arc as StdArc;
|
||||
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use fonts_traits::ByteIndex;
|
||||
use html5ever::{local_name, ns};
|
||||
use pixels::{Image, ImageMetadata};
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use range::Range;
|
||||
use script_layout_interface::wrapper_traits::{LayoutDataTrait, LayoutNode, ThreadSafeLayoutNode};
|
||||
use script_layout_interface::{
|
||||
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()`].
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue