diff --git a/apps/tauri/package.json b/apps/tauri/package.json index eb466ad..0901688 100644 --- a/apps/tauri/package.json +++ b/apps/tauri/package.json @@ -41,11 +41,11 @@ "@tauri-apps/plugin-opener": "^2", "@tensamin/ui": "*", "@tensamin/shared": "workspace:*", - "lucide-react": "^1.7.0", + "lucide-react": "^1.14.0", "react": "^19.2.0", "react-dom": "^19.2.0" }, "devDependencies": { - "@tauri-apps/cli": "^2" + "@tauri-apps/cli": "^2.11.0" } } diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index 9ffbdd2..cd60019 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -298,9 +298,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -405,7 +405,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -468,9 +468,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "shlex", @@ -547,9 +547,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -683,7 +683,7 @@ dependencies = [ "cookie", "document-features", "idna", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -714,7 +714,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-graphics-types", "foreign-types", @@ -727,7 +727,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "libc", ] @@ -823,14 +823,20 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.9" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ - "quote", - "syn 2.0.117", + "ctor-proc-macro", + "dtor", ] +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "darling" version = "0.23.0" @@ -963,7 +969,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -975,7 +981,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -1097,6 +1103,21 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dunce" version = "1.0.5" @@ -1111,14 +1132,14 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "embed-resource" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" +checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "vswhom", "winreg", ] @@ -1212,9 +1233,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" @@ -1613,7 +1634,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -1746,9 +1767,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -2019,9 +2040,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -2067,12 +2088,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2215,9 +2236,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -2253,7 +2274,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "serde", "unicode-segmentation", ] @@ -2266,7 +2287,7 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser 0.29.6", "html5ever 0.29.1", - "indexmap 2.13.1", + "indexmap 2.14.0", "selectors 0.24.0", ] @@ -2308,15 +2329,15 @@ dependencies = [ [[package]] name = "libbz2-rs-sys" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdbus-sys" @@ -2349,14 +2370,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -2519,9 +2540,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.17.2" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" +checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" dependencies = [ "crossbeam-channel", "dpi", @@ -2532,10 +2553,10 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png 0.17.16", + "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2544,7 +2565,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2553,12 +2574,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -2580,7 +2595,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "libc", ] @@ -2644,7 +2659,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -2665,7 +2680,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "dispatch2 0.3.1", "objc2", @@ -2697,7 +2712,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-foundation", ] @@ -2720,7 +2735,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", ] @@ -2730,7 +2745,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-foundation", ] @@ -2741,7 +2756,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "dispatch2 0.3.1", "libc", @@ -2754,7 +2769,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "dispatch2 0.3.1", "libc", @@ -2774,13 +2789,23 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-media" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "dispatch2 0.3.1", "objc2", @@ -2796,7 +2821,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -2808,7 +2833,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-core-foundation", @@ -2838,7 +2863,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -2862,7 +2887,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", ] @@ -2895,7 +2920,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-foundation", ] @@ -2906,7 +2931,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -2918,7 +2943,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", ] @@ -2929,9 +2954,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", + "block2", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", "objc2-foundation", ] @@ -2941,7 +2985,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-app-kit", @@ -2965,9 +3009,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.3" +version = "5.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" dependencies = [ "dunce", "is-wsl", @@ -3155,7 +3199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3165,7 +3209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3273,9 +3317,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -3285,13 +3329,13 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ "base64 0.22.1", - "indexmap 2.13.1", - "quick-xml 0.38.4", + "indexmap 2.14.0", + "quick-xml 0.39.2", "serde", "time", ] @@ -3315,7 +3359,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crc32fast", "fdeflate", "flate2", @@ -3408,7 +3452,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.10+spec-1.1.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -3465,15 +3509,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.39.2" @@ -3520,9 +3555,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3597,16 +3632,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3671,9 +3706,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -3748,7 +3783,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -3757,9 +3792,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -3772,18 +3807,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -3901,7 +3936,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cssparser 0.36.0", "derive_more 2.1.1", "log", @@ -3983,7 +4018,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -4030,7 +4065,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.1", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -4359,15 +4394,16 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.8" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" +checksum = "1cf65722394c2ac443e80120064987f8914ee1d4e4e36e63cdf10f2990f01159" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "core-foundation", "core-graphics", "crossbeam-channel", + "dbus", "dispatch2 0.3.1", "dlopen2", "dpi", @@ -4378,13 +4414,14 @@ dependencies = [ "libc", "log", "ndk", - "ndk-context", "ndk-sys", "objc2", "objc2-app-kit", "objc2-foundation", + "objc2-ui-kit", "once_cell", "parking_lot", + "percent-encoding", "raw-window-handle", "tao-macros", "unicode-segmentation", @@ -4425,8 +4462,8 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.10.3" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.11.0" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#8e97fa947f8b562abbb6b6cc7cb459bcd48a22a0" dependencies = [ "anyhow", "bytes", @@ -4458,12 +4495,12 @@ dependencies = [ "serde_repr", "serialize-to-javascript", "swift-rs", - "tauri-build 2.5.6 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-build 2.6.0 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", "tauri-macros", "tauri-runtime", "tauri-runtime-cef", "tauri-runtime-wry", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", "thiserror 2.0.18", "tokio", "tray-icon", @@ -4476,9 +4513,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.5.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +checksum = "be9aa8c59a894f76c29a002501c589de5eb4987a5913d62a6e0a47f320901988" dependencies = [ "anyhow", "cargo_toml", @@ -4490,16 +4527,15 @@ dependencies = [ "semver", "serde", "serde_json", - "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tauri-utils 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "tauri-winres", - "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-build" -version = "2.5.6" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.6.0" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#8e97fa947f8b562abbb6b6cc7cb459bcd48a22a0" dependencies = [ "anyhow", "cargo_toml", @@ -4511,16 +4547,15 @@ dependencies = [ "semver", "serde", "serde_json", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", "tauri-winres", - "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.5.5" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.6.0" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#8e97fa947f8b562abbb6b6cc7cb459bcd48a22a0" dependencies = [ "base64 0.22.1", "brotli", @@ -4535,7 +4570,7 @@ dependencies = [ "serde_json", "sha2", "syn 2.0.117", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", "thiserror 2.0.18", "time", "url", @@ -4545,22 +4580,22 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.5.5" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.6.0" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#8e97fa947f8b562abbb6b6cc7cb459bcd48a22a0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.117", "tauri-codegen", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", ] [[package]] name = "tauri-plugin" -version = "2.5.4" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" +checksum = "f8d5f58bfd0cdcfdbc0a68dc08b354eea2afc551b421de91b07b69e0dd769d57" dependencies = [ "anyhow", "glob", @@ -4568,8 +4603,7 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.9.12+spec-1.1.0", + "tauri-utils 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir", ] @@ -4589,9 +4623,9 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.4.8" +version = "2.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95" +checksum = "94deb2e2e4641514ac496db2cddcfc850d6fc9d51ea17b82292a0490bd20ba5b" dependencies = [ "dunce", "plist", @@ -4600,7 +4634,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tauri-utils 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 2.0.18", "tracing", "url", @@ -4632,8 +4666,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.10.1" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.11.0" dependencies = [ "cookie", "dpi", @@ -4645,7 +4678,7 @@ dependencies = [ "raw-window-handle", "serde", "serde_json", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", "thiserror 2.0.18", "url", "windows 0.61.3", @@ -4654,7 +4687,6 @@ dependencies = [ [[package]] name = "tauri-runtime-cef" version = "0.1.0" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "base64 0.22.1", "cef", @@ -4673,7 +4705,7 @@ dependencies = [ "serde_json", "sha2", "tauri-runtime", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", "url", "windows 0.61.3", "x11-dl", @@ -4681,8 +4713,8 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.10.1" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.11.0" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#8e97fa947f8b562abbb6b6cc7cb459bcd48a22a0" dependencies = [ "gtk", "http", @@ -4697,7 +4729,7 @@ dependencies = [ "softbuffer", "tao", "tauri-runtime", - "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-utils 2.9.0", "url", "webkit2gtk", "webview2-com", @@ -4707,50 +4739,13 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" -dependencies = [ - "anyhow", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever 0.29.1", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-utils" -version = "2.8.3" -source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +version = "2.9.0" dependencies = [ "anyhow", "brotli", "cargo_metadata", "ctor", + "dom_query", "dunce", "glob", "html5ever 0.29.1", @@ -4761,6 +4756,7 @@ dependencies = [ "log", "memchr", "phf 0.11.3", + "plist", "proc-macro2", "quote", "regex", @@ -4780,14 +4776,51 @@ dependencies = [ ] [[package]] -name = "tauri-winres" -version = "0.3.5" +name = "tauri-utils" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +checksum = "55f61d2bf7188fbcf2b0ed095b67a6bc498f713c939314bb19eb700118a573b7" +dependencies = [ + "anyhow", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf 0.11.3", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ "dunce", "embed-resource", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", ] [[package]] @@ -4833,7 +4866,7 @@ dependencies = [ "serde", "serde_json", "tauri", - "tauri-build 2.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tauri-build 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "tauri-plugin-barcode-scanner", "tauri-plugin-deep-link", "tauri-plugin-opener", @@ -4932,9 +4965,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.51.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -4975,7 +5008,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde_core", "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", @@ -4984,6 +5017,21 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", +] + [[package]] name = "toml_datetime" version = "0.6.3" @@ -5017,7 +5065,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -5028,7 +5076,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -5037,14 +5085,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.10+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -5053,7 +5101,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -5083,7 +5131,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", @@ -5140,9 +5188,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.21.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" dependencies = [ "crossbeam-channel", "dirs", @@ -5154,10 +5202,10 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png 0.17.16", + "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5174,9 +5222,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "uds_windows" @@ -5350,9 +5398,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5425,11 +5473,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -5438,14 +5486,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -5456,9 +5504,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -5466,9 +5514,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5476,9 +5524,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -5489,9 +5537,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -5513,7 +5561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.1", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -5537,9 +5585,9 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", "semver", ] @@ -5562,7 +5610,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "rustix", "wayland-backend", "wayland-scanner", @@ -5574,7 +5622,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -5586,7 +5634,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5615,9 +5663,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -5635,9 +5683,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", @@ -5691,9 +5739,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -6038,15 +6086,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -6080,30 +6119,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -6134,12 +6156,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6152,12 +6168,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6170,24 +6180,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6200,12 +6198,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6218,12 +6210,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -6236,12 +6222,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6254,12 +6234,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.5.40" @@ -6274,15 +6248,12 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -6306,6 +6277,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -6325,7 +6302,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.1", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -6355,8 +6332,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.1", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -6375,7 +6352,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "semver", "serde", @@ -6393,9 +6370,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" -version = "0.54.4" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" +checksum = "3013fd6116aac351dd2e18f349b28b2cfef3a5ff3253a9d0ce2d7193bb1b4429" dependencies = [ "base64 0.22.1", "block2", @@ -6530,9 +6507,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.14.0" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" dependencies = [ "async-broadcast", "async-executor", @@ -6557,7 +6534,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.15", + "winnow 1.0.2", "zbus_macros", "zbus_names", "zvariant", @@ -6565,9 +6542,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.14.0" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", @@ -6580,12 +6557,12 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 0.7.15", + "winnow 1.0.2", "zvariant", ] @@ -6692,23 +6669,23 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.10.0" +version = "5.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +checksum = "c4db0ecb8987cf5e92653c57c098f7f0e39a03112edb796f4fe089fb7eaa14ff" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.15", + "winnow 1.0.2", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.10.0" +version = "5.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +checksum = "5b949b639ab1b4bed763aa7481ba0e368af68d8b55532f8ed4bec86a59f2ca98" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", @@ -6719,13 +6696,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" dependencies = [ "proc-macro2", "quote", "serde", "syn 2.0.117", - "winnow 0.7.15", + "winnow 1.0.2", ] diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 7c2b00d..e641521 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -15,42 +15,47 @@ name = "mobile_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2", features = [] } +tauri-build = { version = "2.6", features = [] } [dependencies] base64 = "0.22" image = { version = "0.25", default-features = false, features = ["jpeg"] } -tauri-plugin-opener = "2" +tauri-plugin-opener = "2.5.3" serde = { version = "1", features = ["derive"] } serde_json = "1" -tauri-plugin-deep-link = "2" +tauri-plugin-deep-link = "2.4.7" [target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies] xcap = "0.4.1" [target.'cfg(target_os = "android")'.dependencies.tauri] -version = "2" +version = "2.11" features = [] default-features = true [target.'cfg(target_os = "windows")'.dependencies.tauri] -version = "2" +version = "2.11" features = ["compression", "common-controls-v6", "dynamic-acl"] default-features = true [target.'cfg(target_os = "linux")'.dependencies.tauri] -version = "2" +version = "2.11" features = ["common-controls-v6", "cef", "compression", "dynamic-acl", "x11"] default-features = false [target.'cfg(target_os = "macos")'.dependencies.tauri] -version = "2" +version = "2.11" features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"] default-features = false [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] -tauri-plugin-barcode-scanner = "2" +tauri-plugin-barcode-scanner = "2.4.4" [patch.crates-io.tauri] git = "https://github.com/tauri-apps/tauri" branch = "feat/cef" + +[patch."https://github.com/tauri-apps/tauri"] +tauri-runtime = { path = "vendor/tauri-runtime" } +tauri-runtime-cef = { path = "vendor/tauri-runtime-cef" } +tauri-utils = { path = "vendor/tauri-utils" } diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml index 697b23b..65c984e 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -25,8 +25,6 @@ - - diff --git a/apps/tauri/src-tauri/gen/android/build.gradle.kts b/apps/tauri/src-tauri/gen/android/build.gradle.kts index 607240b..9e8a013 100644 --- a/apps/tauri/src-tauri/gen/android/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/build.gradle.kts @@ -1,3 +1,6 @@ +import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.LibraryExtension + buildscript { repositories { google() @@ -5,7 +8,7 @@ buildscript { } dependencies { classpath("com.android.tools.build:gradle:8.11.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21") } } @@ -16,7 +19,24 @@ allprojects { } } +subprojects { + plugins.withId("com.android.application") { + extensions.configure("android") { + lint { + checkReleaseBuilds = false + } + } + } + + plugins.withId("com.android.library") { + extensions.configure("android") { + lint { + checkReleaseBuilds = false + } + } + } +} + tasks.register("clean").configure { delete("build") } - diff --git a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt index a7e39eb..aff1505 100644 --- a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt +++ b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt @@ -1,10 +1,13 @@ import java.io.File +import javax.inject.Inject import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations open class BuildTask : DefaultTask() { @Input @@ -14,6 +17,13 @@ open class BuildTask : DefaultTask() { @Input var release: Boolean? = null + @Internal + var baseProjectDir: File? = null + + @get:Inject + protected open val execOperations: ExecOperations + get() = throw UnsupportedOperationException("Gradle injects ExecOperations") + @TaskAction fun assemble() { val executable = """bun"""; @@ -48,15 +58,16 @@ open class BuildTask : DefaultTask() { val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") val target = target ?: throw GradleException("target cannot be null") val release = release ?: throw GradleException("release cannot be null") + val baseProjectDir = baseProjectDir ?: throw GradleException("baseProjectDir cannot be null") val args = listOf("tauri", "android", "android-studio-script"); - project.exec { - workingDir(File(project.projectDir, rootDirRel)) + execOperations.exec { + workingDir(File(baseProjectDir, rootDirRel)) executable(executable) args(args) - if (project.logger.isEnabled(LogLevel.DEBUG)) { + if (logger.isEnabled(LogLevel.DEBUG)) { args("-vv") - } else if (project.logger.isEnabled(LogLevel.INFO)) { + } else if (logger.isEnabled(LogLevel.INFO)) { args("-v") } if (release) { @@ -65,4 +76,4 @@ open class BuildTask : DefaultTask() { args(listOf("--target", target)) }.assertNormalExitValue() } -} \ No newline at end of file +} diff --git a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/RustPlugin.kt b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/RustPlugin.kt index 4aa7fca..dea2b9d 100644 --- a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/RustPlugin.kt +++ b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/RustPlugin.kt @@ -72,6 +72,7 @@ open class RustPlugin : Plugin { rootDirRel = config.rootDirRel target = targetName release = profile == "release" + baseProjectDir = project.projectDir } buildTask.dependsOn(targetBuildTask) @@ -82,4 +83,4 @@ open class RustPlugin : Plugin { } } } -} \ No newline at end of file +} diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 66e9c06..bf61021 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -183,8 +183,31 @@ fn get_screen_share_capabilities() -> ScreenShareCapabilities { } } +fn setup_app( + _app: &mut tauri::App, +) -> Result<(), Box> { + #[cfg(any(target_os = "linux", windows))] + { + use tauri_plugin_deep_link::DeepLinkExt; + + if let Err(error) = _app.deep_link().register_all() { + if cfg!(debug_assertions) { + eprintln!("Skipping deep link registration during dev: {error}"); + } else { + return Err(Box::new(error)); + } + } + } + + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + #[cfg(any(target_os = "linux", target_os = "macos"))] + let builder = tauri::Builder::::default(); + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] let builder = tauri::Builder::default(); #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -207,21 +230,7 @@ pub fn run() { let builder = builder.plugin(tauri_plugin_barcode_scanner::init()); builder - .setup(|_app| { - #[cfg(any(target_os = "linux", windows))] - { - use tauri_plugin_deep_link::DeepLinkExt; - - if let Err(error) = _app.deep_link().register_all() { - if cfg!(debug_assertions) { - eprintln!("Skipping deep link registration during dev: {error}"); - } else { - return Err(error.into()); - } - } - } - Ok(()) - }) + .setup(setup_app) .invoke_handler(tauri::generate_handler![ list_screen_share_sources, list_audio_outputs, diff --git a/apps/tauri/src-tauri/vendor/Cargo.toml b/apps/tauri/src-tauri/vendor/Cargo.toml new file mode 100644 index 0000000..0cbf1e5 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = ["tauri-runtime", "tauri-runtime-cef", "tauri-utils"] +resolver = "2" + +[workspace.package] +authors = ["Tauri Programme within The Commons Conservancy"] +homepage = "https://tauri.app/" +repository = "https://github.com/tauri-apps/tauri" +categories = ["gui", "web-programming"] +license = "Apache-2.0 OR MIT" +edition = "2024" +rust-version = "1.88" diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/Cargo.toml b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/Cargo.toml new file mode 100644 index 0000000..97d2e10 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "tauri-runtime-cef" +version = "0.1.0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +dioxus-debug-cell = "0.1" +tauri-runtime = { version = "2.9.2", path = "../tauri-runtime" } +tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [ + "html-manipulation", +] } +html5ever = "0.29" +raw-window-handle = "0.6" +url = "2" +http = "1" +cef = { version = "=146.4.1", default-features = false } +# Not actually used directly, just locking it. +cef-dll-sys = "=146.4.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +kuchiki = { package = "kuchikiki", version = "0.8.8-speedreader" } +sha2 = "0.10" +base64 = "0.22" +dirs = "6" + +[target."cfg(windows)".dependencies] +windows = { version = "0.61", features = [ + "Win32_Graphics", + "Win32_Graphics_Gdi", + "Win32_UI_HiDpi", + "Win32_UI_Input", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_System_LibraryLoader", +] } + +[target."cfg(target_os = \"macos\")".dependencies] +objc2 = "0.6" +objc2-app-kit = { version = "0.3", features = [] } +objc2-foundation = { version = "0.3", features = ["NSNotification"] } + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +gtk = { version = "0.18", features = ["v3_24"] } +x11-dl = "2.21" + +[features] +default = ["sandbox"] +devtools = [] +macos-private-api = ["tauri-runtime/macos-private-api"] +sandbox = ["cef/sandbox"] diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl.rs new file mode 100644 index 0000000..ca31e6c --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl.rs @@ -0,0 +1,4351 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use base64::Engine; +use cef::{rc::*, *}; +use cef_dll_sys::cef_runtime_style_t; +use dioxus_debug_cell::RefCell; +use sha2::{Digest, Sha256}; +use std::{ + collections::HashMap, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering}, + mpsc::channel, + }, +}; +use tauri_runtime::{ + ExitRequestedEventAction, RunEvent, UserEvent, + dpi::{ + LogicalPosition, LogicalSize, PhysicalPosition, PhysicalRect, PhysicalSize, Position, Rect, + Size, + }, + webview::{InitializationScript, PendingWebview, UriSchemeProtocolHandler, WebviewAttributes}, + window::{PendingWindow, WindowEvent, WindowId}, +}; +#[cfg(target_os = "macos")] +use tauri_utils::TitleBarStyle; +use tauri_utils::html::normalize_script_for_csp; + +use crate::{ + AppWebview, AppWindow, CefRuntime, CefWindowBuilder, DevToolsProtocolHandler, Message, + RuntimeStyle as CefRuntimeStyle, WebviewAtribute, WebviewMessage, WindowMessage, + cef_webview::CefWebview, +}; + +use std::cell::Cell; + +// Tracks whether we're inside a user event callback. When set, `post_message` +// defers through the CEF task runner instead of executing synchronously, to +// avoid Win32 message-pump re-entrancy from APIs like ShowWindow/SetFocus +// or locking a mutex while already locked on the same thread. +thread_local! { + static IN_EVENT_CALLBACK: Cell = const { Cell::new(false) }; +} + +/// Returns true if we're currently inside a user event callback. +pub fn is_in_event_callback() -> bool { + IN_EVENT_CALLBACK.get() +} + +/// Run a function within the context of an event callback, ensuring that [`is_in_event_callback`] returns true for the duration of the callback. +fn in_callback(f: impl FnOnce() -> R) -> R { + struct Guard; + impl Drop for Guard { + fn drop(&mut self) { + IN_EVENT_CALLBACK.set(false); + } + } + + IN_EVENT_CALLBACK.set(true); + let _guard = Guard; + f() +} + +mod cookie; +mod drag_window; +pub mod request_handler; + +use cookie::{CollectAllCookiesVisitor, CollectUrlCookiesVisitor}; + +#[cfg(target_os = "linux")] +type CefOsEvent<'a> = Option<&'a mut sys::XEvent>; +#[cfg(target_os = "macos")] +type CefOsEvent<'a> = *mut u8; +#[cfg(windows)] +type CefOsEvent<'a> = Option<&'a mut sys::MSG>; +type AddressChangedHandler = dyn Fn(&url::Url) + Send + Sync; + +/// CEF transparent color value (ARGB) +const TRANSPARENT: u32 = 0x00000000; + +#[inline] +fn color_to_cef_argb(color: tauri_utils::config::Color) -> u32 { + let (r, g, b, a) = color.into(); + ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) +} + +/// Convert position to the coordinate space expected by CEF. +/// On Windows, CEF uses physical coordinates; on other platforms, logical. +#[inline] +fn position_to_cef(position: Position, scale_factor: f64) -> cef::Point { + #[cfg(windows)] + let p = position.to_physical::(scale_factor); + #[cfg(not(windows))] + let p = position.to_logical::(scale_factor); + cef::Point { x: p.x, y: p.y } +} + +/// Convert size to the coordinate space expected by CEF. +/// On Windows, CEF uses physical coordinates; on other platforms, logical. +#[inline] +fn size_to_cef(size: Size, scale_factor: f64) -> cef::Size { + #[cfg(windows)] + let s = size.to_physical::(scale_factor); + #[cfg(not(windows))] + let s = size.to_logical::(scale_factor); + cef::Size { + width: s.width, + height: s.height, + } +} + +/// Convert rect to the coordinate space expected by CEF. +/// On Windows, CEF uses physical coordinates; on other platforms, logical. +#[inline] +fn rect_to_cef(rect: Rect, scale_factor: f64) -> cef::Rect { + let p = position_to_cef(rect.position, scale_factor); + let s = size_to_cef(rect.size, scale_factor); + cef::Rect { + x: p.x, + y: p.y, + width: s.width, + height: s.height, + } +} + +#[inline] +fn theme_to_color_variant(theme: Option) -> ColorVariant { + match theme { + Some(tauri_utils::Theme::Dark) => ColorVariant::DARK, + Some(tauri_utils::Theme::Light) => ColorVariant::LIGHT, + _ => ColorVariant::SYSTEM, + } +} + +#[inline] +fn color_variant_to_theme(variant: ColorVariant) -> Option { + if variant == ColorVariant::DARK { + Some(tauri_utils::Theme::Dark) + } else if variant == ColorVariant::LIGHT { + Some(tauri_utils::Theme::Light) + } else { + None + } +} + +fn set_window_theme_scheme(app_window: &AppWindow, theme: Option) { + let variant = theme_to_color_variant(theme); + for webview in &app_window.webviews { + if let Some(browser) = webview.inner.browser() + && let Some(host) = browser.host() + && let Some(request_context) = host.request_context() + { + request_context.set_chrome_color_scheme(variant, 0); + } + } +} + +fn apply_window_theme_scheme(app_window: &AppWindow, theme: Option) { + set_window_theme_scheme(app_window, theme); + if let Some(window) = app_window.window() { + // Ask CEF Views to refresh themed colors immediately. + window.theme_changed(); + } +} + +fn apply_request_context_theme_scheme( + request_context: Option<&RequestContext>, + theme: Option, +) { + if let Some(request_context) = request_context { + request_context.set_chrome_color_scheme(theme_to_color_variant(theme), 0); + } +} + +#[cfg(target_os = "macos")] +fn apply_macos_window_theme(window: Option<&cef::Window>, theme: Option) { + use objc2::rc::Retained; + use objc2_app_kit::{ + NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSAppearanceNameDarkAqua, NSView, + }; + + let Some(window) = window else { + return; + }; + let ns_view = unsafe { Retained::::retain(window.window_handle() as _) }; + let Some(ns_view) = ns_view else { + return; + }; + let Some(ns_window) = ns_view.window() else { + return; + }; + let appearance = match theme { + Some(tauri_utils::Theme::Dark) => unsafe { + NSAppearance::appearanceNamed(NSAppearanceNameDarkAqua) + }, + Some(tauri_utils::Theme::Light) => unsafe { + NSAppearance::appearanceNamed(NSAppearanceNameAqua) + }, + _ => None, + }; + unsafe { ns_window.setAppearance(appearance.as_deref()) }; +} + +fn native_window_theme(app_window: &AppWindow) -> Option { + app_window.webviews.iter().find_map(|webview| { + webview + .inner + .browser() + .and_then(|browser| browser.host()) + .and_then(|host| host.request_context()) + .and_then(|request_context| { + color_variant_to_theme(request_context.chrome_color_scheme_mode()) + .or_else(|| color_variant_to_theme(request_context.chrome_color_scheme_variant())) + }) + }) +} + +/// Convert a CEF Display to a tauri Monitor +pub(crate) fn display_to_monitor(display: &cef::Display) -> tauri_runtime::monitor::Monitor { + let bounds = display.bounds(); + let work = display.work_area(); + let scale = display.device_scale_factor() as f64; + let physical_size = + LogicalSize::new(bounds.width as u32, bounds.height as u32).to_physical::(scale); + let physical_position = LogicalPosition::new(bounds.x, bounds.y).to_physical::(scale); + let work_physical_size = + LogicalSize::new(work.width as u32, work.height as u32).to_physical::(scale); + let work_physical_position = LogicalPosition::new(work.x, work.y).to_physical::(scale); + tauri_runtime::monitor::Monitor { + name: None, + size: PhysicalSize::new(physical_size.width, physical_size.height), + position: PhysicalPosition::new(physical_position.x, physical_position.y), + work_area: PhysicalRect { + position: PhysicalPosition::new(work_physical_position.x, work_physical_position.y), + size: PhysicalSize::new(work_physical_size.width, work_physical_size.height), + }, + scale_factor: display.device_scale_factor() as f64, + } +} + +/// Get the primary monitor +pub(crate) fn get_primary_monitor() -> Option { + cef::display_get_primary().map(|d| display_to_monitor(&d)) +} + +/// Get the monitor from a point +pub(crate) fn get_monitor_from_point(x: f64, y: f64) -> Option { + let rect = cef::Rect { + x: x as i32, + y: y as i32, + width: 1, + height: 1, + }; + cef::display_get_matching_bounds(Some(&rect), 1).map(|d| display_to_monitor(&d)) +} + +/// Get all available monitors +pub(crate) fn get_available_monitors() -> Vec { + let mut displays: Vec> = vec![None; cef::display_get_count()]; + cef::display_get_alls(Some(&mut displays)); + displays + .into_iter() + .flatten() + .map(|d| display_to_monitor(&d)) + .collect() +} + +/// Convert tauri Icon to CEF Image +fn icon_to_cef_image(icon: tauri_runtime::Icon<'static>) -> Option { + let rgba = icon.rgba.to_vec(); + let width = icon.width; + let height = icon.height; + + // Create a CEF Image + let image = cef::image_create()?; + + // Add bitmap data to the image + // RGBA_8888 color type, OPAQUE alpha type (for icons without transparency, or use PREMULTIPLIED for transparency) + use sys::cef_alpha_type_t; + let result = image.add_bitmap( + 1.0, // scale_factor + width as i32, + height as i32, + cef::ColorType::default(), // RGBA_8888 + cef::AlphaType::from(cef_alpha_type_t::CEF_ALPHA_TYPE_PREMULTIPLIED), // Use premultiplied for RGBA with alpha + Some(&rgba), + ); + + if result == 1 { Some(image) } else { None } +} + +/// Set window icon using CEF native API +fn set_window_icon(window: &cef::Window, icon: tauri_runtime::Icon<'static>) { + if let Some(mut cef_image) = icon_to_cef_image(icon) { + window.set_window_app_icon(Some(&mut cef_image)); + } +} + +/// Set overlay icon using CEF native API (set_window_app_icon) +fn set_overlay_icon(window: &cef::Window, icon: Option>) { + match icon { + Some(icon_data) => { + if let Some(mut cef_image) = icon_to_cef_image(icon_data) { + window.set_window_app_icon(Some(&mut cef_image)); + } + } + None => { + window.set_window_app_icon(None); + } + } +} + +#[inline] +fn apply_content_protection(window: &cef::Window, protected: bool) { + #[cfg(target_os = "linux")] + { + let _ = (window, protected); + } + #[cfg(windows)] + { + use windows::Win32::Foundation::HWND; + use windows::Win32::UI::WindowsAndMessaging::{ + SetWindowDisplayAffinity, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, + }; + let hwnd = window.window_handle(); + unsafe { + let _ = SetWindowDisplayAffinity( + HWND(hwnd.0 as _), + if protected { + WDA_EXCLUDEFROMCAPTURE + } else { + WDA_NONE + }, + ); + } + } + + #[cfg(target_os = "macos")] + { + // Set NSWindow sharing type to NSWindowSharingNone/NSWindowSharingReadOnly + // Safety: must be called on main thread; CEF window APIs run on main thread. + unsafe { + use objc2::rc::Retained; + use objc2_app_kit::{NSView, NSWindowSharingType}; + let ns_view = Retained::::retain(window.window_handle() as _); + let ns_window = ns_view.as_ref().and_then(|v| v.window()); + let sharing = if protected { + NSWindowSharingType::None + } else { + NSWindowSharingType::ReadOnly + }; + if let Some(ns_window) = ns_window { + ns_window.setSharingType(sharing); + } + } + } +} + +#[derive(Clone)] +pub struct CefInitScript { + pub script: InitializationScript, + pub hash: String, +} + +impl CefInitScript { + pub fn new(script: InitializationScript) -> Self { + let hash = hash_script(script.script.as_str()); + Self { script, hash } + } +} + +fn hash_script(script: &str) -> String { + let normalized = normalize_script_for_csp(script.as_bytes()); + let mut hasher = Sha256::new(); + hasher.update(&normalized); + let hash = hasher.finalize(); + format!( + "'sha256-{}'", + base64::engine::general_purpose::STANDARD.encode(hash) + ) +} + +pub type SchemeHandlerRegistry = Arc< + Mutex< + HashMap< + (i32, String), + ( + String, + Arc>, + Arc>, + ), + >, + >, +>; + +pub type RunEventCallback = Arc)>>>; + +#[derive(Clone)] +pub struct Context { + pub windows: Arc>>, + pub callback: RunEventCallback, + pub next_window_id: Arc, + pub next_webview_id: Arc, + pub next_window_event_id: Arc, + pub next_webview_event_id: Arc, + pub scheme_handler_registry: SchemeHandlerRegistry, +} + +impl Context { + pub fn next_window_id(&self) -> WindowId { + self.next_window_id.fetch_add(1, Ordering::Relaxed).into() + } + + pub fn next_webview_id(&self) -> u32 { + self.next_webview_id.fetch_add(1, Ordering::Relaxed) + } + + pub fn next_window_event_id(&self) -> u32 { + self.next_window_event_id.fetch_add(1, Ordering::Relaxed) + } + + pub fn next_webview_event_id(&self) -> u32 { + self.next_webview_event_id.fetch_add(1, Ordering::Relaxed) + } +} + +wrap_app! { + pub struct TauriApp { + context: Context, + custom_schemes: Vec, + deep_link_schemes: Vec, + command_line_args: Vec<(String, Option)>, + } + + impl App { + fn browser_process_handler(&self) -> Option { + Some(AppBrowserProcessHandler::new( + self.context.clone(), + self.deep_link_schemes.clone(), + )) + } + + fn on_before_command_line_processing( + &self, + _process_type: Option<&CefString>, + command_line: Option<&mut CommandLine>, + ) { + if let Some(command_line) = command_line { + for (arg, value) in &self.command_line_args { + if let Some(value) = value { + command_line.append_switch_with_value( + Some(&CefString::from(arg.as_str())), + Some(&CefString::from(value.as_str())), + ); + } else if arg.starts_with("-") { + command_line.append_switch(Some(&CefString::from(arg.as_str()))); + } else { + command_line.append_argument(Some(&CefString::from(arg.as_str()))); + } + } + } + } + } +} + +wrap_browser_process_handler! { + struct AppBrowserProcessHandler { + context: Context, + deep_link_schemes: Vec, + } + + impl BrowserProcessHandler { + fn on_context_initialized(&self) { + (self.context.callback.borrow())(RunEvent::Ready); + } + + fn on_already_running_app_relaunch( + &self, + command_line: Option<&mut CommandLine>, + _current_directory: Option<&CefString>, + ) -> std::os::raw::c_int { + let Some(command_line) = command_line else { + return 0; + }; + let mut list = CefStringList::new(); + command_line.arguments(Some(&mut list)); + let args: Vec = list.into_iter().collect(); + if args.len() == 1 + && let Ok(url) = url::Url::parse(&args[0]) { + let scheme = url.scheme().to_string(); + if self.deep_link_schemes.iter().any(|s| s == &scheme) { + (self.context.callback.borrow())(RunEvent::Opened { + urls: vec![url], + }); + return 1; + } + } + // TODO: add event + 1 + } + } +} + +wrap_load_handler! { + struct BrowserLoadHandler { + initialization_scripts: Arc>, + on_page_load_handler: Option>, + custom_scheme_domain_names: Vec, + custom_protocol_scheme: String, + } + + impl LoadHandler { + fn on_load_start( + &self, + _browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + _transition_type: TransitionType, + ) { + let Some(handler) = &self.on_page_load_handler else { return }; + let Some(frame) = frame else { return }; + + let is_main_frame = frame.is_main() == 1; + if !is_main_frame { + return; + } + + let url = frame.url(); + let url_str = cef::CefString::from(&url).to_string(); + if let Ok(url) = url::Url::parse(&url_str) { + handler(url, tauri_runtime::webview::PageLoadEvent::Started); + } + } + + fn on_load_end( + &self, + _browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + http_status_code: ::std::os::raw::c_int, + ) { + let Some(frame) = frame else { return }; + + if let Some(handler) = &self.on_page_load_handler + && frame.is_main() == 1 { + let url = frame.url(); + let url_str = cef::CefString::from(&url).to_string(); + if let Ok(url) = url::Url::parse(&url_str) { + handler(url, tauri_runtime::webview::PageLoadEvent::Finished); + } + } + + // run init scripts for http/https pages that are not custom schemes + // custom schemes are handled by the request handler + // where we inject scripts directly in the html + + if !(200..300).contains(&http_status_code) { + return; + } + + let url = frame.url(); + let url_str = cef::CefString::from(&url).to_string(); + let url_obj = url::Url::parse(&url_str).ok(); + + let is_custom_scheme_url = url_obj + .as_ref() + .map(|u| { + let scheme = u.scheme(); + if scheme == self.custom_protocol_scheme { + let host_str = u.host_str().unwrap_or("").to_string(); + scheme == self.custom_protocol_scheme && self.custom_scheme_domain_names.contains(&host_str) + } else { + false + } + }); + // if we can't parse the URL, also return + if is_custom_scheme_url.unwrap_or(true) { return; } + + let is_main_frame = frame.is_main() == 1; + + let scripts_to_execute = if is_main_frame { + Box::new(self.initialization_scripts.iter().map(|s| &s.script.script)) as Box> + } else { + Box::new(self.initialization_scripts + .iter() + .filter(|s| !s.script.for_main_frame_only) + .map(|s| &s.script.script)) as Box> + }; + + for script in scripts_to_execute { + let script_url = format!("{}://__tauri_init_script__", url_obj.as_ref().map(|u| u.scheme()).unwrap_or("http")); + + frame.execute_java_script( + Some(&cef::CefString::from(script.as_str())), + Some(&cef::CefString::from(script_url.as_str())), + 0, + ); + } + } + } +} + +wrap_display_handler! { + struct BrowserDisplayHandler { + document_title_changed_handler: Option>, + address_changed_handler: Option>, + } + + impl DisplayHandler { + fn on_title_change( + &self, + _browser: Option<&mut Browser>, + title: Option<&CefString>, + ) { + let Some(handler) = &self.document_title_changed_handler else { return }; + let Some(title) = title else { return }; + let title_str = title.to_string(); + handler(title_str); + } + + fn on_address_change( + &self, + _browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + url: Option<&CefString>, + ) { + // Only fire for main frame URL changes (matches on_before_browse behavior) + if let Some(frame) = frame + && frame.is_main() == 0 { + return; + } + let Some(handler) = &self.address_changed_handler else { return }; + let Some(url) = url else { return }; + let url_str = url.to_string(); + let Ok(parsed) = url::Url::parse(&url_str) else { return }; + handler(&parsed); + } + } +} + +wrap_context_menu_handler! { + struct BrowserContextMenuHandler { + devtools_enabled: bool, + } + + impl ContextMenuHandler { + fn on_before_context_menu( + &self, + _browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _params: Option<&mut ContextMenuParams>, + model: Option<&mut MenuModel>, + ) { + if !self.devtools_enabled + && let Some(model) = model { + model.remove_at(model.count() - 1); + } + } + } +} + +cef::wrap_dev_tools_message_observer! { + struct TauriDevToolsProtocolObserver { + handlers: Arc>>>, + } + + impl DevToolsMessageObserver { + fn on_dev_tools_message( + &self, + _browser: Option<&mut cef::Browser>, + message: Option<&[u8]>, + ) -> std::os::raw::c_int { + if let Some(msg) = message { + let protocol = crate::DevToolsProtocol::Message(msg.to_vec()); + if let Ok(handlers) = self.handlers.lock() { + for handler in handlers.iter() { + handler(protocol.clone()); + } + } + } + 0 + } + + fn on_dev_tools_method_result( + &self, + _browser: Option<&mut Browser>, + message_id: std::os::raw::c_int, + success: std::os::raw::c_int, + result: Option<&[u8]>, + ) { + let protocol = crate::DevToolsProtocol::MethodResult { + message_id, + success: success != 0, + result: result.map(|r| r.to_vec()).unwrap_or_default(), + }; + if let Ok(handlers) = self.handlers.lock() { + for handler in handlers.iter() { + handler(protocol.clone()); + } + } + } + + fn on_dev_tools_event( + &self, + _browser: Option<&mut Browser>, + method: Option<&CefString>, + params: Option<&[u8]>, + ) { + let protocol = crate::DevToolsProtocol::Event { + method: method + .map(|m| format!("{m}")) + .unwrap_or_default(), + params: params.map(|p| p.to_vec()).unwrap_or_default(), + }; + if let Ok(handlers) = self.handlers.lock() { + for handler in handlers.iter() { + handler(protocol.clone()); + } + } + } + } +} + +fn runtime_evaluate_result_to_json(result: Option<&[u8]>) -> String { + let Some(result) = result else { + return String::new(); + }; + let Ok(result) = serde_json::from_slice::(result) else { + return String::new(); + }; + + if result.get("exceptionDetails").is_some() { + return String::new(); + } + + let remote_object = result.get("result").unwrap_or(&result); + remote_object + .get("value") + .and_then(|value| serde_json::to_string(value).ok()) + .unwrap_or_default() +} + +cef::wrap_dev_tools_message_observer! { + struct EvalScriptWithCallbackDevToolsObserver { + message_id: Arc, + callback: Arc>>>, + registration: Arc>>, + } + + impl DevToolsMessageObserver { + fn on_dev_tools_method_result( + &self, + _browser: Option<&mut Browser>, + message_id: std::os::raw::c_int, + success: std::os::raw::c_int, + result: Option<&[u8]>, + ) { + if message_id != self.message_id.load(Ordering::Relaxed) { + return; + } + + let Some(callback) = self.callback.lock().unwrap().take() else { + return; + }; + + let result = if success != 0 { + runtime_evaluate_result_to_json(result) + } else { + String::new() + }; + callback(result); + + let _ = self.registration.lock().unwrap().take(); + } + } +} + +/// Registers a DevTools protocol observer. Returns the [`cef::Registration`] which must be +/// kept alive for the observer to stay registered. The observer is unregistered when +/// the Registration is dropped. +fn add_dev_tools_observer( + browser: &cef::Browser, + handlers: Arc>>>, +) -> Option { + browser.host().and_then(|host| { + let mut observer = TauriDevToolsProtocolObserver::new(handlers); + host.add_dev_tools_message_observer(Some(&mut observer)) + }) +} + +wrap_keyboard_handler! { + struct BrowserKeyboardHandler { + devtools_enabled: bool, + } + + impl KeyboardHandler { + fn on_pre_key_event( + &self, + _browser: Option<&mut Browser>, + event: Option<&KeyEvent>, + _os_event: CefOsEvent, + _is_keyboard_shortcut: Option<&mut ::std::os::raw::c_int>, + ) -> ::std::os::raw::c_int { + // If devtools is disabled, block devtools keyboard shortcuts + if !self.devtools_enabled { + let Some(event) = event else { return 0; }; + + // Check if this is a keydown event + use cef::sys::cef_key_event_type_t; + let keydown_type: cef::KeyEventType = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); + if event.type_ != keydown_type { + return 0; + } + + // Get modifier keys + use cef::sys::cef_event_flags_t; + #[cfg(windows)] + let modifiers = event.modifiers as i32; + #[cfg(not(windows))] + let modifiers = event.modifiers; + + #[cfg(not(target_os = "macos"))] + let ctrl = (modifiers & (cef_event_flags_t::EVENTFLAG_CONTROL_DOWN.0)) != 0; + #[cfg(not(target_os = "macos"))] + let shift = (modifiers & (cef_event_flags_t::EVENTFLAG_SHIFT_DOWN.0)) != 0; + + let key_code = event.windows_key_code; + + // Block F12 (key code 123) + if key_code == 123 { + if let Some(is_keyboard_shortcut) = _is_keyboard_shortcut { + *is_keyboard_shortcut = 1; + } + return 1; + } + + // Block Ctrl+Shift+I (key code 73 = 'I') on Linux/Windows + #[cfg(not(target_os = "macos"))] + if key_code == 73 && ctrl && shift { + if let Some(is_keyboard_shortcut) = _is_keyboard_shortcut { + *is_keyboard_shortcut = 1; + } + return 1; + } + + // Block Cmd+Opt+I on macOS + #[cfg(target_os = "macos")] + { + let meta = (modifiers & cef_event_flags_t::EVENTFLAG_COMMAND_DOWN.0) != 0; + let alt = (modifiers & cef_event_flags_t::EVENTFLAG_ALT_DOWN.0) != 0; + if key_code == 73 && meta && alt { + if let Some(is_keyboard_shortcut) = _is_keyboard_shortcut { + *is_keyboard_shortcut = 1; + } + return 1; + } + } + } + + 0 + } + } +} + +wrap_permission_handler! { + struct BrowserPermissionHandler {} + + impl PermissionHandler { + fn on_request_media_access_permission( + &self, + _browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _requesting_origin: Option<&CefString>, + requested_permissions: u32, + callback: Option<&mut MediaAccessCallback>, + ) -> ::std::os::raw::c_int { + let Some(callback) = callback else { + return 0; + }; + // Allow microphone and camera when requested + let allowed = requested_permissions & (sys::cef_media_access_permission_types_t::CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE as u32 | sys::cef_media_access_permission_types_t::CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE as u32); + if allowed != 0 { + callback.cont(requested_permissions); + return 1; + } + 0 + } + + fn on_show_permission_prompt( + &self, + _browser: Option<&mut Browser>, + _prompt_id: u64, + _requesting_origin: Option<&CefString>, + requested_permissions: u32, + callback: Option<&mut PermissionPromptCallback>, + ) -> ::std::os::raw::c_int { + let Some(callback) = callback else { + return 0; + }; + // Allow permission prompt (e.g. microphone/camera) + callback.cont(PermissionRequestResult::from( + cef::sys::cef_permission_request_result_t::CEF_PERMISSION_RESULT_ACCEPT, + )); + 1 + } + } +} + +wrap_download_handler! { + struct BrowserDownloadHandler { + download_handler: Arc, + } + + impl DownloadHandler { + fn can_download( + &self, + _browser: Option<&mut Browser>, + _url: Option<&CefStringUtf16>, + _request_method: Option<&CefStringUtf16>, + ) -> ::std::os::raw::c_int { + // on_before_download is the one that actually validates the download + // so we return 1 to allow the download here + 1 + } + + fn on_before_download( + &self, + _browser: Option<&mut Browser>, + download_item: Option<&mut DownloadItem>, + suggested_name: Option<&CefStringUtf16>, + callback: Option<&mut BeforeDownloadCallback>, + ) -> ::std::os::raw::c_int { + let Some(download_item) = download_item else { return 0; }; + let Some(callback) = callback else { return 0; }; + + let url_str = CefString::from(&download_item.url()).to_string(); + let Ok(url) = url::Url::parse(&url_str) else { return 0; }; + + let suggested_path = suggested_name + .map(|s| s.to_string()) + .map(std::path::PathBuf::from) + .unwrap_or_default(); + + let mut destination = suggested_path.clone(); + + // Call handler with Requested event + let should_allow = (self.download_handler)(tauri_runtime::webview::DownloadEvent::Requested { + url: url.clone(), + destination: &mut destination, + }); + + if should_allow { + // Set the download path + let destination_cef = CefStringUtf16::from(destination.to_string_lossy().as_ref()); + + // if the user callback did not modify the destination, show the dialog + let show_dialog = destination == suggested_path; + callback.cont(Some(&destination_cef), show_dialog as ::std::os::raw::c_int); + } + 1 + } + + fn on_download_updated( + &self, + _browser: Option<&mut Browser>, + download_item: Option<&mut DownloadItem>, + _callback: Option<&mut DownloadItemCallback>, + ) { + let Some(download_item) = download_item else { return; }; + + // Get download URL + let url_str = CefString::from(&download_item.url()).to_string(); + let Ok(url) = url::Url::parse(&url_str) else { return; }; + + // Check download state - CEF returns i32 where 0 is false, non-zero is true + let is_complete = download_item.is_complete() != 0; + let is_canceled = download_item.is_canceled() != 0; + let success = is_complete && !is_canceled; + + // Get full path if available - full_path() returns CefStringUserfreeUtf16 + let full_path = if is_complete || is_canceled { + let path_cef = download_item.full_path(); + let path_str = CefString::from(&path_cef).to_string(); + if !path_str.is_empty() { + Some(std::path::PathBuf::from(path_str)) + } else { + None + } + } else { + None + }; + + // Only call handler when download is finished (complete or canceled) + if is_complete || is_canceled { + // Call handler with Finished event + (self.download_handler)(tauri_runtime::webview::DownloadEvent::Finished { + url, + path: full_path, + success, + }); + } + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum WindowKind { + /// Full browser window created with browser_host_create_browser_sync + Browser, + /// Tauri window created with window_create_top_level + Tauri, +} + +wrap_life_span_handler! { + struct BrowserLifeSpanHandler { + window_kind: WindowKind, + window_id: WindowId, + context: Context, + new_window_handler: Option>>>, + initial_url: Option, + } + + impl LifeSpanHandler { + fn on_after_created(&self, browser: Option<&mut Browser>) { + if let Some(browser) = browser + && let Some(initial_url) = &self.initial_url { + check_and_reload_if_blank(browser.clone(), initial_url.clone()); + } + } + + fn on_before_close(&self, browser: Option<&mut Browser>) { + match self.window_kind { + WindowKind::Browser => { + on_window_destroyed(self.window_id, &self.context); + } + WindowKind::Tauri => { + let Some(browser) = browser else { + return; + }; + let browser_id = browser.identifier(); + + let (webview, is_last_in_window) = { + let mut windows = self.context.windows.borrow_mut(); + let Some(app_window) = windows.get_mut(&self.window_id) else { + return; + }; + let webview_index = app_window + .webviews + .iter() + .position(|w| *w.browser_id.borrow() == browser_id); + let Some(index) = webview_index else { + return; + }; + let webview = app_window.webviews.remove(index); + let webview_id = webview.webview_id; + app_window + .webview_event_listeners + .lock() + .unwrap() + .remove(&webview_id); + let is_last = app_window.webviews.is_empty(); + (webview, is_last) + }; + + { + let mut registry = self.context.scheme_handler_registry.lock().unwrap(); + let schemes: Vec<_> = webview + .uri_scheme_protocols + .keys() + .cloned() + .collect(); + for scheme in schemes { + registry.remove(&(browser_id, scheme)); + } + } + + // safe to drop - CEF callbacks can borrow windows + drop(webview); + + if is_last_in_window { + on_window_destroyed(self.window_id, &self.context); + } + } + } + } + + fn on_before_popup( + &self, + _browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _popup_id: std::os::raw::c_int, + target_url: Option<&CefString>, + _target_frame_name: Option<&CefString>, + _target_disposition: WindowOpenDisposition, + _user_gesture: std::os::raw::c_int, + popup_features: Option<&PopupFeatures>, + _window_info: Option<&mut WindowInfo>, + _client: Option<&mut Option>, + _settings: Option<&mut BrowserSettings>, + _extra_info: Option<&mut Option>, + _no_javascript_access: Option<&mut i32>, + ) -> std::os::raw::c_int { + let Some(handler) = &self.new_window_handler else { + // No handler, allow default behavior + return 0; + }; + + let Some(target_url) = target_url else { + // No URL, deny + return 1; + }; + + let url_str = target_url.to_string(); + let Ok(url) = url::Url::parse(&url_str) else { + // Invalid URL, deny + return 1; + }; + + // Extract size and position from popup_features + // Note: PopupFeatures fields may vary by CEF version, so we handle them defensively + let size = popup_features.and({ + // Try to access width/height fields - structure may vary + // For now, we'll use None if we can't determine the size + None // TODO: Implement proper PopupFeatures field access when CEF API is available + }); + + let position = popup_features.and({ + // Try to access x/y fields - structure may vary + // For now, we'll use None if we can't determine the position + None // TODO: Implement proper PopupFeatures field access when CEF API is available + }); + + let features = tauri_runtime::webview::NewWindowFeatures::new( + size, + position, + crate::NewWindowOpener {}, + ); + + let response = handler(url, features); + + match response { + tauri_runtime::webview::NewWindowResponse::Allow => { + // Allow CEF to handle the popup with default behavior + 0 + } + tauri_runtime::webview::NewWindowResponse::Create { window_id: _window_id } => { + // We need to create a window and associate it with the popup + // For now, we'll deny the popup and let the handler create the window + // The window creation should happen via the message system + // This is a limitation - CEF doesn't easily support creating a window + // and associating it with a popup in the callback + // We return 1 to cancel the popup, and the handler should create the window + 1 + } + tauri_runtime::webview::NewWindowResponse::Deny => { + // Deny the popup + 1 + } + } + } + } +} + +wrap_client! { + struct BrowserClient { + window_kind: WindowKind, + window_id: WindowId, + initialization_scripts: Arc>, + on_page_load_handler: Option>, + document_title_changed_handler: Option>, + navigation_handler: Option>, + address_changed_handler: Option>, + new_window_handler: Option>>>, + download_handler: Option>, + devtools_enabled: bool, + custom_scheme_domain_names: Vec, + custom_protocol_scheme: String, + context: Context, + initial_url: Option, + } + + impl Client { + fn request_handler(&self) -> Option { + Some(request_handler::WebRequestHandler::new( + self.initialization_scripts.clone(), + self.navigation_handler.clone(), + )) + } + + fn life_span_handler(&self) -> Option { + Some(BrowserLifeSpanHandler::new( + self.window_kind, + self.window_id, + self.context.clone(), + self.new_window_handler.clone(), + self.initial_url.clone(), + )) + } + + fn load_handler(&self) -> Option { + Some(BrowserLoadHandler::new( + self.initialization_scripts.clone(), + self.on_page_load_handler.clone(), + self.custom_scheme_domain_names.clone(), + self.custom_protocol_scheme.clone(), + )) + } + + fn display_handler(&self) -> Option { + Some(BrowserDisplayHandler::new( + self.document_title_changed_handler.clone(), + self.address_changed_handler.clone(), + )) + } + + fn download_handler(&self) -> Option { + self.download_handler.clone().map(|handler| BrowserDownloadHandler::new(handler)) + } + + fn context_menu_handler(&self) -> Option { + Some(BrowserContextMenuHandler::new(self.devtools_enabled)) + } + + fn keyboard_handler(&self) -> Option { + Some(BrowserKeyboardHandler::new(self.devtools_enabled)) + } + + fn permission_handler(&self) -> Option { + Some(BrowserPermissionHandler::new()) + } + } +} + +wrap_browser_view_delegate! { + struct BrowserViewDelegateImpl { + browser_id: Arc>, + browser_runtime_style: CefRuntimeStyle, + scheme_handler_registry: SchemeHandlerRegistry, + webview_label: String, + uri_scheme_protocols: Arc>>>, + initialization_scripts: Arc>, + devtools_protocol_handlers: Arc>>>, + devtools_observer_registration: Arc>>, + webview_attributes: Arc>, + } + + impl ViewDelegate { + fn on_theme_changed(&self, view: Option<&mut View>) { + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + { + let Some(view) = view else { return; }; + + let webview_attributes = self.webview_attributes.borrow(); + + if webview_attributes.transparent { + view.set_background_color(TRANSPARENT); + } else if let Some(color) = webview_attributes.background_color { + let color = color_to_cef_argb(color); + view.set_background_color(color); + } + } + } + } + + impl BrowserViewDelegate { + fn on_browser_created(&self, _browser_view: Option<&mut BrowserView>, browser: Option<&mut Browser>) { + if let Some(browser) = browser { + let real_id = browser.identifier(); + let _ = std::mem::replace(&mut *self.browser_id.borrow_mut(), real_id); + + // Only add the observer when at least one listener is registered + if !self.devtools_protocol_handlers.lock().unwrap().is_empty() + && let Some(registration) = add_dev_tools_observer(browser, self.devtools_protocol_handlers.clone()) { + self.devtools_observer_registration.lock().unwrap().replace(registration); + } + + let mut registry = self.scheme_handler_registry.lock().unwrap(); + for (scheme, handler) in self.uri_scheme_protocols.iter() { + registry.insert( + (real_id, scheme.clone()), + ( + self.webview_label.clone(), + handler.clone(), + self.initialization_scripts.clone(), + ), + ); + } + } + } + + fn browser_runtime_style(&self) -> RuntimeStyle { + use cef::sys::cef_runtime_style_t; + + match self.browser_runtime_style { + CefRuntimeStyle::Alloy => RuntimeStyle::from(cef_runtime_style_t::CEF_RUNTIME_STYLE_ALLOY), + CefRuntimeStyle::Chrome => RuntimeStyle::from(cef_runtime_style_t::CEF_RUNTIME_STYLE_CHROME), + } + } + } +} + +wrap_window_delegate! { + struct AppWindowDelegate { + window_id: WindowId, + callback: RunEventCallback, + force_close: Arc, + windows: Arc>>, + attributes: Arc>, + last_emitted_position: RefCell>, + last_emitted_size: RefCell>, + suppress_next_theme_changed: RefCell, + context: Context + } + + impl ViewDelegate { + fn minimum_size(&self, view: Option<&mut View>) -> cef::Size { + let window = view.and_then(|v| v.window()); + let scale = window + .and_then(|w| w.display()) + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + let mut min_w: i32 = 0; + let mut min_h: i32 = 0; + let Ok(attributes) = self.attributes.try_borrow() else { + return cef::Size { width: 0, height: 0 }; + }; + if let Some(min_size) = attributes.min_inner_size { + let logical = min_size.to_logical::(scale); + min_w = min_w.max(logical.width as i32); + min_h = min_h.max(logical.height as i32); + } + if let Some(constraints) = attributes.inner_size_constraints.as_ref() { + if let Some(w) = constraints.min_width { + let w_lg = i32::from(w.to_logical::(scale)); + min_w = min_w.max(w_lg); + } + if let Some(h) = constraints.min_height { + let h_lg = i32::from(h.to_logical::(scale)); + min_h = min_h.max(h_lg); + } + } + + if min_w != 0 || min_h != 0 { + cef::Size { width: min_w, height: min_h } + } else { + cef::Size { width: 0, height: 0 } + } + } + + fn maximum_size(&self, view: Option<&mut View>) -> cef::Size { + let window = view.and_then(|v| v.window()); + let scale = window + .and_then(|w| w.display()) + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + let mut max_w: Option = None; + let mut max_h: Option = None; + let Ok(attributes) = self.attributes.try_borrow() else { + return cef::Size { width: 0, height: 0 }; + }; + + if let Some(max_size) = attributes.max_inner_size { + let logical = max_size.to_logical::(scale); + max_w = Some(logical.width as i32); + max_h = Some(logical.height as i32); + } + if let Some(constraints) = attributes.inner_size_constraints.as_ref() { + if let Some(w) = constraints.max_width { + let w_lg = i32::from(w.to_logical::(scale)); + max_w = Some(match max_w { Some(v) => v.min(w_lg), None => w_lg }); + } + if let Some(h) = constraints.max_height { + let h_lg = i32::from(h.to_logical::(scale)); + max_h = Some(match max_h { Some(v) => v.min(h_lg), None => h_lg }); + } + } + + if max_w.is_some() || max_h.is_some() { + cef::Size { + width: max_w.unwrap_or(0), + height: max_h.unwrap_or(0), + } + } else { + cef::Size { width: 0, height: 0 } + } + } + + fn on_theme_changed(&self, view: Option<&mut View>) { + let attrs = self.attributes.borrow(); + + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + if attrs.transparent.unwrap_or_default() { + if let Some(view) = view { + view.set_background_color(TRANSPARENT); + } + } else if let Some(color) = attrs.background_color { + if let Some(view) = view { + let color = color_to_cef_argb(color); + view.set_background_color(color); + } + } + + // macOS resets traffic light button positions during the layout pass + // that follows an appearance change, so we must defer the reapply + // to run after that layout completes. + #[cfg(target_os = "macos")] + if let Some(position) = attrs.traffic_light_position { + send_message_task( + &self.context, + Message::Window { + window_id: self.window_id, + message: WindowMessage::SetTrafficLightPosition(position), + }, + ); + } + + if std::mem::take(&mut *self.suppress_next_theme_changed.borrow_mut()) { + return; + } + + let (system_theme, explicit_theme) = { + let windows = self.windows.borrow(); + let Some(app_window) = windows.get(&self.window_id) else { + return; + }; + + let Some(system_theme) = native_window_theme(app_window) else { + return; + }; + + let explicit_theme = app_window.attributes.borrow().theme; + (system_theme, explicit_theme) + }; + + if let Some(explicit_theme) = explicit_theme + && let Some(app_window) = self.windows.borrow().get(&self.window_id) + { + #[cfg(target_os = "macos")] + { + *self.suppress_next_theme_changed.borrow_mut() = true; + send_message_task( + &self.context, + Message::Window { + window_id: self.window_id, + message: WindowMessage::SetTheme(Some(explicit_theme)), + }, + ); + } + set_window_theme_scheme(app_window, Some(explicit_theme)); + } + + send_window_event( + self.window_id, + &self.windows, + &self.callback, + WindowEvent::ThemeChanged(system_theme), + ); + } + } + + impl PanelDelegate {} + + impl WindowDelegate { + fn on_window_created(&self, window: Option<&mut Window>) { + if let Some(window) = window { + // Setup necessary handling for `start_window_dragging` to work on Windows + #[cfg(windows)] + drag_window::windows::subclass_window_for_dragging(window); + + let a = self.attributes.borrow(); + #[cfg(target_os = "macos")] + apply_macos_window_theme(Some(window), a.theme); + if let Some(icon) = a.icon.clone() { + set_window_icon(window, icon); + } + + #[cfg(target_os = "macos")] + { + let decorations = a.decorations.unwrap_or(true); + + // default to transparent title bar if decorations are disabled, otherwise use visible title bar + let default_style = if decorations { + TitleBarStyle::Visible + } else { + TitleBarStyle::Transparent + }; + let style = a.title_bar_style.unwrap_or(default_style); + + // default to hidden title if decorations are disabled, otherwise show title + let hidden_title = a.hidden_title.unwrap_or(!decorations); + + apply_titlebar_style(window, style, hidden_title); + } + + if let Some(title) = &a.title { + window.set_title(Some(&CefString::from(title.as_str()))); + } + + if let Some(inner_size) = a.inner_size + + && let Some(display) = window.display() { + let scale = display.device_scale_factor() as f64; + let size = size_to_cef(inner_size, scale); + + // On Windows, the size set via CEF APIs is the outer size (including borders), + // so we need to adjust it to set the correct inner size. + #[cfg(windows)] + let size = crate::utils::windows::adjust_size(window.window_handle(), size); + + window.set_size(Some(&size)); + } + + if let Some(position) = &a.position + && let Some(display) = window.display() { + let device_scale_factor = display.device_scale_factor() as f64; + let position = position_to_cef(*position, device_scale_factor); + window.set_position(Some(&position)); + } + + if a.center { + // Use CEF's native centering API + window.center_window(Some(&window.size())); + } + + if let Some(focused) = a.focused + && focused { + window.request_focus(); + } + + if let Some(maximized) = a.maximized + && maximized { + window.maximize(); + } + + if let Some(fullscreen) = a.fullscreen + && fullscreen { + window.set_fullscreen(1); + } + + if let Some(always_on_top) = a.always_on_top + && always_on_top { + window.set_always_on_top(1); + } + + if let Some(_always_on_bottom) = a.always_on_bottom { + // TODO: Implement always on bottom for CEF + } + + if let Some(visible_on_all_workspaces) = a.visible_on_all_workspaces + && visible_on_all_workspaces { + // TODO: Implement visible on all workspaces for CEF + } + + if let Some(content_protected) = a.content_protected { + apply_content_protection(window, content_protected); + } + + if let Some(skip_taskbar) = a.skip_taskbar + && skip_taskbar { + // TODO: Implement skip taskbar for CEF + } + + if let Some(shadow) = a.shadow + && !shadow { + // TODO: Implement shadow control for CEF + } + + if let Some(focusable) = a.focusable { + window.set_focusable(if focusable { 1 } else { 0 }); + } + + if a.visible.unwrap_or(true) { + window.show(); + } + + // Set traffic light position on macOS after window is fully created + // by posting a task to the UI thread to avoid issues with early setting + #[cfg(target_os = "macos")] + if let Some(pos) = a.traffic_light_position { + let window_message = WindowMessage::SetTrafficLightPosition(pos); + let message = Message::Window { + window_id: self.window_id, + message: window_message, + }; + + send_message_task(&self.context, message); + } + } + } + + fn is_frameless(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { + // Map `decorations: false` to frameless window + let decorated = self + .attributes + .borrow() + .decorations + .unwrap_or(true); + (!decorated) as i32 + } + + fn with_standard_window_buttons(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { + 1 + } + + fn on_window_destroyed(&self, _window: Option<&mut Window>) { + on_window_destroyed(self.window_id, &self.context); + } + + fn can_resize(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { + self + .attributes + .borrow() + .resizable + .unwrap_or(true) as i32 + } + + fn can_maximize(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { + // Can maximize if maximizable is true and resizable is true (or not set, defaulting to true) + let a = self.attributes.borrow(); + let resizable = a.resizable.unwrap_or(true); + let maximizable = a.maximizable.unwrap_or(true); + (resizable && maximizable) as i32 + } + + fn can_minimize(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { + self + .attributes + .borrow() + .minimizable + .unwrap_or(true) as i32 + } + + fn can_close(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { + if self.force_close.load(Ordering::SeqCst) { + close_window_browsers(self.window_id, &self.windows); + return 1; + } + let closable = self + .attributes + .borrow() + .closable + .unwrap_or(true); + + if !closable { + return 0; + } + + let (tx, rx) = channel(); + let event = WindowEvent::CloseRequested { signal_tx: tx }; + + send_window_event(self.window_id, &self.windows, &self.callback, event.clone()); + + let should_prevent = matches!(rx.try_recv(), Ok(true)); + + if should_prevent { + 0 + } else { + close_window_browsers(self.window_id, &self.windows) as i32 + } + } + + fn on_window_bounds_changed( + &self, + window: Option<&mut Window>, + bounds: Option<&cef::Rect>, + ) { + let (Some(window), Some(bounds)) = (window, bounds) else { return; }; + + #[cfg(target_os = "macos")] + if let Some(pos) = &self.attributes.borrow().traffic_light_position { + apply_traffic_light_position(window.window_handle(), pos); + } + + #[cfg(not(windows))] + let size = LogicalSize::new(bounds.width as u32, bounds.height as u32); + + // On Windows, we need to get the inner size because the bounds include the window borders. + #[cfg(windows)] + let size = crate::utils::windows::inner_size(window.window_handle()); + + // Update autoresize overlay bounds + let bounds_updates: Vec<(CefWebview, cef::Rect)> = + if let Ok(windows_ref) = self.windows.try_borrow() { + if let Some(app_window) = windows_ref.get(&self.window_id) { + app_window + .webviews + .iter() + .filter_map(|wrapper| { + if wrapper.inner.is_browser() { + wrapper.bounds.lock().unwrap().as_ref().map(|b| { + let new_rect = cef::Rect { + x: (size.width as f32 * b.x_rate) as i32, + y: (size.height as f32 * b.y_rate) as i32, + width: (size.width as f32 * b.width_rate) as i32, + height: (size.height as f32 * b.height_rate) as i32, + }; + (wrapper.inner.clone(), new_rect) + }) + } else { + None + } + }) + .collect() + } else { + Vec::new() + } + } else { + Vec::new() + }; + + for (inner, rect) in bounds_updates { + inner.set_bounds(Some(&rect)); + } + + let scale = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + let physical_position = LogicalPosition::new(bounds.x, bounds.y) + .to_physical::(scale); + let position_changed = { + let mut emitted_pos = self.last_emitted_position.borrow_mut(); + let changed = *emitted_pos != physical_position; + if changed { + *emitted_pos = physical_position; + } + changed + }; + if position_changed { + send_window_event( + self.window_id, + &self.windows, + &self.callback, + WindowEvent::Moved(physical_position), + ); + } + + let physical_size = LogicalSize::new( + bounds.width as u32, + bounds.height as u32, + ).to_physical::(scale); + let size_changed = { + let mut emitted_size = self.last_emitted_size.borrow_mut(); + let changed = *emitted_size != physical_size; + if changed { + *emitted_size = physical_size; + } + changed + }; + if size_changed { + send_window_event( + self.window_id, + &self.windows, + &self.callback, + WindowEvent::Resized(physical_size), + ); + } + } + + fn on_window_activation_changed( + &self, + _window: Option<&mut Window>, + active: ::std::os::raw::c_int, + ) { + send_window_event( + self.window_id, + &self.windows, + &self.callback, + WindowEvent::Focused(active == 1), + ); + } + } +} + +fn get_webview( + context: &Context, + window_id: WindowId, + webview_id: u32, +) -> Option { + context + .windows + .borrow() + .get(&window_id) + .and_then(|app_window| { + app_window + .webviews + .iter() + .find(|w| w.webview_id == webview_id) + .cloned() + }) +} + +fn get_main_frame( + context: &Context, + window_id: WindowId, + webview_id: u32, +) -> Option { + get_webview(context, window_id, webview_id) + .and_then(|bv| bv.inner.browser()) + .and_then(|b| b.main_frame()) +} + +fn get_browser( + context: &Context, + window_id: WindowId, + webview_id: u32, +) -> Option { + get_webview(context, window_id, webview_id).and_then(|bv| bv.inner.browser()) +} + +fn handle_webview_message( + context: &Context, + window_id: WindowId, + webview_id: u32, + message: WebviewMessage, +) { + match message { + WebviewMessage::AddEventListener(event_id, handler) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + let listeners = app_window.webview_event_listeners.clone(); + let mut listeners_map = listeners.lock().unwrap(); + let webview_listeners = listeners_map + .entry(webview_id) + .or_insert_with(|| Arc::new(Mutex::new(HashMap::new()))); + webview_listeners.lock().unwrap().insert(event_id, handler); + } + } + WebviewMessage::EvaluateScript(script) => { + if let Some(frame) = get_main_frame(context, window_id, webview_id) { + frame.execute_java_script( + Some(&cef::CefString::from(script.as_str())), + Some(&cef::CefString::from("")), + 0, + ); + } + } + WebviewMessage::EvaluateScriptWithCallback(script, callback) => { + if let Some(host) = get_browser(context, window_id, webview_id).and_then(|b| b.host()) { + let message_id = context.next_webview_event_id() as i32 + 1; + let message_id = Arc::new(AtomicI32::new(message_id)); + let callback = Arc::new(Mutex::new(Some(callback))); + let registration = Arc::new(Mutex::new(None)); + let mut observer = EvalScriptWithCallbackDevToolsObserver::new( + message_id.clone(), + callback.clone(), + registration.clone(), + ); + + if let Some(observer_registration) = + host.add_dev_tools_message_observer(Some(&mut observer)) + { + *registration.lock().unwrap() = Some(observer_registration); + + let message = serde_json::json!({ + "id": message_id.load(Ordering::Relaxed), + "method": "Runtime.evaluate", + "params": { + "expression": script, + "returnByValue": true, + } + }) + .to_string(); + + if host.send_dev_tools_message(Some(message.as_bytes())) != 1 { + let _ = registration.lock().unwrap().take(); + if let Some(callback) = callback.lock().unwrap().take() { + callback(String::new()); + } + } + } else if let Some(callback) = callback.lock().unwrap().take() { + callback(String::new()); + } + } + } + WebviewMessage::Navigate(url) => { + if let Some(frame) = get_main_frame(context, window_id, webview_id) { + frame.load_url(Some(&cef::CefString::from(url.as_str()))) + } + } + WebviewMessage::Reload => { + if let Some(browser) = get_browser(context, window_id, webview_id) { + browser.reload() + } + } + WebviewMessage::GoBack => { + if let Some(browser) = get_browser(context, window_id, webview_id) { + browser.go_back() + } + } + WebviewMessage::CanGoBack(tx) => { + if let Some(browser) = get_browser(context, window_id, webview_id) { + let _ = tx.send(Ok(browser.can_go_back() != 0)); + } else { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + } + } + WebviewMessage::GoForward => { + if let Some(browser) = get_browser(context, window_id, webview_id) { + browser.go_forward() + } + } + WebviewMessage::CanGoForward(tx) => { + if let Some(browser) = get_browser(context, window_id, webview_id) { + let _ = tx.send(Ok(browser.can_go_forward() != 0)); + } else { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + } + } + WebviewMessage::Print => { + if let Some(host) = get_browser(context, window_id, webview_id).and_then(|b| b.host()) { + host.print() + } + } + WebviewMessage::Close => { + let webview_to_close = { + let mut windows = context.windows.borrow_mut(); + if let Some(app_window) = windows.get_mut(&window_id) { + let webview_index = app_window + .webviews + .iter() + .position(|w| w.webview_id == webview_id); + + if let Some(index) = webview_index { + let wrapper = app_window.webviews.remove(index); + app_window + .webview_event_listeners + .lock() + .unwrap() + .remove(&webview_id); + Some(wrapper) + } else { + None + } + } else { + None + } + }; + + if let Some(wrapper) = webview_to_close { + let browser_id = *wrapper.browser_id.borrow(); + { + let mut registry = context.scheme_handler_registry.lock().unwrap(); + for scheme in wrapper.uri_scheme_protocols.keys() { + registry.remove(&(browser_id, scheme.clone())); + } + } + wrapper.inner.close(); + } + } + WebviewMessage::Show => { + if let Some(wrapper) = get_webview(context, window_id, webview_id) { + wrapper.inner.set_visible(1) + } + } + WebviewMessage::Hide => { + if let Some(wrapper) = get_webview(context, window_id, webview_id) { + wrapper.inner.set_visible(0) + } + } + WebviewMessage::SetPosition(position) => { + let data = context + .windows + .borrow() + .get(&window_id) + .and_then(|app_window| { + let device_scale_factor = app_window + .window() + .and_then(|window| window.display()) + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + let position = position_to_cef(position, device_scale_factor); + + app_window + .webviews + .iter() + .find(|w| w.webview_id == webview_id) + .map(|wrapper| { + let current_bounds = wrapper.inner.bounds(); + let new_bounds = cef::Rect { + x: position.x, + y: position.y, + width: current_bounds.width, + height: current_bounds.height, + }; + let inner = wrapper.inner.clone(); + let bounds_arc = wrapper.bounds.clone(); + let is_browser = wrapper.inner.is_browser(); + let window_bounds = if is_browser { + app_window.window().map(|w| w.bounds()) + } else { + None + }; + (inner, new_bounds, is_browser, bounds_arc, window_bounds) + }) + }); + + if let Some((inner, new_bounds, is_browser, bounds_arc, window_bounds)) = data { + inner.set_bounds(Some(&new_bounds)); + if is_browser + && let Some(b) = &mut *bounds_arc.lock().unwrap() + && let Some(wb) = window_bounds + { + let window_size = LogicalSize::new(wb.width as u32, wb.height as u32); + b.x_rate = new_bounds.x as f32 / window_size.width as f32; + b.y_rate = new_bounds.y as f32 / window_size.height as f32; + } + } + } + WebviewMessage::SetSize(size) => { + let data = context + .windows + .borrow() + .get(&window_id) + .and_then(|app_window| { + let device_scale_factor = app_window + .window() + .and_then(|window| window.display()) + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + let size = size_to_cef(size, device_scale_factor); + + app_window + .webviews + .iter() + .find(|w| w.webview_id == webview_id) + .map(|wrapper| { + let current_bounds = wrapper.inner.bounds(); + let new_bounds = cef::Rect { + x: current_bounds.x, + y: current_bounds.y, + width: size.width, + height: size.height, + }; + let inner = wrapper.inner.clone(); + let bounds_arc = wrapper.bounds.clone(); + let is_browser = wrapper.inner.is_browser(); + let window_bounds = if is_browser { + app_window.window().map(|w| w.bounds()) + } else { + None + }; + (inner, new_bounds, is_browser, bounds_arc, window_bounds) + }) + }); + + if let Some((inner, new_bounds, is_browser, bounds_arc, window_bounds)) = data { + inner.set_bounds(Some(&new_bounds)); + if is_browser + && let Some(b) = &mut *bounds_arc.lock().unwrap() + && let Some(wb) = window_bounds + { + let window_size = LogicalSize::new(wb.width as u32, wb.height as u32); + b.width_rate = new_bounds.width as f32 / window_size.width as f32; + b.height_rate = new_bounds.height as f32 / window_size.height as f32; + } + } + } + WebviewMessage::SetBounds(bounds) => { + let data = context + .windows + .borrow() + .get(&window_id) + .and_then(|app_window| { + let device_scale_factor = app_window + .window() + .and_then(|window| window.display()) + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + let new_bounds = rect_to_cef(bounds, device_scale_factor); + app_window + .webviews + .iter() + .find(|w| w.webview_id == webview_id) + .map(|wrapper| { + let inner = wrapper.inner.clone(); + let bounds_arc = wrapper.bounds.clone(); + let is_browser = wrapper.inner.is_browser(); + let window_bounds = if is_browser { + app_window.window().map(|w| w.bounds()) + } else { + None + }; + (inner, new_bounds, is_browser, bounds_arc, window_bounds) + }) + }); + + if let Some((inner, new_bounds, is_browser, bounds_arc, window_bounds)) = data { + inner.set_bounds(Some(&new_bounds)); + if is_browser + && let Some(b) = &mut *bounds_arc.lock().unwrap() + && let Some(wb) = window_bounds + { + let window_size = LogicalSize::new(wb.width as u32, wb.height as u32); + b.x_rate = new_bounds.x as f32 / window_size.width as f32; + b.y_rate = new_bounds.y as f32 / window_size.height as f32; + b.width_rate = new_bounds.width as f32 / window_size.width as f32; + b.height_rate = new_bounds.height as f32 / window_size.height as f32; + } + } + } + WebviewMessage::SetFocus => { + if let Some(host) = get_webview(context, window_id, webview_id) + .and_then(|bv| bv.inner.browser()) + .and_then(|b| b.host()) + { + host.set_focus(1) + } + } + WebviewMessage::Reparent(target_window_id, tx) => { + let reparent_data = { + let mut windows = context.windows.borrow_mut(); + + if !windows.contains_key(&target_window_id) { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + return; + }; + + let Some(webview_wrapper) = windows.get_mut(&window_id).and_then(|app_window| { + app_window + .webviews + .iter() + .position(|w| w.webview_id == webview_id) + .map(|index| app_window.webviews.remove(index)) + }) else { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + return; + }; + + let target_cef_window = match windows.get(&target_window_id) { + Some(tw) => match &tw.window { + crate::AppWindowKind::Window(window) => window.clone(), + crate::AppWindowKind::BrowserWindow => { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + return; + } + }, + None => { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + return; + } + }; + + (webview_wrapper, target_cef_window) + }; + + let (webview_wrapper, target_cef_window) = reparent_data; + + let bounds = webview_wrapper.inner.bounds(); + webview_wrapper.inner.set_parent(&target_cef_window); + webview_wrapper.inner.set_bounds(Some(&bounds)); + + { + let mut windows = context.windows.borrow_mut(); + if let Some(target_window) = windows.get_mut(&target_window_id) { + target_window.webviews.push(webview_wrapper); + let _ = tx.send(Ok(())); + } else { + let _ = tx.send(Err(tauri_runtime::Error::FailedToSendMessage)); + } + } + } + WebviewMessage::SetAutoResize(auto_resize) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(wrapper) = app_window + .webviews + .iter() + .find(|w| w.webview_id == webview_id) + && wrapper.inner.is_browser() + { + if auto_resize { + if let Some(window) = app_window.window() { + let window_bounds = window.bounds(); + let window_size = + LogicalSize::new(window_bounds.width as u32, window_bounds.height as u32); + + let ob = wrapper.inner.bounds(); + let pos = LogicalPosition::new(ob.x, ob.y); + let size = LogicalSize::new(ob.width as u32, ob.height as u32); + + *wrapper.bounds.lock().unwrap() = Some(crate::WebviewBounds { + x_rate: pos.x as f32 / window_size.width as f32, + y_rate: pos.y as f32 / window_size.height as f32, + width_rate: size.width as f32 / window_size.width as f32, + height_rate: size.height as f32 / window_size.height as f32, + }); + } + } else { + *wrapper.bounds.lock().unwrap() = None; + } + } + } + WebviewMessage::SetZoom(scale_factor) => { + if let Some(host) = get_webview(context, window_id, webview_id) + .and_then(|bv| bv.inner.browser()) + .and_then(|b| b.host()) + { + // CEF uses a logarithmic zoom level where percentage = 1.2^level + // (Chromium's kTextSizeMultiplierRatio). Convert from Tauri linear + // scale factor (1.0 = 100%) to CEF's level (0.0 = 100%) + const CEF_ZOOM_BASE: f64 = 1.2; + let zoom_level = if scale_factor > 0.0 { + scale_factor.ln() / CEF_ZOOM_BASE.ln() + } else { + 0.0 + }; + host.set_zoom_level(zoom_level) + } + } + WebviewMessage::SetBackgroundColor(color) => { + if let Some(bv) = context + .windows + .borrow() + .get(&window_id) + .and_then(|app_window| { + app_window + .webviews + .iter() + .find(|w| w.webview_id == webview_id) + }) + { + bv.webview_attributes.borrow_mut().background_color = color; + + bv.inner.set_background_color(color.map(color_to_cef_argb)); + } + } + WebviewMessage::ClearAllBrowsingData => { + // TODO: Implement clear browsing data + } + // Getters + WebviewMessage::Url(tx) => { + let result = get_main_frame(context, window_id, webview_id) + .map(|frame| cef::CefString::from(&frame.url()).to_string()) + .ok_or(tauri_runtime::Error::FailedToSendMessage); + let _ = tx.send(result); + } + WebviewMessage::Bounds(tx) => { + let result = get_webview(context, window_id, webview_id) + .map(|webview| { + let bounds = webview.inner.bounds(); + let scale = webview.inner.scale_factor(); + let logical_position = LogicalPosition::new(bounds.x, bounds.y); + let logical_size = LogicalSize::new(bounds.width as u32, bounds.height as u32); + let physical_position = logical_position.to_physical::(scale); + let physical_size = logical_size.to_physical::(scale); + tauri_runtime::dpi::Rect { + position: Position::Physical(physical_position), + size: Size::Physical(physical_size), + } + }) + .ok_or(tauri_runtime::Error::FailedToSendMessage); + let _ = tx.send(result); + } + WebviewMessage::Position(tx) => { + let result = get_webview(context, window_id, webview_id) + .map(|webview| { + let bounds = webview.inner.bounds(); + let scale = webview.inner.scale_factor(); + LogicalPosition::new(bounds.x, bounds.y).to_physical::(scale) + }) + .ok_or(tauri_runtime::Error::FailedToSendMessage); + let _ = tx.send(result); + } + WebviewMessage::Size(tx) => { + let result = get_webview(context, window_id, webview_id) + .map(|webview| { + let bounds = webview.inner.bounds(); + let scale = webview.inner.scale_factor(); + let size = LogicalSize::new(bounds.width as u32, bounds.height as u32); + size.to_physical::(scale) + }) + .ok_or(tauri_runtime::Error::FailedToSendMessage); + let _ = tx.send(result); + } + WebviewMessage::WithWebview(f) => { + if let Some(browser_view) = get_browser(context, window_id, webview_id) { + f(Box::new(browser_view)); + } + } + // Devtools + #[cfg(any(debug_assertions, feature = "devtools"))] + WebviewMessage::OpenDevTools => { + if let Some(host) = get_browser(context, window_id, webview_id).and_then(|b| b.host()) { + let window_info = cef::WindowInfo::default(); + let settings = cef::BrowserSettings::default(); + let inspect_at = cef::Point { x: 0, y: 0 }; + host.show_dev_tools( + Some(&window_info), + Option::<&mut cef::Client>::None, + Some(&settings), + Some(&inspect_at), + ); + } + } + #[cfg(any(debug_assertions, feature = "devtools"))] + WebviewMessage::CloseDevTools => { + if let Some(host) = get_browser(context, window_id, webview_id).and_then(|b| b.host()) { + host.close_dev_tools() + } + } + #[cfg(any(debug_assertions, feature = "devtools"))] + WebviewMessage::IsDevToolsOpen(tx) => { + let result = get_browser(context, window_id, webview_id) + .and_then(|b| b.host()) + .map(|host| host.has_dev_tools() != 0) + .unwrap_or(false); + let _ = tx.send(result); + } + WebviewMessage::SendDevToolsMessage(message, tx) => { + let result = get_browser(context, window_id, webview_id) + .and_then(|b| b.host()) + .map(|host| { + let result = host.send_dev_tools_message(Some(&message)); + if result == 1 { + Ok(()) + } else { + Err(tauri_runtime::Error::FailedToSendMessage) + } + }) + .unwrap_or(Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WebviewMessage::OnDevToolsProtocol(handler, tx) => { + let result = match get_webview(context, window_id, webview_id) { + Some(webview) => { + let mut handlers = webview.devtools_protocol_handlers.lock().unwrap(); + handlers.push(handler); + // Add the observer when the first listener is registered + if handlers.len() == 1 + && let Some(browser) = get_browser(context, window_id, webview_id) + && let Some(registration) = + add_dev_tools_observer(&browser, webview.devtools_protocol_handlers.clone()) + { + *webview.devtools_observer_registration.lock().unwrap() = Some(registration); + } + Ok(()) + } + None => Err(tauri_runtime::Error::FailedToSendMessage), + }; + let _ = tx.send(result); + } + WebviewMessage::CookiesForUrl(url, tx) => { + // Collect cookies for a specific URL + let url_str = url.as_str().to_string(); + + cef::cookie_manager_get_global_manager(None) + .map(|manager| { + let collected: Arc>>> = + Arc::new(Mutex::new(Vec::new())); + let tx_ = tx.clone(); + + let mut visitor = CollectUrlCookiesVisitor::new(tx_, collected.clone()); + let url_cef = cef::CefString::from(url_str.as_str()); + manager.visit_url_cookies(Some(&url_cef), 1, Some(&mut visitor)); + }) + .or_else(|| { + let _ = tx.send(Ok(Vec::new())); + None + }); + } + WebviewMessage::Cookies(tx) => { + // Collect all cookies + cef::cookie_manager_get_global_manager(None) + .map(|manager| { + let collected: Arc>>> = + Arc::new(Mutex::new(Vec::new())); + let tx_ = tx.clone(); + + let mut visitor = CollectAllCookiesVisitor::new(tx_, collected.clone()); + manager.visit_all_cookies(Some(&mut visitor)); + }) + .or_else(|| { + let _ = tx.send(Ok(Vec::new())); + None + }); + } + WebviewMessage::SetCookie(cookie) => { + if let Some(manager) = cef::cookie_manager_get_global_manager(None) { + // Try to infer a URL for the cookie scope using the currently loaded URL + let url = get_main_frame(context, window_id, webview_id) + .map(|frame| cef::CefString::from(&frame.url()).to_string()) + .unwrap_or_default(); + + let mut cef_cookie = cef::Cookie { + name: cef::CefString::from(cookie.name()), + value: cef::CefString::from(cookie.value()), + ..Default::default() + }; + if let Some(d) = cookie.domain() { + cef_cookie.domain = cef::CefString::from(d); + } + if let Some(p) = cookie.path() { + cef_cookie.path = cef::CefString::from(p); + } + if cookie.secure().unwrap_or(false) { + cef_cookie.secure = 1; + } + if cookie.http_only().unwrap_or(false) { + cef_cookie.httponly = 1; + } + + let url_cef = if url.is_empty() { + None + } else { + Some(cef::CefString::from(url.as_str())) + }; + manager.set_cookie( + url_cef.as_ref(), + Some(&cef_cookie), + Option::<&mut cef::SetCookieCallback>::None, + ); + } + } + WebviewMessage::DeleteCookie(cookie) => { + if let Some(manager) = cef::cookie_manager_get_global_manager(None) { + // Resolve current URL for targeted deletion + let url = get_main_frame(context, window_id, webview_id) + .map(|frame| cef::CefString::from(&frame.url()).to_string()) + .unwrap_or_default(); + let url_cef = if url.is_empty() { + None + } else { + Some(cef::CefString::from(url.as_str())) + }; + let name_cef = Some(cef::CefString::from(cookie.name())); + manager.delete_cookies( + url_cef.as_ref(), + name_cef.as_ref(), + Option::<&mut cef::DeleteCookiesCallback>::None, + ); + } + } + } +} + +#[cfg(target_os = "macos")] +fn start_window_dragging(window: &cef::Window) { + use objc2::rc::Retained; + use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventType, NSView}; + + unsafe { + let ns_view = Retained::::retain(window.window_handle() as _); + if let Some(ns_view) = ns_view + && let Some(ns_window) = ns_view.window() + { + // Get current mouse location + let mouse_location = NSEvent::mouseLocation(); + + // Try to get the current event from NSApp + let mut event = None; + if let Some(mtm) = objc2::MainThreadMarker::new() { + let ns_app = objc2_app_kit::NSApp(mtm); + event = ns_app.currentEvent(); + } + + // Create a mouse event for dragging + // If we have a current event, try to use its properties + let drag_event = if let Some(current_event) = event { + let event_modifier_flags = current_event.modifierFlags(); + let event_timestamp = current_event.timestamp(); + let event_window_number = current_event.windowNumber(); + + NSEvent::mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure( + NSEventType::LeftMouseDown, + mouse_location, + event_modifier_flags, + event_timestamp, + event_window_number, + None, + 0, + 1, + 1.0, + ) + } else { + NSEvent::mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure( + NSEventType::LeftMouseDown, + mouse_location, + NSEventModifierFlags::empty(), + 0.0, + ns_window.windowNumber(), + None, + 0, + 1, + 1.0, + ) + }; + + if let Some(event) = drag_event { + ns_window.performWindowDragWithEvent(&event); + } + } + } +} + +#[cfg(windows)] +fn start_window_dragging(window: &cef::Window) { + use windows::Win32::Foundation::*; + use windows::Win32::UI::Input::KeyboardAndMouse::*; + use windows::Win32::UI::WindowsAndMessaging::*; + + unsafe { + let hwnd = window.window_handle(); + + let mut pos = std::mem::zeroed(); + let _ = GetCursorPos(&mut pos); + + let points = POINTS { + x: pos.x as i16, + y: pos.y as i16, + }; + + let _ = ReleaseCapture(); + + let _ = PostMessageW( + Some(HWND(hwnd.0 as _)), + WM_NCLBUTTONDOWN, + WPARAM(HTCAPTION as usize), + LPARAM(&points as *const _ as isize), + ); + } +} + +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +fn start_window_dragging(window: &cef::Window) { + use std::ffi::CString; + use std::os::raw::c_long; + use x11_dl::xlib; + + let Some(xlib) = xlib::Xlib::open().ok() else { + return; + }; + + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + if display.is_null() { + return; + } + + let win = window.window_handle(); + + let mut root_x: std::ffi::c_int = 0; + let mut root_y: std::ffi::c_int = 0; + let mut _win_x: std::ffi::c_int = 0; + let mut _win_y: std::ffi::c_int = 0; + let mut _mask: std::ffi::c_uint = 0; + let mut root: xlib::Window = (xlib.XDefaultRootWindow)(display); + let mut _child_return: xlib::Window = 0; + let _ = (xlib.XQueryPointer)( + display, + win, + &mut root, + &mut _child_return, + &mut root_x, + &mut root_y, + &mut _win_x, + &mut _win_y, + &mut _mask, + ); + + let net_wm_moveresize = CString::new("_NET_WM_MOVERESIZE").unwrap(); + let atom = (xlib.XInternAtom)(display, net_wm_moveresize.as_ptr(), xlib::False); + if atom == 0 { + (xlib.XCloseDisplay)(display); + return; + } + + // EWMH _NET_WM_MOVERESIZE: direction 8 = move, button 1 = left, source 1 = application + const NET_WM_MOVERESIZE_MOVE: c_long = 8; + const SOURCE_APPLICATION: c_long = 1; + + let mut data: xlib::ClientMessageData = std::mem::zeroed(); + { + let longs = >::as_mut(&mut data); + longs[0] = root_x as i64; + longs[1] = root_y as i64; + longs[2] = NET_WM_MOVERESIZE_MOVE; + longs[3] = 1; // Button 1 (left) + longs[4] = SOURCE_APPLICATION; + } + + let xclient = xlib::XClientMessageEvent { + type_: xlib::ClientMessage, + serial: 0, + send_event: xlib::True, + display, + window: win, + message_type: atom, + format: 32, + data, + }; + + let mut event: xlib::XEvent = xclient.into(); + let _ = (xlib.XSendEvent)(display, root, xlib::False, 0, &mut event); + (xlib.XFlush)(display); + (xlib.XCloseDisplay)(display); + } +} + +fn handle_window_message( + context: &Context, + window_id: WindowId, + message: WindowMessage, +) { + match message { + WindowMessage::Close => { + on_close_requested(window_id, &context.windows, &context.callback); + } + WindowMessage::Destroy => { + on_window_close(window_id, &context.windows); + } + WindowMessage::AddEventListener(event_id, handler) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window + .window_event_listeners + .lock() + .unwrap() + .insert(event_id, handler); + } + } + // Getters + WindowMessage::ScaleFactor(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .and_then(|w| { + w.window() + .and_then(|window| window.display().map(|d| Ok(d.device_scale_factor() as f64))) + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::InnerPosition(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => { + let bounds = window.bounds(); + let scale = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + Ok(LogicalPosition::new(bounds.x, bounds.y).to_physical::(scale)) + } + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::OuterPosition(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => { + let bounds = window.bounds(); + let scale = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + Ok(LogicalPosition::new(bounds.x, bounds.y).to_physical::(scale)) + } + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::InnerSize(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => { + #[cfg(not(windows))] + let size = { + let scale = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + let bounds = window.bounds(); + LogicalSize::new(bounds.width as u32, bounds.height as u32).to_physical::(scale) + }; + + // On Windows, window.bounds() is the outer size, not the inner size. + #[cfg(windows)] + let size = crate::utils::windows::inner_size(window.window_handle()); + + Ok(size) + } + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::OuterSize(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => { + let bounds = window.bounds(); + let scale = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + Ok( + LogicalSize::new(bounds.width as u32, bounds.height as u32).to_physical::(scale), + ) + } + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsFullscreen(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => Ok(window.is_fullscreen() == 1), + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsMinimized(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => Ok(window.is_minimized() == 1), + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsMaximized(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => Ok(window.is_maximized() == 1), + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsFocused(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => Ok(window.has_focus() == 1), + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsDecorated(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| Ok(w.attributes.borrow().decorations.unwrap_or(true))) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsResizable(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| Ok(w.attributes.borrow().resizable.unwrap_or(true))) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsMaximizable(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| Ok(w.attributes.borrow().maximizable.unwrap_or(true))) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsMinimizable(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| Ok(w.attributes.borrow().minimizable.unwrap_or(true))) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsClosable(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| Ok(w.attributes.borrow().closable.unwrap_or(true))) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsVisible(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => Ok(window.is_visible() == 1), + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::Title(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => { + let title = window.title(); + Ok(cef::CefString::from(&title).to_string()) + } + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::CurrentMonitor(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .and_then(|w| w.window()) + .map(|window| { + let b = window.bounds(); + cef::display_get_matching_bounds(Some(&b), 1).map(|d| { + let bounds = d.bounds(); + let work = d.work_area(); + let scale = d.device_scale_factor() as f64; + let physical_size = + LogicalSize::new(bounds.width as u32, bounds.height as u32).to_physical::(scale); + let physical_position = + LogicalPosition::new(bounds.x, bounds.y).to_physical::(scale); + let work_physical_size = + LogicalSize::new(work.width as u32, work.height as u32).to_physical::(scale); + let work_physical_position = + LogicalPosition::new(work.x, work.y).to_physical::(scale); + tauri_runtime::monitor::Monitor { + name: None, + size: PhysicalSize::new(physical_size.width, physical_size.height), + position: PhysicalPosition::new(physical_position.x, physical_position.y), + work_area: PhysicalRect { + position: PhysicalPosition::new(work_physical_position.x, work_physical_position.y), + size: PhysicalSize::new(work_physical_size.width, work_physical_size.height), + }, + scale_factor: d.device_scale_factor() as f64, + } + }) + }) + .map(Ok) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::PrimaryMonitor(tx) => { + let result = Ok(get_primary_monitor()); + let _ = tx.send(result); + } + WindowMessage::MonitorFromPoint(tx, x, y) => { + let result = Ok(get_monitor_from_point(x, y)); + let _ = tx.send(result); + } + WindowMessage::AvailableMonitors(tx) => { + let monitors = get_available_monitors(); + let _ = tx.send(Ok(monitors)); + } + WindowMessage::Theme(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| Ok(native_window_theme(w).unwrap_or(tauri_utils::Theme::Light))) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::IsEnabled(tx) => { + let _ = tx.send(Ok(true)); + } + WindowMessage::IsAlwaysOnTop(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => Ok(window.is_always_on_top() == 1), + crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), + }) + .unwrap_or_else(|| Err(tauri_runtime::Error::FailedToSendMessage)); + let _ = tx.send(result); + } + WindowMessage::RawWindowHandle(tx) => { + let result = context + .windows + .borrow() + .get(&window_id) + .map(|w| match &w.window { + crate::AppWindowKind::Window(window) => { + #[cfg(target_os = "linux")] + unsafe { + let xid = window.window_handle(); + Ok(raw_window_handle::WindowHandle::borrow_raw( + raw_window_handle::RawWindowHandle::Xlib(raw_window_handle::XlibWindowHandle::new( + xid, + )), + )) + } + + #[cfg(target_os = "macos")] + unsafe { + let ns_view = window.window_handle(); + if let Some(nn) = std::ptr::NonNull::new(ns_view) { + Ok(raw_window_handle::WindowHandle::borrow_raw( + raw_window_handle::RawWindowHandle::AppKit( + raw_window_handle::AppKitWindowHandle::new(nn), + ), + )) + } else { + Err(raw_window_handle::HandleError::Unavailable) + } + } + + #[cfg(windows)] + unsafe { + let hwnd = window.window_handle().0 as isize; + if let Some(nz) = std::num::NonZeroIsize::new(hwnd) { + Ok(raw_window_handle::WindowHandle::borrow_raw( + raw_window_handle::RawWindowHandle::Win32( + raw_window_handle::Win32WindowHandle::new(nz), + ), + )) + } else { + Err(raw_window_handle::HandleError::Unavailable) + } + } + } + crate::AppWindowKind::BrowserWindow => Err(raw_window_handle::HandleError::Unavailable), + }) + .unwrap_or(Err(raw_window_handle::HandleError::Unavailable)); + let _ = tx.send(result); + } + // Setters + WindowMessage::Center => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.center_window(Some(&window.size())); + } + } + WindowMessage::RequestUserAttention(_attention_type) => { + // TODO: Implement user attention + } + WindowMessage::SetEnabled(_enabled) => { + // TODO: Implement enabled + } + WindowMessage::SetResizable(resizable) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().resizable = Some(resizable); + } + } + WindowMessage::SetMaximizable(maximizable) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().maximizable = Some(maximizable); + } + } + WindowMessage::SetMinimizable(minimizable) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().minimizable = Some(minimizable); + } + } + WindowMessage::SetClosable(closable) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().closable = Some(closable); + } + } + WindowMessage::SetTitle(title) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.set_title(Some(&cef::CefString::from(title.as_str()))); + } + } + WindowMessage::Maximize => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.maximize(); + } + } + WindowMessage::Unmaximize => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.restore(); + } + } + WindowMessage::Minimize => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.minimize(); + } + } + WindowMessage::Unminimize => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.restore(); + } + } + WindowMessage::Show => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.show(); + } + } + WindowMessage::Hide => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.hide(); + } + } + WindowMessage::SetDecorations(decorations) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().decorations = Some(decorations); + } + } + WindowMessage::SetShadow(_shadow) => { + // TODO: Implement shadow + } + WindowMessage::SetAlwaysOnBottom(always_on_bottom) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().always_on_bottom = Some(always_on_bottom); + } + // TODO: Apply always on bottom via platform-specific CEF APIs if available + } + WindowMessage::SetAlwaysOnTop(always_on_top) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().always_on_top = Some(always_on_top); + if let Some(window) = app_window.window() { + window.set_always_on_top(if always_on_top { 1 } else { 0 }); + } + } + } + WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().visible_on_all_workspaces = + Some(visible_on_all_workspaces); + } + // TODO: Apply visible on all workspaces via platform-specific CEF APIs if available + } + WindowMessage::SetContentProtected(protected) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().content_protected = Some(protected); + if let Some(window) = app_window.window() { + apply_content_protection(&window, protected); + } + } + } + #[allow(unused_mut)] + WindowMessage::SetSize(mut size) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + && let Some(display) = window.display() + { + let device_scale_factor = display.device_scale_factor() as f64; + + let size = size_to_cef(size, device_scale_factor); + + // On Windows, the size set via CEF APIs is the outer size (including borders), + // so we need to adjust it to set the correct inner size. + #[cfg(windows)] + let size = crate::utils::windows::adjust_size(window.window_handle(), size); + + window.set_size(Some(&size)); + } + } + WindowMessage::SetMinSize(size) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().min_inner_size = size; + } + } + WindowMessage::SetMaxSize(size) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().max_inner_size = size; + } + } + WindowMessage::SetSizeConstraints(constraints) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().inner_size_constraints = Some(constraints); + } + } + WindowMessage::SetPosition(position) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + && let Some(display) = window.display() + { + let device_scale_factor = display.device_scale_factor() as f64; + let position = position_to_cef(position, device_scale_factor); + window.set_position(Some(&position)); + } + } + WindowMessage::SetFullscreen(fullscreen) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.set_fullscreen(if fullscreen { 1 } else { 0 }); + } + } + #[cfg(target_os = "macos")] + WindowMessage::SetSimpleFullscreen(_fullscreen) => { + // TODO: Implement simple fullscreen + } + WindowMessage::SetFocus => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.request_focus(); + } + } + WindowMessage::SetFocusable(focusable) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + window.set_focusable(if focusable { 1 } else { 0 }); + } + } + WindowMessage::SetIcon(icon) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + set_window_icon(&window, icon); + } + } + WindowMessage::SetSkipTaskbar(_skip) => { + // TODO: Implement skip taskbar + } + WindowMessage::SetCursorGrab(_grab) => { + // TODO: Implement cursor grab + } + WindowMessage::SetCursorVisible(_visible) => { + // TODO: Implement cursor visible + } + WindowMessage::SetCursorIcon(_icon) => { + // TODO: Implement cursor icon + } + WindowMessage::SetCursorPosition(_position) => { + // TODO: Implement cursor position + } + WindowMessage::SetIgnoreCursorEvents(_ignore) => { + // TODO: Implement ignore cursor events + } + WindowMessage::SetProgressBar(_progress_state) => { + // TODO: Implement progress bar + } + WindowMessage::SetBadgeCount(_count, _desktop_filename) => { + // TODO: Implement badge count + } + WindowMessage::SetBadgeLabel(_label) => { + // TODO: Implement badge label + } + WindowMessage::SetOverlayIcon(icon) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + set_overlay_icon(&window, icon); + } + } + WindowMessage::SetTitleBarStyle(_style) => { + // TODO: Implement title bar style + } + WindowMessage::SetTrafficLightPosition(_position) => { + #[cfg(target_os = "macos")] + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().traffic_light_position = Some(_position); + if let Some(window) = app_window.window() { + apply_traffic_light_position(window.window_handle(), &_position); + } + } + } + WindowMessage::SetTheme(theme) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + { + let mut attributes = app_window.attributes.borrow_mut(); + attributes.theme = theme; + } + apply_window_theme_scheme(app_window, theme); + #[cfg(target_os = "macos")] + { + let window = app_window.window(); + apply_macos_window_theme(window.as_ref(), theme); + } + // theme changed event is sent by the on_theme_changed handler + } + } + WindowMessage::SetBackgroundColor(color) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window.attributes.borrow_mut().background_color = color; + let Some(window) = app_window.window() else { + return; + }; + let color = color + .map(color_to_cef_argb) + .unwrap_or_else(|| window.theme_color(ColorId::COLOR_PRIMARY_BACKGROUND.get_raw() as _)); + window.set_background_color(color); + } + } + WindowMessage::StartDragging => { + if let Some(app_window) = context.windows.borrow().get(&window_id) + && let Some(window) = app_window.window() + { + start_window_dragging(&window); + } + } + WindowMessage::StartResizeDragging(_direction) => { + // TODO: Implement start resize dragging + } + } +} + +pub fn handle_message(context: &Context, message: Message) { + match message { + Message::CreateWindow { + window_id, + webview_id, + pending, + after_window_creation: _todo, + } => create_window(context, window_id, webview_id, *pending), + Message::CreateWebview { + window_id, + webview_id, + pending, + } => create_webview( + WebviewKind::WindowChild, + context, + window_id, + webview_id, + *pending, + ), + Message::Window { window_id, message } => { + handle_window_message(context, window_id, message); + } + Message::Webview { + window_id, + webview_id, + message, + } => handle_webview_message(context, window_id, webview_id, message), + Message::RequestExit(code) => { + let (tx, rx) = channel(); + in_callback(|| { + (context.callback.borrow())(RunEvent::ExitRequested { + code: Some(code), + tx, + }); + }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + in_callback(|| (context.callback.borrow())(RunEvent::Exit)); + } + } + Message::Task(t) => t(), + Message::UserEvent(evt) => { + in_callback(|| (context.callback.borrow())(RunEvent::UserEvent(evt))); + } + Message::Noop => {} + } +} + +wrap_task! { + pub struct SendMessageTask { + context: Context, + message: Arc>>, + } + + impl Task { + fn execute(&self) { + handle_message(&self.context, std::mem::replace(&mut self.message.borrow_mut(), Message::Noop)); + } + } +} + +fn create_browser_window( + context: &Context, + window_id: WindowId, + webview_id: u32, + label: String, + window_builder: CefWindowBuilder, + webview: PendingWebview>, +) { + let PendingWebview { + label: webview_label, + opener: _, + mut webview_attributes, + platform_specific_attributes: _, + uri_scheme_protocols, + ipc_handler: _, + navigation_handler, + new_window_handler, + document_title_changed_handler, + address_changed_handler, + url, + web_resource_request_handler: _, + mut on_page_load_handler, + download_handler, + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate_handler: _, + } = webview; + + let address_changed_handler = address_changed_handler + .map(|h| Arc::new(move |url: &url::Url| h(url)) as Arc); + + let initialization_scripts = std::mem::take(&mut webview_attributes.initialization_scripts) + .into_iter() + .map(CefInitScript::new) + .collect::>(); + let initialization_scripts = Arc::new(initialization_scripts); + + let on_page_load_handler = on_page_load_handler.take().map(Arc::from); + let document_title_changed_handler = document_title_changed_handler.map(Arc::from); + let navigation_handler = navigation_handler.map(Arc::from); + let new_window_handler = new_window_handler.map(Arc::from); + + let devtools_enabled = (cfg!(debug_assertions) || cfg!(feature = "devtools")) + && webview_attributes.devtools.unwrap_or(true); + + let custom_protocol_scheme = if webview_attributes.use_https_scheme { + "https" + } else { + "http" + }; + + // Build cached domain names for custom schemes and clone protocols for storage + // before uri_scheme_protocols is moved + let scheme_keys: Vec = uri_scheme_protocols.keys().cloned().collect(); + let custom_scheme_domain_names: Vec = scheme_keys + .iter() + .map(|scheme| format!("{scheme}.localhost")) + .collect(); + + let uri_scheme_protocols: HashMap>> = + uri_scheme_protocols + .into_iter() + .map(|(k, v)| (k, Arc::new(v))) + .collect(); + + let custom_schemes = uri_scheme_protocols.keys().cloned().collect::>(); + + let mut request_context = request_context_from_webview_attributes( + context, + &webview_attributes, + &custom_schemes, + custom_protocol_scheme, + &initialization_scripts, + ); + apply_request_context_theme_scheme(request_context.as_ref(), window_builder.theme); + + let browser_settings = browser_settings_from_webview_attributes(&webview_attributes); + + // Create the AppWindow with BrowserWindow variant before creating the browser + let force_close = Arc::new(AtomicBool::new(false)); + let attributes = Arc::new(RefCell::new(window_builder)); + + let initial_url = url.clone(); + let url = CefString::from(url.as_str()); + + let mut client = BrowserClient::new( + WindowKind::Browser, + window_id, + initialization_scripts.clone(), + on_page_load_handler, + document_title_changed_handler, + navigation_handler, + address_changed_handler, + new_window_handler, + download_handler, + devtools_enabled, + custom_scheme_domain_names.clone(), + custom_protocol_scheme.to_string(), + context.clone(), + Some(initial_url), + ); + + let mut bounds = cef::Rect { + x: 0, + y: 0, + width: 800, + height: 600, + }; + let device_scale_factor = display_get_primary() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.); + if let Some(size) = attributes.borrow().inner_size { + let size = size_to_cef(size, device_scale_factor); + bounds.width = size.width; + bounds.height = size.height; + } + if let Some(position) = attributes.borrow().position { + let position = position_to_cef(position, device_scale_factor); + bounds.x = position.x; + bounds.y = position.y; + } + + let window_info = cef::WindowInfo { + bounds, + ..Default::default() + }; + + let Some(browser) = browser_host_create_browser_sync( + Some(&window_info), + Some(&mut client), + Some(&url), + Some(&browser_settings), + None, + request_context.as_mut(), + ) else { + eprintln!("Failed to create browser"); + return; + }; + + let devtools_protocol_handlers = Arc::new(Mutex::new(Vec::< + Arc, + >::new())); + let devtools_observer_registration = Arc::new(Mutex::new(add_dev_tools_observer( + &browser, + devtools_protocol_handlers.clone(), + ))); + + let browser = CefWebview::Browser(browser); + let browser_id_val = browser.browser_id(); + + { + let mut registry = context.scheme_handler_registry.lock().unwrap(); + for (scheme, handler) in &uri_scheme_protocols { + registry.insert( + (browser_id_val, scheme.clone()), + ( + webview_label.clone(), + handler.clone(), + initialization_scripts.clone(), + ), + ); + } + } + + context.windows.borrow_mut().insert( + window_id, + AppWindow { + label, + window: crate::AppWindowKind::BrowserWindow, + force_close: force_close.clone(), + attributes: attributes.clone(), + webviews: vec![AppWebview { + webview_id, + browser_id: Arc::new(RefCell::new(browser_id_val)), + label: webview_label, + inner: browser, + bounds: Arc::new(Mutex::new(None)), + devtools_enabled, + uri_scheme_protocols: Arc::new(uri_scheme_protocols), + initialization_scripts, + devtools_protocol_handlers, + devtools_observer_registration, + webview_attributes: Arc::new(RefCell::new(webview_attributes)), + }], + window_event_listeners: Arc::new(Mutex::new(HashMap::new())), + webview_event_listeners: Arc::new(Mutex::new(HashMap::new())), + }, + ); +} + +pub(crate) fn create_window( + context: &Context, + window_id: WindowId, + webview_id: u32, + pending: PendingWindow>, +) { + let PendingWindow { + label, + window_builder, + webview, + } = pending; + + if window_builder.browser_window { + if let Some(webview) = webview { + return create_browser_window( + context, + window_id, + webview_id, + label, + window_builder, + webview, + ); + } else { + panic!("unexpected browser_window without webview config"); + } + } + + let force_close = Arc::new(AtomicBool::new(false)); + let attributes = Arc::new(RefCell::new(window_builder)); + + let mut delegate = AppWindowDelegate::::new( + window_id, + context.callback.clone(), + force_close.clone(), + context.windows.clone(), + attributes.clone(), + RefCell::new(Default::default()), + RefCell::new(Default::default()), + RefCell::new(false), + context.clone(), + ); + + let window = window_create_top_level(Some(&mut delegate)).expect("Failed to create window"); + + context.windows.borrow_mut().insert( + window_id, + AppWindow { + label, + window: crate::AppWindowKind::Window(window), + force_close, + attributes, + webviews: Vec::new(), + window_event_listeners: Arc::new(Mutex::new(HashMap::new())), + webview_event_listeners: Arc::new(Mutex::new(HashMap::new())), + }, + ); + + if let Some(webview) = webview { + create_webview( + WebviewKind::WindowContent, + context, + window_id, + webview_id, + webview, + ); + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub(crate) enum WebviewKind { + // webview is the entire window content + WindowContent, + // webview is a child of the window, which can contain other webviews too + WindowChild, +} + +wrap_task! { + struct WindowEventTask { + window_id: WindowId, + windows: Arc>>, + callback: RunEventCallback, + event: WindowEvent, + } + + impl Task { + fn execute(&self) { + send_window_event( + self.window_id, + &self.windows, + &self.callback, + self.event.clone(), + ); + } + } +} + +#[cfg(target_os = "macos")] +fn send_message_task(context: &Context, message: Message) { + let mut task = SendMessageTask::new(context.clone(), Arc::new(RefCell::new(message))); + cef::post_task(sys::cef_thread_id_t::TID_UI.into(), Some(&mut task)); +} + +fn send_window_event( + window_id: WindowId, + windows: &Arc>>, + callback: &RunEventCallback, + event: WindowEvent, +) { + let Ok(windows_ref) = windows.try_borrow() else { + // post task to run later - windows currently mutably borrowed + // happens usually on reparent or destroy when there's a focus change event + let mut task = + WindowEventTask::new(window_id, windows.clone(), callback.clone(), event.clone()); + + cef::post_task(sys::cef_thread_id_t::TID_UI.into(), Some(&mut task)); + return; + }; + + if let Some(w) = windows_ref.get(&window_id) { + let label = w.label.clone(); + let window_event_listeners = w.window_event_listeners.clone(); + + drop(windows_ref); + + in_callback(|| { + let listeners = window_event_listeners.lock().unwrap(); + let handlers: Vec<_> = listeners.values().collect(); + for handler in handlers.iter() { + handler(&event); + } + }); + + in_callback(|| (callback.borrow())(RunEvent::WindowEvent { label, event })); + } +} + +fn on_close_requested( + window_id: WindowId, + windows: &Arc>>, + callback: &RunEventCallback, +) { + let (tx, rx) = channel(); + let event = WindowEvent::CloseRequested { signal_tx: tx }; + + send_window_event(window_id, windows, callback, event.clone()); + + let prevent = rx.try_recv().unwrap_or_default(); + + if !prevent { + on_window_close(window_id, windows); + } +} + +// Collects the browser hosts from the webviews. +fn collect_hosts(webviews: &[AppWebview]) -> Vec { + webviews + .iter() + .filter_map(|webview| webview.inner.browser().and_then(|b| b.host())) + .collect() +} + +/// Force-close all windows, triggering the normal CEF lifecycle: +/// force_close → can_close → close_window_browsers → on_before_close → on_window_destroyed. +pub fn close_all_windows(windows: &Arc>>) { + let window_ids: Vec<_> = windows.borrow().keys().copied().collect(); + for window_id in window_ids { + on_window_close(window_id, windows); + } +} + +/// Close all browsers for a specific window. +/// +/// Returns true if all browsers were closed. +fn close_window_browsers( + window_id: WindowId, + windows: &Arc>>, +) -> bool { + let hosts = { + let windows_ref = windows.borrow(); + let Some(app_window) = windows_ref.get(&window_id) else { + return true; + }; + collect_hosts(&app_window.webviews) + }; + + let mut all_closed = true; + for host in hosts { + host.close_dev_tools(); + if host.try_close_browser() != 1 { + all_closed = false; + } + } + + all_closed +} + +fn on_window_close(window_id: WindowId, windows: &Arc>>) { + let cef_window = { + let windows_ref = windows.borrow(); + let Some(app_window) = windows_ref.get(&window_id) else { + return; + }; + app_window.force_close.store(true, Ordering::SeqCst); + app_window.window() + }; + + if let Some(window) = cef_window { + window.close(); + } +} + +fn on_window_destroyed(window_id: WindowId, context: &Context) { + if context.windows.borrow().get(&window_id).is_none() { + return; + } + + let event = WindowEvent::Destroyed; + send_window_event(window_id, &context.windows, &context.callback, event); + + let removed_window = { + let mut guard = context.windows.borrow_mut(); + guard.remove(&window_id) + }; + + if let Some(ref app_window) = removed_window { + let mut registry = context.scheme_handler_registry.lock().unwrap(); + for webview in &app_window.webviews { + let browser_id = *webview.browser_id.borrow(); + for scheme in webview.uri_scheme_protocols.keys() { + registry.remove(&(browser_id, scheme.clone())); + } + } + } + + drop(removed_window); + + let is_empty = context.windows.borrow().is_empty(); + if is_empty { + let (tx, rx) = channel(); + (context.callback.borrow())(RunEvent::ExitRequested { code: None, tx }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + (context.callback.borrow())(RunEvent::Exit); + } + } +} + +pub(crate) fn create_webview( + kind: WebviewKind, + context: &Context, + window_id: WindowId, + webview_id: u32, + pending: PendingWebview>, +) { + let PendingWebview { + label, + opener: _, + mut webview_attributes, + platform_specific_attributes, + uri_scheme_protocols, + ipc_handler: _, + navigation_handler, + new_window_handler, + document_title_changed_handler, + address_changed_handler, + url, + web_resource_request_handler: _, + mut on_page_load_handler, + download_handler, + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate_handler: _, + } = pending; + + let address_changed_handler = address_changed_handler + .map(|h| Arc::new(move |url: &url::Url| h(url)) as Arc); + + let window = match context + .windows + .borrow() + .get(&window_id) + .and_then(|app_window| app_window.window()) + { + Some(w) => w, + None => { + eprintln!("Window {window_id:?} not found or is a browser window when creating webview",); + return; + } + }; + + let initialization_scripts = std::mem::take(&mut webview_attributes.initialization_scripts) + .into_iter() + .map(CefInitScript::new) + .collect::>(); + let initialization_scripts = Arc::new(initialization_scripts); + + let on_page_load_handler = on_page_load_handler.take().map(Arc::from); + let document_title_changed_handler = document_title_changed_handler.map(Arc::from); + let navigation_handler = navigation_handler.map(Arc::from); + let new_window_handler = new_window_handler.map(Arc::from); + + let devtools_enabled = (cfg!(debug_assertions) || cfg!(feature = "devtools")) + && webview_attributes.devtools.unwrap_or(true); + + let custom_protocol_scheme = if webview_attributes.use_https_scheme { + "https" + } else { + "http" + }; + + let custom_schemes = uri_scheme_protocols.keys().cloned().collect::>(); + let custom_scheme_domain_names: Vec = custom_schemes + .iter() + .map(|scheme| format!("{scheme}.localhost")) + .collect(); + + let initial_url = url.clone(); + let url = CefString::from(url.as_str()); + + let mut client = BrowserClient::new( + WindowKind::Tauri, + window_id, + initialization_scripts.clone(), + on_page_load_handler, + document_title_changed_handler, + navigation_handler, + address_changed_handler, + new_window_handler, + download_handler, + devtools_enabled, + custom_scheme_domain_names.clone(), + custom_protocol_scheme.to_string(), + context.clone(), + Some(initial_url.clone()), + ); + + let uri_scheme_protocols: HashMap>> = + uri_scheme_protocols + .into_iter() + .map(|(k, v)| (k, Arc::new(v))) + .collect(); + + let mut request_context = request_context_from_webview_attributes( + context, + &webview_attributes, + &custom_schemes, + custom_protocol_scheme, + &initialization_scripts, + ); + let window_theme = context + .windows + .borrow() + .get(&window_id) + .and_then(|w| w.attributes.borrow().theme); + apply_request_context_theme_scheme(request_context.as_ref(), window_theme); + + let browser_settings = browser_settings_from_webview_attributes(&webview_attributes); + + let bounds = webview_attributes.bounds.map(|bounds| { + let device_scale_factor = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + rect_to_cef(bounds, device_scale_factor) + }); + + let window_handle = window.window_handle(); + + let runtime_style = platform_specific_attributes + .iter() + .map(|attr| match attr { + WebviewAtribute::RuntimeStyle { style } => *style, + }) + .next() + .unwrap_or(if matches!(kind, WebviewKind::WindowChild) { + CefRuntimeStyle::Alloy + } else { + CefRuntimeStyle::Chrome + }); + + let cef_runtime_style: RuntimeStyle = match runtime_style { + CefRuntimeStyle::Alloy => cef_runtime_style_t::CEF_RUNTIME_STYLE_ALLOY.into(), + CefRuntimeStyle::Chrome => cef_runtime_style_t::CEF_RUNTIME_STYLE_CHROME.into(), + }; + + if kind == WebviewKind::WindowChild { + #[cfg(target_os = "macos")] + let window_handle = ensure_valid_content_view(window_handle); + + let mut window_info = cef::WindowInfo::default().set_as_child( + window_handle, + bounds.as_ref().unwrap_or(&cef::Rect::default()), + ); + window_info.runtime_style = cef_runtime_style; + + let Some(browser_host) = browser_host_create_browser_sync( + Some(&window_info), + Some(&mut client), + Some(&url), + Some(&browser_settings), + Option::<&mut DictionaryValue>::None, + request_context.as_mut(), + ) else { + eprintln!("Failed to create browser"); + return; + }; + + // On Windows, set the browser window to be topmost to esnure correct z-order + #[cfg(windows)] + set_browser_on_top(&browser_host); + + let devtools_protocol_handlers = Arc::new(Mutex::new(Vec::< + Arc, + >::new())); + let devtools_observer_registration = Arc::new(Mutex::new(None)); + + let browser = CefWebview::Browser(browser_host); + + browser.set_bounds(bounds.as_ref()); + + // On Linux, explicitly set parent after creation as set_as_child may not work correctly + #[cfg(target_os = "linux")] + { + // Try to set parent - if window handle isn't available yet, this will be a no-op + // but the browser should become visible once the handle is available + browser.set_parent(&window); + // Ensure browser is visible after setting parent + browser.set_visible(1); + // Set bounds again after reparenting to ensure correct size + browser.set_bounds(bounds.as_ref()); + } + + let initial_bounds_ratio = if webview_attributes.auto_resize { + Some(webview_bounds_ratio(&window, bounds.clone(), &browser)) + } else { + None + }; + + let browser_id_val = browser.browser_id(); + { + let mut registry = context.scheme_handler_registry.lock().unwrap(); + for (scheme, handler) in &uri_scheme_protocols { + registry.insert( + (browser_id_val, scheme.clone()), + ( + label.clone(), + handler.clone(), + initialization_scripts.clone(), + ), + ); + } + } + + context + .windows + .borrow_mut() + .get_mut(&window_id) + .unwrap() + .webviews + .push(AppWebview { + label, + webview_id, + browser_id: Arc::new(RefCell::new(browser_id_val)), + bounds: Arc::new(Mutex::new(initial_bounds_ratio)), + inner: browser, + devtools_enabled, + uri_scheme_protocols: Arc::new(uri_scheme_protocols), + initialization_scripts, + devtools_protocol_handlers, + devtools_observer_registration, + webview_attributes: Arc::new(RefCell::new(webview_attributes)), + }); + } else { + let browser_id = Arc::new(RefCell::new(0)); + let uri_scheme_protocols = Arc::new(uri_scheme_protocols); + let devtools_protocol_handlers = Arc::new(Mutex::new(Vec::< + Arc, + >::new())); + let devtools_observer_registration = Arc::new(Mutex::new(None)); + let webview_attributes = Arc::new(RefCell::new(webview_attributes)); + + #[allow(clippy::unnecessary_find_map)] + let mut browser_view_delegate = BrowserViewDelegateImpl::new( + browser_id.clone(), + runtime_style, + context.scheme_handler_registry.clone(), + label.clone(), + uri_scheme_protocols.clone(), + initialization_scripts.clone(), + devtools_protocol_handlers.clone(), + devtools_observer_registration.clone(), + webview_attributes.clone(), + ); + + let browser_view = browser_view_create( + Some(&mut client), + Some(&url), + Some(&browser_settings), + Option::<&mut DictionaryValue>::None, + request_context.as_mut(), + Some(&mut browser_view_delegate), + ) + .expect("Failed to create browser view"); + + let browser_webview = CefWebview::BrowserView(browser_view.clone()); + + window.add_child_view(Some(&mut View::from(&browser_view))); + + context + .windows + .borrow_mut() + .get_mut(&window_id) + .unwrap() + .webviews + .push(AppWebview { + inner: browser_webview, + label, + webview_id, + browser_id, + bounds: Arc::new(Mutex::new(None)), + devtools_enabled, + uri_scheme_protocols, + initialization_scripts, + devtools_protocol_handlers, + devtools_observer_registration, + webview_attributes, + }); + } +} + +#[cfg(windows)] +fn set_browser_on_top(browser: &cef::Browser) { + use windows::Win32::Foundation::HWND; + use windows::Win32::UI::WindowsAndMessaging::*; + + let Some(host) = browser.host() else { + return; + }; + + let hwnd = HWND(host.window_handle().0 as _); + + let _ = unsafe { + SetWindowPos( + hwnd, + Some(HWND_TOP), + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, + ) + }; +} + +// there is some race condition on CEF that causes the app loading to fail +// when there is a network service crash +// "[85296:47750637:0127/131203.017395:ERROR:content/browser/network_service_instance_impl.cc:610] Network service crashed or was terminated, restarting service." +// we check the app URL for a while until it actually loads the initial URL +fn check_and_reload_if_blank(browser: cef::Browser, initial_url: String) { + if initial_url == "about:blank" { + return; + } + + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(1)); + + let start_time = std::time::Instant::now(); + let timeout = std::time::Duration::from_secs(5); + let check_interval = std::time::Duration::from_millis(100); + + while start_time.elapsed() < timeout { + if let Some(frame) = browser.main_frame() { + let url = frame.url(); + let current_url = cef::CefString::from(&url).to_string(); + if current_url.is_empty() || current_url == "about:blank" { + frame.load_url(Some(&cef::CefString::from(initial_url.as_str()))); + // Continue checking in case it loads about:blank again + } else { + // URL has changed to something else (not about:blank), we can stop checking + return; + } + } + std::thread::sleep(check_interval); + } + }); +} + +fn webview_bounds_ratio( + window: &cef::Window, + webview_bounds: Option, + browser: &CefWebview, +) -> crate::WebviewBounds { + #[cfg(not(windows))] + let window_size = { + let window_bounds = window.bounds(); + LogicalSize::new(window_bounds.width as u32, window_bounds.height as u32) + }; + + // On Windows, CEF's window bounds is the outer size not the inner size. + #[cfg(windows)] + let window_size = crate::utils::windows::inner_size(window.window_handle()); + + let ob = webview_bounds.unwrap_or_else(|| browser.bounds()); + + crate::WebviewBounds { + x_rate: ob.x as f32 / window_size.width as f32, + y_rate: ob.y as f32 / window_size.height as f32, + width_rate: ob.width as f32 / window_size.width as f32, + height_rate: ob.height as f32 / window_size.height as f32, + } +} + +fn browser_settings_from_webview_attributes( + webview_attributes: &WebviewAttributes, +) -> BrowserSettings { + BrowserSettings { + javascript: State::from(if webview_attributes.javascript_disabled { + sys::cef_state_t::STATE_DISABLED + } else { + sys::cef_state_t::STATE_ENABLED + }), + javascript_access_clipboard: State::from(if webview_attributes.clipboard { + sys::cef_state_t::STATE_ENABLED + } else { + sys::cef_state_t::STATE_DISABLED + }), + background_color: webview_attributes + .background_color + .map(color_to_cef_argb) + .unwrap_or(0), + ..Default::default() + } +} + +fn request_context_from_webview_attributes( + context: &Context, + webview_attributes: &WebviewAttributes, + custom_schemes: &[String], + custom_protocol_scheme: &str, + _initialization_scripts: &[CefInitScript], +) -> Option { + let global_context = + request_context_get_global_context().expect("Failed to get global request context"); + + let cache_path: CefStringUtf16 = if webview_attributes.incognito { + CefStringUtf16::from("") + } else if let Some(_data_directory) = &webview_attributes.data_directory { + // TODO: setting a custom data directory must be a child of the root data directory, but it returns None on browser_view_create + eprintln!("data directory is not yet implemented"); + (&global_context.cache_path()).into() + // CefStringUtf16::from(data_directory.to_string_lossy().as_ref()) + } else { + (&global_context.cache_path()).into() + }; + + let request_context_settings = RequestContextSettings { + cache_path, + ..Default::default() + }; + + let request_context = request_context_create_context( + Some(&request_context_settings), + Option::<&mut RequestContextHandler>::None, + ); + if let Some(request_context) = &request_context { + for custom_scheme in custom_schemes { + request_context.register_scheme_handler_factory( + Some(&custom_protocol_scheme.into()), + Some(&format!("{custom_scheme}.localhost").as_str().into()), + Some(&mut request_handler::UriSchemeHandlerFactory::new( + context.scheme_handler_registry.clone(), + custom_scheme.clone(), + )), + ); + } + } + + request_context +} + +#[cfg(target_os = "macos")] +fn apply_titlebar_style(window: &cef::Window, style: TitleBarStyle, hidden_title: bool) { + use objc2::rc::Retained; + use objc2_app_kit::NSWindowTitleVisibility; + use objc2_app_kit::{NSView, NSWindowStyleMask}; + + let content_view = unsafe { Retained::::retain(window.window_handle() as _) }; + let Some(content_view) = content_view else { + return; + }; + + let Some(ns_window) = content_view.window() else { + return; + }; + + let mut mask = ns_window.styleMask(); + + match style { + TitleBarStyle::Visible => { + mask &= !NSWindowStyleMask::FullSizeContentView; + ns_window.setTitlebarAppearsTransparent(false); + ns_window.setStyleMask(mask); + } + TitleBarStyle::Transparent => { + ns_window.setTitlebarAppearsTransparent(true); + mask &= !NSWindowStyleMask::FullSizeContentView; + ns_window.setStyleMask(mask); + } + TitleBarStyle::Overlay => { + ns_window.setTitlebarAppearsTransparent(true); + mask |= NSWindowStyleMask::FullSizeContentView; + ns_window.setStyleMask(mask); + } + unknown => { + eprintln!("unknown title bar style applied: {unknown}"); + } + } + + if hidden_title { + ns_window.setTitleVisibility(NSWindowTitleVisibility::Hidden); + } +} + +/// On macOS, if the window content view is CEF's default `BridgedContentView`, +/// and does not have the expected subviews, replace it with a generic `NSView` +/// to avoid interactivity issues. +/// +/// Returns the new content view pointer, or the original window handle if no replacement was made. +/// +/// Subsequent calls to this function are no-ops, since the content view has already +/// been replaced and is no longer a BridgedContentView. +/// +/// SAFETY: Only call this function for Windows that are intended to host multiple webviews. +#[cfg(target_os = "macos")] +pub(crate) fn ensure_valid_content_view( + window_handle: *mut std::ffi::c_void, +) -> *mut std::ffi::c_void { + use objc2::rc::Retained; + use objc2::{MainThreadMarker, MainThreadOnly}; + use objc2_app_kit::NSView; + + let nsview = unsafe { Retained::::retain(window_handle as _) }; + let nsview = nsview.expect("NSView is null"); + + let class = nsview.class().name().to_string_lossy(); + let subviews = unsafe { nsview.subviews() }; + + // Filter subviews to only those that are expected in a valid CEF content view, + // which can only happen if a WebviewKind::WindowContent webview + // has been created in it using CEF's window.add_child_view API. + fn is_cef_view(subview: &Retained) -> bool { + let class = subview.class().name().to_string_lossy(); + class == "ViewsCompositorSuperview" || class == "WebContentsViewCocoa" + } + + // If it's a BridgedContentView without the expected subviews, + // replace it with a generic NSView to avoid interactivity issues. + if class == "BridgedContentView" && subviews.iter().filter(is_cef_view).count() != 2 { + let mtm = MainThreadMarker::new().expect("Not on main thread"); + + // Create a new generic NSView + let generic_nsview = NSView::alloc(mtm); + let generic_nsview = unsafe { NSView::init(generic_nsview) }; + + // Re-add subviews to the new generic NSView (excluding CEF's views) + for subview in subviews.iter().filter(|v| !is_cef_view(v)) { + unsafe { subview.removeFromSuperview() }; + unsafe { generic_nsview.addSubview(&subview) }; + } + + // Set the new generic NSView as the content view of the window + let nswindow = nsview.window().expect("NSWindow is null"); + nswindow.setContentView(Some(&generic_nsview)); + + // Return the new content view pointer + return Retained::into_raw(generic_nsview) as *mut std::ffi::c_void; + } + + // No replacement needed; return the original handle + window_handle +} + +#[cfg(target_os = "macos")] +fn apply_traffic_light_position(window: *mut std::ffi::c_void, position: &Position) { + use objc2::msg_send; + use objc2::rc::Retained; + use objc2_app_kit::{NSView, NSWindowButton}; + + let nsview = unsafe { Retained::::retain(window as _) }; + let Some(nsview) = nsview else { + return; + }; + + let Some(nswindow) = nsview.window() else { + return; + }; + + let Some(close) = nswindow.standardWindowButton(NSWindowButton::CloseButton) else { + return; + }; + let Some(miniaturize) = nswindow.standardWindowButton(NSWindowButton::MiniaturizeButton) else { + return; + }; + let Some(zoom) = nswindow.standardWindowButton(NSWindowButton::ZoomButton) else { + return; + }; + + let pos = position.to_logical::(nswindow.backingScaleFactor()); + let (x, y) = (pos.x, pos.y); + + let title_bar_container_view = unsafe { close.superview().unwrap().superview().unwrap() }; + + let close_rect = NSView::frame(&close); + let title_bar_frame_height = close_rect.size.height + y; + let mut title_bar_rect = NSView::frame(&title_bar_container_view); + title_bar_rect.size.height = title_bar_frame_height; + title_bar_rect.origin.y = nswindow.frame().size.height - title_bar_frame_height; + let _: () = unsafe { msg_send![&title_bar_container_view, setFrame: title_bar_rect] }; + + let window_buttons = vec![close, miniaturize.clone(), zoom]; + let space_between = NSView::frame(&miniaturize).origin.x - close_rect.origin.x; + + for (i, button) in window_buttons.into_iter().enumerate() { + let mut rect = NSView::frame(&button); + rect.origin.x = x + (i as f64 * space_between); + unsafe { button.setFrameOrigin(rect.origin) }; + } +} + +#[cfg(target_os = "macos")] +pub fn set_application_visibility(visible: bool) { + use objc2::MainThreadMarker; + use objc2_app_kit::NSApp; + + let mtm = MainThreadMarker::new().expect("not on main thread"); + let app = NSApp(mtm); + + if visible { + unsafe { app.unhide(None) }; + } else { + app.hide(None); + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/cookie.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/cookie.rs new file mode 100644 index 0000000..cf7ba74 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/cookie.rs @@ -0,0 +1,82 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use cef::{rc::*, *}; +use std::sync::{Arc, Mutex, mpsc::Sender}; + +cef::wrap_cookie_visitor! { + pub struct CollectUrlCookiesVisitor { + pub tx: Sender>>>, + pub collected: Arc>>>, + } + + impl CookieVisitor { + fn visit( + &self, + cookie: Option<&cef::Cookie>, + count: ::std::os::raw::c_int, + total: ::std::os::raw::c_int, + _delete_cookie: Option<&mut ::std::os::raw::c_int>, + ) -> ::std::os::raw::c_int { + if let Some(c) = cookie { + let name = c.name.to_string(); + let value = c.value.to_string(); + let domain = c.domain.to_string(); + let path = c.path.to_string(); + + let mut builder = tauri_runtime::Cookie::build((name, value)); + if !domain.is_empty() { builder = builder.domain(domain); } + if !path.is_empty() { builder = builder.path(path); } + if c.secure == 1 { builder = builder.secure(true); } + if c.httponly == 1 { builder = builder.http_only(true); } + let ck = builder.build(); + + self.collected.lock().unwrap().push(ck.into_owned()); + } + + if (count + 1) >= total { + let _ = self.tx.send(Ok(self.collected.lock().unwrap().clone())); + } + 1 + } + } +} + +cef::wrap_cookie_visitor! { + pub struct CollectAllCookiesVisitor { + pub tx: Sender>>>, + pub collected: Arc>>>, + } + + impl CookieVisitor { + fn visit( + &self, + cookie: Option<&cef::Cookie>, + count: ::std::os::raw::c_int, + total: ::std::os::raw::c_int, + _delete_cookie: Option<&mut ::std::os::raw::c_int>, + ) -> ::std::os::raw::c_int { + if let Some(c) = cookie { + let name = c.name.to_string(); + let value = c.value.to_string(); + let domain = c.domain.to_string(); + let path = c.path.to_string(); + + let mut builder = tauri_runtime::Cookie::build((name, value)); + if !domain.is_empty() { builder = builder.domain(domain); } + if !path.is_empty() { builder = builder.path(path); } + if c.secure == 1 { builder = builder.secure(true); } + if c.httponly == 1 { builder = builder.http_only(true); } + let ck = builder.build(); + + self.collected.lock().unwrap().push(ck.into_owned()); + } + + if (count + 1) >= total { + let _ = self.tx.send(Ok(self.collected.lock().unwrap().clone())); + } + 1 + } + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/drag_window.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/drag_window.rs new file mode 100644 index 0000000..a394544 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/drag_window.rs @@ -0,0 +1,72 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(windows)] +pub mod windows { + use cef::*; + use windows::Win32::Foundation::*; + use windows::Win32::UI::WindowsAndMessaging::*; + use windows::core::{PCWSTR, w}; + + /// Same as [WNDPROC] but without the Option wrapper. + type WindowProc = unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> LRESULT; + + const ORIGINAL_WND_PROP: PCWSTR = w!("TAURI_CEF_ORIGINAL_WND_PROC"); + + /// Subclasses the given window to handle draggable regions + /// by replacing its window procedure with `root_window_proc` + /// and storing the original procedure as a property to be called later. + pub fn subclass_window_for_dragging(window: &mut cef::Window) { + let hwnd = window.window_handle(); + let hwnd = HWND(hwnd.0 as _); + subclass_window(hwnd, root_window_proc); + } + + /// Subclasses a window by replacing its window procedure with the given `proc` + /// and storing the original procedure as a property for later use. + fn subclass_window(hwnd: HWND, proc: WindowProc) { + // If already subclassed, return early + let orginial_wnd_proc = unsafe { GetPropW(hwnd, ORIGINAL_WND_PROP) }; + if !orginial_wnd_proc.is_invalid() { + return; + } + + // Reset last error + unsafe { SetLastError(ERROR_SUCCESS) }; + + // Set the new window procedure and get the orginal one + let original_wnd_proc = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, proc as isize) }; + if original_wnd_proc == 0 && unsafe { GetLastError() } != ERROR_SUCCESS { + return; + } + + unsafe { + // Store the original window proc as a property for later use + let _ = SetPropW( + hwnd, + ORIGINAL_WND_PROP, + Some(HANDLE(original_wnd_proc as _)), + ); + } + } + + /// The root window procedure to handle WM_NCLBUTTONDOWN + /// by calling DefWindowProcW directly to allow dragging + /// and forwarding other messages to the original CEF window procedure. + unsafe extern "system" fn root_window_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + if msg == WM_NCLBUTTONDOWN { + return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; + } + + // For other messages, call the original CEF window procedure + let original_wnd_proc = GetPropW(hwnd, ORIGINAL_WND_PROP); + let original_wnd_proc = std::mem::transmute::<_, WindowProc>(original_wnd_proc.0); + CallWindowProcW(Some(original_wnd_proc), hwnd, msg, wparam, lparam) + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/request_handler.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/request_handler.rs new file mode 100644 index 0000000..1072644 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/request_handler.rs @@ -0,0 +1,432 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + borrow::Cow, + io::{Cursor, Read}, + sync::Arc, +}; + +use cef::{rc::*, *}; +use dioxus_debug_cell::RefCell; +use html5ever::{LocalName, interface::QualName, namespace_url, ns}; +use http::{ + HeaderMap, HeaderName, HeaderValue, + header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE}, +}; +use kuchiki::NodeRef; +use tauri_runtime::webview::UriSchemeProtocolHandler; +use tauri_utils::{ + config::{Csp, CspDirectiveSources}, + html::{parse as parse_html, serialize_node}, +}; +use url::Url; + +use super::CefInitScript; + +type HttpResponse = Arc>>>>>; + +fn csp_inject_initialization_scripts_hashes( + existing_csp: String, + initialization_scripts: &[CefInitScript], +) -> String { + if initialization_scripts.is_empty() { + return existing_csp; + } + + // For custom schemes, include ALL script hashes (we inject all scripts into HTML) + // This matches the HTML injection behavior in inject_scripts_into_html_body + let script_hashes: Vec = initialization_scripts + .iter() + .map(|s| s.hash.clone()) + .collect(); + + if script_hashes.is_empty() { + return existing_csp; + } + + // Parse CSP using tauri-utils + let mut csp_map: std::collections::HashMap = + Csp::Policy(existing_csp.to_string()).into(); + + // Update or create script-src directive with script hashes + let script_src = csp_map + .entry("script-src".to_string()) + .or_insert_with(|| CspDirectiveSources::List(vec!["'self'".to_string()])); + + // Extend with script hashes + script_src.extend(script_hashes); + + // Convert back to CSP string + Csp::DirectiveMap(csp_map).to_string() +} + +/// Helper function to inject initialization scripts into HTML body +fn inject_scripts_into_html_body( + body: &[u8], + initialization_scripts: &[CefInitScript], +) -> Option> { + // Check if body is valid UTF-8 HTML + let Ok(body_str) = std::str::from_utf8(body) else { + return None; + }; + + // Parse HTML and inject scripts + let document = parse_html(body_str.to_string()); + + let head = if let Ok(ref head_node) = document.select_first("head") { + head_node.as_node().clone() + } else { + let head_node = NodeRef::new_element( + QualName::new(None, ns!(html), LocalName::from("head")), + None, + ); + document.prepend(head_node.clone()); + head_node + }; + + // Inject initialization scripts (for custom schemes, inject all scripts) + for init_script in initialization_scripts.iter().rev() { + let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None); + script_el.append(NodeRef::new_text(init_script.script.script.as_str())); + head.prepend(script_el); + } + + // Serialize the modified HTML + Some(serialize_node(&document)) +} + +wrap_resource_request_handler! { + pub struct WebResourceRequestHandler { + initialization_scripts: Arc>, + } + + impl ResourceRequestHandler { + + + fn on_before_resource_load( + &self, + _browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _request: Option<&mut Request>, + _callback: Option<&mut Callback>, + ) -> ReturnValue { + sys::cef_return_value_t::RV_CONTINUE.into() + } + } +} + +wrap_request_handler! { + pub struct WebRequestHandler { + initialization_scripts: Arc>, + navigation_handler: Option>, + } + + impl RequestHandler { + fn on_before_browse( + &self, + _browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + request: Option<&mut Request>, + _user_gesture: ::std::os::raw::c_int, + _is_redirect: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int { + let Some(frame) = frame else { + return 0; + }; + // we only fire main frame navigation events to match the behavior of the wry runtime + if frame.is_main() == 0 { + return 0; + } + let Some(handler) = &self.navigation_handler else { + return 0; + }; + let Some(request) = request else { + return 0; + }; + + let url_str = CefString::from(&request.url()).to_string(); + let Ok(url) = url::Url::parse(&url_str) else { + return 0; + }; + let should_navigate = handler(&url); + if should_navigate { + 0 + } else { + 1 + } + } + + fn resource_request_handler( + &self, + _browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _request: Option<&mut Request>, + _is_navigation: ::std::os::raw::c_int, + _is_download: ::std::os::raw::c_int, + _request_initiator: Option<&CefString>, + _disable_default_handling: Option<&mut ::std::os::raw::c_int>, + ) -> Option { + Some(WebResourceRequestHandler::new( + self.initialization_scripts.clone(), + )) + } + } +} + +wrap_resource_handler! { + pub struct WebResourceHandler { + webview_label: String, + handler: Arc>, + initialization_scripts: Arc>, + // we clone response to send it to the handler thread + response: HttpResponse, + } + + impl ResourceHandler { + fn process_request( + &self, + request: Option<&mut Request>, + callback: Option<&mut Callback>, + ) -> ::std::os::raw::c_int { + let Some(request) = request else { return 0 }; + let Some(callback) = callback else { return 0 }; + + let url = CefString::from(&request.url()).to_string(); + let url = Url::parse(&url).ok(); + + if let Some(url) = url { + let callback = ThreadSafe(callback.clone()); + let response_store = ThreadSafe(self.response.clone()); + let initialization_scripts = self.initialization_scripts.clone(); + let responder = Box::new(move |response: http::Response>| { + // Check if this is an HTML response that needs script injection + let content_type = response.headers().get(CONTENT_TYPE); + let is_html = content_type + .and_then(|ct| ct.to_str().ok()) + .map(|ct| ct.to_lowercase().starts_with("text/html")) + .unwrap_or(false); + + let (parts, body) = response.into_parts(); + let body_bytes = body.into_owned(); + + let modified_body = if is_html { + inject_scripts_into_html_body(&body_bytes, &initialization_scripts) + .unwrap_or(body_bytes) + } else { + body_bytes + }; + + let mut response = http::Response::from_parts(parts, Cursor::new(modified_body)); + + + let csp = response + .headers_mut() + .get_mut(CONTENT_SECURITY_POLICY); + + if let Some(csp) = csp { + let csp_string = csp.to_str().unwrap().to_string(); + let new_csp = csp_inject_initialization_scripts_hashes( + csp_string, + &initialization_scripts, + ); + *csp = HeaderValue::from_str(&new_csp).unwrap(); + } + + + response_store.into_owned().borrow_mut().replace(response); + + let callback = callback.into_owned(); + callback.cont(); + }); + + let label = self.webview_label.clone(); + let handler = self.handler.clone(); + + let data = read_request_body(request); + let headers = get_request_headers(request); + let method_str = CefString::from(&request.method()).to_string(); + let method = http::Method::from_bytes(method_str.as_bytes()) + .unwrap_or(http::Method::GET); + + std::thread::spawn(move || { + let mut http_request = http::Request::builder().method(method).uri(url.as_str()).body(data).unwrap(); + *http_request.headers_mut() = headers; + // handler is Arc>, so we need to dereference to call it + (**handler)(&label, http_request, responder); + }); + 1 + } else { + 0 + } + } + + fn read( + &self, + data_out: *mut u8, + bytes_to_read: ::std::os::raw::c_int, + bytes_read: Option<&mut ::std::os::raw::c_int>, + _callback: Option<&mut ResourceReadCallback>, + ) -> ::std::os::raw::c_int { + let Ok(bytes_to_read) = usize::try_from(bytes_to_read) else { + return 0; + }; + let data_out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read) }; + let count = self.response.borrow_mut().as_mut().and_then(|response| response.body_mut().read(data_out).ok()).unwrap_or(0); + if let Some(bytes_read) = bytes_read { + let Ok(count) = count.try_into() else { + return 0; + }; + *bytes_read = count; + if count > 0 { + return 1; + } + } + 0 + } + + fn response_headers( + &self, + response: Option<&mut Response>, + response_length: Option<&mut i64>, + redirect_url: Option<&mut CefString>, + ) { + let (Some(response), Some(response_data)) = (response, &*self.response.borrow()) else { return }; + + response.set_status(response_data.status().as_u16() as i32); + let mut content_type = None; + + // First pass: collect CSP header and set other headers + for (name, value) in response_data.headers() { + let Ok(value) = value.to_str() else { continue; }; + + response.set_header_by_name(Some(&name.as_str().into()), Some(&value.into()), 0); + + if name == CONTENT_TYPE { + content_type.replace(value.to_string()); + } + } + + response.set_header_by_name( + Some(&"Cache-Control".into()), + Some(&"no-store".into()), + 1, + ); + + let mime_type = content_type + .as_ref() + .and_then(|t| t.split(';').next()) + .map(str::trim) + .unwrap_or("text/plain"); + response.set_mime_type(Some(&mime_type.into())); + + if let Some(length) = response_length { *length = -1; } + + if let Some(redirect_url) = redirect_url { + let _ = std::mem::take(redirect_url); + } + } + } +} + +wrap_scheme_handler_factory! { + pub struct UriSchemeHandlerFactory { + registry: super::SchemeHandlerRegistry, + scheme: String, + } + + impl SchemeHandlerFactory { + fn create( + &self, + browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _scheme_name: Option<&CefString>, + _request: Option<&mut Request>, + ) -> Option { + let browser = browser?; + let id = browser.identifier(); + + // get handler from our regsitry based on browser ID and scheme + let (webview_label, handler, initialization_scripts) = self + .registry + .lock() + .unwrap() + .get(&(id, self.scheme.clone())) + .cloned()?; + + Some(WebResourceHandler::new(webview_label, handler, initialization_scripts, Arc::new(RefCell::new(None)))) + } + } +} + +struct ThreadSafe(T); + +impl ThreadSafe { + fn into_owned(self) -> T { + self.0 + } +} + +unsafe impl Send for ThreadSafe {} +unsafe impl Sync for ThreadSafe {} + +fn read_request_body(request: &mut Request) -> Vec { + let mut body = Vec::new(); + + if let Some(post_data) = request.post_data() { + let mut elements = vec![None; post_data.element_count()]; + post_data.elements(Some(&mut elements)); + for element in elements.into_iter().flatten() { + match element.get_type().as_ref() { + sys::cef_postdataelement_type_t::PDE_TYPE_BYTES => { + let size = element.bytes_count(); + if size > 0 { + let mut buf = vec![0u8; size]; + // Copy bytes into our buffer + let copied = element.bytes(size, buf.as_mut_ptr()); + // Safety: CEF promises it wrote `copied` bytes into buf + unsafe { + buf.set_len(copied); + } + body.extend(buf); + } + } + sys::cef_postdataelement_type_t::PDE_TYPE_FILE => { + // Read file from disk + let file_path = CefString::from(&element.file()).to_string(); + if let Ok(mut file) = std::fs::File::open(&file_path) { + use std::io::Read; + let mut buf = Vec::new(); + if file.read_to_end(&mut buf).is_ok() { + body.extend(buf); + } + } + } + _ => {} + } + } + } + + body +} + +fn get_request_headers(request: &mut Request) -> HeaderMap { + let mut headers = HeaderMap::new(); + + let mut map = CefStringMultimap::new(); + + request.header_map(Some(&mut map)); + + // Iterate through all entries + for (name, value) in map { + for v in value { + headers.append( + HeaderName::from_bytes(name.as_bytes()).unwrap(), + HeaderValue::from_str(&v).unwrap(), + ); + } + } + + headers +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview.rs new file mode 100644 index 0000000..f82d922 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview.rs @@ -0,0 +1,110 @@ +use cef::*; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(windows)] +mod windows; + +#[cfg(target_os = "linux")] +mod linux; + +#[derive(Clone)] +pub enum CefWebview { + BrowserView(cef::BrowserView), + Browser(cef::Browser), +} + +impl CefWebview { + pub fn is_browser(&self) -> bool { + matches!(self, CefWebview::Browser(_)) + } + + pub fn browser(&self) -> Option { + match self { + CefWebview::BrowserView(view) => view.browser(), + CefWebview::Browser(browser) => Some(browser.clone()), + } + } + + pub fn browser_id(&self) -> i32 { + match self { + CefWebview::BrowserView(view) => view.browser().map_or(-1, |b| b.identifier()), + CefWebview::Browser(browser) => browser.identifier(), + } + } + + pub fn set_background_color(&self, color: Option) { + if let CefWebview::BrowserView(view) = self { + let window = view.window(); + let color = color.or_else(|| { + window.map(|w| w.theme_color(ColorId::COLOR_PRIMARY_BACKGROUND.get_raw() as _)) + }); + + if let Some(color) = color { + view.set_background_color(color); + } + } + } + + pub fn bounds(&self) -> cef::Rect { + match self { + CefWebview::BrowserView(view) => view.bounds(), + CefWebview::Browser(browser) => browser.bounds(), + } + } + + pub fn set_bounds(&self, rect: Option<&cef::Rect>) { + match self { + CefWebview::BrowserView(view) => view.set_bounds(rect), + CefWebview::Browser(browser) => browser.set_bounds(rect), + } + } + + pub fn scale_factor(&self) -> f64 { + match self { + CefWebview::BrowserView(view) => view + .window() + .and_then(|w| w.display()) + .map_or(1.0, |d| d.device_scale_factor() as f64), + CefWebview::Browser(browser) => browser.scale_factor(), + } + } + + pub fn set_visible(&self, visible: i32) { + match self { + CefWebview::BrowserView(view) => view.set_visible(visible), + CefWebview::Browser(browser) => browser.set_visible(visible), + } + } + + pub fn close(&self) { + match self { + CefWebview::BrowserView(_) => {} + CefWebview::Browser(browser) => browser.close(), + } + } + + pub fn set_parent(&self, parent: &cef::Window) { + match self { + CefWebview::BrowserView(_) => {} + CefWebview::Browser(browser) => browser.set_parent(parent), + } + } +} + +trait CefBrowserExt { + fn bounds(&self) -> cef::Rect; + fn set_bounds(&self, rect: Option<&cef::Rect>); + fn scale_factor(&self) -> f64; + fn set_visible(&self, visible: i32); + fn close(&self); + fn set_parent(&self, parent: &cef::Window); + + #[cfg(target_os = "macos")] + fn nsview(&self) -> Option>; + #[cfg(windows)] + fn hwnd(&self) -> Option<::windows::Win32::Foundation::HWND>; + #[cfg(target_os = "linux")] + fn xid(&self) -> Option; +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/linux.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/linux.rs new file mode 100644 index 0000000..90542e5 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/linux.rs @@ -0,0 +1,208 @@ +use cef::*; +use std::sync::LazyLock; +use x11_dl::xlib; + +use crate::cef_webview::CefBrowserExt; + +static X11: LazyLock> = LazyLock::new(|| xlib::Xlib::open().ok()); + +impl CefBrowserExt for cef::Browser { + fn xid(&self) -> Option { + let host = self.host()?; + let xid = host.window_handle(); + Some(xid) + } + + fn bounds(&self) -> cef::Rect { + let Some(xid) = self.xid() else { + return cef::Rect::default(); + }; + + let Some(xlib) = X11.as_ref() else { + return cef::Rect::default(); + }; + + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + if display.is_null() { + return cef::Rect::default(); + } + + let mut root: xlib::Window = 0; + let mut x: i32 = 0; + let mut y: i32 = 0; + let mut width: u32 = 0; + let mut height: u32 = 0; + let mut border_width: u32 = 0; + let mut depth: u32 = 0; + + let status = (xlib.XGetGeometry)( + display, + xid as xlib::Window, + &mut root, + &mut x, + &mut y, + &mut width, + &mut height, + &mut border_width, + &mut depth, + ); + + (xlib.XCloseDisplay)(display); + + if status == 0 { + return cef::Rect::default(); + } + + // XGetGeometry returns position relative to parent, which is what we need + cef::Rect { + x, + y, + width: width as i32, + height: height as i32, + } + } + } + + fn set_bounds(&self, rect: Option<&cef::Rect>) { + let Some(rect) = rect else { + return; + }; + + let Some(xid) = self.xid() else { + return; + }; + + let Some(xlib) = X11.as_ref() else { + return; + }; + + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + if display.is_null() { + return; + } + + (xlib.XMoveResizeWindow)( + display, + xid as xlib::Window, + rect.x, + rect.y, + rect.width as u32, + rect.height as u32, + ); + // Ensure window is mapped and raised after setting bounds + (xlib.XMapRaised)(display, xid as xlib::Window); + (xlib.XFlush)(display); + (xlib.XCloseDisplay)(display); + } + } + + fn scale_factor(&self) -> f64 { + // Get scale factor from primary display + // CEF on Linux doesn't provide direct access to the window's display, + // so we use the primary display as a reasonable default + cef::display_get_primary() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0) + } + + fn set_visible(&self, visible: i32) { + let Some(xid) = self.xid() else { + return; + }; + + let Some(xlib) = X11.as_ref() else { + return; + }; + + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + if display.is_null() { + return; + } + + if visible != 0 { + (xlib.XMapWindow)(display, xid as xlib::Window); + } else { + (xlib.XUnmapWindow)(display, xid as xlib::Window); + } + (xlib.XFlush)(display); + (xlib.XCloseDisplay)(display); + } + } + + fn close(&self) { + let Some(xid) = self.xid() else { + return; + }; + + let Some(xlib) = X11.as_ref() else { + return; + }; + + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + if display.is_null() { + return; + } + + (xlib.XDestroyWindow)(display, xid as xlib::Window); + (xlib.XFlush)(display); + (xlib.XCloseDisplay)(display); + } + } + + fn set_parent(&self, parent: &cef::Window) { + let Some(xid) = self.xid() else { + return; + }; + + let parent_xid = parent.window_handle(); + if parent_xid == 0 { + return; + } + + let Some(xlib) = X11.as_ref() else { + return; + }; + + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + if display.is_null() { + return; + } + + // Check if window exists before reparenting + let mut root: xlib::Window = 0; + let mut parent_window: xlib::Window = 0; + let mut children: *mut xlib::Window = std::ptr::null_mut(); + let mut nchildren: u32 = 0; + let status = (xlib.XQueryTree)( + display, + xid as xlib::Window, + &mut root, + &mut parent_window, + &mut children, + &mut nchildren, + ); + + if status != 0 && !children.is_null() { + (xlib.XFree)(children as *mut std::ffi::c_void); + } + + (xlib.XReparentWindow)( + display, + xid as xlib::Window, + parent_xid as xlib::Window, + 0, + 0, + ); + + // Ensure window is mapped and raised after reparenting + (xlib.XMapRaised)(display, xid as xlib::Window); + (xlib.XFlush)(display); + (xlib.XCloseDisplay)(display); + } + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/macos.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/macos.rs new file mode 100644 index 0000000..5323f52 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/macos.rs @@ -0,0 +1,99 @@ +use crate::cef_webview::CefBrowserExt; +use cef::*; +use objc2::rc::Retained; +use objc2_app_kit::NSView; +use objc2_foundation::{NSPoint, NSRect, NSSize}; + +impl CefBrowserExt for cef::Browser { + fn nsview(&self) -> Option> { + let host = self.host()?; + let nsview = host.window_handle() as *mut NSView; + unsafe { Retained::::retain(nsview) } + } + + fn bounds(&self) -> cef::Rect { + let Some(nsview) = self.nsview() else { + return cef::Rect::default(); + }; + + let parent = unsafe { nsview.superview().unwrap() }; + let parent_frame = parent.frame(); + let webview_frame = nsview.frame(); + + cef::Rect { + x: webview_frame.origin.x as i32, + y: (parent_frame.size.height - webview_frame.origin.y - webview_frame.size.height) as i32, + width: webview_frame.size.width as i32, + height: webview_frame.size.height as i32, + } + } + + fn set_bounds(&self, rect: Option<&cef::Rect>) { + let Some(rect) = rect else { + return; + }; + + let Some(nsview) = self.nsview() else { + return; + }; + + let parent = unsafe { nsview.superview().unwrap() }; + let parent_frame = parent.frame(); + + let origin = NSPoint { + x: rect.x as f64, + y: (parent_frame.size.height - (rect.y as f64 + rect.height as f64)), + }; + + let size = NSSize { + width: rect.width as f64, + height: rect.height as f64, + }; + + unsafe { nsview.setFrame(NSRect { origin, size }) }; + } + + fn scale_factor(&self) -> f64 { + let Some(nsview) = self.nsview() else { + return 1.0; + }; + + let screen = nsview.window().and_then(|w| w.screen()); + screen.map(|s| s.backingScaleFactor()).unwrap_or(1.0) + } + + fn set_visible(&self, visible: i32) { + let Some(nsview) = self.nsview() else { + return; + }; + + if visible != 0 { + nsview.setHidden(false); + } else { + nsview.setHidden(true); + } + } + + fn close(&self) { + let Some(nsview) = self.nsview() else { + return; + }; + + unsafe { nsview.removeFromSuperview() }; + } + + fn set_parent(&self, parent: &cef::Window) { + crate::cef_impl::ensure_valid_content_view(parent.window_handle()); + + let Some(nsview) = self.nsview() else { + return; + }; + + let parent_nsview = parent.window_handle(); + let Some(parent_nsview) = (unsafe { Retained::::retain(parent_nsview as _) }) else { + return; + }; + + unsafe { parent_nsview.addSubview(&nsview) }; + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/windows.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/windows.rs new file mode 100644 index 0000000..40ede5b --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/windows.rs @@ -0,0 +1,210 @@ +use cef::*; +use std::sync::LazyLock; + +use crate::cef_webview::CefBrowserExt; +use windows::{ + Win32::{ + Foundation::*, + Graphics::Gdi::*, + System::LibraryLoader::*, + UI::{HiDpi::*, WindowsAndMessaging::*}, + }, + core::{HRESULT, HSTRING, PCSTR}, +}; + +impl CefBrowserExt for cef::Browser { + fn hwnd(&self) -> Option { + let host = self.host()?; + let hwnd = host.window_handle(); + Some(HWND(hwnd.0 as _)) + } + + fn bounds(&self) -> cef::Rect { + let Some(hwnd) = self.hwnd() else { + return cef::Rect::default(); + }; + + let mut rect = RECT::default(); + let _ = unsafe { GetClientRect(hwnd, &mut rect) }; + + let position_point = &mut [POINT { + x: rect.left, + y: rect.top, + }]; + unsafe { MapWindowPoints(Some(hwnd), GetParent(hwnd).ok(), position_point) }; + + cef::Rect { + x: position_point[0].x, + y: position_point[0].y, + width: (rect.right - rect.left) as i32, + height: (rect.bottom - rect.top) as i32, + } + } + + fn set_bounds(&self, rect: Option<&cef::Rect>) { + let Some(rect) = rect else { + return; + }; + + let Some(hwnd) = self.hwnd() else { + return; + }; + + let _ = unsafe { + SetWindowPos( + hwnd, + None, + rect.x, + rect.y, + rect.width, + rect.height, + SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOZORDER, + ) + }; + } + + fn scale_factor(&self) -> f64 { + let Some(hwnd) = self.hwnd() else { + return 1.0; + }; + + let dpi = unsafe { hwnd_dpi(hwnd) }; + dpi_to_scale_factor(dpi) + } + + fn set_visible(&self, visible: i32) { + let Some(hwnd) = self.hwnd() else { + return; + }; + + if visible != 0 { + let _ = unsafe { ShowWindow(hwnd, SW_SHOW) }; + unsafe { ensure_render_target(hwnd) }; + } else { + let _ = unsafe { ShowWindow(hwnd, SW_HIDE) }; + } + } + + fn close(&self) { + let Some(hwnd) = self.hwnd() else { + return; + }; + + let _ = unsafe { DestroyWindow(hwnd) }; + } + + fn set_parent(&self, parent: &cef::Window) { + let Some(hwnd) = self.hwnd() else { + return; + }; + + let parent_hwnd = HWND(parent.window_handle().0 as _); + let _ = unsafe { SetParent(hwnd, Some(parent_hwnd)) }; + } +} + +/// Toggle visibility on Chrome_WidgetWin_1 children to force CEF to rebind +/// the compositor surface and emit a fresh paint after the child webview has +/// been hidden. +/// +/// Required when the child has been hidden, particularly when paired with CDP +/// `Page.setWebLifecycleState: frozen`, which pauses the renderer's compositor. +/// The Chrome_RenderWidgetHostHWND host window survives the freeze, but its +/// compositor surface is stale and won't repaint on its own when the parent is +/// shown again — leaving the widget visible with blank content. The hide+show +/// dance on Chrome_WidgetWin_1 forces Chromium to rebind the surface and +/// schedule a paint. +unsafe fn ensure_render_target(hwnd: HWND) { + use windows::core::PCWSTR; + + const CHROME_WIDGET: PCWSTR = windows::core::w!("Chrome_WidgetWin_1"); + + let mut child = unsafe { FindWindowExW(Some(hwnd), None, CHROME_WIDGET, PCWSTR::null()) }; + while let Ok(child_hwnd) = child { + let _ = unsafe { ShowWindow(child_hwnd, SW_HIDE) }; + let _ = unsafe { ShowWindow(child_hwnd, SW_SHOW) }; + child = unsafe { FindWindowExW(Some(hwnd), Some(child_hwnd), CHROME_WIDGET, PCWSTR::null()) }; + } +} + +fn get_function_impl(library: &str, function: &str) -> FARPROC { + let library = HSTRING::from(library); + assert_eq!(function.chars().last(), Some('\0')); + + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = unsafe { LoadLibraryW(&library) }.unwrap_or_default(); + if module.is_invalid() { + return None; + } + + unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) } +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + get_function_impl($lib, concat!(stringify!($func), '\0')) + .map(|f| unsafe { std::mem::transmute::<_, $func>(f) }) + }; +} + +pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32; +pub type GetDpiForMonitor = unsafe extern "system" fn( + hmonitor: HMONITOR, + dpi_type: MONITOR_DPI_TYPE, + dpi_x: *mut u32, + dpi_y: *mut u32, +) -> HRESULT; + +static GET_DPI_FOR_WINDOW: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow)); +static GET_DPI_FOR_MONITOR: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor)); + +pub const BASE_DPI: u32 = 96; +pub fn dpi_to_scale_factor(dpi: u32) -> f64 { + dpi as f64 / BASE_DPI as f64 +} + +#[allow(non_snake_case)] +pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { + if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { + // We are on Windows 10 Anniversary Update (1607) or later. + match GetDpiForWindow(hwnd) { + 0 => BASE_DPI, // 0 is returned if hwnd is invalid + #[allow(clippy::unnecessary_cast)] + dpi => dpi as u32, + } + } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if monitor.is_invalid() { + return BASE_DPI; + } + + let mut dpi_x = 0; + let mut dpi_y = 0; + #[allow(clippy::unnecessary_cast)] + if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK { + dpi_x as u32 + } else { + BASE_DPI + } + } else { + let hdc = GetDC(Some(hwnd)); + if hdc.is_invalid() { + return BASE_DPI; + } + + // We are on Vista or later. + if IsProcessDPIAware().as_bool() { + // If the process is DPI aware, then scaling must be handled by the application using + // this DPI value. + GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32 + } else { + // If the process is DPI unaware, then scaling is performed by the OS; we thus return + // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the + // application and the WM. + BASE_DPI + } + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/lib.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/lib.rs new file mode 100644 index 0000000..cf8734e --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/lib.rs @@ -0,0 +1,2541 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![allow(clippy::arc_with_non_send_sync)] +#![allow(clippy::too_many_arguments)] + +use cef::{CefString, ImplCommandLine, ImplTaskRunner}; +use tauri_runtime::{ + Cookie, DeviceEventFilter, EventLoopProxy, Icon, InitAttribute, ProgressBarState, Result, + RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch, + WebviewEventId, WindowDispatch, WindowEventId, + dpi::{PhysicalPosition, PhysicalSize, Position, Rect, Size}, + monitor::Monitor, + webview::{DetachedWebview, PendingWebview, WebviewAttributes}, + window::{ + CursorIcon, DetachedWindow, DetachedWindowWebview, PendingWindow, RawWindow, WebviewEvent, + WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, + }, +}; + +#[cfg(target_os = "macos")] +use tauri_utils::TitleBarStyle; +use tauri_utils::{ + Theme, + config::{Color, WindowConfig}, +}; +use url::Url; + +#[cfg(windows)] +use windows::Win32::Foundation::HWND; + +use dioxus_debug_cell::RefCell; +use std::{ + collections::HashMap, + fmt, + fs::create_dir_all, + sync::{ + Arc, Mutex, + atomic::AtomicBool, + mpsc::{Sender, channel}, + }, + thread::{self, ThreadId}, +}; + +#[cfg(target_os = "macos")] +use crate::application::AppDelegateEvent; +use crate::cef_webview::CefWebview; + +mod cef_impl; +mod cef_webview; +mod utils; + +type DevToolsProtocolHandler = dyn Fn(DevToolsProtocol) + Send + Sync; + +pub fn webview_version() -> Result { + Ok(format!( + "{}.{}.{}.{}", + cef_dll_sys::CHROME_VERSION_MAJOR, + cef_dll_sys::CHROME_VERSION_MINOR, + cef_dll_sys::CHROME_VERSION_PATCH, + cef_dll_sys::CHROME_VERSION_BUILD + )) +} + +#[macro_export] +macro_rules! getter { + ($self: ident, $rx: expr, $message: expr) => {{ + $crate::send_user_message(&$self.context, $message)?; + $rx + .recv() + .map_err(|_| tauri_runtime::Error::FailedToReceiveMessage) + }}; +} + +macro_rules! webview_getter { + ($self: ident, $message_variant: path) => {{ + let (tx, rx) = channel(); + getter!( + $self, + rx, + Message::Webview { + window_id: *$self.window_id.lock().unwrap(), + webview_id: $self.webview_id, + message: $message_variant(tx) + } + ) + }}; +} + +macro_rules! window_getter { + ($self: ident, $message_variant: path) => {{ + let (tx, rx) = channel(); + getter!( + $self, + rx, + Message::Window { + window_id: $self.window_id, + message: $message_variant(tx) + } + ) + }}; +} + +type AfterWindowCreation = Box; + +enum Message { + Task(Box), + CreateWindow { + window_id: WindowId, + webview_id: u32, + pending: Box>>, + after_window_creation: Option, + }, + CreateWebview { + window_id: WindowId, + webview_id: u32, + pending: Box>>, + }, + Window { + window_id: WindowId, + message: WindowMessage, + }, + Webview { + window_id: WindowId, + webview_id: u32, + message: WebviewMessage, + }, + RequestExit(i32), + UserEvent(T), + Noop, +} + +enum WindowMessage { + Close, + Destroy, + AddEventListener(WindowEventId, Box), + // Getters + ScaleFactor(Sender>), + InnerPosition(Sender>>), + OuterPosition(Sender>>), + InnerSize(Sender>>), + OuterSize(Sender>>), + IsFullscreen(Sender>), + IsMinimized(Sender>), + IsMaximized(Sender>), + IsFocused(Sender>), + IsDecorated(Sender>), + IsResizable(Sender>), + IsMaximizable(Sender>), + IsMinimizable(Sender>), + IsClosable(Sender>), + IsVisible(Sender>), + Title(Sender>), + CurrentMonitor(Sender>>), + PrimaryMonitor(Sender>>), + MonitorFromPoint(Sender>>, f64, f64), + AvailableMonitors(Sender>>), + Theme(Sender>), + IsEnabled(Sender>), + IsAlwaysOnTop(Sender>), + RawWindowHandle( + Sender< + std::result::Result, raw_window_handle::HandleError>, + >, + ), + // Setters + Center, + RequestUserAttention(Option), + SetEnabled(bool), + SetResizable(bool), + SetMaximizable(bool), + SetMinimizable(bool), + SetClosable(bool), + SetTitle(String), + Maximize, + Unmaximize, + Minimize, + Unminimize, + Show, + Hide, + SetDecorations(bool), + SetShadow(bool), + SetAlwaysOnBottom(bool), + SetAlwaysOnTop(bool), + SetVisibleOnAllWorkspaces(bool), + SetContentProtected(bool), + SetSize(Size), + SetMinSize(Option), + SetMaxSize(Option), + SetSizeConstraints(tauri_runtime::window::WindowSizeConstraints), + SetPosition(Position), + SetFullscreen(bool), + #[cfg(target_os = "macos")] + SetSimpleFullscreen(bool), + SetFocus, + SetFocusable(bool), + SetIcon(Icon<'static>), + SetSkipTaskbar(bool), + SetCursorGrab(bool), + SetCursorVisible(bool), + SetCursorIcon(CursorIcon), + SetCursorPosition(Position), + SetIgnoreCursorEvents(bool), + SetProgressBar(ProgressBarState), + SetBadgeCount(Option, Option), + SetBadgeLabel(Option), + SetOverlayIcon(Option>), + SetTitleBarStyle(tauri_utils::TitleBarStyle), + SetTrafficLightPosition(Position), + SetTheme(Option), + SetBackgroundColor(Option), + StartDragging, + StartResizeDragging(tauri_runtime::ResizeDirection), +} + +pub enum WebviewMessage { + AddEventListener(WebviewEventId, Box), + EvaluateScript(String), + EvaluateScriptWithCallback(String, Box), + CookiesForUrl(Url, Sender>>>), + Cookies(Sender>>>), + SetCookie(Cookie<'static>), + DeleteCookie(Cookie<'static>), + Navigate(Url), + Reload, + GoBack, + CanGoBack(Sender>), + GoForward, + CanGoForward(Sender>), + Print, + Close, + Show, + Hide, + SetPosition(Position), + SetSize(Size), + SetBounds(Rect), + SetFocus, + Reparent(WindowId, Sender>), + SetAutoResize(bool), + SetZoom(f64), + SetBackgroundColor(Option), + ClearAllBrowsingData, + // Getters + Url(Sender>), + Bounds(Sender>), + Position(Sender>>), + Size(Sender>>), + WithWebview(Box) + Send>), + // Devtools + #[cfg(any(debug_assertions, feature = "devtools"))] + OpenDevTools, + #[cfg(any(debug_assertions, feature = "devtools"))] + CloseDevTools, + #[cfg(any(debug_assertions, feature = "devtools"))] + IsDevToolsOpen(Sender), + SendDevToolsMessage(Vec, Sender>), + OnDevToolsProtocol(Arc, Sender>), +} + +/// A DevTools protocol message delivered to [`on_dev_tools_protocol`](CefWebviewDispatcher::on_dev_tools_protocol) callbacks. +#[derive(Debug, Clone)] +pub enum DevToolsProtocol { + /// Raw UTF-8 encoded JSON message (method result or event). + Message(Vec), + /// DevTools protocol event with method name and params. + Event { method: String, params: Vec }, + /// Result of a DevTools method call. + MethodResult { + message_id: i32, + success: bool, + result: Vec, + }, +} + +impl Clone for Message { + fn clone(&self) -> Self { + match self { + Self::UserEvent(t) => Self::UserEvent(t.clone()), + _ => unimplemented!(), + } + } +} + +#[derive(Clone)] +pub(crate) struct AppWebview { + pub webview_id: u32, + #[allow(dead_code)] + pub label: String, + pub inner: CefWebview, + // browser_view.browser is null on the scheme handler factory, + // so we need to use the browser_id to identify the browser + pub browser_id: Arc>, + pub bounds: Arc>>, + #[allow(unused)] + pub devtools_enabled: bool, + pub uri_scheme_protocols: + Arc>>>, + #[allow(dead_code)] + pub initialization_scripts: Arc>, + pub devtools_protocol_handlers: Arc>>>, + /// Keeps the DevTools message observer registered. Dropping this unregisters the observer. + #[allow(dead_code)] + pub devtools_observer_registration: Arc>>, + pub webview_attributes: Arc>, +} + +#[derive(Debug, Clone)] +pub struct WebviewBounds { + pub x_rate: f32, + pub y_rate: f32, + pub width_rate: f32, + pub height_rate: f32, +} + +pub type WindowEventHandler = Box; +pub type WindowEventListeners = Arc>>; +pub type WebviewEventHandler = Box; +pub type WebviewEventListeners = + Arc>>>>>; + +pub(crate) enum AppWindowKind { + Window(cef::Window), + BrowserWindow, +} + +pub(crate) struct AppWindow { + pub label: String, + pub window: AppWindowKind, + pub force_close: Arc, + pub attributes: Arc>, + pub webviews: Vec, + pub window_event_listeners: WindowEventListeners, + pub webview_event_listeners: WebviewEventListeners, +} + +impl AppWindow { + fn window(&self) -> Option { + match &self.window { + AppWindowKind::Window(window) => Some(window.clone()), + AppWindowKind::BrowserWindow => None, + } + } +} + +#[derive(Clone)] +pub struct RuntimeContext { + main_thread_task_runner: cef::TaskRunner, + main_thread_id: ThreadId, + cef_context: cef_impl::Context, +} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for RuntimeContext {} + +// SAFETY: we ensure this type is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for RuntimeContext {} + +impl RuntimeContext { + fn post_message(&self, message: Message) -> Result<()> { + if thread::current().id() == self.main_thread_id && !cef_impl::is_in_event_callback() { + // On main thread and not inside a user callback, execute directly. + cef_impl::handle_message(&self.cef_context, message); + } else { + // Off main thread, or inside a user callback where synchronous execution + // could cause re-entrancy and deadlocks. Defer through the CEF TaskRunner. + self + .main_thread_task_runner + .post_task(Some(&mut cef_impl::SendMessageTask::new( + self.cef_context.clone(), + Arc::new(RefCell::new(message)), + ))); + } + Ok(()) + } + + fn create_window( + &self, + pending: PendingWindow>, + after_window_creation: Option, + ) -> Result>> { + let label = pending.label.clone(); + let context = self.clone(); + let window_id = self.cef_context.next_window_id(); + let (webview_id, use_https_scheme, devtools) = pending + .webview + .as_ref() + .map(|w| { + ( + Some(context.cef_context.next_webview_id()), + w.webview_attributes.use_https_scheme, + w.webview_attributes.devtools, + ) + }) + .unwrap_or((None, false, None)); + + self.post_message(Message::CreateWindow { + window_id, + webview_id: webview_id.unwrap_or_default(), + pending: Box::new(pending), + after_window_creation: after_window_creation.map(|f| Box::new(f) as AfterWindowCreation), + })?; + + let dispatcher = CefWindowDispatcher { + window_id, + context: self.clone(), + }; + + let detached_webview = webview_id.map(|id| { + let webview = DetachedWebview { + label: label.clone(), + dispatcher: CefWebviewDispatcher { + window_id: Arc::new(Mutex::new(window_id)), + webview_id: id, + context: self.clone(), + }, + }; + DetachedWindowWebview { + webview, + use_https_scheme, + devtools, + } + }); + + Ok(DetachedWindow { + id: window_id, + label, + dispatcher, + webview: detached_webview, + }) + } + + fn create_webview( + &self, + window_id: WindowId, + pending: PendingWebview>, + ) -> Result>> { + let label = pending.label.clone(); + let webview_id = self.cef_context.next_webview_id(); + + self.post_message(Message::CreateWebview { + window_id, + webview_id, + pending: Box::new(pending), + })?; + + let dispatcher = CefWebviewDispatcher { + window_id: Arc::new(Mutex::new(window_id)), + webview_id, + context: self.clone(), + }; + + Ok(DetachedWebview { label, dispatcher }) + } +} + +// Mirrors tauri-runtime-wry's send_user_message behavior: if we're already on the main +// thread, handle the message immediately; otherwise, post it to the main thread. +pub(crate) fn send_user_message( + context: &RuntimeContext, + message: Message, +) -> Result<()> { + if thread::current().id() == context.main_thread_id { + cef_impl::handle_message(&context.cef_context, message); + } else { + context + .main_thread_task_runner + .post_task(Some(&mut cef_impl::SendMessageTask::new( + context.cef_context.clone(), + Arc::new(RefCell::new(message)), + ))); + } + Ok(()) +} + +impl fmt::Debug for RuntimeContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RuntimeContext").finish() + } +} + +#[derive(Debug, Clone)] +pub struct CefRuntimeHandle { + context: RuntimeContext, +} + +impl RuntimeHandle for CefRuntimeHandle { + type Runtime = CefRuntime; + + fn create_proxy(&self) -> >::EventLoopProxy { + EventProxy { + context: self.context.clone(), + } + } + + #[cfg(target_os = "macos")] + fn set_activation_policy( + &self, + _activation_policy: tauri_runtime::ActivationPolicy, + ) -> Result<()> { + Ok(()) + } + + #[cfg(target_os = "macos")] + fn set_dock_visibility(&self, _visible: bool) -> Result<()> { + Ok(()) + } + + fn request_exit(&self, code: i32) -> Result<()> { + // Request exit by posting a task to quit the message loop + self.context.post_message(Message::RequestExit(code)) + } + + /// Create a new webview window. + fn create_window) + Send + 'static>( + &self, + pending: PendingWindow, + after_window_creation: Option, + ) -> Result> { + self.context.create_window(pending, after_window_creation) + } + + fn create_webview( + &self, + window_id: WindowId, + pending: PendingWebview, + ) -> Result> { + self.context.create_webview(window_id, pending) + } + + /// Run a task on the main thread. + fn run_on_main_thread(&self, f: F) -> Result<()> { + self.context.post_message(Message::Task(Box::new(f))) + } + + fn display_handle( + &self, + ) -> std::result::Result, raw_window_handle::HandleError> { + #[cfg(target_os = "linux")] + return Ok(unsafe { + raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Xlib( + raw_window_handle::XlibDisplayHandle::new(None, 0), + )) + }); + #[cfg(target_os = "macos")] + return Ok(unsafe { + raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::AppKit( + raw_window_handle::AppKitDisplayHandle::new(), + )) + }); + #[cfg(windows)] + return Ok(unsafe { + raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Windows( + raw_window_handle::WindowsDisplayHandle::new(), + )) + }); + #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))] + unimplemented!(); + } + + fn primary_monitor(&self) -> Option { + crate::cef_impl::get_primary_monitor() + } + + fn monitor_from_point(&self, x: f64, y: f64) -> Option { + crate::cef_impl::get_monitor_from_point(x, y) + } + + fn available_monitors(&self) -> Vec { + crate::cef_impl::get_available_monitors() + } + + fn set_theme(&self, _theme: Option) {} + + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + fn show(&self) -> Result<()> { + self.context.post_message(Message::Task(Box::new(|| { + cef_impl::set_application_visibility(true); + }))) + } + + /// Hides the application. + #[cfg(target_os = "macos")] + fn hide(&self) -> Result<()> { + self.context.post_message(Message::Task(Box::new(|| { + cef_impl::set_application_visibility(false); + }))) + } + + fn set_device_event_filter(&self, _filter: DeviceEventFilter) {} + + #[cfg(target_os = "android")] + fn find_class<'a>( + &self, + env: &mut jni::JNIEnv<'a>, + activity: &jni::objects::JObject<'_>, + name: impl Into, + ) -> std::result::Result, jni::errors::Error> { + todo!() + } + + #[cfg(target_os = "android")] + fn run_on_android_context(&self, f: F) + where + F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static, + { + todo!() + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn fetch_data_store_identifiers) + Send + 'static>( + &self, + _cb: F, + ) -> Result<()> { + todo!() + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn remove_data_store) + Send + 'static>( + &self, + _uuid: [u8; 16], + _cb: F, + ) -> Result<()> { + todo!() + } + + fn cursor_position(&self) -> Result> { + Ok(PhysicalPosition::new(0.0, 0.0)) + } +} + +#[derive(Debug, Clone)] +pub struct CefWebviewDispatcher { + window_id: Arc>, + webview_id: u32, + context: RuntimeContext, +} + +#[derive(Debug, Clone)] +pub struct CefWindowDispatcher { + window_id: WindowId, + context: RuntimeContext, +} + +#[derive(Debug, Clone)] +pub struct CefWindowBuilder { + title: Option, + position: Option, + inner_size: Option, + min_inner_size: Option, + max_inner_size: Option, + inner_size_constraints: Option, + center: bool, + prevent_overflow: Option, + resizable: Option, + maximizable: Option, + minimizable: Option, + closable: Option, + fullscreen: Option, + focused: Option, + focusable: Option, + maximized: Option, + visible: Option, + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + transparent: Option, + decorations: Option, + always_on_bottom: Option, + always_on_top: Option, + visible_on_all_workspaces: Option, + content_protected: Option, + skip_taskbar: Option, + shadow: Option, + theme: Option, + background_color: Option, + #[cfg(target_os = "macos")] + title_bar_style: Option, + #[cfg(target_os = "macos")] + traffic_light_position: Option, + #[cfg(target_os = "macos")] + hidden_title: Option, + #[cfg(target_os = "macos")] + tabbing_identifier: Option, + #[cfg(windows)] + window_classname: Option, + #[cfg(windows)] + owner: Option, + #[cfg(windows)] + parent: Option, + #[cfg(windows)] + drag_and_drop: Option, + has_icon: bool, + icon: Option>, + browser_window: bool, +} + +impl Default for CefWindowBuilder { + fn default() -> Self { + Self { + title: None, + position: None, + inner_size: None, + min_inner_size: None, + max_inner_size: None, + inner_size_constraints: None, + center: false, + prevent_overflow: None, + resizable: None, + maximizable: None, + minimizable: None, + closable: None, + fullscreen: None, + focused: Some(true), + focusable: None, + maximized: None, + visible: Some(true), + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + transparent: None, + decorations: Some(true), + always_on_bottom: None, + always_on_top: None, + visible_on_all_workspaces: None, + content_protected: None, + skip_taskbar: None, + shadow: None, + theme: None, + background_color: None, + #[cfg(target_os = "macos")] + title_bar_style: None, + #[cfg(target_os = "macos")] + traffic_light_position: None, + #[cfg(target_os = "macos")] + hidden_title: None, + #[cfg(target_os = "macos")] + tabbing_identifier: None, + #[cfg(windows)] + window_classname: None, + #[cfg(windows)] + owner: None, + #[cfg(windows)] + parent: None, + #[cfg(windows)] + drag_and_drop: None, + has_icon: false, + icon: None, + browser_window: false, + } + } +} + +impl CefWindowBuilder { + pub fn browser_window(mut self) -> Self { + self.browser_window = true; + self + } +} + +impl WindowBuilderBase for CefWindowBuilder {} + +impl WindowBuilder for CefWindowBuilder { + fn new() -> Self { + Self::default().title("Tauri App") + } + + fn with_config(config: &WindowConfig) -> Self { + let mut builder = Self::default(); + + builder = builder + .title(config.title.to_string()) + .inner_size(config.width, config.height) + .focused(config.focus) + .focusable(config.focusable) + .visible(config.visible) + .resizable(config.resizable) + .fullscreen(config.fullscreen) + .decorations(config.decorations) + .maximized(config.maximized) + .always_on_bottom(config.always_on_bottom) + .always_on_top(config.always_on_top) + .visible_on_all_workspaces(config.visible_on_all_workspaces) + .content_protected(config.content_protected) + .skip_taskbar(config.skip_taskbar) + .theme(config.theme) + .closable(config.closable) + .maximizable(config.maximizable) + .minimizable(config.minimizable) + .shadow(config.shadow); + + let mut constraints = tauri_runtime::window::WindowSizeConstraints::default(); + if let Some(min_width) = config.min_width { + constraints.min_width = Some(tauri_runtime::dpi::LogicalUnit::new(min_width).into()); + } + if let Some(min_height) = config.min_height { + constraints.min_height = Some(tauri_runtime::dpi::LogicalUnit::new(min_height).into()); + } + if let Some(max_width) = config.max_width { + constraints.max_width = Some(tauri_runtime::dpi::LogicalUnit::new(max_width).into()); + } + if let Some(max_height) = config.max_height { + constraints.max_height = Some(tauri_runtime::dpi::LogicalUnit::new(max_height).into()); + } + builder = builder.inner_size_constraints(constraints); + + if let Some(color) = config.background_color { + builder = builder.background_color(color); + } + + if let (Some(x), Some(y)) = (config.x, config.y) { + builder = builder.position(x, y); + } + + if config.center { + builder = builder.center(); + } + + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + { + builder = builder.transparent(config.transparent); + } + + #[cfg(target_os = "macos")] + { + builder = builder + .hidden_title(config.hidden_title) + .title_bar_style(config.title_bar_style); + if let Some(identifier) = &config.tabbing_identifier { + builder = builder.tabbing_identifier(identifier); + } + if let Some(position) = &config.traffic_light_position { + builder = builder.traffic_light_position(tauri_runtime::dpi::LogicalPosition::new( + position.x, position.y, + )); + } + } + + #[cfg(windows)] + { + if let Some(window_classname) = &config.window_classname { + builder = builder.window_classname(window_classname); + } + } + + builder + } + + fn center(mut self) -> Self { + self.center = true; + self + } + + fn position(mut self, x: f64, y: f64) -> Self { + self.position = Some(Position::Logical(tauri_runtime::dpi::LogicalPosition::new( + x, y, + ))); + self + } + + fn inner_size(mut self, width: f64, height: f64) -> Self { + self.inner_size = Some(Size::Logical(tauri_runtime::dpi::LogicalSize::new( + width, height, + ))); + self + } + + fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self { + self.min_inner_size = Some(Size::Logical(tauri_runtime::dpi::LogicalSize::new( + min_width, min_height, + ))); + self + } + + fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self { + self.max_inner_size = Some(Size::Logical(tauri_runtime::dpi::LogicalSize::new( + max_width, max_height, + ))); + self + } + + fn inner_size_constraints( + mut self, + constraints: tauri_runtime::window::WindowSizeConstraints, + ) -> Self { + self.inner_size_constraints = Some(constraints); + self + } + + fn prevent_overflow(mut self) -> Self { + self.prevent_overflow = Some(Size::Physical(PhysicalSize::new(0, 0))); + self + } + + fn prevent_overflow_with_margin(mut self, margin: Size) -> Self { + self.prevent_overflow = Some(margin); + self + } + + fn resizable(mut self, resizable: bool) -> Self { + self.resizable = Some(resizable); + self + } + + fn maximizable(mut self, maximizable: bool) -> Self { + self.maximizable = Some(maximizable); + self + } + + fn minimizable(mut self, minimizable: bool) -> Self { + self.minimizable = Some(minimizable); + self + } + + fn closable(mut self, closable: bool) -> Self { + self.closable = Some(closable); + self + } + + fn title>(mut self, title: S) -> Self { + self.title = Some(title.into()); + self + } + + fn fullscreen(mut self, fullscreen: bool) -> Self { + self.fullscreen = Some(fullscreen); + self + } + + fn focused(mut self, focused: bool) -> Self { + self.focused = Some(focused); + self + } + + fn focusable(mut self, focusable: bool) -> Self { + self.focusable = Some(focusable); + self + } + + fn maximized(mut self, maximized: bool) -> Self { + self.maximized = Some(maximized); + self + } + + fn visible(mut self, visible: bool) -> Self { + self.visible = Some(visible); + self + } + + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + fn transparent(self, transparent: bool) -> Self { + let mut s = self; + s.transparent = Some(transparent); + s + } + + fn decorations(mut self, decorations: bool) -> Self { + self.decorations = Some(decorations); + self + } + + fn always_on_bottom(mut self, always_on_bottom: bool) -> Self { + self.always_on_bottom = Some(always_on_bottom); + self + } + + fn always_on_top(mut self, always_on_top: bool) -> Self { + self.always_on_top = Some(always_on_top); + self + } + + fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self { + self.visible_on_all_workspaces = Some(visible_on_all_workspaces); + self + } + + fn content_protected(mut self, protected: bool) -> Self { + self.content_protected = Some(protected); + self + } + + fn icon(mut self, icon: Icon<'_>) -> Result { + self.has_icon = true; + self.icon.replace(icon.into_owned()); + Ok(self) + } + + fn skip_taskbar(mut self, skip: bool) -> Self { + self.skip_taskbar = Some(skip); + self + } + + fn window_classname>(self, classname: S) -> Self { + #[cfg(windows)] + { + let mut s = self; + s.window_classname = Some(classname.into()); + s + } + #[cfg(not(windows))] + { + let _classname = classname; + self + } + } + + fn shadow(mut self, enable: bool) -> Self { + self.shadow = Some(enable); + self + } + + #[cfg(windows)] + fn owner(mut self, owner: HWND) -> Self { + self.owner = Some(owner); + self + } + + #[cfg(windows)] + fn parent(mut self, parent: HWND) -> Self { + self.parent = Some(parent); + self + } + + #[cfg(target_os = "macos")] + fn parent(self, _parent: *mut std::ffi::c_void) -> Self { + self + } + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn transient_for(self, _parent: &impl gtk::glib::IsA) -> Self { + self + } + + #[cfg(windows)] + fn drag_and_drop(mut self, enabled: bool) -> Self { + self.drag_and_drop = Some(enabled); + self + } + + #[cfg(target_os = "macos")] + fn title_bar_style(mut self, style: TitleBarStyle) -> Self { + self.title_bar_style = Some(style); + self + } + + #[cfg(target_os = "macos")] + fn traffic_light_position>(mut self, position: P) -> Self { + self.traffic_light_position = Some(position.into()); + self + } + + #[cfg(target_os = "macos")] + fn hidden_title(mut self, hidden: bool) -> Self { + self.hidden_title = Some(hidden); + self + } + + #[cfg(target_os = "macos")] + fn tabbing_identifier(mut self, identifier: &str) -> Self { + self.tabbing_identifier = Some(identifier.into()); + self + } + + fn theme(mut self, theme: Option) -> Self { + self.theme = theme; + self + } + + fn has_icon(&self) -> bool { + self.has_icon + } + + fn get_theme(&self) -> Option { + self.theme + } + + fn background_color(self, color: tauri_utils::config::Color) -> Self { + let mut s = self; + s.background_color = Some(color); + s + } +} + +/// CEF-specific webview APIs. +impl CefWebviewDispatcher { + /// Send a message to the DevTools agent. The message should be a UTF-8 encoded JSON + /// string following the Chrome DevTools Protocol format. + pub fn send_dev_tools_message(&self, message: &[u8]) -> Result<()> { + let (tx, rx) = channel(); + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SendDevToolsMessage(message.to_vec(), tx), + })?; + rx.recv() + .map_err(|_| tauri_runtime::Error::FailedToReceiveMessage)? + } + + /// Register a callback to receive DevTools protocol messages. Messages include + /// both method results and events from the DevTools agent. + pub fn on_dev_tools_protocol( + &self, + f: F, + ) -> Result<()> { + let (tx, rx) = channel(); + let handler = + Arc::new(move |protocol: DevToolsProtocol| f(protocol)) as Arc; + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::OnDevToolsProtocol(handler, tx), + })?; + rx.recv() + .map_err(|_| tauri_runtime::Error::FailedToReceiveMessage)? + } +} + +impl WebviewDispatch for CefWebviewDispatcher { + type Runtime = CefRuntime; + + fn run_on_main_thread(&self, f: F) -> Result<()> { + self.context.post_message(Message::Task(Box::new(f))) + } + + fn on_webview_event( + &self, + f: F, + ) -> tauri_runtime::WebviewEventId { + let id = self.context.cef_context.next_webview_event_id(); + let _ = self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::AddEventListener(id, Box::new(f)), + }); + id + } + + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::WithWebview(Box::new(f)), + }) + } + + #[cfg(any(debug_assertions, feature = "devtools"))] + fn open_devtools(&self) { + let _ = self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::OpenDevTools, + }); + } + + #[cfg(any(debug_assertions, feature = "devtools"))] + fn close_devtools(&self) { + let _ = self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::CloseDevTools, + }); + } + + #[cfg(any(debug_assertions, feature = "devtools"))] + fn is_devtools_open(&self) -> Result { + webview_getter!(self, WebviewMessage::IsDevToolsOpen) + } + + fn set_zoom(&self, scale_factor: f64) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetZoom(scale_factor), + }) + } + + fn eval_script>(&self, script: S) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::EvaluateScript(script.into()), + }) + } + + fn eval_script_with_callback>( + &self, + script: S, + callback: impl Fn(String) + Send + 'static, + ) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::EvaluateScriptWithCallback(script.into(), Box::new(callback)), + }) + } + + fn url(&self) -> Result { + webview_getter!(self, WebviewMessage::Url)? + } + + fn bounds(&self) -> Result { + webview_getter!(self, WebviewMessage::Bounds)? + } + + fn position(&self) -> Result> { + webview_getter!(self, WebviewMessage::Position)? + } + + fn size(&self) -> Result> { + webview_getter!(self, WebviewMessage::Size)? + } + + fn navigate(&self, url: Url) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::Navigate(url), + }) + } + + fn reload(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::Reload, + }) + } + + fn go_back(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::GoBack, + }) + } + + fn can_go_back(&self) -> Result { + webview_getter!(self, WebviewMessage::CanGoBack)? + } + + fn go_forward(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::GoForward, + }) + } + + fn can_go_forward(&self) -> Result { + webview_getter!(self, WebviewMessage::CanGoForward)? + } + + fn print(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::Print, + }) + } + + fn close(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::Close, + }) + } + + fn set_bounds(&self, bounds: Rect) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetBounds(bounds), + }) + } + + fn set_size(&self, size: Size) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetSize(size), + }) + } + + fn set_position(&self, position: Position) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetPosition(position), + }) + } + + fn set_focus(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetFocus, + }) + } + + fn reparent(&self, window_id: WindowId) -> Result<()> { + let mut current_window_id = self.window_id.lock().unwrap(); + let (tx, rx) = channel(); + self.context.post_message(Message::Webview { + window_id: *current_window_id, + webview_id: self.webview_id, + message: WebviewMessage::Reparent(window_id, tx), + })?; + + rx.recv().unwrap()?; + + *current_window_id = window_id; + Ok(()) + } + + fn cookies_for_url(&self, url: Url) -> Result>> { + let current_window_id = self.window_id.lock().unwrap(); + let (tx, rx) = channel(); + self.context.post_message(Message::Webview { + window_id: *current_window_id, + webview_id: self.webview_id, + message: WebviewMessage::CookiesForUrl(url, tx), + })?; + + rx.recv().unwrap() + } + + fn cookies(&self) -> Result>> { + webview_getter!(self, WebviewMessage::Cookies)? + } + + fn set_cookie(&self, cookie: Cookie<'_>) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetCookie(cookie.into_owned()), + }) + } + + fn delete_cookie(&self, cookie: Cookie<'_>) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::DeleteCookie(cookie.into_owned()), + }) + } + + fn set_auto_resize(&self, auto_resize: bool) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetAutoResize(auto_resize), + }) + } + + fn clear_all_browsing_data(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::ClearAllBrowsingData, + }) + } + + fn hide(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::Hide, + }) + } + + fn show(&self) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::Show, + }) + } + + fn set_background_color(&self, color: Option) -> Result<()> { + self.context.post_message(Message::Webview { + window_id: *self.window_id.lock().unwrap(), + webview_id: self.webview_id, + message: WebviewMessage::SetBackgroundColor(color), + }) + } +} + +impl WindowDispatch for CefWindowDispatcher { + type Runtime = CefRuntime; + + type WindowBuilder = CefWindowBuilder; + + fn run_on_main_thread(&self, f: F) -> Result<()> { + self.context.post_message(Message::Task(Box::new(f))) + } + + fn on_window_event(&self, f: F) -> WindowEventId { + let context = self.context.clone(); + let window_id = self.window_id; + let event_id = context.cef_context.next_window_event_id(); + let handler = Box::new(f); + + // Register the listener on the main thread + let _ = context.post_message(Message::Window { + window_id, + message: WindowMessage::AddEventListener(event_id, handler), + }); + + event_id + } + + fn scale_factor(&self) -> Result { + window_getter!(self, WindowMessage::ScaleFactor)? + } + + fn inner_position(&self) -> Result> { + window_getter!(self, WindowMessage::InnerPosition)? + } + + fn outer_position(&self) -> Result> { + window_getter!(self, WindowMessage::OuterPosition)? + } + + fn inner_size(&self) -> Result> { + window_getter!(self, WindowMessage::InnerSize)? + } + + fn outer_size(&self) -> Result> { + window_getter!(self, WindowMessage::OuterSize)? + } + + fn is_fullscreen(&self) -> Result { + window_getter!(self, WindowMessage::IsFullscreen)? + } + + fn is_minimized(&self) -> Result { + window_getter!(self, WindowMessage::IsMinimized)? + } + + fn is_maximized(&self) -> Result { + window_getter!(self, WindowMessage::IsMaximized)? + } + + fn is_focused(&self) -> Result { + window_getter!(self, WindowMessage::IsFocused)? + } + + fn is_decorated(&self) -> Result { + window_getter!(self, WindowMessage::IsDecorated)? + } + + fn is_resizable(&self) -> Result { + window_getter!(self, WindowMessage::IsResizable)? + } + + fn is_maximizable(&self) -> Result { + window_getter!(self, WindowMessage::IsMaximizable)? + } + + fn is_minimizable(&self) -> Result { + window_getter!(self, WindowMessage::IsMinimizable)? + } + + fn is_closable(&self) -> Result { + window_getter!(self, WindowMessage::IsClosable)? + } + + fn is_visible(&self) -> Result { + window_getter!(self, WindowMessage::IsVisible)? + } + + fn title(&self) -> Result { + window_getter!(self, WindowMessage::Title)? + } + + fn current_monitor(&self) -> Result> { + window_getter!(self, WindowMessage::CurrentMonitor)? + } + + fn primary_monitor(&self) -> Result> { + window_getter!(self, WindowMessage::PrimaryMonitor)? + } + + fn monitor_from_point(&self, x: f64, y: f64) -> Result> { + let (tx, rx) = channel(); + getter!( + self, + rx, + Message::Window { + window_id: self.window_id, + message: WindowMessage::MonitorFromPoint(tx, x, y) + } + )? + } + + fn available_monitors(&self) -> Result> { + window_getter!(self, WindowMessage::AvailableMonitors)? + } + + fn theme(&self) -> Result { + window_getter!(self, WindowMessage::Theme)? + } + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn gtk_window(&self) -> Result { + unimplemented!() + } + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn default_vbox(&self) -> Result { + unimplemented!() + } + + fn window_handle( + &self, + ) -> std::result::Result, raw_window_handle::HandleError> { + let (tx, rx) = channel(); + self + .context + .post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::RawWindowHandle(tx), + }) + .map_err(|_| raw_window_handle::HandleError::Unavailable)?; + rx.recv() + .map_err(|_| raw_window_handle::HandleError::Unavailable)? + } + + fn center(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Center, + }) + } + + fn request_user_attention(&self, request_type: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::RequestUserAttention(request_type), + }) + } + + fn create_window) + Send + 'static>( + &mut self, + pending: PendingWindow, + after_window_creation: Option, + ) -> Result> { + self.context.create_window(pending, after_window_creation) + } + + fn create_webview( + &mut self, + pending: PendingWebview, + ) -> Result> { + self.context.create_webview(self.window_id, pending) + } + + fn set_resizable(&self, resizable: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetResizable(resizable), + }) + } + + fn set_maximizable(&self, maximizable: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetMaximizable(maximizable), + }) + } + + fn set_minimizable(&self, minimizable: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetMinimizable(minimizable), + }) + } + + fn set_closable(&self, closable: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetClosable(closable), + }) + } + + fn set_title>(&self, title: S) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetTitle(title.into()), + }) + } + + fn maximize(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Maximize, + }) + } + + fn unmaximize(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Unmaximize, + }) + } + + fn minimize(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Minimize, + }) + } + + fn unminimize(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Unminimize, + }) + } + + fn show(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Show, + }) + } + + fn hide(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Hide, + }) + } + + fn close(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Close, + }) + } + + fn destroy(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Destroy, + }) + } + + fn set_decorations(&self, decorations: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetDecorations(decorations), + }) + } + + fn set_shadow(&self, shadow: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetShadow(shadow), + }) + } + + fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetAlwaysOnBottom(always_on_bottom), + }) + } + + fn set_always_on_top(&self, always_on_top: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetAlwaysOnTop(always_on_top), + }) + } + + fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces), + }) + } + + fn set_content_protected(&self, protected: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetContentProtected(protected), + }) + } + + fn set_size(&self, size: Size) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetSize(size), + }) + } + + fn set_min_size(&self, size: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetMinSize(size), + }) + } + + fn set_max_size(&self, size: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetMaxSize(size), + }) + } + + fn set_position(&self, position: Position) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetPosition(position), + }) + } + + fn set_fullscreen(&self, fullscreen: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetFullscreen(fullscreen), + }) + } + + #[cfg(target_os = "macos")] + fn set_simple_fullscreen(&self, fullscreen: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetSimpleFullscreen(fullscreen), + }) + } + + fn set_focus(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetFocus, + }) + } + + fn set_focusable(&self, focusable: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetFocusable(focusable), + }) + } + + fn set_icon(&self, icon: Icon<'_>) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetIcon(icon.into_owned()), + }) + } + + fn set_skip_taskbar(&self, skip: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetSkipTaskbar(skip), + }) + } + + fn set_cursor_grab(&self, grab: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetCursorGrab(grab), + }) + } + + fn set_cursor_visible(&self, visible: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetCursorVisible(visible), + }) + } + + fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetCursorIcon(icon), + }) + } + + fn set_cursor_position>(&self, position: Pos) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetCursorPosition(position.into()), + }) + } + + fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetIgnoreCursorEvents(ignore), + }) + } + + fn start_dragging(&self) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::StartDragging, + }) + } + + fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::StartResizeDragging(direction), + }) + } + + fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetProgressBar(progress_state), + }) + } + + fn set_badge_count(&self, count: Option, desktop_filename: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetBadgeCount(count, desktop_filename), + }) + } + + fn set_badge_label(&self, label: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetBadgeLabel(label), + }) + } + + fn set_overlay_icon(&self, icon: Option>) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetOverlayIcon(icon.map(|i| i.into_owned())), + }) + } + + fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetTitleBarStyle(style), + }) + } + + fn set_traffic_light_position(&self, position: Position) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetTrafficLightPosition(position), + }) + } + + fn set_size_constraints( + &self, + constraints: tauri_runtime::window::WindowSizeConstraints, + ) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetSizeConstraints(constraints), + }) + } + + fn set_theme(&self, theme: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetTheme(theme), + }) + } + + fn set_enabled(&self, enabled: bool) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetEnabled(enabled), + }) + } + + fn is_enabled(&self) -> Result { + window_getter!(self, WindowMessage::IsEnabled)? + } + + fn is_always_on_top(&self) -> Result { + window_getter!(self, WindowMessage::IsAlwaysOnTop)? + } + + fn set_background_color(&self, color: Option) -> Result<()> { + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::SetBackgroundColor(color), + }) + } +} + +#[derive(Clone)] +pub struct EventProxy { + context: RuntimeContext, +} + +// SAFETY: we ensure the context is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for EventProxy {} + +// SAFETY: we ensure the context is only used on the main thread. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Sync for EventProxy {} + +impl fmt::Debug for EventProxy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EventProxy").finish() + } +} + +impl EventLoopProxy for EventProxy { + fn send_event(&self, event: T) -> Result<()> { + self.context.post_message(Message::UserEvent(event)) + } +} + +#[derive(Debug)] +pub struct CefRuntime { + pub context: RuntimeContext, + event_tx: std::sync::mpsc::Sender>, + event_rx: std::sync::mpsc::Receiver>, +} + +#[cfg(target_os = "macos")] +fn is_cef_helper_process() -> bool { + const HELPER_SUFFIXES: &[&str] = &[ + " Helper (GPU)", + " Helper (Renderer)", + " Helper (Plugin)", + " Helper (Alerts)", + " Helper", + ]; + std::env::current_exe() + .ok() + .and_then(|p| { + p.file_name() + .and_then(|s| s.to_str()) + .map(|name| HELPER_SUFFIXES.iter().any(|s| name.ends_with(s))) + }) + .unwrap_or_default() +} + +impl CefRuntime { + fn init(runtime_args: RuntimeInitArgs) -> Self { + let args = cef::args::Args::new(); + + let (event_tx, event_rx) = channel(); + let windows: Arc>> = Default::default(); + + #[cfg(target_os = "macos")] + let (_sandbox, _loader) = { + let is_helper = is_cef_helper_process(); + + #[cfg(feature = "sandbox")] + let sandbox = if is_helper { + let mut sandbox = cef::sandbox::Sandbox::new(); + sandbox.initialize(args.as_main_args()); + Some(sandbox) + } else { + None + }; + #[cfg(not(feature = "sandbox"))] + let sandbox = (); + + let loader = + cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), is_helper); + assert!(loader.load()); + + if !is_helper { + let event_tx_ = event_tx.clone(); + init_ns_app(Box::new(move |event| match event { + AppDelegateEvent::ShouldTerminate { tx } => { + // Cancel macOS termination — we handle shutdown ourselves. + // + // Start closing all browsers (including devtools). The actual + // destruction is async and completes via the CEF message loop. + // + // Signal the main loop to exit. The post-loop safety net will + // pump the message loop until all browsers are fully destroyed + // before calling cef::shutdown(). + + tx.send(objc2_app_kit::NSApplicationTerminateReply::TerminateCancel) + .unwrap(); + event_tx_.send(RunEvent::Exit).unwrap(); + } + AppDelegateEvent::OpenURLs { urls } => { + event_tx_.send(RunEvent::Opened { urls }).unwrap(); + } + })); + } + + (sandbox, loader) + }; + + let _ = cef::api_hash(cef::sys::CEF_API_VERSION_LAST, 0); + + let cache_base = dirs::cache_dir().unwrap_or_else(std::env::temp_dir); + let cache_path = cache_base.join(&runtime_args.identifier).join("cef"); + + // Ensure the cache directory exists + let _ = create_dir_all(&cache_path); + + let event_tx_ = event_tx.clone(); + let cef_context = cef_impl::Context { + windows: windows.clone(), + callback: Arc::new(RefCell::new(Box::new(move |event| { + event_tx_.send(event).unwrap(); + }))), + next_webview_event_id: Default::default(), + next_webview_id: Default::default(), + next_window_id: Default::default(), + next_window_event_id: Default::default(), + scheme_handler_registry: Default::default(), + }; + + let mut command_line_args = Vec::new(); + let mut deep_link_schemes = Vec::new(); + for arg in runtime_args.platform_specific_attributes { + match arg { + RuntimeInitAttribute::CommandLineArgs { args } => command_line_args.extend(args), + RuntimeInitAttribute::DeepLinkSchemes { schemes } => deep_link_schemes.extend(schemes), + } + } + command_line_args.push(("--enable-media-stream".to_string(), None)); + + let mut app = cef_impl::TauriApp::new( + cef_context.clone(), + runtime_args.custom_schemes, + deep_link_schemes, + command_line_args, + ); + + let cmd = args.as_cmd_line().unwrap(); + let switch = CefString::from("type"); + let is_browser_process = cmd.has_switch(Some(&switch)) != 1; + + let ret = cef::execute_process( + Some(args.as_main_args()), + Some(&mut app), + std::ptr::null_mut(), + ); + + if is_browser_process { + assert!(ret == -1, "cannot execute browser process"); + } else { + assert!(ret >= 0, "cannot execute non-browser process"); + // non-browser process does not initialize cef + std::process::exit(0); + } + + let settings = cef::Settings { + no_sandbox: !cfg!(feature = "sandbox") as i32, + cache_path: cache_path.to_string_lossy().to_string().as_str().into(), + ..Default::default() + }; + assert_eq!( + cef::initialize( + Some(args.as_main_args()), + Some(&settings), + Some(&mut app), + std::ptr::null_mut() + ), + 1 + ); + + let main_thread_id = thread::current().id(); + let context = RuntimeContext { + main_thread_task_runner: cef::task_runner_get_for_current_thread().expect("null task runner"), + main_thread_id, + cef_context, + }; + Self { + context, + event_tx, + event_rx, + } + } +} + +/// Helper function for non-browser CEF processes (renderer, GPU, plugin, etc.). +/// This should be called from the entry point macro when the process is not the browser process. +pub fn run_cef_helper_process() { + let args = cef::args::Args::new(); + + #[cfg(all(target_os = "macos", feature = "sandbox"))] + let _sandbox = { + let mut sandbox = cef::sandbox::Sandbox::new(); + sandbox.initialize(args.as_main_args()); + sandbox + }; + + #[cfg(target_os = "macos")] + let _loader = { + let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true); + assert!(loader.load()); + loader + }; + + cef::execute_process( + Some(args.as_main_args()), + None::<&mut cef::App>, + std::ptr::null_mut(), + ); +} + +/// Platform-specific runtime init attributes. +pub enum RuntimeInitAttribute { + /// Command line arguments passed to CEF. + CommandLineArgs { args: Vec<(String, Option)> }, + /// Deep link schemes. + DeepLinkSchemes { schemes: Vec }, +} + +impl InitAttribute for RuntimeInitAttribute { + fn new(config: &tauri_utils::config::Config) -> Result> { + let mut attrs = Vec::new(); + if let Some(plugin_config) = config + .plugins + .0 + .get("deep-link") + .and_then(|c| c.get("desktop").cloned()) + { + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum DesktopDeepLinks { + One(tauri_utils::config::DeepLinkProtocol), + List(Vec), + } + + let protocols: DesktopDeepLinks = + serde_json::from_value(plugin_config).map_err(tauri_runtime::Error::Json)?; + let schemes = match protocols { + DesktopDeepLinks::One(p) => p.schemes, + DesktopDeepLinks::List(p) => p.into_iter().flat_map(|p| p.schemes).collect(), + }; + + attrs.push(RuntimeInitAttribute::DeepLinkSchemes { schemes }); + } + Ok(attrs) + } +} + +/// Webview attributes. +pub enum WebviewAtribute { + /// Sets the browser runtime style. + RuntimeStyle { style: RuntimeStyle }, +} + +/// The browser runtime style. +#[derive(Clone, Copy)] +pub enum RuntimeStyle { + /// Alloy runtime. + /// + /// Used by default on multiwebview mode. + Alloy, + /// Chrome runtime. + /// + /// Used by default on webview window mode. + /// + /// Only a single browser view can use the Chrome runtime in a given window. + Chrome, +} + +#[derive(Debug)] +pub struct NewWindowOpener {} + +impl Runtime for CefRuntime { + type WindowDispatcher = CefWindowDispatcher; + type WebviewDispatcher = CefWebviewDispatcher; + type Handle = CefRuntimeHandle; + type EventLoopProxy = EventProxy; + type PlatformSpecificWebviewAttribute = WebviewAtribute; + type PlatformSpecificInitAttribute = RuntimeInitAttribute; + type WindowOpener = NewWindowOpener; + + fn new(args: RuntimeInitArgs) -> Result { + Ok(Self::init(args)) + } + + #[cfg(any(windows, target_os = "linux"))] + fn new_any_thread(args: RuntimeInitArgs) -> Result { + Ok(Self::init(args)) + } + + fn create_proxy(&self) -> Self::EventLoopProxy { + EventProxy { + context: self.context.clone(), + } + } + + fn handle(&self) -> Self::Handle { + CefRuntimeHandle { + context: self.context.clone(), + } + } + + fn create_window) + Send + 'static>( + &self, + pending: PendingWindow, + _after_window_creation: Option, + ) -> Result> { + let label = pending.label.clone(); + let window_id = self.context.cef_context.next_window_id(); + let (webview_id, use_https_scheme, devtools) = pending + .webview + .as_ref() + .map(|w| { + ( + Some(self.context.cef_context.next_webview_id()), + w.webview_attributes.use_https_scheme, + w.webview_attributes.devtools, + ) + }) + .unwrap_or((None, false, None)); + + cef_impl::create_window( + &self.context.cef_context, + window_id, + webview_id.unwrap_or_default(), + pending, + ); + + let dispatcher = CefWindowDispatcher { + window_id, + context: self.context.clone(), + }; + + let detached_webview = webview_id.map(|id| { + let webview = DetachedWebview { + label: label.clone(), + dispatcher: CefWebviewDispatcher { + window_id: Arc::new(Mutex::new(window_id)), + webview_id: id, + context: self.context.clone(), + }, + }; + DetachedWindowWebview { + webview, + use_https_scheme, + devtools, + } + }); + + Ok(DetachedWindow { + id: window_id, + label, + dispatcher, + webview: detached_webview, + }) + } + + fn create_webview( + &self, + window_id: WindowId, + pending: PendingWebview, + ) -> Result> { + let label = pending.label.clone(); + let webview_id = self.context.cef_context.next_webview_id(); + + cef_impl::create_webview( + cef_impl::WebviewKind::WindowChild, + &self.context.cef_context, + window_id, + webview_id, + pending, + ); + + let dispatcher = CefWebviewDispatcher { + window_id: Arc::new(Mutex::new(window_id)), + webview_id, + context: self.context.clone(), + }; + + Ok(DetachedWebview { label, dispatcher }) + } + + fn primary_monitor(&self) -> Option { + crate::cef_impl::get_primary_monitor() + } + + fn monitor_from_point(&self, x: f64, y: f64) -> Option { + crate::cef_impl::get_monitor_from_point(x, y) + } + + fn available_monitors(&self) -> Vec { + crate::cef_impl::get_available_monitors() + } + + fn set_theme(&self, _theme: Option) {} + + #[cfg(target_os = "macos")] + fn set_activation_policy(&mut self, _activation_policy: tauri_runtime::ActivationPolicy) {} + + #[cfg(target_os = "macos")] + fn set_dock_visibility(&mut self, _visible: bool) {} + + #[cfg(target_os = "macos")] + fn show(&self) { + cef_impl::set_application_visibility(true); + } + + #[cfg(target_os = "macos")] + fn hide(&self) { + cef_impl::set_application_visibility(false); + } + + fn set_device_event_filter(&mut self, _filter: DeviceEventFilter) {} + + fn custom_scheme_url(scheme: &str, https: bool) -> String { + // CEF always uses http/https format regardless of platform + format!( + "{}://{scheme}.localhost", + if https { "https" } else { "http" } + ) + } + + #[cfg(any( + target_os = "macos", + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn run_iteration)>(&mut self, _callback: F) {} + + fn run_return) + 'static>(self, _callback: F) -> i32 { + 0 + } + + fn run) + 'static>(self, callback: F) { + let callback = Arc::new(RefCell::new(callback)); + let callback_ = callback.clone(); + let event_tx_ = self.event_tx.clone(); + let _ = std::mem::replace( + &mut *self.context.cef_context.callback.borrow_mut(), + Box::new(move |event| { + if let RunEvent::Exit = event { + // notify the event loop to exit + let _ = event_tx_.send(RunEvent::Exit); + } else { + // Try to call callback directly, if busy queue to channel + if let Ok(mut cb) = callback.try_borrow_mut() { + cb(event); + } else { + let _ = event_tx_.send(event); + } + } + }), + ); + + 'main_loop: loop { + while let Ok(event) = self.event_rx.try_recv() { + if matches!(&event, RunEvent::Exit) { + // Exit event is triggered when we break out of the loop + break 'main_loop; + } + + (self.context.cef_context.callback.borrow())(event); + } + + // Do CEF message loop work + // This processes one iteration of the message loop + cef::do_message_loop_work(); + + // Emit MainEventsCleared event + (self.context.cef_context.callback.borrow())(RunEvent::MainEventsCleared); + } + + // We need to run the message loop until all windows are closed. Otherwise, we run into use after free crashes. + cef_impl::close_all_windows(&self.context.cef_context.windows); + while !self.context.cef_context.windows.borrow().is_empty() { + cef::do_message_loop_work(); + } + + cef::shutdown(); + + // Final Exit event + // use callback_ directly because cef_context.callback posts Exit events to the event loop rx + (callback_.borrow_mut())(RunEvent::Exit); + } + + fn cursor_position(&self) -> Result> { + Ok(PhysicalPosition::new(0.0, 0.0)) + } +} + +#[cfg(target_os = "macos")] +fn init_ns_app(on_event: Box) { + use objc2::{ClassType, MainThreadMarker, msg_send, rc::Retained, runtime::NSObjectProtocol}; + use objc2_app_kit::{NSApp, NSApplication}; + + use application::{AppDelegate, SimpleApplication}; + + let mtm = MainThreadMarker::new().unwrap(); + + unsafe { + // Initialize the SimpleApplication instance. + // SAFETY: mtm ensures that here is the main thread. + + use objc2::runtime::ProtocolObject; + + let app: Retained = msg_send![SimpleApplication::class(), sharedApplication]; + let delegate = AppDelegate::new(mtm, on_event); + let proto_delegate = ProtocolObject::from_ref(&*delegate); + app.setDelegate(Some(proto_delegate)); + } + + // If there was an invocation to NSApp prior to here, + // then the NSApp will not be a SimpleApplication. + // The following assertion ensures that this doesn't happen. + assert!(NSApp(mtm).isKindOfClass(SimpleApplication::class())); +} + +#[cfg(target_os = "macos")] +mod application { + use std::{cell::Cell, sync::mpsc::channel}; + + use cef::application_mac::{CefAppProtocol, CrAppControlProtocol, CrAppProtocol}; + use objc2::{ + DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send, + rc::Retained, + runtime::{Bool, NSObject, NSObjectProtocol}, + }; + use objc2_app_kit::{NSApplication, NSApplicationDelegate, NSApplicationTerminateReply}; + use objc2_foundation::{NSArray, NSURL}; + + pub enum AppDelegateEvent { + ShouldTerminate { + tx: std::sync::mpsc::Sender, + }, + OpenURLs { + urls: Vec, + }, + } + + pub struct CefAppDelegateIvars { + pub on_event: Box, + } + + define_class!( + #[unsafe(super(NSObject))] + #[name = "CefAppDelegate"] + #[ivars = CefAppDelegateIvars] + #[thread_kind = MainThreadOnly] + pub struct AppDelegate; + + unsafe impl NSObjectProtocol for AppDelegate {} + + #[allow(non_snake_case)] + unsafe impl NSApplicationDelegate for AppDelegate { + #[unsafe(method(application:openURLs:))] + unsafe fn application_openURLs(&self, _application: &NSApplication, urls: &NSArray) { + let converted_urls: Vec = urls + .iter() + .filter_map(|ns_url| unsafe { + ns_url + .absoluteString() + .and_then(|url_string| url_string.to_string().parse().ok()) + }) + .collect(); + + let handler = &self.ivars().on_event; + handler(AppDelegateEvent::OpenURLs { + urls: converted_urls, + }); + } + + #[unsafe(method(applicationShouldTerminate:))] + unsafe fn applicationShouldTerminate( + &self, + _sender: &NSApplication, + ) -> NSApplicationTerminateReply { + let (tx, rx) = channel(); + let handler = &self.ivars().on_event; + handler(AppDelegateEvent::ShouldTerminate { tx }); + rx.try_recv() + .unwrap_or(NSApplicationTerminateReply::TerminateNow) + } + } + ); + + impl AppDelegate { + pub fn new(mtm: MainThreadMarker, on_event: Box) -> Retained { + let delegate = Self::alloc(mtm).set_ivars(CefAppDelegateIvars { on_event }); + let delegate: Retained = unsafe { msg_send![super(delegate), init] }; + delegate + } + } + + /// Instance variables of `SimpleApplication`. + pub struct SimpleApplicationIvars { + handling_send_event: Cell, + } + + define_class!( + /// A `NSApplication` subclass that implements the required CEF protocols. + /// + /// This class provides the necessary `CefAppProtocol` conformance to + /// ensure that events are handled correctly by the Chromium framework on macOS. + #[unsafe(super(NSApplication))] + #[ivars = SimpleApplicationIvars] + pub struct SimpleApplication; + + unsafe impl CrAppControlProtocol for SimpleApplication { + #[unsafe(method(setHandlingSendEvent:))] + unsafe fn set_handling_send_event(&self, handling_send_event: Bool) { + self.ivars().handling_send_event.set(handling_send_event); + } + } + + unsafe impl CrAppProtocol for SimpleApplication { + #[unsafe(method(isHandlingSendEvent))] + unsafe fn is_handling_send_event(&self) -> Bool { + self.ivars().handling_send_event.get() + } + } + + unsafe impl CefAppProtocol for SimpleApplication {} + ); +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/utils.rs b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/utils.rs new file mode 100644 index 0000000..164ecd2 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/utils.rs @@ -0,0 +1,42 @@ +#[cfg(windows)] +pub mod windows { + use tauri_runtime::dpi::PhysicalSize; + use windows::Win32::Foundation::*; + use windows::Win32::UI::WindowsAndMessaging::*; + + pub fn inner_size(hwnd: cef::sys::HWND) -> PhysicalSize { + let hwnd = HWND(hwnd.0 as _); + let mut rect = RECT::default(); + let _ = unsafe { GetClientRect(hwnd, &mut rect) }; + + PhysicalSize::new( + (rect.right - rect.left) as u32, + (rect.bottom - rect.top) as u32, + ) + } + + /// Adjusts the given size to account for window borders, so that the resulting inner size matches the requested size. + /// + /// Expects and returns a size in physical pixels. + pub fn adjust_size(hwnd: cef::sys::HWND, size: cef::Size) -> cef::Size { + let hwnd = HWND(hwnd.0 as _); + + let mut client_rect = RECT::default(); + let _ = unsafe { GetClientRect(hwnd, &mut client_rect) }; + let client_width = client_rect.right - client_rect.left; + let client_height = client_rect.bottom - client_rect.top; + + let mut window_rect = RECT::default(); + let _ = unsafe { GetWindowRect(hwnd, &mut window_rect) }; + let window_width = window_rect.right - window_rect.left; + let window_height = window_rect.bottom - window_rect.top; + + let width_diff = window_width - client_width; + let height_diff = window_height - client_height; + + cef::Size { + width: size.width + width_diff, + height: size.height + height_diff, + } + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/CHANGELOG.md b/apps/tauri/src-tauri/vendor/tauri-runtime/CHANGELOG.md new file mode 100644 index 0000000..2e227da --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/CHANGELOG.md @@ -0,0 +1,1083 @@ +# Changelog + +## \[2.11.0] + +### New Features + +- [`001c8fe3d`](https://www.github.com/tauri-apps/tauri/commit/001c8fe3d288802de9a8c29cfd2f46f9220d97c5) ([#14722](https://www.github.com/tauri-apps/tauri/pull/14722)) Add a WebView option to control browser-level general autofill behavior. This option does not disable password or credit card autofill. On Windows (WebView2), setting it to true disables the general autofill "Suggestions" UI, which may appear even when `autocomplete="off"` is specified on input elements. On Linux, macOS, iOS, and Android, this option is currently unsupported and performs no operation. +- [`b27be063f`](https://www.github.com/tauri-apps/tauri/commit/b27be063ff3052cb1071ac3ec719cfa104460fa4) ([#14925](https://www.github.com/tauri-apps/tauri/pull/14925)) Add `eval_with_callback` to the Tauri webview APIs and runtime dispatch layers. +- [`cc5c97602`](https://www.github.com/tauri-apps/tauri/commit/cc5c976027b0ab2431c13ec5b2e201d4414a8a6e) ([#14486](https://www.github.com/tauri-apps/tauri/pull/14486)) Trigger `RunEvent::Opened` on Android. +- [`eb0312ea9`](https://www.github.com/tauri-apps/tauri/commit/eb0312ea9e493954298ac0b3fdaae7eafb52750e) ([#15199](https://www.github.com/tauri-apps/tauri/pull/15199)) Propagates the `Event::Suspended` and `Event::Resumed` events from `tao` when they are emitted on mobile targets. +- [`093e2b47c`](https://www.github.com/tauri-apps/tauri/commit/093e2b47c01361c18783e9ff18750388e41650c5) ([#14484](https://www.github.com/tauri-apps/tauri/pull/14484)) Support creating multiple windows on Android (activity embedding) and iOS (scenes). +- [`1063c48c5`](https://www.github.com/tauri-apps/tauri/commit/1063c48c5e7d099ad74d28a937edf42e3f5c9f03) ([#14523](https://www.github.com/tauri-apps/tauri/pull/14523)) Add handler for web content process termination on macOS and iOS. + +### Dependencies + +- Upgraded to `tauri-utils@2.9.0` + +## \[2.10.1] + +### Dependencies + +- Upgraded to `tauri-utils@2.8.3` + +## \[2.10.0] + +### Bug Fixes + +- [`9b242e40c`](https://www.github.com/tauri-apps/tauri/commit/9b242e40c844189c877a91e513ae6196202d5ae9) ([#14700](https://www.github.com/tauri-apps/tauri/pull/14700) by [@mewi99](https://www.github.com/tauri-apps/tauri/../../mewi99)) Fix compilation errors when targeting BSD. + +### Dependencies + +- Upgraded to `tauri-utils@2.8.2` +- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) **Breaking Change** for `with_webview` users: Updated webkit2gtk-rs crates to `v2.0.2`. + +## \[2.9.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.8.1` + +## \[2.9.1] + +### Bug Fixes + +- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes. + +## \[2.9.0] + +### New Features + +- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scroll_bar_style` option to the Webview and WebviewWindow builders. + The possible values for this option are gated behind conditional compilation + flags, and will need to be applied using conditional compilation if customised. + +### Dependencies + +- Upgraded to `tauri-utils@2.8.0` + +## \[2.8.0] + +### New Features + +- [`68874c68c`](https://www.github.com/tauri-apps/tauri/commit/68874c68c566638b4c21a3aa67844d1bdaeb6dab) ([#13564](https://www.github.com/tauri-apps/tauri/pull/13564) by [@robertrpf](https://www.github.com/tauri-apps/tauri/../../robertrpf)) Add window focusable attribute and set_focusable API. +- [`22d6bcacb`](https://www.github.com/tauri-apps/tauri/commit/22d6bcacbb2001eb292ebd8c5d021447700f9512) ([#14008](https://www.github.com/tauri-apps/tauri/pull/14008) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Implement `App::set_device_event_filter` for `AppHandle` also. +- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_document_title_changed` and `WebviewWindowBuilder::on_document_title_changed`. +- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_new_window` and `WebviewWindowBuilder::on_new_window`. +- [`dfadcb764`](https://www.github.com/tauri-apps/tauri/commit/dfadcb764bdf84089a5487005a7b4f3b7cf09494) ([#13661](https://www.github.com/tauri-apps/tauri/pull/13661) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Added `WebviewDispatch::set_cookie()` and `WebviewDispatch::delete_cookie()`. +- [`5110a762e`](https://www.github.com/tauri-apps/tauri/commit/5110a762e9db978a28a15400bf76e3c864da2a86) ([#13830](https://www.github.com/tauri-apps/tauri/pull/13830) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Added `Window::set_simple_fullscreen`. + +### Dependencies + +- Upgraded to `tauri-utils@2.7.0` + +## \[2.7.1] + +### Dependencies + +- Upgraded to `tauri-utils@2.6.0` + +## \[2.7.0] + +### Dependencies + +- Upgraded to `tauri-utils@2.5.0` +- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader. + +## \[2.6.0] + +### New Features + +- [`dd4f13ce4`](https://www.github.com/tauri-apps/tauri/commit/dd4f13ce4b3cd89cde2fa3f18a063c272f215621) ([#13185](https://www.github.com/tauri-apps/tauri/pull/13185)) MacOS: Add `set_dock_visibility` method to support setting the visibility of the application in the dock. +- [`8cf662e34`](https://www.github.com/tauri-apps/tauri/commit/8cf662e34bf738a0d16bb7b9aeb35667e2e4984b) ([#13076](https://www.github.com/tauri-apps/tauri/pull/13076)) - add API to run initialization scripts on all frames + \- `WebviewBuilder::initialization_script_on_all_frames` + \- `WebviewWindowBuilder::initialization_script_on_all_frames` + \- `WebviewAttributes::initialization_script_on_all_frames` +- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `WebviewAttributes::input_accessory_view_builder` on iOS. +- [`c1cd0a2dd`](https://www.github.com/tauri-apps/tauri/commit/c1cd0a2ddb5bc3e99451cbe399b5fc9f0035f571) ([#13090](https://www.github.com/tauri-apps/tauri/pull/13090)) macOS/iOS: add option to disable or enable link previews when building a webview (the webkit api has it enabled by default) + + - `WebViewBuilder.allow_link_preview(allow_link_preview: bool)` + - `WebviewWindowBuilder.allow_link_preview(allow_link_preview: bool)` +- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation + +### Dependencies + +- Upgraded to `tauri-utils@2.4.0` +- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update windows to 0.61. + +## \[2.5.1] + +### Dependencies + +- Upgraded to `tauri-utils@2.3.1` + +## \[2.5.0] + +### New Features + +- [`be2e6b85f`](https://www.github.com/tauri-apps/tauri/commit/be2e6b85fed226732b4a98f68cc5d72b4f8f5a13) ([#12944](https://www.github.com/tauri-apps/tauri/pull/12944) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) add `Window.is_always_on_top()` and `WebviewWindow.is_always_on_top()` +- [`658e5f5d1`](https://www.github.com/tauri-apps/tauri/commit/658e5f5d1dc1bd970ae572a42447448d064a7fee) ([#12668](https://www.github.com/tauri-apps/tauri/pull/12668) by [@thomaseizinger](https://www.github.com/tauri-apps/tauri/../../thomaseizinger)) Add `App::run_return` function. Contrary to `App::run`, this will **not** exit the process but instead return the requested exit-code. This allows the host app to perform further cleanup after Tauri has exited. `App::run_return` is not available on iOS and fallbacks to the regular `App::run` functionality. + + The `App::run_iteration` function is deprecated as part of this because calling it in a loop - as suggested by the name - will cause a busy-loop. +- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS. +- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `traffic_light_position` window builder method to set the traffic light buttons position on macOS. +- [`cedb24d49`](https://www.github.com/tauri-apps/tauri/commit/cedb24d494b84111daa3206c05196c8b89f1e994) ([#12665](https://www.github.com/tauri-apps/tauri/pull/12665) by [@charrondev](https://www.github.com/tauri-apps/tauri/../../charrondev)) Added `WebviewDispatch::cookies()` and `WebviewDispatch::cookies_for_url()`. + +### Dependencies + +- Upgraded to `tauri-utils@2.3.0` + +## \[2.4.0] + +### Enhancements + +- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only). +- [`d6520a21c`](https://www.github.com/tauri-apps/tauri/commit/d6520a21ce02c3e2be2955999946c2cb7bdb07aa) ([#12541](https://www.github.com/tauri-apps/tauri/pull/12541) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `wry` to 0.50, `windows` to 0.60, `webview2-com` to 0.36, and `objc2` to 0.6. This can be a **breaking change** if you use the `with_webview` API! + +### Dependencies + +- Upgraded to `tauri-utils@2.2.0` + +## \[2.3.0] + +### New Features + +- [`18bd639f6`](https://www.github.com/tauri-apps/tauri/commit/18bd639f6e22c0188aa219739f367b5bf5ab0398) ([#11798](https://www.github.com/tauri-apps/tauri/pull/11798) by [@lars-berger](https://www.github.com/tauri-apps/tauri/../../lars-berger)) Add `WebviewWindowBuilder/WebviewBuilder::data_store_identifier` on macOS. +- [`dc4d79477`](https://www.github.com/tauri-apps/tauri/commit/dc4d79477665bc3bfefb4048772414cf5d78e3df) ([#11628](https://www.github.com/tauri-apps/tauri/pull/11628) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Add `WebviewWindowBuilder/WebviewBuilder::extensions_path` on Linux and Windows. +- [`020ea0556`](https://www.github.com/tauri-apps/tauri/commit/020ea05561348dcd6d2a7df358f8a5190f661ba2) ([#11661](https://www.github.com/tauri-apps/tauri/pull/11661) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Add badging APIs: + + - `Window/WebviewWindow::set_badge_count` for Linux, macOS and IOS. + - `Window/WebviewWindow::set_overlay_icon` for Windows Only. + - `Window/WebviewWindow::set_badge_label`for macOS Only. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.1` + +## \[2.2.0] + +### New Features + +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`. +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview. +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows. + +### Bug Fixes + +- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` + +## \[2.1.1] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.2` + +## \[2.1.0] + +### Bug Fixes + +- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix commands, that use `Webview` or `WebviewWindow` as an argument, receiving an incorrect webview when using multi webviews. +- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix events only emitted to first webview only when using multi webviews. +- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix custom protocols receiving an incorrect webview label when using multi webviews + +## \[2.0.1] + +### What's Changed + +- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.1` + +## \[2.0.0] + +### What's Changed + +- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78. +- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable! + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0` + +## \[2.0.0-rc.13] + +### New Features + +- [`a247170e1`](https://www.github.com/tauri-apps/tauri/commit/a247170e1f620a9b012274b11cfe51e90327d6e9) ([#11056](https://www.github.com/tauri-apps/tauri/pull/11056) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Expose the ability to enabled browser extensions in WebView2 on Windows. +- [`9014a3f17`](https://www.github.com/tauri-apps/tauri/commit/9014a3f1765ca406ea5c3e5224267a79c52cd53d) ([#11066](https://www.github.com/tauri-apps/tauri/pull/11066) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindow::clear_all_browsing_data` and `Webview::clear_all_browsing_data` to clear the webview browsing data. +- [`95df53a2e`](https://www.github.com/tauri-apps/tauri/commit/95df53a2ed96873cd35a4b14a5e312d07e4e3004) ([#11143](https://www.github.com/tauri-apps/tauri/pull/11143) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add the ability to set theme dynamically using `Window::set_theme`, `App::set_theme` +- [`d9d2502b4`](https://www.github.com/tauri-apps/tauri/commit/d9d2502b41e39efde679e30c8955006e2ba9ea64) ([#11140](https://www.github.com/tauri-apps/tauri/pull/11140) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewDispatch::hide` and `WebviewDispatch::show` methods. +- [`de7414aab`](https://www.github.com/tauri-apps/tauri/commit/de7414aab935e45540594ea930eb60bae4dbc979) ([#11154](https://www.github.com/tauri-apps/tauri/pull/11154) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Window::set_enabled` and `Window::is_enabled` methods + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.13` + +## \[2.0.0-rc.12] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.12` + +## \[2.0.0-rc.11] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.11` + +## \[2.0.0-rc.10] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.10` + +## \[2.0.0-rc.9] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.9` + +## \[2.0.0-rc.8] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.8` + +## \[2.0.0-rc.7] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.7` + +## \[2.0.0-rc.6] + +### What's Changed + +- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.6` + +## \[2.0.0-rc.5] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.5` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.4` + +## \[2.0.0-rc.3] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.3` +- [`d39c392b7`](https://www.github.com/tauri-apps/tauri/commit/d39c392b7cec746da423211f9c74632abe4b6af5) ([#10655](https://www.github.com/tauri-apps/tauri/pull/10655) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update `tao` to 0.29 and `wry` to 0.42. + +## \[2.0.0-rc.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.2` + +## \[2.0.0-rc.1] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.1` + +## \[2.0.0-rc.0] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-rc.0` + +## \[2.0.0-beta.21] + +### What's Changed + +- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `inner_size_constraints` method on `WindowBuilder` trait and `set_size_constraints` method on `WindowDispatch` trait. + +## \[2.0.0-beta.20] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.19` + +## \[2.0.0-beta.19] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.18` +- [`d4c908cfb`](https://www.github.com/tauri-apps/tauri/commit/d4c908cfb8c567abdaf99b85f65f482ea81967e5) ([#10048](https://www.github.com/tauri-apps/tauri/pull/10048)) Update `windows` crate to version `0.57` and `webview2-com` crate to version `0.31` + +## \[2.0.0-beta.18] + +### Enhancements + +- [`276c4b143`](https://www.github.com/tauri-apps/tauri/commit/276c4b14385e17cff15a2e5b57fd2a7cddef9f08)([#9832](https://www.github.com/tauri-apps/tauri/pull/9832)) Added `WindowBuilder::get_theme`. + +### What's Changed + +- [`9ac930380`](https://www.github.com/tauri-apps/tauri/commit/9ac930380a5df3fe700e68e75df8684d261ca292)([#9850](https://www.github.com/tauri-apps/tauri/pull/9850)) Emit `cargo:rustc-check-cfg` instruction so Cargo validates custom cfg attributes on Rust 1.80 (or nightly-2024-05-05). + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.17` + +## \[2.0.0-beta.17] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.16` + +## \[2.0.0-beta.16] + +### New Features + +- [`78839b6d2`](https://www.github.com/tauri-apps/tauri/commit/78839b6d2f1005a5e6e1a54b0305136bae0c3a7c)([#4865](https://www.github.com/tauri-apps/tauri/pull/4865)) Add `RunEvent::Reopen` for handle click on dock icon on macOS. + +### What's Changed + +- [`783ef0f2d`](https://www.github.com/tauri-apps/tauri/commit/783ef0f2d331f520fa827c3112f36c0b519b9292)([#9647](https://www.github.com/tauri-apps/tauri/pull/9647)) Changed `WebviewDispatch::url` getter to return a result. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.15` + +## \[2.0.0-beta.15] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.14` + +## \[2.0.0-beta.14] + +### New Features + +- [`477bb8cd4`](https://www.github.com/tauri-apps/tauri/commit/477bb8cd4ea88ade3f6c1f268ad1701a68150161)([#9297](https://www.github.com/tauri-apps/tauri/pull/9297)) Add `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter to get the current cursor position. + +## \[2.0.0-beta.13] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.13` + +## \[2.0.0-beta.12] + +### New Features + +- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Added the `set_zoom` function to the webview API. +- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Add `zoom_hotkeys_enabled` to enable browser native zoom controls on creating webviews. +- [`4973d73a2`](https://www.github.com/tauri-apps/tauri/commit/4973d73a237dc5c60618c1011e202278e7a29b5c)([#9386](https://www.github.com/tauri-apps/tauri/pull/9386)) Provide a basic zoom hotkey polyfill for non-Windows platforms + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.12` + +## \[2.0.0-beta.11] + +### What's Changed + +- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Updated `http` crate to `1.1` + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.11` + +### Breaking Changes + +- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) The IPC handler closure now receives a `http::Request` instead of a String representing the request body. +- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names. +- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Moved `window::dpi` module to the root of the crate. + +## \[2.0.0-beta.10] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.10` + +## \[2.0.0-beta.9] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.9` + +## \[2.0.0-beta.8] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.8` + +## \[2.0.0-beta.7] + +### New Features + +- [`46de49aaa`](https://www.github.com/tauri-apps/tauri/commit/46de49aaad4a148fafc31d591be0e2ed12256507)([#9059](https://www.github.com/tauri-apps/tauri/pull/9059)) Added `set_auto_resize` method for the webview. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.7` + +### Breaking Changes + +- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Add a lifetime parameter for `Icon` type. Also changed `rgba` field to be `Cow<'a, [u8]>` + +## \[2.0.0-beta.6] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.6` + +## \[2.0.0-beta.5] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.5` + +## \[2.0.0-beta.4] + +### New Features + +- [`fdcaf935`](https://www.github.com/tauri-apps/tauri/commit/fdcaf935fa75ecfa2806939c4faad4fe9e880386)([#8939](https://www.github.com/tauri-apps/tauri/pull/8939)) Added the `reparent` function to the webview API. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.4` + +## \[2.0.0-beta.3] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.3` + +## \[2.0.0-beta.2] + +### What's Changed + +- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Add `WebviewEvent`, `RunEvent::WebviewEvent` and `WebviewDispatch::on_webview_event`. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.2` + +### Breaking Changes + +- [`2f55bfec`](https://www.github.com/tauri-apps/tauri/commit/2f55bfecbf0244f3b5aa1ad7622182fca3fcdcbb)([#8795](https://www.github.com/tauri-apps/tauri/pull/8795)) Update raw-window-handle to 0.6. + +## \[2.0.0-beta.1] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.1` + +## \[2.0.0-beta.0] + +### New Features + +- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) Added `Window::destroy` to force close a window. +- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Add multiwebview support behind the `unstable` feature flag. See `WindowBuilder` and `WebviewBuilder` for more information. +- [`00e15675`](https://www.github.com/tauri-apps/tauri/commit/00e1567584721644797b587205187f9cbe4e5cd1)([#8708](https://www.github.com/tauri-apps/tauri/pull/8708)) Added `RuntimeHandle::request_exit` function. + +### Bug Fixes + +- [`95da1a27`](https://www.github.com/tauri-apps/tauri/commit/95da1a27476e01e06f6ce0335df8535b662dd9c4)([#8713](https://www.github.com/tauri-apps/tauri/pull/8713)) Fix calling `set_activation_policy` when the event loop is running. + +### What's Changed + +- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Added `WindowBuilder::transient_for` and Renamed `WindowBuilder::owner_window` to `WindowBuilder::owner` and `WindowBuilder::parent_window` to `WindowBuilder::parent`. +- [`7f033f6d`](https://www.github.com/tauri-apps/tauri/commit/7f033f6dcd54c69a4193765a5c1584755ba92c61)([#8537](https://www.github.com/tauri-apps/tauri/pull/8537)) Add `Window::start_resize_dragging` and `ResizeDirection` enum. +- [`6639a579`](https://www.github.com/tauri-apps/tauri/commit/6639a579c76d45210f33a72d37e21d4c5a9d334b)([#8441](https://www.github.com/tauri-apps/tauri/pull/8441)) Added the `WindowConfig::proxy_url` `WebviewBuilder::proxy_url() / WebviewWindowBuilder::proxy_url()` options when creating a webview. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-beta.0` + +### Breaking Changes + +- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Changed `WindowBuilder::with_config` to take a reference to a `WindowConfig` instead of an owned value. + +## \[1.0.0-alpha.8] + +### New Features + +- [`29ced5ce`](https://www.github.com/tauri-apps/tauri/commit/29ced5ceec40b2934094ade2db9a8855f294e1d1)([#8159](https://www.github.com/tauri-apps/tauri/pull/8159)) Added download event closure via `PendingWindow::download_handler`. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.13` + +## \[1.0.0-alpha.7] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.12` + +## \[1.0.0-alpha.6] + +### Dependencies + +- [\`\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Update dependencies. + +## \[1.0.0-alpha.5] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.11` + +## \[1.0.0-alpha.4] + +### New Features + +- [`74d2464d`](https://www.github.com/tauri-apps/tauri/commit/74d2464d0e490fae341ad73bdf2964cf215fe6c5)([#8116](https://www.github.com/tauri-apps/tauri/pull/8116)) Added `on_page_load` hook for `PendingWindow`. + +### Enhancements + +- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.10` +- [`9580df1d`](https://www.github.com/tauri-apps/tauri/commit/9580df1d7b027befb9e5f025ea2cbaf2dcc82c8e)([#8084](https://www.github.com/tauri-apps/tauri/pull/8084)) Upgrade `gtk` to 0.18. +- [`c7c2507d`](https://www.github.com/tauri-apps/tauri/commit/c7c2507da16a9beb71bf06745fe7ac1325ab7c2a)([#8035](https://www.github.com/tauri-apps/tauri/pull/8035)) Update `windows` to version `0.51` and `webview2-com` to version `0.27` + +## \[1.0.0-alpha.3] + +### New Features + +- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added `set_progress_bar` to `Window`. +- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added `Window::set_always_on_bottom` and the `always_on_bottom` option when creating a window. +- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70. + +### Enhancements + +- [`46dcb941`](https://www.github.com/tauri-apps/tauri/commit/46dcb94110ac16d0d4328fa149bb86975b658f59)([#8006](https://www.github.com/tauri-apps/tauri/pull/8006)) Include mobile on docs.rs targets. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.9` + +### Breaking Changes + +- [`2558fab8`](https://www.github.com/tauri-apps/tauri/commit/2558fab861006936296e8511e43ccd69a38f61b0)([#7939](https://www.github.com/tauri-apps/tauri/pull/7939)) Added `WindowEventId` type and Changed `Dispatch::on_window_event` return type from `Uuid` to `WindowEventId` + +## \[1.0.0-alpha.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.8` + +## \[1.0.0-alpha.1] + +### Enhancements + +- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Changed custom protocol closure type to enable asynchronous usage. + +### What's Changed + +- [`6177150b`](https://www.github.com/tauri-apps/tauri/commit/6177150b6f83b52ca359d6e20f7e540f7554e4eb)([#7601](https://www.github.com/tauri-apps/tauri/pull/7601)) Changed `FileDropEvent` to include drop and hover position. + +### Breaking Changes + +- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) `tauri-runtime` no longer implements its own HTTP types and relies on the `http` crate instead. + +## \[1.0.0-alpha.0] + +### New Features + +- [`4db363a0`](https://www.github.com/tauri-apps/tauri/commit/4db363a03c182349f8491f46ced258d84723b11f)([#6589](https://www.github.com/tauri-apps/tauri/pull/6589)) Added `visible_on_all_workspaces` configuration option to `WindowBuilder`, `Window`, and `WindowConfig`. +- [`84c41597`](https://www.github.com/tauri-apps/tauri/commit/84c4159754b2e59244211ed9e1fc702d851a0562)([#6394](https://www.github.com/tauri-apps/tauri/pull/6394)) Added `primary_monitor` and `available_monitors` to `Runtime` and `RuntimeHandle`. +- [`2a000e15`](https://www.github.com/tauri-apps/tauri/commit/2a000e150d02dff28c8b20ad097b29e209160045)([#7235](https://www.github.com/tauri-apps/tauri/pull/7235)) Added `navigate` function to `Dispatch` trait. +- [`3b98141a`](https://www.github.com/tauri-apps/tauri/commit/3b98141aa26f74c641a4090874247b97079bd58a)([#3736](https://www.github.com/tauri-apps/tauri/pull/3736)) Added the `Opened` variant to `RunEvent`. + +### Dependencies + +- Upgraded to `tauri-utils@2.0.0-alpha.7` + +### Breaking Changes + +- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `Dispatch::create_window`, `Runtime::create_window` and `RuntimeHandle::create_window` has been changed to accept a 3rd parameter which is a closure that takes `RawWindow` and to be executed right after the window is created and before the webview is added to the window. +- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) System tray and menu related APIs and structs have all been removed and are now implemented in tauri outside of the runtime-space. +- [`3a2c3e74`](https://www.github.com/tauri-apps/tauri/commit/3a2c3e74710bef9a14932dce74c351cca6215429)([#7306](https://www.github.com/tauri-apps/tauri/pull/7306)) The `PendingWindow#navigation_handler` closure now receives a `&Url` argument instead of `Url`. +- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `Runtime::new` and `Runtime::new_any_thread` now accept a `RuntimeInitArgs`. +- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Removed `system-tray` feature flag + +## \[0.13.0-alpha.6] + +### New Features + +- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `window_effects` option when creating a window and `Window::set_effects` to change it at runtime. + +## \[0.13.0-alpha.5] + +- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository. +- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository. + +## \[0.13.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - Bumped due to a bump in tauri-utils. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[0.13.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[0.13.0-alpha.2] + +- Add `find_class`, `run_on_android_context` on `RuntimeHandle`. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 +- Added the `shadow` option when creating a window and `Window::set_shadow`. + - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08 +- Implemented `with_webview` on Android and iOS. + - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11 + +## \[0.13.0-alpha.1] + +- Update gtk to 0.16. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 + +## \[0.13.0-alpha.0] + +- Parse `android` and `ios` Tauri configuration files. + - Bumped due to a bump in tauri-utils. + - [b3a3afc7](https://www.github.com/tauri-apps/tauri/commit/b3a3afc7de8de4021d73559288f5192732a706cf) feat(core): detect android and ios platform configuration files ([#4997](https://www.github.com/tauri-apps/tauri/pull/4997)) on 2022-08-22 +- First mobile alpha release! + - Bumped due to a bump in tauri-utils. + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[0.14.2] + +### Dependencies + +- Upgraded to `tauri-utils@1.5.2` + +## \[0.14.1] + +### Enhancements + +- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://.localhost/` for custom protocols on Windows. + +### Dependencies + +- Upgraded to `tauri-utils@1.5.0` + +## \[0.14.0] + +### New Features + +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` methods to `WindowBuilder`. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods to the `Dispatch` trait. +- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` getter. + +### Enhancements + +- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85. + +### Bug Fixes + +- [`2b487c94`](https://www.github.com/tauri-apps/tauri/commit/2b487c946737352187d7e042dd6142873e62a4ca)([#7012](https://www.github.com/tauri-apps/tauri/pull/7012)) Fixes typo in `CursorIcon` deserialization of the `ZoomIn` variant. + +### What's Changed + +- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Use `u32` instead of `u64` for js event listener ids +- [`ff5e4dbb`](https://www.github.com/tauri-apps/tauri/commit/ff5e4dbbb01bf3fc9c5143df732c75eef6fd98cb)([#6794](https://www.github.com/tauri-apps/tauri/pull/6794)) impl `From<&WindowConfig>` for `WebviewAttributes`. + +## \[0.13.0] + +- Added the `additional_browser_args` option when creating a window. + - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14 +- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime. + - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13 +- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods. + - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13 +- Add `is_minimized()` window method. + - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13 +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Pin raw-window-handle to 0.5.0 to keep MSRV. + - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17 +- Added `navigation_handler` field on `PendingWindow`. + - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27 +- Add `title` getter on window. + - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13 +- Added `TrayHandle::set_tooltip` and `SystemTray::with_tooltip`. + - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01 +- Added window's `url()` getter. + - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26 +- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support. + - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27 + +## \[0.12.2] + +- Block remote URLs from accessing the IPC. + - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.12.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - Bumped due to a bump in tauri-utils. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[0.12.0] + +- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated. + - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08 +- Added `Runtime::show()`, `RuntimeHandle::show()`, `Runtime::hide()`, `RuntimeHandle::hide()` for hiding/showing the entire application on macOS. + - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 +- Added `tabbing_identifier` to the window builder on macOS. + - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19 +- Added methods to set the system tray title on macOS. + - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30 +- Added the `user_agent` option when creating a window. + - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02 + +## \[0.11.2] + +- Block remote URLs from accessing the IPC. + - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.11.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - Bumped due to a bump in tauri-utils. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[0.11.0] + +- Added APIs to create a system tray at runtime. + - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09 +- Update windows to 0.39.0 and webview2-com to 0.19.1. + - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03 + +## \[0.10.3] + +- Block remote URLs from accessing the IPC. + - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12 + +## \[0.10.2] + +- Added option to disable tray menu on left click on macOS. + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 + +## \[0.10.1] + +- Expose `platform::windows_version` function. + - Bumped due to a bump in tauri-utils. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[0.10.0] + +- Added `fn new` constructors for `PhysicalSize`, `LogicalSize`, `PhysicalPosition` and `LogicalPosition` and missing conversion methods. + - [c7d13a1c](https://www.github.com/tauri-apps/tauri/commit/c7d13a1c60cdbe0c42834ea059321d7a3a7f01a0) feat(core): add missing methods to the dpi module ([#4393](https://www.github.com/tauri-apps/tauri/pull/4393)) on 2022-06-19 +- Implement `raw_window_handle::HasRawWindowHandle` on Linux. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- Removed the `hwnd` and `ns_window` functions from `Dispatch` in favor of `raw_window_handle`. + - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26 +- The theme API is now implemented on macOS 10.14+. + - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17 + +## \[0.9.0] + +- Upgrade to `stable`! + - Bumped due to a bump in tauri-utils. + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[0.8.1] + +- Add `Menu::os_default` which will create a menu filled with default menu items and submenus. + - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15 + +## \[0.8.0] + +- Removed `TrayIcon` and renamed `WindowIcon` to `Icon`, a shared type for both icons. + - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14 + +## \[0.7.0] + +- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`. + - Bumped due to a bump in tauri-utils. + - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10 + +## \[0.6.0] + +- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+. + - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24 + +## \[0.5.1] + +- Fix `.mjs` not being recognised as a file extension for JavaScript files (`text/javascript`). + - [45c45253](https://www.github.com/tauri-apps/tauri/commit/45c45253866ce0de317a6a547af3ea0434d4bcac) fix: add mjs mime type (fix: [#4098](https://www.github.com/tauri-apps/tauri/pull/4098)) ([#4108](https://www.github.com/tauri-apps/tauri/pull/4108)) on 2022-05-13 + +## \[0.5.0] + +- Expose methods to access the underlying native handles of the webview. + - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04 + +## \[0.4.0] + +- The `AboutMetadata` string setters now take `impl Into`. + - [b14aa896](https://www.github.com/tauri-apps/tauri/commit/b14aa89673c3563522e5c04baf9630fa1c4739b0) feat(core): improve `AboutMetadata` setters on 2022-03-29 +- \**Breaking change::* Added the `clipboard` Cargo feature. + - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24 +- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`. + - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21 +- \**Breaking change::* Added the `global-shortcut` Cargo feature. + - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24 +- Added `WindowEvent::ThemeChanged(theme)`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` getter on `Window`. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 +- Added `theme` setter to the WindowBuilder. + - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21 + +## \[0.3.4] + +- Added `close_devtools` and `is_devtools_open` APIs to the `Dispatch` trait. + - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28 +- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`. + - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28 +- Support window parenting on macOS + - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23 +- The file drop event is now part of the `WindowEvent` enum instead of a having a dedicated handler. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 +- **Breaking change:** Use the dedicated `WindowEvent` enum on `RunEvent`. + - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28 +- Added `create_proxy` to the `Runtime` and `RuntimeHandle` traits. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Force `Error` boxed errors to be `Sync`. + - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22 +- **Breaking change:** Move the `FileDropEvent` struct to the `window` module. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 +- Allow specifying a user event type for the event loop message. + - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15 +- Added the `WindowEvent::FileDrop` variant. + - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13 + +## \[0.3.3] + +- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features. + - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05 +- The `PendingWindow::new` and `PendingWindow::with_config` functions now return `Result` validating the window label. + - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03 + +## \[0.3.2] + +- Fix requirements for `RuntimeHandle`, `ClipboardManager`, `GlobalShortcutHandle` and `TrayHandle`. + - [84895a9c](https://www.github.com/tauri-apps/tauri/commit/84895a9cd270fc743e236d0f4d4cd6210b24a30f) fix(runtime): trait requirements ([#3489](https://www.github.com/tauri-apps/tauri/pull/3489)) on 2022-02-17 + +## \[0.3.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - Bumped due to a bump in tauri-utils. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[0.3.0] + +- Replace `WindowBuilder`'s `has_menu` with `get_menu`. + - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08 +- The `run_return` API is now available on Linux. + - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07 +- Add `Menu::with_items` constructor, taking an iterator of `MenuEntry`. + - [7cc95e10](https://www.github.com/tauri-apps/tauri/commit/7cc95e10ec66d8b155e9bb7f89cf73df56d1f107) feat(core): add `Menu::with_items`, closes [#2807](https://www.github.com/tauri-apps/tauri/pull/2807) ([#2966](https://www.github.com/tauri-apps/tauri/pull/2966)) on 2021-12-27 +- Change event loop callbacks definition to allow callers to move in mutable values. + - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27 +- Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows). + - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07 +- Added `run_on_main_thread` API on `RuntimeHandle`. + - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04 +- **Breaking change:** Renamed the `RPC` interface to `IPC`. + - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09 +- Added `open_devtools` to the `Dispatcher` trait. + - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- The window label is now validated and must be alphanumeric, resulting in a panic if it isn't. + - [680554de](https://www.github.com/tauri-apps/tauri/commit/680554de3ef6b7fccf87c441ad355cfef7aab6fe) feat: validate window label \[TRI-021] ([#13](https://www.github.com/tauri-apps/tauri/pull/13)) on 2021-10-23 +- Added `clipboard` field on the `WebviewAttributes` struct, which must be set to `true` to enable clipboard access on the webview. + - [d42ccfb3](https://www.github.com/tauri-apps/tauri/commit/d42ccfb34f71851dfeb22fe74c83a8bdbddb5550) feat: add `clipboard` flag to `WebviewAttributes` \[TRI-032] ([#12](https://www.github.com/tauri-apps/tauri/pull/12)) on 2021-10-23 +- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches. + - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24 +- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`. + - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11 + +## \[0.2.1] + +- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttributes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure. + - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23 +- Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing. + - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23 + +## \[0.2.0] + +- Fix blur/focus events being incorrect on Windows. + - [d832d575](https://www.github.com/tauri-apps/tauri/commit/d832d575d9b03a0ff78accabe4631cc638c08c3b) fix(windows): use webview events on windows ([#2277](https://www.github.com/tauri-apps/tauri/pull/2277)) on 2021-07-23 + +- Add `ExitRequested` event that allows preventing the app from exiting when all windows are closed, and an `AppHandle.exit()` function to exit the app manually. + - [892c63a0](https://www.github.com/tauri-apps/tauri/commit/892c63a0538f8d62680dce5848657128ad6b7af3) feat([#2287](https://www.github.com/tauri-apps/tauri/pull/2287)): Add `ExitRequested` event to let users prevent app from exiting ([#2293](https://www.github.com/tauri-apps/tauri/pull/2293)) on 2021-08-09 + +- Fixes minimum window height being used as maximum height. + - [e3f99165](https://www.github.com/tauri-apps/tauri/commit/e3f9916526b226866137cb663e5cafab2b6a0e01) fix(core) minHeight being used as maxHeight ([#2247](https://www.github.com/tauri-apps/tauri/pull/2247)) on 2021-07-19 + +- Update gtk and its related libraries to v0.14. This also remove requirements of `clang` as build dependency. + - [63ad3039](https://www.github.com/tauri-apps/tauri/commit/63ad303903bbee7c9a7382413b342e2a05d3ea75) chore(linux): bump gtk to v0.14 ([#2361](https://www.github.com/tauri-apps/tauri/pull/2361)) on 2021-08-07 + +- Implement `Debug` on public API structs and enums. + - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11 + +- Panic when a dispatcher getter method (`Window`, `GlobalShortcutHandle`, `ClipboardManager` and `MenuHandle` APIs) is called on the main thread. + - [50ffdc06](https://www.github.com/tauri-apps/tauri/commit/50ffdc06fbde56aba32b4291fd130104935d1408) feat(core): panic when a dispatcher getter is used on the main thread ([#2455](https://www.github.com/tauri-apps/tauri/pull/2455)) on 2021-08-16 + +- Remove menu feature flag since there's no package dependency need to be installed on any platform anymore. + - [f81ebddf](https://www.github.com/tauri-apps/tauri/commit/f81ebddfcc1aea0d4989706aef43538e8ea98bea) feat: remove menu feature flag ([#2415](https://www.github.com/tauri-apps/tauri/pull/2415)) on 2021-08-13 + +- Adds `Resumed` and `MainEventsCleared` variants to the `RunEvent` enum. + - [6be3f433](https://www.github.com/tauri-apps/tauri/commit/6be3f4339168651fe4e003b09f7d181fd12cd5a8) feat(core): add `Resumed` and `MainEventsCleared` events, closes [#2127](https://www.github.com/tauri-apps/tauri/pull/2127) ([#2439](https://www.github.com/tauri-apps/tauri/pull/2439)) on 2021-08-15 + +- Adds `set_activation_policy` API to the `Runtime` trait (macOS only). + - [4a031add](https://www.github.com/tauri-apps/tauri/commit/4a031add69014a1f3823f4ea19b172a2557f6794) feat(core): expose `set_activation_policy`, closes [#2258](https://www.github.com/tauri-apps/tauri/pull/2258) ([#2420](https://www.github.com/tauri-apps/tauri/pull/2420)) on 2021-08-13 + +- Allow creation of empty Window with `create_tao_window()` and management with `send_tao_window_event()` on the AppHandler. + - [88080855](https://www.github.com/tauri-apps/tauri/commit/8808085541a629b8e22b612a06cef01cf9b3722e) feat(window): Allow creation of Window without `wry` ([#2321](https://www.github.com/tauri-apps/tauri/pull/2321)) on 2021-07-29 + - [15566cfd](https://www.github.com/tauri-apps/tauri/commit/15566cfd64f5072fa4980a6ce5b33259958e9021) feat(core): add API to send wry window message to the event loop ([#2339](https://www.github.com/tauri-apps/tauri/pull/2339)) on 2021-08-02 + +- - Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color. + +- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however. + +- [426a6b49](https://www.github.com/tauri-apps/tauri/commit/426a6b49962de8faf061db2e820ac10fcbb300d6) feat(macOS): Implement tray icon template ([#2322](https://www.github.com/tauri-apps/tauri/pull/2322)) on 2021-07-29 + +- Add `Event::Ready` on the `run()` callback. Triggered once when the event loop is ready. + - [28c6b7ad](https://www.github.com/tauri-apps/tauri/commit/28c6b7adfe98e701b158e936eafb7541ddc700e0) feat: add `Event::Ready` ([#2433](https://www.github.com/tauri-apps/tauri/pull/2433)) on 2021-08-15 + +## \[0.1.4] + +- Allow preventing window close when the user requests it. + - [8157a68a](https://www.github.com/tauri-apps/tauri/commit/8157a68af1d94de1b90a14aa44139bb123b3436b) feat(core): allow listening to event loop events & prevent window close ([#2131](https://www.github.com/tauri-apps/tauri/pull/2131)) on 2021-07-06 +- Expose `gtk_window` getter. + - [e0a8e09c](https://www.github.com/tauri-apps/tauri/commit/e0a8e09cab6799eeb9ec524b5f7780d1e5a84299) feat(core): expose `gtk_window`, closes [#2083](https://www.github.com/tauri-apps/tauri/pull/2083) ([#2141](https://www.github.com/tauri-apps/tauri/pull/2141)) on 2021-07-02 +- `Params` has been removed, along with all the associated types on it. Functions that previously accepted those + associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If + you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the + explicit type and let the compiler infer it instead. + +`tauri`: + +- See `Params` note +- If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a + simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition + of functions/items that previously took it. If you are using a custom runtime, you *may* need to pass the runtime type + to these functions. +- If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all + methods that were previously taking the custom type now takes an `Into` or a `&str`. The types were already + required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change + affects you. + +`tauri-macros`: + +- (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when + the specified feature is enabled. + +`tauri-runtime`: + +- See `Params` note +- Removed `Params`, `MenuId`, `Tag`, `TagRef`. +- Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use. + - All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic. +- `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the + `Runtime` type directly +- `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly. +- (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`. + +`tauri-runtime-wry`: + +- See `Params` note +- update menu and runtime related types to the ones changed in `tauri-runtime`. + +`tauri-utils`: + +- `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into` to become trait object + safe. +- [fd8fab50](https://www.github.com/tauri-apps/tauri/commit/fd8fab507c8fa1b113b841af14c6693eb3955f6b) refactor(core): remove `Params` and replace with strings ([#2191](https://www.github.com/tauri-apps/tauri/pull/2191)) on 2021-07-15 + +## \[0.1.3] + +- `Window` is now `Send + Sync` on Windows. + - [fe32afcc](https://www.github.com/tauri-apps/tauri/commit/fe32afcc933920d6282ae1d63b041b182278a031) fix(core): `Window` must be `Send + Sync` on Windows, closes [#2078](https://www.github.com/tauri-apps/tauri/pull/2078) ([#2093](https://www.github.com/tauri-apps/tauri/pull/2093)) on 2021-06-27 + +## \[0.1.2] + +- Adds `clipboard` APIs (write and read text). + - [285bf64b](https://www.github.com/tauri-apps/tauri/commit/285bf64bf9569efb2df904c69c6df405ff0d62e2) feat(core): add clipboard writeText and readText APIs ([#2035](https://www.github.com/tauri-apps/tauri/pull/2035)) on 2021-06-21 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `focus` API to the WindowBuilder. + - [5f351622](https://www.github.com/tauri-apps/tauri/commit/5f351622c7812ad1bb56ddb37364ccaa4124c24b) feat(core): add focus API to the WindowBuilder and WindowOptions, [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `is_decorated` getter on Window. + - [f58a2114](https://www.github.com/tauri-apps/tauri/commit/f58a2114fbfd5307c349f05c88f2e08fd8baa8aa) feat(core): add `is_decorated` Window getter on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `is_resizable` getter on Window. + - [1e8af280](https://www.github.com/tauri-apps/tauri/commit/1e8af280c27f381828d6209722b10e889082fa00) feat(core): add `is_resizable` Window getter on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `is_visible` getter on Window. + - [36506c96](https://www.github.com/tauri-apps/tauri/commit/36506c967de82bc7ff453d11e6104ecf66d7a588) feat(core): add `is_visible` API on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `accelerator` method to the `CustomMenuItem` struct to define a keyboard shortcut for the menu item. + - [034c2601](https://www.github.com/tauri-apps/tauri/commit/034c26013bce0c7bbe6db067ea7fd24a53a5c998) feat(core): add `accelerator` method to `CustomMenuItem` ([#2043](https://www.github.com/tauri-apps/tauri/pull/2043)) on 2021-06-22 +- Adds global shortcut interfaces. + - [3280c4aa](https://www.github.com/tauri-apps/tauri/commit/3280c4aa91e50a8ccdd561a8b48a12a4a13ea8d5) refactor(core): global shortcut is now provided by `tao` ([#2031](https://www.github.com/tauri-apps/tauri/pull/2031)) on 2021-06-21 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `request_user_attention` API to the `Dispatcher` trait. + - [7dcca6e9](https://www.github.com/tauri-apps/tauri/commit/7dcca6e9281182b11ad3d4a79871f09b30b9b419) feat(core): add `request_user_attention` API, closes [#2023](https://www.github.com/tauri-apps/tauri/pull/2023) ([#2026](https://www.github.com/tauri-apps/tauri/pull/2026)) on 2021-06-20 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `fn run_iteration` (macOS and Windows only) to the Runtime trait. + - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21 +- Adds `show_menu`, `hide_menu` and `is_menu_visible` APIs to the `Dispatcher` trait. + - [954460c5](https://www.github.com/tauri-apps/tauri/commit/954460c5205d57444ef4b1412051fbedf3e38676) feat(core): MenuHandle `show`, `hide`, `is_visible` and `toggle` APIs ([#1958](https://www.github.com/tauri-apps/tauri/pull/1958)) on 2021-06-15 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `set_focus` API on Window. + - [bb6992f8](https://www.github.com/tauri-apps/tauri/commit/bb6992f888196ca7c87bb2fe74ad2bd8bf393e05) feat(core): add `set_focus` window API, fixes [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `set_skip_taskbar` API on Window. + - [e06aa277](https://www.github.com/tauri-apps/tauri/commit/e06aa277384450cfef617c0e57b0d5d403bb1e7f) feat(core): add `set_skip_taskbar` API on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `skip_taskbar` API to the WindowBuilder. + - [5525b03a](https://www.github.com/tauri-apps/tauri/commit/5525b03a78a2232c650043fbd9894ce1553cad41) feat(core): add `skip_taskbar` API to the WindowBuilder/WindowOptions on 2021-05-30 + - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23 +- Adds `Window#center` and `WindowBuilder#center` APIs. + - [5cba6eb4](https://www.github.com/tauri-apps/tauri/commit/5cba6eb4d28d53f06855d60d4d0eae6b95233ccf) feat(core): add window `center` API, closes [#1822](https://www.github.com/tauri-apps/tauri/pull/1822) ([#1954](https://www.github.com/tauri-apps/tauri/pull/1954)) on 2021-06-05 +- Adds `parent_window` and `owner_window` setters to the `WindowBuilder` (Windows only). + - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21 +- Adds window native handle getter (HWND on Windows). + - [abf78c58](https://www.github.com/tauri-apps/tauri/commit/abf78c5860cdc52fbfd2bc5dbca29a864e2da8f9) fix(core): set parent window handle on dialogs, closes [#1876](https://www.github.com/tauri-apps/tauri/pull/1876) ([#1889](https://www.github.com/tauri-apps/tauri/pull/1889)) on 2021-05-21 + +## \[0.1.1] + +- Fixes `system-tray` feature usage. + - [1ab8dd9](https://www.github.com/tauri-apps/tauri/commit/1ab8dd93e670d2a2d070c7a6ec48308a0ab32f1a) fix(core): `system-tray` cargo feature usage, fixes [#1798](https://www.github.com/tauri-apps/tauri/pull/1798) ([#1801](https://www.github.com/tauri-apps/tauri/pull/1801)) on 2021-05-12 + +## \[0.1.0] + +- **Breaking:** `Context` fields are now private, and is expected to be created through `Context::new(...)`. + All fields previously available through `Context` are now public methods. + - [5542359](https://www.github.com/tauri-apps/tauri/commit/55423590ddbf560684dab6a0214acf95aadfa8d2) refactor(core): Context fields now private, Icon used on all platforms ([#1774](https://www.github.com/tauri-apps/tauri/pull/1774)) on 2021-05-11 +- `tauri-runtime` crate initial release. + - [665ec1d](https://www.github.com/tauri-apps/tauri/commit/665ec1d4a199ad06e369221da187dc838da71cbf) refactor: move runtime to `tauri-runtime` crate ([#1751](https://www.github.com/tauri-apps/tauri/pull/1751)) on 2021-05-09 + - [45a7a11](https://www.github.com/tauri-apps/tauri/commit/45a7a111e0cf9d9956d713cc9a99fa7a5313eec7) feat(core): add `tauri-wry` crate ([#1756](https://www.github.com/tauri-apps/tauri/pull/1756)) on 2021-05-09 diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/Cargo.toml b/apps/tauri/src-tauri/vendor/tauri-runtime/Cargo.toml new file mode 100644 index 0000000..c1d32a7 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "tauri-runtime" +version = "2.11.0" +description = "Runtime for Tauri applications" +exclude = ["CHANGELOG.md", "/target"] +readme = "README.md" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[package.metadata.docs.rs] +all-features = true +default-target = "x86_64-unknown-linux-gnu" +targets = [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-linux-android", + "x86_64-apple-ios", +] + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" +tauri-utils = { version = "2.9.0", path = "../tauri-utils" } +http = "1" +raw-window-handle = "0.6" +url = { version = "2" } +dpi = { version = "0.1", features = ["serde"] } +# WARNING: cookie::Cookie is re-exported so bumping this is a breaking change, documented to be done as a minor bump +cookie = "0.18" + +[target."cfg(windows)".dependencies.windows] +version = "0.61" +features = ["Win32_Foundation", "Win32_System_WinRT"] + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +gtk = { version = "0.18", features = ["v3_24"] } + +[target."cfg(target_os = \"android\")".dependencies] +jni = "0.21" + +[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies] +objc2 = "0.6" +objc2-ui-kit = { version = "0.3.0", default-features = false, features = [ + "UIView", + "UIResponder", + "UIScene", + "UISceneOptions", +] } + +[target."cfg(target_os = \"macos\")".dependencies] +url = "2" +objc2 = "0.6" +[features] +devtools = [] +macos-private-api = [] diff --git a/licenses/@tauri-apps_api@2.10.1/LICENSE_APACHE-2.0 b/apps/tauri/src-tauri/vendor/tauri-runtime/LICENSE_APACHE-2.0 similarity index 100% rename from licenses/@tauri-apps_api@2.10.1/LICENSE_APACHE-2.0 rename to apps/tauri/src-tauri/vendor/tauri-runtime/LICENSE_APACHE-2.0 diff --git a/licenses/@tauri-apps_api@2.10.1/LICENSE_MIT b/apps/tauri/src-tauri/vendor/tauri-runtime/LICENSE_MIT similarity index 100% rename from licenses/@tauri-apps_api@2.10.1/LICENSE_MIT rename to apps/tauri/src-tauri/vendor/tauri-runtime/LICENSE_MIT diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/README.md b/apps/tauri/src-tauri/vendor/tauri-runtime/README.md new file mode 100644 index 0000000..450fbd9 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/README.md @@ -0,0 +1,44 @@ +# tauri-runtime + + + +[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri) +[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S) + +[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) + +[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) +[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri) + +| Component | Version | +| ------------- | -------------------------------------------------------------------------------------------------------------- | +| tauri-runtime | [![](https://img.shields.io/crates/v/tauri-runtime?style=flat-square)](https://crates.io/crates/tauri-runtime) | + +## About Tauri + +Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily. + +Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task. + +## This module + +This is the glue layer between tauri itself and lower level webview libraries. +None of the exposed API of this crate is stable, and it may break semver +compatibility in the future. The major version only signifies the intended Tauri version. + +To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document. + +## Semver + +**tauri** is following [Semantic Versioning 2.0](https://semver.org/). + +## Licenses + +Code: (c) 2021 - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. + +Logo: CC-BY-NC-ND + +- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum) diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/build.rs b/apps/tauri/src-tauri/vendor/tauri-runtime/build.rs new file mode 100644 index 0000000..9974c50 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/build.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + println!("cargo:rustc-check-cfg=cfg({alias})"); + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} + +fn main() { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/src/dpi.rs b/apps/tauri/src-tauri/vendor/tauri-runtime/src/dpi.rs new file mode 100644 index 0000000..e30d489 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/src/dpi.rs @@ -0,0 +1,60 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +pub use dpi::*; +use serde::Serialize; + +/// A rectangular region. +#[derive(Clone, Copy, Debug, Serialize)] +pub struct Rect { + /// Rect position. + pub position: dpi::Position, + /// Rect size. + pub size: dpi::Size, +} + +impl Default for Rect { + fn default() -> Self { + Self { + position: Position::Logical((0, 0).into()), + size: Size::Logical((0, 0).into()), + } + } +} + +/// A rectangular region in physical pixels. +#[derive(Clone, Copy, Debug, Serialize)] +pub struct PhysicalRect { + /// Rect position. + pub position: dpi::PhysicalPosition

, + /// Rect size. + pub size: dpi::PhysicalSize, +} + +impl Default for PhysicalRect { + fn default() -> Self { + Self { + position: (0, 0).into(), + size: (0, 0).into(), + } + } +} + +/// A rectangular region in logical pixels. +#[derive(Clone, Copy, Debug, Serialize)] +pub struct LogicalRect { + /// Rect position. + pub position: dpi::LogicalPosition

, + /// Rect size. + pub size: dpi::LogicalSize, +} + +impl Default for LogicalRect { + fn default() -> Self { + Self { + position: (0, 0).into(), + size: (0, 0).into(), + } + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/src/lib.rs b/apps/tauri/src-tauri/vendor/tauri-runtime/src/lib.rs new file mode 100644 index 0000000..2780cf0 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/src/lib.rs @@ -0,0 +1,1048 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Internal runtime between Tauri and the underlying webview runtime. +//! +//! None of the exposed API of this crate is stable, and it may break semver +//! compatibility in the future. The major version only signifies the intended Tauri version. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +use raw_window_handle::DisplayHandle; +use serde::Deserialize; +use std::{borrow::Cow, fmt::Debug, sync::mpsc::Sender}; +use tauri_utils::Theme; +use tauri_utils::config::Color; +use url::Url; +use webview::{DetachedWebview, PendingWebview}; + +/// UI scaling utilities. +pub mod dpi; +/// Types useful for interacting with a user's monitors. +pub mod monitor; +pub mod webview; +pub mod window; + +use dpi::{PhysicalPosition, PhysicalSize, Position, Rect, Size}; +use monitor::Monitor; +use window::{ + CursorIcon, DetachedWindow, PendingWindow, RawWindow, WebviewEvent, WindowEvent, + WindowSizeConstraints, +}; +use window::{WindowBuilder, WindowId}; + +use http::{ + header::{InvalidHeaderName, InvalidHeaderValue}, + method::InvalidMethod, + status::InvalidStatusCode, +}; + +/// Cookie extraction +pub use cookie::Cookie; + +pub type WindowEventId = u32; +pub type WebviewEventId = u32; + +/// Progress bar status. +#[derive(Debug, Clone, Copy, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ProgressBarStatus { + /// Hide progress bar. + None, + /// Normal state. + Normal, + /// Indeterminate state. **Treated as Normal on Linux and macOS** + Indeterminate, + /// Paused state. **Treated as Normal on Linux** + Paused, + /// Error state. **Treated as Normal on Linux** + Error, +} + +/// Progress Bar State +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProgressBarState { + /// The progress bar status. + pub status: Option, + /// The progress bar progress. This can be a value ranging from `0` to `100` + pub progress: Option, + /// The `.desktop` filename with the Unity desktop window manager, for example `myapp.desktop` **Linux Only** + pub desktop_filename: Option, +} + +/// Type of user attention requested on a window. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(tag = "type")] +pub enum UserAttentionType { + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(tag = "type")] +pub enum DeviceEventFilter { + /// Always filter out device events. + Always, + /// Filter out device events while the window is not focused. + #[default] + Unfocused, + /// Report all device events regardless of window focus. + Never, +} + +/// Defines the orientation that a window resize will be performed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +pub enum ResizeDirection { + East, + North, + NorthEast, + NorthWest, + South, + SouthEast, + SouthWest, + West, +} + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + /// Failed to create webview. + #[error("failed to create webview: {0}")] + CreateWebview(Box), + // TODO: Make it take an error like `CreateWebview` in v3 + /// Failed to create window. + #[error("failed to create window")] + CreateWindow, + /// The given window label is invalid. + #[error("Window labels must only include alphanumeric characters, `-`, `/`, `:` and `_`.")] + InvalidWindowLabel, + /// Failed to send message to webview. + #[error("failed to send message to the webview")] + FailedToSendMessage, + /// Failed to receive message from webview. + #[error("failed to receive message from webview")] + FailedToReceiveMessage, + /// Failed to serialize/deserialize. + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + /// Failed to load window icon. + #[error("invalid icon: {0}")] + InvalidIcon(Box), + /// Failed to get monitor on window operation. + #[error("failed to get monitor")] + FailedToGetMonitor, + /// Failed to get cursor position. + #[error("failed to get cursor position")] + FailedToGetCursorPosition, + #[error("Invalid header name: {0}")] + InvalidHeaderName(#[from] InvalidHeaderName), + #[error("Invalid header value: {0}")] + InvalidHeaderValue(#[from] InvalidHeaderValue), + #[error("Invalid status code: {0}")] + InvalidStatusCode(#[from] InvalidStatusCode), + #[error("Invalid method: {0}")] + InvalidMethod(#[from] InvalidMethod), + #[error("Infallible error, something went really wrong: {0}")] + Infallible(#[from] std::convert::Infallible), + #[error("the event loop has been closed")] + EventLoopClosed, + #[error("Invalid proxy url")] + InvalidProxyUrl, + #[error("window not found")] + WindowNotFound, + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[error("failed to remove data store")] + FailedToRemoveDataStore, + #[error("Could not find the webview runtime, make sure it is installed")] + WebviewRuntimeNotInstalled, +} + +/// Result type. +pub type Result = std::result::Result; + +/// Window icon. +#[derive(Debug, Clone)] +pub struct Icon<'a> { + /// RGBA bytes of the icon. + pub rgba: Cow<'a, [u8]>, + /// Icon width. + pub width: u32, + /// Icon height. + pub height: u32, +} + +impl<'a> Icon<'a> { + pub fn into_owned(self) -> Icon<'static> { + Icon { + rgba: std::borrow::Cow::Owned(self.rgba.into_owned()), + width: self.width, + height: self.height, + } + } +} + +/// A type that can be used as an user event. +pub trait UserEvent: Debug + Clone + Send + 'static {} + +impl UserEvent for T {} + +/// Event triggered on the event loop run. +#[derive(Debug)] +#[non_exhaustive] +pub enum RunEvent { + /// Event loop is exiting. + Exit, + /// Event loop is about to exit + ExitRequested { + /// The exit code. + code: Option, + tx: Sender, + }, + /// An event associated with a window. + WindowEvent { + /// The window label. + label: String, + /// The detailed event. + event: WindowEvent, + }, + /// An event associated with a webview. + WebviewEvent { + /// The webview label. + label: String, + /// The detailed event. + event: WebviewEvent, + }, + /// Application ready. + Ready, + /// Sent if the event loop is being resumed. + Resumed, + /// Emitted when all of the event loop's input events have been processed and redraw processing is about to begin. + /// + /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the "main body" of your event loop. + MainEventsCleared, + /// Emitted when the user wants to open the specified resource with the app. + Opened { urls: Vec }, + /// Emitted when the NSApplicationDelegate's applicationShouldHandleReopen gets called + #[cfg(target_os = "macos")] + Reopen { + /// Indicates whether the NSApplication object found any visible windows in your application. + has_visible_windows: bool, + }, + /// A custom event defined by the user. + UserEvent(T), + /// Emitted when a scene is requested by the system. + /// + /// This event is emitted when a scene is requested by the system. + /// Scenes created by [`Window::new`] are not emitted with this event. + /// It is also not emitted for the main scene. + #[cfg(target_os = "ios")] + SceneRequested { + /// Scene that was requested by the system. + scene: objc2::rc::Retained, + /// Options that were used to request the scene. + /// + /// This lets you determine why the scene was requested. + options: objc2::rc::Retained, + }, +} + +/// Action to take when the event loop is about to exit +#[derive(Debug)] +pub enum ExitRequestedEventAction { + /// Prevent the event loop from exiting + Prevent, +} + +/// Application's activation policy. Corresponds to NSApplicationActivationPolicy. +#[cfg(target_os = "macos")] +#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] +#[non_exhaustive] +pub enum ActivationPolicy { + /// Corresponds to NSApplicationActivationPolicyRegular. + Regular, + /// Corresponds to NSApplicationActivationPolicyAccessory. + Accessory, + /// Corresponds to NSApplicationActivationPolicyProhibited. + Prohibited, +} + +/// A [`Send`] handle to the runtime. +pub trait RuntimeHandle: Debug + Clone + Send + Sync + Sized + 'static { + type Runtime: Runtime; + + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + fn create_proxy(&self) -> >::EventLoopProxy; + + /// Sets the activation policy for the application. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> Result<()>; + + /// Sets the dock visibility for the application. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn set_dock_visibility(&self, visible: bool) -> Result<()>; + + /// Requests an exit of the event loop. + fn request_exit(&self, code: i32) -> Result<()>; + + /// Create a new window. + fn create_window( + &self, + pending: PendingWindow, + after_window_creation: Option, + ) -> Result>; + + /// Create a new webview. + fn create_webview( + &self, + window_id: WindowId, + pending: PendingWebview, + ) -> Result>; + + /// Run a task on the main thread. + fn run_on_main_thread(&self, f: F) -> Result<()>; + + /// Get a handle to the display controller of the windowing system. + fn display_handle( + &self, + ) -> std::result::Result, raw_window_handle::HandleError>; + + /// Returns the primary monitor of the system. + /// + /// Returns None if it can't identify any monitor as a primary one. + fn primary_monitor(&self) -> Option; + + /// Returns the monitor that contains the given point. + fn monitor_from_point(&self, x: f64, y: f64) -> Option; + + /// Returns the list of all the monitors available on the system. + fn available_monitors(&self) -> Vec; + + /// Get the cursor position relative to the top-left hand corner of the desktop. + fn cursor_position(&self) -> Result>; + + /// Sets the app theme. + fn set_theme(&self, theme: Option); + + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn show(&self) -> Result<()>; + + /// Hides the application. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn hide(&self) -> Result<()>; + + /// Change the device event filter mode. + /// + /// See [Runtime::set_device_event_filter] for details. + /// + /// ## Platform-specific + /// + /// See [Runtime::set_device_event_filter] for details. + fn set_device_event_filter(&self, filter: DeviceEventFilter); + + /// Finds an Android class in the project scope. + #[cfg(target_os = "android")] + fn find_class<'a>( + &self, + env: &mut jni::JNIEnv<'a>, + activity: &jni::objects::JObject<'_>, + name: impl Into, + ) -> std::result::Result, jni::errors::Error>; + + /// Dispatch a closure to run on the Android context. + /// + /// The closure takes the JNI env, the Android activity instance and the possibly null webview. + #[cfg(target_os = "android")] + fn run_on_android_context(&self, f: F) + where + F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static; + + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))] + fn fetch_data_store_identifiers) + Send + 'static>( + &self, + cb: F, + ) -> Result<()>; + + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))] + fn remove_data_store) + Send + 'static>( + &self, + uuid: [u8; 16], + cb: F, + ) -> Result<()>; +} + +pub trait EventLoopProxy: Debug + Clone + Send + Sync { + fn send_event(&self, event: T) -> Result<()>; +} + +#[derive(Default)] +pub struct RuntimeInitArgs { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub app_id: Option, + #[cfg(windows)] + pub msg_hook: Option bool + 'static>>, + pub identifier: String, + pub custom_schemes: Vec, + pub platform_specific_attributes: Vec, +} + +/// Builds platform-specific init attributes from config. These are merged with +/// user-provided attributes before runtime creation. Implement for the runtime's +/// [`Runtime::PlatformSpecificInitAttribute`]; default is to return none (implement for `()`). +pub trait InitAttribute: Send + Sync + 'static { + /// Returns attributes derived from config (e.g. deep-link schemes). Merged with user attrs by the app. + fn new(config: &tauri_utils::config::Config) -> Result> + where + Self: Sized; +} + +impl InitAttribute for () { + fn new(_config: &tauri_utils::config::Config) -> Result> { + Ok(vec![]) + } +} + +/// The webview runtime interface. +pub trait Runtime: Debug + Sized + 'static { + /// The window message dispatcher. + type WindowDispatcher: WindowDispatch; + /// The webview message dispatcher. + type WebviewDispatcher: WebviewDispatch; + /// The runtime handle type. + type Handle: RuntimeHandle; + /// The proxy type. + type EventLoopProxy: EventLoopProxy; + /// The platform specific webview attributes. + type PlatformSpecificWebviewAttribute: Send + Sync + 'static; + /// The platform specific runtime init arguments. Must implement [`InitAttribute`]. + type PlatformSpecificInitAttribute: InitAttribute + Send + Sync + 'static; + /// Data about the window that requested the new window for [`PendingWebview::new_window_handler`]. + type WindowOpener: Send + Sync + Debug; + + /// Creates a new webview runtime. Must be used on the main thread. + fn new(args: RuntimeInitArgs) -> Result; + + /// Creates a new webview runtime on any thread. + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))) + )] + fn new_any_thread(args: RuntimeInitArgs) -> Result; + + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + fn create_proxy(&self) -> Self::EventLoopProxy; + + /// Gets a runtime handle. + fn handle(&self) -> Self::Handle; + + /// Create a new window. + fn create_window( + &self, + pending: PendingWindow, + after_window_creation: Option, + ) -> Result>; + + /// Create a new webview. + fn create_webview( + &self, + window_id: WindowId, + pending: PendingWebview, + ) -> Result>; + + /// Returns the primary monitor of the system. + /// + /// Returns None if it can't identify any monitor as a primary one. + fn primary_monitor(&self) -> Option; + + /// Returns the monitor that contains the given point. + fn monitor_from_point(&self, x: f64, y: f64) -> Option; + + /// Returns the list of all the monitors available on the system. + fn available_monitors(&self) -> Vec; + + /// Get the cursor position relative to the top-left hand corner of the desktop. + fn cursor_position(&self) -> Result>; + + /// Sets the app theme. + fn set_theme(&self, theme: Option); + + /// Sets the activation policy for the application. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn set_activation_policy(&mut self, activation_policy: ActivationPolicy); + + /// Sets the dock visibility for the application. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn set_dock_visibility(&mut self, visible: bool); + + /// Shows the application, but does not automatically focus it. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn show(&self); + + /// Hides the application. + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] + fn hide(&self); + + /// Change the device event filter mode. + /// + /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`] + /// will ignore them by default for unfocused windows on Windows. This method allows changing + /// the filter to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - ** Linux / macOS / iOS / Android**: Unsupported. + /// + /// [`tao`]: https://crates.io/crates/tao + fn set_device_event_filter(&mut self, filter: DeviceEventFilter); + + /// Returns the URL for a custom scheme. + fn custom_scheme_url(scheme: &str, _https: bool) -> String; + + /// Runs an iteration of the runtime event loop and returns control flow to the caller. + #[cfg(desktop)] + fn run_iteration) + 'static>(&mut self, callback: F); + + /// Equivalent to [`Runtime::run`] but returns the exit code instead of exiting the process. + fn run_return) + 'static>(self, callback: F) -> i32; + + /// Run the webview runtime. + fn run) + 'static>(self, callback: F); +} + +/// Webview dispatcher. A thread-safe handle to the webview APIs. +pub trait WebviewDispatch: Debug + Clone + Send + Sync + Sized + 'static { + /// The runtime this [`WebviewDispatch`] runs under. + type Runtime: Runtime; + + /// Run a task on the main thread. + fn run_on_main_thread(&self, f: F) -> Result<()>; + + /// Registers a webview event handler. + fn on_webview_event(&self, f: F) -> WebviewEventId; + + /// Runs a closure with the platform webview object as argument. + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()>; + + /// Open the web inspector which is usually called devtools. + #[cfg(any(debug_assertions, feature = "devtools"))] + fn open_devtools(&self); + + /// Close the web inspector which is usually called devtools. + #[cfg(any(debug_assertions, feature = "devtools"))] + fn close_devtools(&self); + + /// Gets the devtools window's current open state. + #[cfg(any(debug_assertions, feature = "devtools"))] + fn is_devtools_open(&self) -> Result; + + // GETTERS + + /// Returns the webview's current URL. + fn url(&self) -> Result; + + /// Returns the webview's bounds. + fn bounds(&self) -> Result; + + /// Returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the window. + fn position(&self) -> Result>; + + /// Returns the physical size of the webviews's client area. + fn size(&self) -> Result>; + + // SETTER + + /// Navigate to the given URL. + fn navigate(&self, url: Url) -> Result<()>; + + /// Reloads the current page. + fn reload(&self) -> Result<()>; + + fn go_back(&self) -> Result<()>; + + fn can_go_back(&self) -> Result; + + fn go_forward(&self) -> Result<()>; + + fn can_go_forward(&self) -> Result; + + /// Opens the dialog to prints the contents of the webview. + fn print(&self) -> Result<()>; + + /// Closes the webview. + fn close(&self) -> Result<()>; + + /// Sets the webview's bounds. + fn set_bounds(&self, bounds: Rect) -> Result<()>; + + /// Resizes the webview. + fn set_size(&self, size: Size) -> Result<()>; + + /// Updates the webview position. + fn set_position(&self, position: Position) -> Result<()>; + + /// Bring the window to front and focus the webview. + fn set_focus(&self) -> Result<()>; + + /// Hide the webview + fn hide(&self) -> Result<()>; + + /// Show the webview + fn show(&self) -> Result<()>; + + /// Executes javascript on the window this [`WindowDispatch`] represents. + fn eval_script>(&self, script: S) -> Result<()>; + + /// Evaluate JavaScript with callback function on the webview this [`WebviewDispatch`] represents. + /// The evaluation result will be serialized into a JSON string and passed to the callback function. + /// + /// Exception is ignored because of the limitation on Windows. You can catch it yourself and return as string as a workaround. + fn eval_script_with_callback>( + &self, + script: S, + callback: impl Fn(String) + Send + 'static, + ) -> Result<()>; + + /// Moves the webview to the given window. + fn reparent(&self, window_id: WindowId) -> Result<()>; + + /// Get cookies for a particular url. + /// + /// # Stability + /// + /// See [WebviewDispatch::cookies]. + fn cookies_for_url(&self, url: Url) -> Result>>; + + /// Return all cookies in the cookie store. + /// + /// # Stability + /// + /// The return value of this function leverages [`cookie::Cookie`] which re-exports the cookie crate. + /// This dependency might receive updates in minor Tauri releases. + fn cookies(&self) -> Result>>; + + /// Set a cookie for the webview. + /// + /// # Stability + /// + /// See [WebviewDispatch::cookies]. + fn set_cookie(&self, cookie: cookie::Cookie<'_>) -> Result<()>; + + /// Delete a cookie for the webview. + /// + /// # Stability + /// + /// See [WebviewDispatch::cookies]. + fn delete_cookie(&self, cookie: cookie::Cookie<'_>) -> Result<()>; + + /// Sets whether the webview should automatically grow and shrink its size and position when the parent window resizes. + fn set_auto_resize(&self, auto_resize: bool) -> Result<()>; + + /// Set the webview zoom level + fn set_zoom(&self, scale_factor: f64) -> Result<()>; + + /// Set the webview background. + fn set_background_color(&self, color: Option) -> Result<()>; + + /// Clear all browsing data for this webview. + fn clear_all_browsing_data(&self) -> Result<()>; +} + +/// Window dispatcher. A thread-safe handle to the window APIs. +pub trait WindowDispatch: Debug + Clone + Send + Sync + Sized + 'static { + /// The runtime this [`WindowDispatch`] runs under. + type Runtime: Runtime; + + /// The window builder type. + type WindowBuilder: WindowBuilder; + + /// Run a task on the main thread. + fn run_on_main_thread(&self, f: F) -> Result<()>; + + /// Registers a window event handler. + fn on_window_event(&self, f: F) -> WindowEventId; + + // GETTERS + + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + fn scale_factor(&self) -> Result; + + /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. + fn inner_position(&self) -> Result>; + + /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. + fn outer_position(&self) -> Result>; + + /// Returns the physical size of the window's client area. + /// + /// The client area is the content of the window, excluding the title bar and borders. + fn inner_size(&self) -> Result>; + + /// Returns the physical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. + fn outer_size(&self) -> Result>; + + /// Gets the window's current fullscreen state. + fn is_fullscreen(&self) -> Result; + + /// Gets the window's current minimized state. + fn is_minimized(&self) -> Result; + + /// Gets the window's current maximized state. + fn is_maximized(&self) -> Result; + + /// Gets the window's current focus state. + fn is_focused(&self) -> Result; + + /// Gets the window's current decoration state. + fn is_decorated(&self) -> Result; + + /// Gets the window's current resizable state. + fn is_resizable(&self) -> Result; + + /// Gets the window's native maximize button state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + fn is_maximizable(&self) -> Result; + + /// Gets the window's native minimize button state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + fn is_minimizable(&self) -> Result; + + /// Gets the window's native close button state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + fn is_closable(&self) -> Result; + + /// Gets the window's current visibility state. + fn is_visible(&self) -> Result; + + /// Whether the window is enabled or disable. + fn is_enabled(&self) -> Result; + + /// Gets the window alwaysOnTop flag state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + fn is_always_on_top(&self) -> Result; + + /// Gets the window's current title. + fn title(&self) -> Result; + + /// Returns the monitor on which the window currently resides. + /// + /// Returns None if current monitor can't be detected. + fn current_monitor(&self) -> Result>; + + /// Returns the primary monitor of the system. + /// + /// Returns None if it can't identify any monitor as a primary one. + fn primary_monitor(&self) -> Result>; + + /// Returns the monitor that contains the given point. + fn monitor_from_point(&self, x: f64, y: f64) -> Result>; + + /// Returns the list of all the monitors available on the system. + fn available_monitors(&self) -> Result>; + + /// Returns the `ApplicationWindow` from gtk crate that is used by this window. + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn gtk_window(&self) -> Result; + + /// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window. + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn default_vbox(&self) -> Result; + + /// Returns the name of the Android activity associated with this window. + #[cfg(target_os = "android")] + fn activity_name(&self) -> Result; + + /// Returns the identifier of the UIScene tied to this UIWindow. + #[cfg(target_os = "ios")] + fn scene_identifier(&self) -> Result; + + /// Raw window handle. + fn window_handle( + &self, + ) -> std::result::Result, raw_window_handle::HandleError>; + + /// Returns the current window theme. + fn theme(&self) -> Result; + + // SETTERS + + /// Centers the window. + fn center(&self) -> Result<()>; + + /// Requests user attention to the window. + /// + /// Providing `None` will unset the request for user attention. + fn request_user_attention(&self, request_type: Option) -> Result<()>; + + /// Create a new window. + fn create_window( + &mut self, + pending: PendingWindow, + after_window_creation: Option, + ) -> Result>; + + /// Create a new webview. + fn create_webview( + &mut self, + pending: PendingWebview, + ) -> Result>; + + /// Updates the window resizable flag. + fn set_resizable(&self, resizable: bool) -> Result<()>; + + /// Enable or disable the window. + /// + /// ## Platform-specific + /// + /// - **Android / iOS**: Unsupported. + fn set_enabled(&self, enabled: bool) -> Result<()>; + + /// Updates the window's native maximize button state. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + fn set_maximizable(&self, maximizable: bool) -> Result<()>; + + /// Updates the window's native minimize button state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + fn set_minimizable(&self, minimizable: bool) -> Result<()>; + + /// Updates the window's native close button state. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + fn set_closable(&self, closable: bool) -> Result<()>; + + /// Updates the window title. + fn set_title>(&self, title: S) -> Result<()>; + + /// Maximizes the window. + fn maximize(&self) -> Result<()>; + + /// Unmaximizes the window. + fn unmaximize(&self) -> Result<()>; + + /// Minimizes the window. + fn minimize(&self) -> Result<()>; + + /// Unminimizes the window. + fn unminimize(&self) -> Result<()>; + + /// Shows the window. + fn show(&self) -> Result<()>; + + /// Hides the window. + fn hide(&self) -> Result<()>; + + /// Closes the window. + fn close(&self) -> Result<()>; + + /// Destroys the window. + fn destroy(&self) -> Result<()>; + + /// Updates the decorations flag. + fn set_decorations(&self, decorations: bool) -> Result<()>; + + /// Updates the shadow flag. + fn set_shadow(&self, enable: bool) -> Result<()>; + + /// Updates the window alwaysOnBottom flag. + fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()>; + + /// Updates the window alwaysOnTop flag. + fn set_always_on_top(&self, always_on_top: bool) -> Result<()>; + + /// Updates the window visibleOnAllWorkspaces flag. + fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()>; + + /// Set the window background. + fn set_background_color(&self, color: Option) -> Result<()>; + + /// Prevents the window contents from being captured by other apps. + fn set_content_protected(&self, protected: bool) -> Result<()>; + + /// Resizes the window. + fn set_size(&self, size: Size) -> Result<()>; + + /// Updates the window min inner size. + fn set_min_size(&self, size: Option) -> Result<()>; + + /// Updates the window max inner size. + fn set_max_size(&self, size: Option) -> Result<()>; + + /// Sets this window's minimum inner width. + fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()>; + + /// Updates the window position. + fn set_position(&self, position: Position) -> Result<()>; + + /// Updates the window fullscreen state. + fn set_fullscreen(&self, fullscreen: bool) -> Result<()>; + + #[cfg(target_os = "macos")] + fn set_simple_fullscreen(&self, enable: bool) -> Result<()>; + + /// Bring the window to front and focus. + fn set_focus(&self) -> Result<()>; + + /// Sets whether the window can be focused. + fn set_focusable(&self, focusable: bool) -> Result<()>; + + /// Updates the window icon. + fn set_icon(&self, icon: Icon) -> Result<()>; + + /// Whether to hide the window icon from the taskbar or not. + fn set_skip_taskbar(&self, skip: bool) -> Result<()>; + + /// Grabs the cursor, preventing it from leaving the window. + /// + /// There's no guarantee that the cursor will be hidden. You should + /// hide it by yourself if you want so. + fn set_cursor_grab(&self, grab: bool) -> Result<()>; + + /// Modifies the cursor's visibility. + /// + /// If `false`, this will hide the cursor. If `true`, this will show the cursor. + fn set_cursor_visible(&self, visible: bool) -> Result<()>; + + // Modifies the cursor icon of the window. + fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()>; + + /// Changes the position of the cursor in window coordinates. + fn set_cursor_position>(&self, position: Pos) -> Result<()>; + + /// Ignores the window cursor events. + fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()>; + + /// Starts dragging the window. + fn start_dragging(&self) -> Result<()>; + + /// Starts resize-dragging the window. + fn start_resize_dragging(&self, direction: ResizeDirection) -> Result<()>; + + /// Sets the badge count on the taskbar + /// The badge count appears as a whole for the application + /// Using `0` or using `None` will remove the badge + /// + /// ## Platform-specific + /// - **Windows:** Unsupported, use [`WindowDispatch::set_overlay_icon`] instead. + /// - **Android:** Unsupported. + /// - **iOS:** iOS expects i32, if the value is larger than i32::MAX, it will be clamped to i32::MAX. + fn set_badge_count(&self, count: Option, desktop_filename: Option) -> Result<()>; + + /// Sets the badge count on the taskbar **macOS only**. Using `None` will remove the badge + fn set_badge_label(&self, label: Option) -> Result<()>; + + /// Sets the overlay icon on the taskbar **Windows only**. Using `None` will remove the icon + /// + /// The overlay icon can be unique for each window. + fn set_overlay_icon(&self, icon: Option) -> Result<()>; + + /// Sets the taskbar progress state. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS**: Progress bar is app-wide and not specific to this window. Only supported desktop environments with `libunity` (e.g. GNOME). + /// - **iOS / Android:** Unsupported. + fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()>; + + /// Sets the title bar style. Available on macOS only. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / iOS / Android:** Unsupported. + fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()>; + + /// Change the position of the window controls. Available on macOS only. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / iOS / Android:** Unsupported. + fn set_traffic_light_position(&self, position: Position) -> Result<()>; + + /// Sets the theme for this window. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS**: Theme is app-wide and not specific to this window. + /// - **iOS / Android:** Unsupported. + fn set_theme(&self, theme: Option) -> Result<()>; +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/src/monitor.rs b/apps/tauri/src-tauri/vendor/tauri-runtime/src/monitor.rs new file mode 100644 index 0000000..9abda36 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/src/monitor.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::dpi::{PhysicalPosition, PhysicalRect, PhysicalSize}; + +/// Monitor descriptor. +#[derive(Debug, Clone)] +pub struct Monitor { + /// A human-readable name of the monitor. + /// `None` if the monitor doesn't exist anymore. + pub name: Option, + /// The monitor's resolution. + pub size: PhysicalSize, + /// The top-left corner position of the monitor relative to the larger full screen area. + pub position: PhysicalPosition, + /// The monitor's work_area. + pub work_area: PhysicalRect, + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + pub scale_factor: f64, +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/src/webview.rs b/apps/tauri/src-tauri/vendor/tauri-runtime/src/webview.rs new file mode 100644 index 0000000..c648bb2 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/src/webview.rs @@ -0,0 +1,820 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! A layer between raw [`Runtime`] webviews and Tauri. +//! +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use crate::window::WindowId; +use crate::{Rect, Runtime, UserEvent, window::is_label_valid}; + +use http::Request; +use tauri_utils::config::{ + BackgroundThrottlingPolicy, Color, ScrollBarStyle as ConfigScrollBarStyle, WebviewUrl, + WindowConfig, WindowEffectsConfig, +}; +use url::Url; + +use std::{ + borrow::Cow, + collections::HashMap, + hash::{Hash, Hasher}, + path::PathBuf, + sync::Arc, +}; + +pub type UriSchemeProtocolHandler = dyn Fn(&str, http::Request>, Box>) + Send>) + + Send + + Sync + + 'static; + +pub type WebResourceRequestHandler = + dyn Fn(http::Request>, &mut http::Response>) + Send + Sync; + +pub type NavigationHandler = dyn Fn(&Url) -> bool + Send; + +pub type NewWindowHandler = + dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send + Sync; + +pub type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send; + +pub type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static; + +pub type AddressChangedHandler = dyn Fn(&Url) + Send + Sync + 'static; + +pub type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync; + +#[cfg(any(target_os = "macos", target_os = "ios"))] +type OnWebContentProcessTerminateHandler = dyn Fn() + Send; + +#[cfg(target_os = "ios")] +type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option> + + Send + + Sync + + 'static; + +/// Download event. +pub enum DownloadEvent<'a> { + /// Download requested. + Requested { + /// The url being downloaded. + url: Url, + /// Represents where the file will be downloaded to. + /// Can be used to set the download location by assigning a new path to it. + /// The assigned path _must_ be absolute. + destination: &'a mut PathBuf, + }, + /// Download finished. + Finished { + /// The URL of the original download request. + url: Url, + /// Potentially representing the filesystem path the file was downloaded to. + path: Option, + /// Indicates if the download succeeded or not. + success: bool, + }, +} + +#[cfg(target_os = "android")] +pub struct CreationContext<'a, 'b> { + pub env: &'a mut jni::JNIEnv<'b>, + pub activity: &'a jni::objects::JObject<'b>, + pub webview: &'a jni::objects::JObject<'b>, +} + +/// Kind of event for the page load handler. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PageLoadEvent { + /// Page started to load. + Started, + /// Page finished loading. + Finished, +} + +/// Window features of a window requested to open. +#[derive(Debug)] +pub struct NewWindowFeatures> { + pub(crate) size: Option>, + pub(crate) position: Option>, + pub(crate) opener: R::WindowOpener, +} + +impl> NewWindowFeatures { + pub fn new( + size: Option>, + position: Option>, + opener: R::WindowOpener, + ) -> Self { + Self { + size, + position, + opener, + } + } + + /// Specifies the size of the content area + /// as defined by the user's operating system where the new window will be generated. + pub fn size(&self) -> Option> { + self.size + } + + /// Specifies the position of the window relative to the work area + /// as defined by the user's operating system where the new window will be generated. + pub fn position(&self) -> Option> { + self.position + } + + /// Returns information about the webview that initiated a new window request. + pub fn opener(&self) -> &R::WindowOpener { + &self.opener + } + + /// Returns information about the webview that initiated a new window request. + pub fn into_opener(self) -> R::WindowOpener { + self.opener + } +} + +/// Response for the new window request handler. +pub enum NewWindowResponse { + /// Allow the window to be opened with the default implementation. + Allow, + /// Allow the window to be opened, with the given window. + /// + /// The window must be created referencing the opener window so it can inherit the appropriate attributes. + #[cfg(not(any(target_os = "android", target_os = "ios")))] + Create { window_id: WindowId }, + /// Deny the window from being opened. + Deny, +} + +/// The scrollbar style to use in the webview. +/// +/// ## Platform-specific +/// +/// - **Windows**: This option must be given the same value for all webviews that target the same data directory. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Default)] +pub enum ScrollBarStyle { + #[default] + /// The default scrollbar style for the webview. + Default, + + #[cfg(windows)] + /// Fluent UI style overlay scrollbars. **Windows Only** + /// + /// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions, + /// see + FluentOverlay, +} + +/// A webview that has yet to be built. +pub struct PendingWebview> { + /// The label that the webview will be named. + pub label: String, + + /// The [`WebviewAttributes`] that the webview will be created with. + pub webview_attributes: WebviewAttributes, + + /// Information about the webview that initiated a new window request. + pub opener: Option, + + /// Runtime specific attributes. + pub platform_specific_attributes: Vec, + + /// Custom protocols to register on the webview + pub uri_scheme_protocols: HashMap>, + + /// How to handle IPC calls on the webview. + pub ipc_handler: Option>, + + /// A handler to decide if incoming url is allowed to navigate. + pub navigation_handler: Option>, + + pub new_window_handler: Option>>, + + pub document_title_changed_handler: Option>, + + pub address_changed_handler: Option>, + + /// The resolved URL to load on the webview. + pub url: String, + + #[cfg(target_os = "android")] + #[allow(clippy::type_complexity)] + pub on_webview_created: + Option) -> Result<(), jni::errors::Error> + Send + Sync>>, + + pub web_resource_request_handler: Option>, + + pub on_page_load_handler: Option>, + + pub download_handler: Option>, + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub on_web_content_process_terminate_handler: Option>, +} + +impl> PendingWebview { + /// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`]. + pub fn new( + webview_attributes: WebviewAttributes, + platform_specific_attributes: Vec, + label: impl Into, + ) -> crate::Result { + let label = label.into(); + if !is_label_valid(&label) { + Err(crate::Error::InvalidWindowLabel) + } else { + Ok(Self { + webview_attributes, + opener: None, + platform_specific_attributes, + uri_scheme_protocols: Default::default(), + label, + ipc_handler: None, + navigation_handler: None, + new_window_handler: None, + document_title_changed_handler: None, + address_changed_handler: None, + url: "tauri://localhost".to_string(), + #[cfg(target_os = "android")] + on_webview_created: None, + web_resource_request_handler: None, + on_page_load_handler: None, + download_handler: None, + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate_handler: None, + }) + } + } + + pub fn register_uri_scheme_protocol< + N: Into, + H: Fn(&str, http::Request>, Box>) + Send>) + + Send + + Sync + + 'static, + >( + &mut self, + uri_scheme: N, + protocol_handler: H, + ) { + let uri_scheme = uri_scheme.into(); + self + .uri_scheme_protocols + .insert(uri_scheme, Box::new(protocol_handler)); + } + + #[cfg(target_os = "android")] + pub fn on_webview_created< + F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + Sync + 'static, + >( + mut self, + f: F, + ) -> Self { + self.on_webview_created.replace(Box::new(f)); + self + } +} + +/// A webview that is not yet managed by Tauri. +#[derive(Debug)] +pub struct DetachedWebview> { + /// Name of the window + pub label: String, + + /// The [`crate::WebviewDispatch`] associated with the window. + pub dispatcher: R::WebviewDispatcher, +} + +impl> Clone for DetachedWebview { + fn clone(&self) -> Self { + Self { + label: self.label.clone(), + dispatcher: self.dispatcher.clone(), + } + } +} + +impl> Hash for DetachedWebview { + /// Only use the [`DetachedWebview`]'s label to represent its hash. + fn hash(&self, state: &mut H) { + self.label.hash(state) + } +} + +impl> Eq for DetachedWebview {} +impl> PartialEq for DetachedWebview { + /// Only use the [`DetachedWebview`]'s label to compare equality. + fn eq(&self, other: &Self) -> bool { + self.label.eq(&other.label) + } +} + +/// The attributes used to create an webview. +#[derive(Debug)] +pub struct WebviewAttributes { + pub url: WebviewUrl, + pub user_agent: Option, + /// A list of initialization javascript scripts to run when loading new pages. + /// When webview load a new page, this initialization code will be executed. + /// It is guaranteed that code is executed before `window.onload`. + /// + /// ## Platform-specific + /// + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, + /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). + /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. + /// + /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) + /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + pub initialization_scripts: Vec, + pub data_directory: Option, + pub drag_drop_handler_enabled: bool, + pub clipboard: bool, + pub accept_first_mouse: bool, + pub additional_browser_args: Option, + pub window_effects: Option, + pub incognito: bool, + pub transparent: bool, + pub focus: bool, + pub bounds: Option, + pub auto_resize: bool, + pub proxy_url: Option, + pub zoom_hotkeys_enabled: bool, + pub browser_extensions_enabled: bool, + pub extensions_path: Option, + pub data_store_identifier: Option<[u8; 16]>, + pub use_https_scheme: bool, + pub devtools: Option, + pub background_color: Option, + pub traffic_light_position: Option, + pub background_throttling: Option, + pub javascript_disabled: bool, + /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default. + /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview + pub allow_link_preview: bool, + pub scroll_bar_style: ScrollBarStyle, + /// Controls the WebView's browser-level general autofill behavior. + /// + /// **This option does not disable password or credit card autofill.** + /// + /// When set to `false`, the WebView will not automatically populate + /// general form fields using previously stored data such as addresses + /// or contact information. + /// + /// If not specified, this is `true` by default. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported. WebView2's autofill feature (called + /// "Suggestions") may not honor `autocomplete="off"` on input + /// elements in some cases. + /// - **Linux / Android / iOS / macOS**: Unsupported and performs no + /// operation. + pub general_autofill_enabled: bool, + /// Allows overriding the keyboard accessory view on iOS. + /// Returning `None` effectively removes the view. + /// + /// The closure parameter is the webview instance. + /// + /// The accessory view is the view that appears above the keyboard when a text input element is focused. + /// It usually displays a view with "Done", "Next" buttons. + /// + /// # Stability + /// + /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases. + #[cfg(target_os = "ios")] + pub input_accessory_view_builder: Option, +} + +unsafe impl Send for WebviewAttributes {} +unsafe impl Sync for WebviewAttributes {} + +#[cfg(target_os = "ios")] +#[non_exhaustive] +pub struct InputAccessoryViewBuilder(pub Box); + +#[cfg(target_os = "ios")] +impl std::fmt::Debug for InputAccessoryViewBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("InputAccessoryViewBuilder").finish() + } +} + +#[cfg(target_os = "ios")] +impl InputAccessoryViewBuilder { + pub fn new(builder: Box) -> Self { + Self(builder) + } +} + +impl From<&WindowConfig> for WebviewAttributes { + fn from(config: &WindowConfig) -> Self { + let mut builder = Self::new(config.url.clone()) + .incognito(config.incognito) + .focused(config.focus) + .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled) + .use_https_scheme(config.use_https_scheme) + .browser_extensions_enabled(config.browser_extensions_enabled) + .background_throttling(config.background_throttling.clone()) + .devtools(config.devtools) + .scroll_bar_style(match config.scroll_bar_style { + ConfigScrollBarStyle::Default => ScrollBarStyle::Default, + #[cfg(windows)] + ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay, + _ => ScrollBarStyle::Default, + }) + .general_autofill_enabled(config.general_autofill_enabled); + + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + { + builder = builder.transparent(config.transparent); + } + #[cfg(target_os = "macos")] + { + if let Some(position) = &config.traffic_light_position { + builder = + builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into()); + } + } + builder = builder.accept_first_mouse(config.accept_first_mouse); + if !config.drag_drop_enabled { + builder = builder.disable_drag_drop_handler(); + } + if let Some(user_agent) = &config.user_agent { + builder = builder.user_agent(user_agent); + } + if let Some(additional_browser_args) = &config.additional_browser_args { + builder = builder.additional_browser_args(additional_browser_args); + } + if let Some(effects) = &config.window_effects { + builder = builder.window_effects(effects.clone()); + } + if let Some(url) = &config.proxy_url { + builder = builder.proxy_url(url.to_owned()); + } + if let Some(color) = config.background_color { + builder = builder.background_color(color); + } + builder.javascript_disabled = config.javascript_disabled; + builder.allow_link_preview = config.allow_link_preview; + #[cfg(target_os = "ios")] + if config.disable_input_accessory_view { + builder + .input_accessory_view_builder + .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None))); + } + builder + } +} + +impl WebviewAttributes { + /// Initializes the default attributes for a webview. + pub fn new(url: WebviewUrl) -> Self { + Self { + url, + user_agent: None, + initialization_scripts: Vec::new(), + data_directory: None, + drag_drop_handler_enabled: true, + clipboard: false, + accept_first_mouse: false, + additional_browser_args: None, + window_effects: None, + incognito: false, + transparent: false, + focus: true, + bounds: None, + auto_resize: false, + proxy_url: None, + zoom_hotkeys_enabled: false, + browser_extensions_enabled: false, + data_store_identifier: None, + extensions_path: None, + use_https_scheme: false, + devtools: None, + background_color: None, + traffic_light_position: None, + background_throttling: None, + javascript_disabled: false, + allow_link_preview: true, + scroll_bar_style: ScrollBarStyle::Default, + general_autofill_enabled: true, + #[cfg(target_os = "ios")] + input_accessory_view_builder: None, + } + } + + /// Sets the user agent + #[must_use] + pub fn user_agent(mut self, user_agent: &str) -> Self { + self.user_agent = Some(user_agent.to_string()); + self + } + + /// Adds an init script for the main frame. + /// + /// When webview load a new page, this initialization code will be executed. + /// It is guaranteed that code is executed before `window.onload`. + /// + /// This is executed only on the main frame. + /// If you only want to run it in all frames, use [`Self::initialization_script_on_all_frames`] instead. + /// + /// ## Platform-specific + /// + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, + /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). + /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. + /// + /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) + /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + #[must_use] + pub fn initialization_script(mut self, script: impl Into) -> Self { + self.initialization_scripts.push(InitializationScript { + script: script.into(), + for_main_frame_only: true, + }); + self + } + + /// Adds an init script for all frames. + /// + /// When webview load a new page, this initialization code will be executed. + /// It is guaranteed that code is executed before `window.onload`. + /// + /// This is executed on all frames, main frame and also sub frames. + /// If you only want to run it in the main frame, use [`Self::initialization_script`] instead. + /// + /// ## Platform-specific + /// + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, + /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). + /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. + /// + /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) + /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + #[must_use] + pub fn initialization_script_on_all_frames(mut self, script: impl Into) -> Self { + self.initialization_scripts.push(InitializationScript { + script: script.into(), + for_main_frame_only: false, + }); + self + } + + /// Data directory for the webview. + #[must_use] + pub fn data_directory(mut self, data_directory: PathBuf) -> Self { + self.data_directory.replace(data_directory); + self + } + + /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows. + #[must_use] + pub fn disable_drag_drop_handler(mut self) -> Self { + self.drag_drop_handler_enabled = false; + self + } + + /// Enables clipboard access for the page rendered on **Linux** and **Windows**. + /// + /// **macOS** doesn't provide such method and is always enabled by default, + /// but you still need to add menu item accelerators to use shortcuts. + #[must_use] + pub fn enable_clipboard_access(mut self) -> Self { + self.clipboard = true; + self + } + + /// Sets whether clicking an inactive window also clicks through to the webview. + #[must_use] + pub fn accept_first_mouse(mut self, accept: bool) -> Self { + self.accept_first_mouse = accept; + self + } + + /// Sets additional browser arguments. **Windows Only** + #[must_use] + pub fn additional_browser_args(mut self, additional_args: &str) -> Self { + self.additional_browser_args = Some(additional_args.to_string()); + self + } + + /// Sets window effects + #[must_use] + pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self { + self.window_effects = Some(effects); + self + } + + /// Enable or disable incognito mode for the WebView. + #[must_use] + pub fn incognito(mut self, incognito: bool) -> Self { + self.incognito = incognito; + self + } + + /// Enable or disable transparency for the WebView. + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + #[must_use] + pub fn transparent(mut self, transparent: bool) -> Self { + self.transparent = transparent; + self + } + + /// Whether the webview should be focused or not. + #[must_use] + pub fn focused(mut self, focus: bool) -> Self { + self.focus = focus; + self + } + + /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes. + #[must_use] + pub fn auto_resize(mut self) -> Self { + self.auto_resize = true; + self + } + + /// Enable proxy for the WebView + #[must_use] + pub fn proxy_url(mut self, url: Url) -> Self { + self.proxy_url = Some(url); + self + } + + /// Whether page zooming by hotkeys is enabled + /// + /// ## Platform-specific: + /// + /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting. + /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`, + /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission + /// + /// - **Android / iOS**: Unsupported. + #[must_use] + pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self { + self.zoom_hotkeys_enabled = enabled; + self + } + + /// Whether browser extensions can be installed for the webview process + /// + /// ## Platform-specific: + /// + /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled) + /// - **MacOS / Linux / iOS / Android** - Unsupported. + #[must_use] + pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self { + self.browser_extensions_enabled = enabled; + self + } + + /// Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`. + /// + /// ## Note + /// + /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux. + /// + /// ## Warning + /// + /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data. + #[must_use] + pub fn use_https_scheme(mut self, enabled: bool) -> Self { + self.use_https_scheme = enabled; + self + } + + /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default. + /// + /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds. + /// + /// ## Platform-specific + /// + /// - macOS: This will call private functions on **macOS**. + /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. + /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. + #[must_use] + pub fn devtools(mut self, enabled: Option) -> Self { + self.devtools = enabled; + self + } + + /// Set the window and webview background color. + /// ## Platform-specific: + /// + /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer. + /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored. + #[must_use] + pub fn background_color(mut self, color: Color) -> Self { + self.background_color = Some(color); + self + } + + /// Change the position of the window controls. Available on macOS only. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / iOS / Android:** Unsupported. + #[must_use] + pub fn traffic_light_position(mut self, position: dpi::Position) -> Self { + self.traffic_light_position = Some(position); + self + } + + /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only. + /// + /// Default is true. + /// + /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / Android:** Unsupported. + #[must_use] + pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self { + self.allow_link_preview = allow_link_preview; + self + } + + /// Change the default background throttling behavior. + /// + /// By default, browsers use a suspend policy that will throttle timers and even unload + /// the whole tab (view) to free resources after roughly 5 minutes when a view became + /// minimized or hidden. This will pause all tasks until the documents visibility state + /// changes back from hidden to visible by bringing the view back to the foreground. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice. + /// - **iOS**: Supported since version 17.0+. + /// - **macOS**: Supported since version 14.0+. + /// + /// see + #[must_use] + pub fn background_throttling(mut self, policy: Option) -> Self { + self.background_throttling = policy; + self + } + + /// Specifies the native scrollbar style to use with the webview. + /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here. + /// + /// Defaults to [`ScrollBarStyle::Default`], which is the browser default. + /// + /// ## Platform-specific + /// + /// - **Windows**: + /// - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher, + /// and does nothing on older versions. + /// - This option must be given the same value for all webviews that target the same data directory. Use + /// [`WebviewAttributes::data_directory`] to change data directories if needed. + /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation. + #[must_use] + pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self { + self.scroll_bar_style = style; + self + } + + /// Controls the WebView's browser-level general autofill behavior. + /// + /// **This option does not disable password or credit card autofill.** + /// + /// When set to `false`, the WebView will not automatically populate + /// general form fields using previously stored data such as addresses + /// or contact information. + /// + /// By default, this is `true`. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported. WebView2's autofill feature (called + /// "Suggestions") may not honor `autocomplete="off"` on input + /// elements in some cases. + /// - **Linux / Android / iOS / macOS**: Unsupported and performs no + /// operation. + #[must_use] + pub fn general_autofill_enabled(mut self, enabled: bool) -> Self { + self.general_autofill_enabled = enabled; + self + } +} + +/// IPC handler. +pub type WebviewIpcHandler = Box, Request) + Send>; + +/// An initialization script +#[derive(Debug, Clone)] +pub struct InitializationScript { + /// The script to run + pub script: String, + /// Whether the script should be injected to main frame only + pub for_main_frame_only: bool, +} diff --git a/apps/tauri/src-tauri/vendor/tauri-runtime/src/window.rs b/apps/tauri/src-tauri/vendor/tauri-runtime/src/window.rs new file mode 100644 index 0000000..de911f2 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-runtime/src/window.rs @@ -0,0 +1,661 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! A layer between raw [`Runtime`] windows and Tauri. + +use crate::{ + Icon, Runtime, UserEvent, WindowDispatch, + webview::{DetachedWebview, PendingWebview}, +}; + +use dpi::PixelUnit; +use serde::{Deserialize, Deserializer, Serialize}; +use tauri_utils::{ + Theme, + config::{Color, WindowConfig}, +}; +#[cfg(windows)] +use windows::Win32::Foundation::HWND; + +use std::{ + hash::{Hash, Hasher}, + marker::PhantomData, + path::PathBuf, + sync::mpsc::Sender, +}; + +/// An event from a window. +#[derive(Debug, Clone)] +pub enum WindowEvent { + /// The size of the window has changed. Contains the client area's new dimensions. + Resized(dpi::PhysicalSize), + /// The position of the window has changed. Contains the window's new position. + Moved(dpi::PhysicalPosition), + /// The window has been requested to close. + CloseRequested { + /// A signal sender. If a `true` value is emitted, the window won't be closed. + signal_tx: Sender, + }, + /// The window has been destroyed. + Destroyed, + /// The window gained or lost focus. + /// + /// The parameter is true if the window has gained focus, and false if it has lost focus. + Focused(bool), + /// The window's scale factor has changed. + /// + /// The following user actions can cause DPI changes: + /// + /// - Changing the display's resolution. + /// - Changing the display's scale factor (e.g. in Control Panel on Windows). + /// - Moving the window to a display with a different scale factor. + ScaleFactorChanged { + /// The new scale factor. + scale_factor: f64, + /// The window inner size. + new_inner_size: dpi::PhysicalSize, + }, + /// An event associated with the drag and drop action. + DragDrop(DragDropEvent), + /// The system window theme has changed. + /// + /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme. + ThemeChanged(Theme), + + /// Emitted when the application has been suspended. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onPause` method of the Activity. + /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + #[cfg(mobile)] + Suspended, + + /// Emitted when the application has been resumed. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. + /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + #[cfg(mobile)] + Resumed, +} + +/// An event from a window. +#[derive(Debug, Clone)] +pub enum WebviewEvent { + /// An event associated with the drag and drop action. + DragDrop(DragDropEvent), +} + +/// The drag drop event payload. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum DragDropEvent { + /// A drag operation has entered the webview. + Enter { + /// List of paths that are being dragged onto the webview. + paths: Vec, + /// The position of the mouse cursor. + position: dpi::PhysicalPosition, + }, + /// A drag operation is moving over the webview. + Over { + /// The position of the mouse cursor. + position: dpi::PhysicalPosition, + }, + /// The file(s) have been dropped onto the webview. + Drop { + /// List of paths that are being dropped onto the window. + paths: Vec, + /// The position of the mouse cursor. + position: dpi::PhysicalPosition, + }, + /// The drag operation has been cancelled or left the window. + Leave, +} + +/// Describes the appearance of the mouse cursor. +#[non_exhaustive] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +pub enum CursorIcon { + /// The platform-dependent default cursor. + #[default] + Default, + /// A simple crosshair. + Crosshair, + /// A hand (often used to indicate links in web browsers). + Hand, + /// Self explanatory. + Arrow, + /// Indicates something is to be moved. + Move, + /// Indicates text that may be selected or edited. + Text, + /// Program busy indicator. + Wait, + /// Help indicator (often rendered as a "?") + Help, + /// Progress indicator. Shows that processing is being done. But in contrast + /// with "Wait" the user may still interact with the program. Often rendered + /// as a spinning beach ball, or an arrow with a watch or hourglass. + Progress, + + /// Cursor showing that something cannot be done. + NotAllowed, + ContextMenu, + Cell, + VerticalText, + Alias, + Copy, + NoDrop, + /// Indicates something can be grabbed. + Grab, + /// Indicates something is grabbed. + Grabbing, + AllScroll, + ZoomIn, + ZoomOut, + + /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor + /// is used when the movement starts from the south-east corner of the box. + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, +} + +impl<'de> Deserialize<'de> for CursorIcon { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "default" => CursorIcon::Default, + "crosshair" => CursorIcon::Crosshair, + "hand" => CursorIcon::Hand, + "arrow" => CursorIcon::Arrow, + "move" => CursorIcon::Move, + "text" => CursorIcon::Text, + "wait" => CursorIcon::Wait, + "help" => CursorIcon::Help, + "progress" => CursorIcon::Progress, + "notallowed" => CursorIcon::NotAllowed, + "contextmenu" => CursorIcon::ContextMenu, + "cell" => CursorIcon::Cell, + "verticaltext" => CursorIcon::VerticalText, + "alias" => CursorIcon::Alias, + "copy" => CursorIcon::Copy, + "nodrop" => CursorIcon::NoDrop, + "grab" => CursorIcon::Grab, + "grabbing" => CursorIcon::Grabbing, + "allscroll" => CursorIcon::AllScroll, + "zoomin" => CursorIcon::ZoomIn, + "zoomout" => CursorIcon::ZoomOut, + "eresize" => CursorIcon::EResize, + "nresize" => CursorIcon::NResize, + "neresize" => CursorIcon::NeResize, + "nwresize" => CursorIcon::NwResize, + "sresize" => CursorIcon::SResize, + "seresize" => CursorIcon::SeResize, + "swresize" => CursorIcon::SwResize, + "wresize" => CursorIcon::WResize, + "ewresize" => CursorIcon::EwResize, + "nsresize" => CursorIcon::NsResize, + "neswresize" => CursorIcon::NeswResize, + "nwseresize" => CursorIcon::NwseResize, + "colresize" => CursorIcon::ColResize, + "rowresize" => CursorIcon::RowResize, + _ => CursorIcon::Default, + }) + } +} + +/// Window size constraints +#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WindowSizeConstraints { + /// The minimum width a window can be, If this is `None`, the window will have no minimum width. + /// + /// The default is `None`. + pub min_width: Option, + /// The minimum height a window can be, If this is `None`, the window will have no minimum height. + /// + /// The default is `None`. + pub min_height: Option, + /// The maximum width a window can be, If this is `None`, the window will have no maximum width. + /// + /// The default is `None`. + pub max_width: Option, + /// The maximum height a window can be, If this is `None`, the window will have no maximum height. + /// + /// The default is `None`. + pub max_height: Option, +} + +/// Do **NOT** implement this trait except for use in a custom [`Runtime`] +/// +/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation. +pub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {} + +/// A builder for all attributes related to a single window. +/// +/// This trait is only meant to be implemented by a custom [`Runtime`] +/// and not by applications. +pub trait WindowBuilder: WindowBuilderBase { + /// Initializes a new window attributes builder. + fn new() -> Self; + + /// Initializes a new window builder from a [`WindowConfig`] + fn with_config(config: &WindowConfig) -> Self; + + /// Show window in the center of the screen. + #[must_use] + fn center(self) -> Self; + + /// The initial position of the window in logical pixels. + #[must_use] + fn position(self, x: f64, y: f64) -> Self; + + /// Window size in logical pixels. + #[must_use] + fn inner_size(self, width: f64, height: f64) -> Self; + + /// Window min inner size in logical pixels. + #[must_use] + fn min_inner_size(self, min_width: f64, min_height: f64) -> Self; + + /// Window max inner size in logical pixels. + #[must_use] + fn max_inner_size(self, max_width: f64, max_height: f64) -> Self; + + /// Window inner size constraints. + #[must_use] + fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self; + + /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[must_use] + fn prevent_overflow(self) -> Self; + + /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) + /// on creation with a margin + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[must_use] + fn prevent_overflow_with_margin(self, margin: dpi::Size) -> Self; + + /// Whether the window is resizable or not. + /// When resizable is set to false, native window's maximize button is automatically disabled. + #[must_use] + fn resizable(self, resizable: bool) -> Self; + + /// Whether the window's native maximize button is enabled or not. + /// If resizable is set to false, this setting is ignored. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + #[must_use] + fn maximizable(self, maximizable: bool) -> Self; + + /// Whether the window's native minimize button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[must_use] + fn minimizable(self, minimizable: bool) -> Self; + + /// Whether the window's native close button is enabled or not. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + #[must_use] + fn closable(self, closable: bool) -> Self; + + /// The title of the window in the title bar. + #[must_use] + fn title>(self, title: S) -> Self; + + /// Whether to start the window in fullscreen or not. + #[must_use] + fn fullscreen(self, fullscreen: bool) -> Self; + + /// Whether the window will be initially focused or not. + #[must_use] + fn focused(self, focused: bool) -> Self; + + /// Whether the window will be focusable or not. + #[must_use] + fn focusable(self, focusable: bool) -> Self; + + /// Whether the window should be maximized upon creation. + #[must_use] + fn maximized(self, maximized: bool) -> Self; + + /// Whether the window should be immediately visible upon creation. + #[must_use] + fn visible(self, visible: bool) -> Self; + + /// Whether the window should be transparent. If this is true, writing colors + /// with alpha values different than `1.0` will produce a transparent window. + #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] + #[cfg_attr( + docsrs, + doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api"))) + )] + #[must_use] + fn transparent(self, transparent: bool) -> Self; + + /// Whether the window should have borders and bars. + #[must_use] + fn decorations(self, decorations: bool) -> Self; + + /// Whether the window should always be below other windows. + #[must_use] + fn always_on_bottom(self, always_on_bottom: bool) -> Self; + + /// Whether the window should always be on top of other windows. + #[must_use] + fn always_on_top(self, always_on_top: bool) -> Self; + + /// Whether the window should be visible on all workspaces or virtual desktops. + #[must_use] + fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self; + + /// Prevents the window contents from being captured by other apps. + #[must_use] + fn content_protected(self, protected: bool) -> Self; + + /// Sets the window icon. + fn icon(self, icon: Icon) -> crate::Result; + + /// Sets whether or not the window icon should be added to the taskbar. + #[must_use] + fn skip_taskbar(self, skip: bool) -> Self; + + /// Set the window background color. + #[must_use] + fn background_color(self, color: Color) -> Self; + + /// Sets whether or not the window has shadow. + /// + /// ## Platform-specific + /// + /// - **Windows:** + /// - `false` has no effect on decorated window, shadows are always ON. + /// - `true` will make undecorated window have a 1px white border, + /// and on Windows 11, it will have a rounded corners. + /// - **Linux:** Unsupported. + #[must_use] + fn shadow(self, enable: bool) -> Self; + + /// Set an owner to the window to be created. + /// + /// From MSDN: + /// - An owned window is always above its owner in the z-order. + /// - The system automatically destroys an owned window when its owner is destroyed. + /// - An owned window is hidden when its owner is minimized. + /// + /// For more information, see + #[cfg(windows)] + #[must_use] + fn owner(self, owner: HWND) -> Self; + + /// Sets a parent to the window to be created. + /// + /// A child window has the WS_CHILD style and is confined to the client area of its parent window. + /// + /// For more information, see + #[cfg(windows)] + #[must_use] + fn parent(self, parent: HWND) -> Self; + + /// Sets a parent to the window to be created. + /// + /// See + #[cfg(target_os = "macos")] + #[must_use] + fn parent(self, parent: *mut std::ffi::c_void) -> Self; + + /// Sets the window to be created transient for parent. + /// + /// See + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn transient_for(self, parent: &impl gtk::glib::IsA) -> Self; + + /// Enables or disables drag and drop support. + #[cfg(windows)] + #[must_use] + fn drag_and_drop(self, enabled: bool) -> Self; + + /// Hide the titlebar. Titlebar buttons will still be visible. + #[cfg(target_os = "macos")] + #[must_use] + fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self; + + /// Change the position of the window controls on macOS. + /// + /// Requires titleBarStyle: Overlay and decorations: true. + #[cfg(target_os = "macos")] + #[must_use] + fn traffic_light_position>(self, position: P) -> Self; + + /// Hide the window title. + #[cfg(target_os = "macos")] + #[must_use] + fn hidden_title(self, hidden: bool) -> Self; + + /// Defines the window [tabbing identifier] for macOS. + /// + /// Windows with matching tabbing identifiers will be grouped together. + /// If the tabbing identifier is not set, automatic tabbing will be disabled. + /// + /// [tabbing identifier]: + #[cfg(target_os = "macos")] + #[must_use] + fn tabbing_identifier(self, identifier: &str) -> Self; + + /// Forces a theme or uses the system settings if None was provided. + fn theme(self, theme: Option) -> Self; + + /// Whether the icon was set or not. + fn has_icon(&self) -> bool; + + fn get_theme(&self) -> Option; + + /// Sets custom name for Windows' window class. **Windows only**. + #[must_use] + fn window_classname>(self, window_classname: S) -> Self; + + /// The name of the activity to create for this webview window. + #[cfg(target_os = "android")] + fn activity_name>(self, class_name: S) -> Self; + + /// Sets the name of the activity that is creating this webview window. + /// + /// This is important to determine which stack the activity will belong to. + #[cfg(target_os = "android")] + fn created_by_activity_name>(self, class_name: S) -> Self; + + /// Sets the identifier of the UIScene that is requesting the creation of this new scene, + /// establishing a relationship between the two scenes. + /// + /// By default the system uses the foreground scene. + #[cfg(target_os = "ios")] + fn requested_by_scene_identifier>(self, identifier: S) -> Self; +} + +/// A window that has yet to be built. +pub struct PendingWindow> { + /// The label that the window will be named. + pub label: String, + + /// The [`WindowBuilder`] that the window will be created with. + pub window_builder: >::WindowBuilder, + + /// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead. + pub webview: Option>, +} + +pub fn is_label_valid(label: &str) -> bool { + label + .chars() + .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_') +} + +pub fn assert_label_is_valid(label: &str) { + assert!( + is_label_valid(label), + "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); +} + +impl> PendingWindow { + /// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`]. + pub fn new( + window_builder: >::WindowBuilder, + label: impl Into, + ) -> crate::Result { + let label = label.into(); + if !is_label_valid(&label) { + Err(crate::Error::InvalidWindowLabel) + } else { + Ok(Self { + window_builder, + label, + webview: None, + }) + } + } + + /// Sets a webview to be created on the window. + pub fn set_webview(&mut self, webview: PendingWebview) -> &mut Self { + self.webview.replace(webview); + self + } +} + +/// Identifier of a window. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct WindowId(u32); + +impl From for WindowId { + fn from(value: u32) -> Self { + Self(value) + } +} + +/// A window that is not yet managed by Tauri. +#[derive(Debug)] +pub struct DetachedWindow> { + /// The identifier of the window. + pub id: WindowId, + /// Name of the window + pub label: String, + + /// The [`WindowDispatch`] associated with the window. + pub dispatcher: R::WindowDispatcher, + + /// The webview dispatcher in case this window has an attached webview. + pub webview: Option>, +} + +/// A detached webview associated with a window. +#[derive(Debug)] +pub struct DetachedWindowWebview> { + pub webview: DetachedWebview, + pub use_https_scheme: bool, + /// Whether devtools was enabled in [`crate::webview::WebviewAttributes`]. `Some(false)` disables the inspector. + pub devtools: Option, +} + +impl> Clone for DetachedWindowWebview { + fn clone(&self) -> Self { + Self { + webview: self.webview.clone(), + use_https_scheme: self.use_https_scheme, + devtools: self.devtools, + } + } +} + +impl> Clone for DetachedWindow { + fn clone(&self) -> Self { + Self { + id: self.id, + label: self.label.clone(), + dispatcher: self.dispatcher.clone(), + webview: self.webview.clone(), + } + } +} + +impl> Hash for DetachedWindow { + /// Only use the [`DetachedWindow`]'s label to represent its hash. + fn hash(&self, state: &mut H) { + self.label.hash(state) + } +} + +impl> Eq for DetachedWindow {} +impl> PartialEq for DetachedWindow { + /// Only use the [`DetachedWindow`]'s label to compare equality. + fn eq(&self, other: &Self) -> bool { + self.label.eq(&other.label) + } +} + +/// A raw window type that contains fields to access +/// the HWND on Windows, gtk::ApplicationWindow on Linux +pub struct RawWindow<'a> { + #[cfg(windows)] + pub hwnd: isize, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub gtk_window: &'a gtk::ApplicationWindow, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub default_vbox: Option<&'a gtk::Box>, + pub _marker: &'a PhantomData<()>, +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/CHANGELOG.md b/apps/tauri/src-tauri/vendor/tauri-utils/CHANGELOG.md new file mode 100644 index 0000000..60f1bec --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/CHANGELOG.md @@ -0,0 +1,1035 @@ +# Changelog + +## \[2.9.0] + +### New Features + +- [`001c8fe3d`](https://www.github.com/tauri-apps/tauri/commit/001c8fe3d288802de9a8c29cfd2f46f9220d97c5) ([#14722](https://www.github.com/tauri-apps/tauri/pull/14722)) Add a WebView option to control browser-level general autofill behavior. This option does not disable password or credit card autofill. On Windows (WebView2), setting it to true disables the general autofill "Suggestions" UI, which may appear even when `autocomplete="off"` is specified on input elements. On Linux, macOS, iOS, and Android, this option is currently unsupported and performs no operation. +- [`926a57bb0`](https://www.github.com/tauri-apps/tauri/commit/926a57bb0851e45d47ad1ee68fc96a9c25754c7c) ([#15201](https://www.github.com/tauri-apps/tauri/pull/15201)) Added uninstaller icon and uninstaller header image support for NSIS installer. + + Notes: + + - For `tauri-bundler` lib users, the `NsisSettings` now has 2 new fields `uninstaller_icon` and `uninstaller_header_image` which can be a breaking change + - When bundling with NSIS, users can add `uninstallerIcon` and `uninstallerHeaderImage` under `bundle > windows > nsis` to configure them. +- [`093e2b47c`](https://www.github.com/tauri-apps/tauri/commit/093e2b47c01361c18783e9ff18750388e41650c5) ([#14484](https://www.github.com/tauri-apps/tauri/pull/14484)) Support creating multiple windows on Android (activity embedding) and iOS (scenes). + +### Dependencies + +- [`e032c3b34`](https://www.github.com/tauri-apps/tauri/commit/e032c3b3421f53bca7b869ffee2be105c5c06ad9) ([#14959](https://www.github.com/tauri-apps/tauri/pull/14959)) Add new `html-manipulation-2` and `build-2` feature flags that use `dom_query` instead of `kuchikiki` for HTML parsing / manipulation. + This allows downstream users to remove `kuchikiki` and its dependencies from their dependency tree. +- [`1ef6a119b`](https://www.github.com/tauri-apps/tauri/commit/1ef6a119b1571d1da0acc08bdb7fd5521a4c6d52) ([#15115](https://www.github.com/tauri-apps/tauri/pull/15115)) Changed `toml` crate version from `0.9` to `">=0.9, <=1"` + +## \[2.8.3] + +### Bug Fixes + +- [`7b16dafb1`](https://www.github.com/tauri-apps/tauri/commit/7b16dafb1dc417536ebb62df8eb66154dd97109d) ([#14986](https://www.github.com/tauri-apps/tauri/pull/14986) by [@montyc1999](https://www.github.com/tauri-apps/tauri/../../montyc1999)) Sort csp/plugin/header configs when generating HashMap constructors so that `generate_context!` is deterministic. + + See: https://github.com/tauri-apps/tauri/issues/14978 for more information +- [`c3cbff3f7`](https://www.github.com/tauri-apps/tauri/commit/c3cbff3f7430161715f80f82128b345a6f7140c9) ([#14662](https://www.github.com/tauri-apps/tauri/pull/14662) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix resource path handles `./` path differently (e.g. `./some-folder` should be the same as `some-folder`) + +## \[2.8.2] + +### Enhancements + +- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes. + +## \[2.8.1] + +### Bug Fixes + +- [`1573c7240`](https://www.github.com/tauri-apps/tauri/commit/1573c72402352949d1fd3ca5c6fdbee46fe69fbb) ([#14561](https://www.github.com/tauri-apps/tauri/pull/14561) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused schema files to have `\r` characters on Windows. + +## \[2.8.0] + +### New Features + +- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scrollBarStyle` option to the window configuration. +- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code. +- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added `FileAssociation::exported_type` and `FileAssociation::content_types` for better support to defining custom types on macOS. +- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Added `bundle > macOS > infoPlist` and `bundle > iOS > infoPlist` configurations to allow defining custom Info.plist extensions. +- [`7b0d4e732`](https://www.github.com/tauri-apps/tauri/commit/7b0d4e73227e42d88732b6d9fe643499dd78ec4e) ([#14265](https://www.github.com/tauri-apps/tauri/pull/14265)) Added `html::normalize_script_for_csp`. +- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Added support to universal app links on macOS with the `plugins > deep-link > desktop > domains` configuration. + +### Enhancements + +- [`59089723f`](https://www.github.com/tauri-apps/tauri/commit/59089723fc20d66f3f305f2008adeb279bf87462) ([#14091](https://www.github.com/tauri-apps/tauri/pull/14091)) Added a config to set a data_directory relative to the app-specific data dir in JavaScript and `tauri.conf.json`. + +## \[2.7.0] + +### New Features + +- [`91508c0b8`](https://www.github.com/tauri-apps/tauri/commit/91508c0b8d16ec61c7706e93b711c5a85aaffb4a) ([#13881](https://www.github.com/tauri-apps/tauri/pull/13881) by [@pepperoni505](https://www.github.com/tauri-apps/tauri/../../pepperoni505)) Introduces a new configuration option that allows you to specify custom folders to watch for changes when running `tauri dev`. +- [`0c402bfb6`](https://www.github.com/tauri-apps/tauri/commit/0c402bfb6bd0bec24d928fcabe2ffef1f5cff19a) ([#13997](https://www.github.com/tauri-apps/tauri/pull/13997) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Increase default iOS deployment target iOS to 14.0. + +### Enhancements + +- [`9300b59f6`](https://www.github.com/tauri-apps/tauri/commit/9300b59f65156a37f7bf9a629b69da6761ae734a) ([#13787](https://www.github.com/tauri-apps/tauri/pull/13787) by [@JakenHerman](https://www.github.com/tauri-apps/tauri/../../JakenHerman)) Added `fips_compliant` field to `WixConfig` so that it can be configured via `tauri.conf.json` as well. +- [`72b4226ee`](https://www.github.com/tauri-apps/tauri/commit/72b4226ee9932b4dafa4837a49420b2c02d14bb7) ([#13809](https://www.github.com/tauri-apps/tauri/pull/13809) by [@Beanow](https://www.github.com/tauri-apps/tauri/../../Beanow)) Reduced `Debug` format size for binary buffers. + +### Bug Fixes + +- [`1a3d1a024`](https://www.github.com/tauri-apps/tauri/commit/1a3d1a024ebb2a3dd5d15849523a55246d78fda6) ([#13995](https://www.github.com/tauri-apps/tauri/pull/13995) by [@will3942](https://www.github.com/tauri-apps/tauri/../../will3942)) Fix Tauri iOS build with binary XCFramework dependencies, allows extracting binaryTargets that are zipped and also not including XCFrameworks when linking. + +## \[2.6.0] + +### New Features + +- [`232265c70`](https://www.github.com/tauri-apps/tauri/commit/232265c70e1c213bbb3f84b5541ddc07d330fce1) ([#13209](https://www.github.com/tauri-apps/tauri/pull/13209) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Added `platform::bundle_type`. +- [`33d079392`](https://www.github.com/tauri-apps/tauri/commit/33d079392ac4a5a153b7d8a6d82fefd6f54a2bdf) ([#13811](https://www.github.com/tauri-apps/tauri/pull/13811) by [@mhbagheri-99](https://www.github.com/tauri-apps/tauri/../../mhbagheri-99)) Allow runner configuration to be an object with cmd, cwd, and args properties. The runner can now be configured as `{ "cmd": "my_runner", "cwd": "/path", "args": ["--quiet"] }` while maintaining backwards compatibility with the existing string format. + +## \[2.5.0] + +### New Features + +- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles. +- [`09c19932d`](https://www.github.com/tauri-apps/tauri/commit/09c19932d2ddf05f28bcdc73796a966532e7ca1c) ([#13304](https://www.github.com/tauri-apps/tauri/pull/13304) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds the option to configure the HTTP `Service-Worker-Allowed` response header in `app > security > headers` + +### Bug Fixes + +- [`eb3f0248c`](https://www.github.com/tauri-apps/tauri/commit/eb3f0248c25f1d259cf0fa9448b3c7c2b75b24a7) ([#13646](https://www.github.com/tauri-apps/tauri/pull/13646) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Only write `This default permission set includes the following` to the reference if the default permission set is not empty +- [`c8a30a61d`](https://www.github.com/tauri-apps/tauri/commit/c8a30a61d20552b43afd6f21cb66d18185314148) ([#13476](https://www.github.com/tauri-apps/tauri/pull/13476) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix capability filtering via `tauri.conf.json > app > security > capabilities` not working when generating allowed commands. +- [`b52da29d5`](https://www.github.com/tauri-apps/tauri/commit/b52da29d5dbdb675ddba438a335e6a59f620e536) ([#13429](https://www.github.com/tauri-apps/tauri/pull/13429) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `mainBinaryName` doesn't work when there's `.` in it + +### What's Changed + +- [`168629646`](https://www.github.com/tauri-apps/tauri/commit/168629646335f24cc7f1c4a61df22688b2198f98) ([#13418](https://www.github.com/tauri-apps/tauri/pull/13418) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Put dynamic ACL into a feature `dynamic-acl`, this is currently enabled by default to align with the previous behaviors, you can disable it through `default-features = false` to reduce the final binary size by not including the ACL references + +### Dependencies + +- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader. + +### Breaking Changes + +- [`b7cdb3b39`](https://www.github.com/tauri-apps/tauri/commit/b7cdb3b39ef7e84773ce9312535825801350fa20) ([#13410](https://www.github.com/tauri-apps/tauri/pull/13410) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Feature gated the HTML manipulation code in `tauri-utils` behined a flag to reduce compile time + +## \[2.4.0] + +### New Features + +- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `disableInputAccessoryView: bool` config for iOS. +- [`0aa48fb9e`](https://www.github.com/tauri-apps/tauri/commit/0aa48fb9e4b9d7b5bf3522000a76ebc1836394ed) ([#13030](https://www.github.com/tauri-apps/tauri/pull/13030)) Added `bundleVersion` to iOS and macOS configuration to support specifying a `CFBundleVersion`. +- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation + +### Bug Fixes + +- [`2dccfab53`](https://www.github.com/tauri-apps/tauri/commit/2dccfab5321fef55d45f3a4c674b6151b1c4424a) ([#13236](https://www.github.com/tauri-apps/tauri/pull/13236)) Fix `fileAssociations` missing `LSHandlerRank` on macOS. + +## \[2.3.1] + +### Enhancements + +- [`a851b6597`](https://www.github.com/tauri-apps/tauri/commit/a851b6597f7e37d12f9e4632945e8466800eb5ff) ([#13057](https://www.github.com/tauri-apps/tauri/pull/13057) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Enhanced the description of generated docs and schema for permission sets to include list of permissions within. + +### Bug Fixes + +- [`4ae14bf2f`](https://www.github.com/tauri-apps/tauri/commit/4ae14bf2f20546c0990c48dc465832ffc46a7247) ([#13093](https://www.github.com/tauri-apps/tauri/pull/13093) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Suppress deprecated warning in `TrayIconConfig`'s codegen + +## \[2.3.0] + +### New Features + +- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this. +- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS. + +### Enhancements + +- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform. + +### Bug Fixes + +- [`3cc4ad3c3`](https://www.github.com/tauri-apps/tauri/commit/3cc4ad3c381ee081cfcf0df28ea51507ad5c9b95) ([#12884](https://www.github.com/tauri-apps/tauri/pull/12884) by [@oscartbeaumont](https://www.github.com/tauri-apps/tauri/../../oscartbeaumont)) fix: allow double `--` in permissions as command can have double `__` + +### Performance Improvements + +- [`1cd8f55ee`](https://www.github.com/tauri-apps/tauri/commit/1cd8f55eed326d61860fee62ba2d2f4464bdcfcc) ([#13033](https://www.github.com/tauri-apps/tauri/pull/13033) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Don't ship global `bundle.global.js` if `app > withGlobalTauri` is set to false + +## \[2.2.0] + +### Enhancements + +- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only). + +## \[2.1.1] + +### Bug Fixes + +- [`46935212b`](https://www.github.com/tauri-apps/tauri/commit/46935212b61da44dc82dfeb803fceebf5659f7b7) ([#11658](https://www.github.com/tauri-apps/tauri/pull/11658) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `.json5` capability files not recognized even with `config-json5` feature enabled + +## \[2.1.0] + +### New Features + +- [`fabc2f283`](https://www.github.com/tauri-apps/tauri/commit/fabc2f283e38b62c721326e44645d47138418cbc) ([#11485](https://www.github.com/tauri-apps/tauri/pull/11485) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds a new configuration option `app > security > headers` to define headers that will be added to every http response from tauri to the web view. This doesn't include IPC messages and error responses. +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`. +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview. +- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level. +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > useHttpsScheme` config option to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `app > windows > windowClassname` config option to specify the name of the window class on Windows. + +### Enhancements + +- [`c33bbf457`](https://www.github.com/tauri-apps/tauri/commit/c33bbf45740274b6918ea6c647f366fb6008e459) ([#11575](https://www.github.com/tauri-apps/tauri/pull/11575) by [@kornelski](https://www.github.com/tauri-apps/tauri/../../kornelski)) Include the path in ACL I/O errors. + +### Bug Fixes + +- [`378142914`](https://www.github.com/tauri-apps/tauri/commit/37814291475814b4a24cc77b6fa457ec9ba7a779) ([#11429](https://www.github.com/tauri-apps/tauri/pull/11429) by [@griffi-gh](https://www.github.com/tauri-apps/tauri/../../griffi-gh)) Enhance resource directory resolution to support running on distros like NixOS. + +## \[2.0.2] + +### New Features + +- Add `bundler > windows > wix > version` to manually specify a wix-compatible version. + +## \[2.0.1] + +### What's Changed + +- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +### What's Changed + +- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78. +- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable! + +## \[2.0.0-rc.13] + +### New Features + +- [`a247170e1`](https://www.github.com/tauri-apps/tauri/commit/a247170e1f620a9b012274b11cfe51e90327d6e9) ([#11056](https://www.github.com/tauri-apps/tauri/pull/11056) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Expose the ability to enabled browser extensions in WebView2 on Windows. +- [`f57a729cd`](https://www.github.com/tauri-apps/tauri/commit/f57a729cd8f7e10d8daf0b9d5b85f9c7ad530496) ([#11039](https://www.github.com/tauri-apps/tauri/pull/11039) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `upgradeCode` in `wix` configuration to set an upgrade code for your MSI installer. This is recommended to be set if you plan to change your `productName`. + +### Bug Fixes + +- [`1efa5e718`](https://www.github.com/tauri-apps/tauri/commit/1efa5e7184009537b688a395596c696173ae0231) ([#11099](https://www.github.com/tauri-apps/tauri/pull/11099) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Rerun build script if the platform-specific configuration file changes. + +## \[2.0.0-rc.12] + +### New Features + +- [`ad294d274`](https://www.github.com/tauri-apps/tauri/commit/ad294d274dd81d2ef91ed73af9163b6e9b8eb964) ([#11032](https://www.github.com/tauri-apps/tauri/pull/11032) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > create` option to choose whether to create this window at app startup or not. + +## \[2.0.0-rc.11] + +### New Features + +- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `mainBinaryName` config option to set the file name for the main binary. + +## \[2.0.0-rc.10] + +### Bug Fixes + +- [`0a47bf043`](https://www.github.com/tauri-apps/tauri/commit/0a47bf04302ca8502d3da21b3bc27818720fe34a) ([#10946](https://www.github.com/tauri-apps/tauri/pull/10946) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that made the `identifier` in `tauri.conf.json` optional while it was actually required. + +## \[2.0.0-rc.9] + +### Dependencies + +- [`d9c8d3cc8`](https://www.github.com/tauri-apps/tauri/commit/d9c8d3cc8d5ca67cd767ffc7a521f801b41ce201) ([#10902](https://www.github.com/tauri-apps/tauri/pull/10902) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Update infer to 0.16, tray icon to 0.17, urlpattern to 0.3, image to 0.25 + +### Breaking Changes + +- [`faa259bac`](https://www.github.com/tauri-apps/tauri/commit/faa259bacf1ace670af763982c6903190faf163a) ([#10907](https://www.github.com/tauri-apps/tauri/pull/10907) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `Assets::iter` function now must return a iterator with `Item = (Cow<'_, str>, Cow<'_, [u8]>)` to be more flexible on contexts where the assets are not `'static`. + +## \[2.0.0-rc.8] + +### Enhancements + +- [`f0acf504a`](https://www.github.com/tauri-apps/tauri/commit/f0acf504a2a972c063188a00143d8bf2b887691d) ([#10858](https://www.github.com/tauri-apps/tauri/pull/10858) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Validate duplicate capability identifier. + +## \[2.0.0-rc.7] + +### New Features + +- [`58dda44a5`](https://www.github.com/tauri-apps/tauri/commit/58dda44a59b915f091602cdfc53385a148469793) ([#10339](https://www.github.com/tauri-apps/tauri/pull/10339) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add a new option `minimumWebview2Version` for Windows NSIS installer to trigger a webview2 update if the user's webview2 is older than this version + +### Bug Fixes + +- [`03f2a5098`](https://www.github.com/tauri-apps/tauri/commit/03f2a50981b8c01b1c196811fce9d93f1bf0820d) ([#10718](https://www.github.com/tauri-apps/tauri/pull/10718) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Update swift-rs fixing a plugin build when native dependencies are used. + +### Breaking Changes + +- [`073bb4f45`](https://www.github.com/tauri-apps/tauri/commit/073bb4f459a923541b94970dfa7e087bccaa2cfd) ([#10772](https://www.github.com/tauri-apps/tauri/pull/10772) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the deprecated `webview_fixed_runtime_path` config option, use the `webview_install_mode` instead. + +## \[2.0.0-rc.6] + +### Bug Fixes + +- [`9bcff3cd7`](https://www.github.com/tauri-apps/tauri/commit/9bcff3cd7997fe13425a21b577f93317831f77fa) ([#10703](https://www.github.com/tauri-apps/tauri/pull/10703) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Properly remove isolation script on Android. + +### What's Changed + +- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path. + +## \[2.0.0-rc.5] + +### Bug Fixes + +- [`da381e07f`](https://www.github.com/tauri-apps/tauri/commit/da381e07f3770988fe6d0859a02331b87cc6723f) ([#10696](https://www.github.com/tauri-apps/tauri/pull/10696) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Implemented `resource_dir` on Android, which returns a URI that needs to be resolved using [AssetManager::open](https://developer.android.com/reference/android/content/res/AssetManager#open\(java.lang.String,%20int\)). This will be handled by the file system plugin. +- [`da381e07f`](https://www.github.com/tauri-apps/tauri/commit/da381e07f3770988fe6d0859a02331b87cc6723f) ([#10696](https://www.github.com/tauri-apps/tauri/pull/10696) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `resource_dir` on iOS. + +## \[2.0.0-rc.4] + +### New Features + +- [`8d148a9e2`](https://www.github.com/tauri-apps/tauri/commit/8d148a9e2566edebfea2d75f32df7c9396d765a4) ([#10634](https://www.github.com/tauri-apps/tauri/pull/10634) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Custom sign command with object notation for whitespaces in the command path and arguments. + +### Bug Fixes + +- [`5c335ae9a`](https://www.github.com/tauri-apps/tauri/commit/5c335ae9ad88e46c2135a557390f6e808c9a6088) ([#10648](https://www.github.com/tauri-apps/tauri/pull/10648) by [@Flakebi](https://www.github.com/tauri-apps/tauri/../../Flakebi)) Prevent build script from rerunning unnecessarily by only writing files when the content changes. + +## \[2.0.0-rc.3] + +### Enhancements + +- [`0bb7b0f35`](https://www.github.com/tauri-apps/tauri/commit/0bb7b0f352960fb5111a40157c0953d19e76f1fd) ([#10559](https://www.github.com/tauri-apps/tauri/pull/10559) by [@Norbiros](https://www.github.com/tauri-apps/tauri/../../Norbiros)) Return autogenerated permissions from `autogenerate_command_permissions`. + +### Bug Fixes + +- [`9e891933d`](https://www.github.com/tauri-apps/tauri/commit/9e891933d8ac7a67e37770a149d0a5dd385ee625) ([#10293](https://www.github.com/tauri-apps/tauri/pull/10293) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `ResourcePaths` iterator returning an unexpected result for mapped resources, for example `"../resources/user.json": "resources/user.json"` generates this resource `resources/user.json/user.json` where it should generate just `resources/user.json`. +- [`9fe846615`](https://www.github.com/tauri-apps/tauri/commit/9fe846615b7c4f310f07897ded881c239e3df30a) ([#10547](https://www.github.com/tauri-apps/tauri/pull/10547) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix plugin permissions documentation heading for permissions table. + +### Dependencies + +- [`0afee5ed8`](https://www.github.com/tauri-apps/tauri/commit/0afee5ed80265c95c4581e173c4886677cfed593) ([#10436](https://www.github.com/tauri-apps/tauri/pull/10436) by [@nyurik](https://www.github.com/tauri-apps/tauri/../../nyurik)) Updated brotli to v6. + +## \[2.0.0-rc.2] + +### Bug Fixes + +- [`f5dfc0280`](https://www.github.com/tauri-apps/tauri/commit/f5dfc02800dbd3bdee671b032454c49ac7102fb4) ([#10533](https://www.github.com/tauri-apps/tauri/pull/10533) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue causing `tauri ios init` to fail if `iOS.minimumSystemVersion` was not configured explicitly. + +## \[2.0.0-rc.1] + +### New Features + +- [`8dc81b6cc`](https://www.github.com/tauri-apps/tauri/commit/8dc81b6cc2b8235b11f74a971d6aa3a5df5e9f68) ([#10496](https://www.github.com/tauri-apps/tauri/pull/10496) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > template` configuration option for custom Xcode project YML Handlebars template using XcodeGen. +- [`02c00abc6`](https://www.github.com/tauri-apps/tauri/commit/02c00abc63cf86e9bf9179cbb143d5145a9397b6) ([#10495](https://www.github.com/tauri-apps/tauri/pull/10495) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > minimumSystemVersion` configuration option. + +### Bug Fixes + +- [`7e810cb2a`](https://www.github.com/tauri-apps/tauri/commit/7e810cb2a3fd934017ae973e737864dfa4bdf64e) ([#10485](https://www.github.com/tauri-apps/tauri/pull/10485) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Fixed an issue where permission files will be generated with ':' in the file path. + +## \[2.0.0-rc.0] + +### New Features + +- [`a5bfbaa62`](https://www.github.com/tauri-apps/tauri/commit/a5bfbaa62b8cd0aacbb33f730d4e30b43c461fe1)([#9962](https://www.github.com/tauri-apps/tauri/pull/9962)) Added `bundle > iOS > frameworks` configuration to define a list of frameworks that are linked to the Xcode project when it is generated. + +### Enhancements + +- [`7aeac39e7`](https://www.github.com/tauri-apps/tauri/commit/7aeac39e7fb97dc57ca278f1c097058275c20aa2) ([#10397](https://www.github.com/tauri-apps/tauri/pull/10397)) Make the set of gtk application id optional, to allow more then one instance of the app running at the same time. + +### Bug Fixes + +- [`498f405ca`](https://www.github.com/tauri-apps/tauri/commit/498f405ca80440447823dd3c9cd53c0f79d655b5) ([#10404](https://www.github.com/tauri-apps/tauri/pull/10404)) Fixed an issue where configuration parsing errors always displayed 'tauri.conf.json' as the file path, even when using 'Tauri.toml' or 'tauri.conf.json5'. + + The error messages now correctly shows the actual config file being used. + +### Security fixes + +- [`426d14bb4`](https://www.github.com/tauri-apps/tauri/commit/426d14bb4164290d93b5a0f61e925cb2dfc4aafa) ([#10423](https://www.github.com/tauri-apps/tauri/pull/10423)) Explicitly check that the main frame's origin is the sender of Isolation Payloads + +## \[2.0.0-beta.19] + +### New Features + +- [`4c239729c`](https://www.github.com/tauri-apps/tauri/commit/4c239729c3e1b899ecbc6793c3682848e8de1729) ([#10167](https://www.github.com/tauri-apps/tauri/pull/10167) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `RawIsolationPayload::content_type` method. + +## \[2.0.0-beta.18] + +### New Features + +- [`fafc238f7`](https://www.github.com/tauri-apps/tauri/commit/fafc238f7288548975ca7d3e5207b925c0295c91) ([#9977](https://www.github.com/tauri-apps/tauri/pull/9977)) Add `bundle > homepage` option, if unset, it will fallback to `homepage` defined in `Cargo.toml`. +- [`656a64974`](https://www.github.com/tauri-apps/tauri/commit/656a64974468bc207bf39537e02ae179bdee9b83) ([#9318](https://www.github.com/tauri-apps/tauri/pull/9318)) Added a configuration option to disable hardened runtime on macOS codesign. +- [`5b769948a`](https://www.github.com/tauri-apps/tauri/commit/5b769948a81cac333f64c870a470ba6525bd5cd3) ([#9959](https://www.github.com/tauri-apps/tauri/pull/9959)) Add `include_image` macro to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons. +- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Add an option to disable NSIS compression `bundle > nsis > compression: "none"` +- [`f21029b1b`](https://www.github.com/tauri-apps/tauri/commit/f21029b1bc25f5cb987e1a25de94c2d364e3e462) ([#9994](https://www.github.com/tauri-apps/tauri/pull/9994)) Add `bundle > nsis > startMenuFolder` option to customize start menu folder for NSIS installer + +### Enhancements + +- [`878198777`](https://www.github.com/tauri-apps/tauri/commit/878198777ef693efdbd394cb4be4b234e8a7ed3d) ([#9999](https://www.github.com/tauri-apps/tauri/pull/9999)) Mark ACL `permissions` array with unique items + +### Breaking Changes + +- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Changed `NsisSettings::compression` field from `Option` to just `NsisCompression` +- [`911242f09`](https://www.github.com/tauri-apps/tauri/commit/911242f0928e0a2add3595fa9de27850fb875fa6) ([#9883](https://www.github.com/tauri-apps/tauri/pull/9883)) Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts` +- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Changed `NsisConfig::compression` field from `Option` to just `NsisCompression` + +## \[2.0.0-beta.17] + +### New Features + +- [`8a1ae2dea`](https://www.github.com/tauri-apps/tauri/commit/8a1ae2deaf3086e531ada25b1627f900e2e421fb)([#9843](https://www.github.com/tauri-apps/tauri/pull/9843)) Added an option to use a Xcode project for the iOS plugin instead of a plain SwiftPM project. +- [`5462e5cad`](https://www.github.com/tauri-apps/tauri/commit/5462e5cadc73c1b9083d852061d7c7f982cfbe53)([#9731](https://www.github.com/tauri-apps/tauri/pull/9731)) Add `installer_hooks` NSIS configuration field +- [`d6d3efbd1`](https://www.github.com/tauri-apps/tauri/commit/d6d3efbd125489cb46642b6d013cdc1eb7fc1a66)([#9865](https://www.github.com/tauri-apps/tauri/pull/9865)) Add `sign_command` in `WindowsConfig` + +### Breaking Changes + +- [`fc1543c65`](https://www.github.com/tauri-apps/tauri/commit/fc1543c65e736622bed93543dcc6504c43e200bb)([#9864](https://www.github.com/tauri-apps/tauri/pull/9864)) Removed `skip_webview_install` (`skipWebviewInstall`) option from config, which has been deprecated for a while now and planned to be removed in v2. Use `webview_install_mode` (`webviewInstallMode`) instead. +- [`265c23886`](https://www.github.com/tauri-apps/tauri/commit/265c23886ee5efbcc6d7188ff5c84cb32fa82aea)([#9375](https://www.github.com/tauri-apps/tauri/pull/9375)) Removed `Config::binary_name` and `PackageInfo::package_name` + +## \[2.0.0-beta.16] + +### Bug Fixes + +- [`be95d8d37`](https://www.github.com/tauri-apps/tauri/commit/be95d8d37c4b1a420c0d28a83b7efa40ab0b0ab5)([#9782](https://www.github.com/tauri-apps/tauri/pull/9782)) Fixes the `ToTokens` implementation for `Capability`. + +## \[2.0.0-beta.15] + +### Bug Fixes + +- [`a5205f179`](https://www.github.com/tauri-apps/tauri/commit/a5205f179e577cce5c05b710b597da8f4e1d0780)([#9691](https://www.github.com/tauri-apps/tauri/pull/9691)) Fixes the ToTokens implementation of the window configuration `proxy_url` field. + +## \[2.0.0-beta.14] + +### Bug Fixes + +- [`3fbc1703f`](https://www.github.com/tauri-apps/tauri/commit/3fbc1703f107dfb2c5a75e848805dcf60d449eb1)([#9676](https://www.github.com/tauri-apps/tauri/pull/9676)) Fixes `schemars` compilation issue. + +## \[2.0.0-beta.13] + +### Bug Fixes + +- [`a1e0e268f`](https://www.github.com/tauri-apps/tauri/commit/a1e0e268f02f7e2934bec48de4cac0dc00529a2b)([#9477](https://www.github.com/tauri-apps/tauri/pull/9477)) Replace `tauri:` prefix with `tauri-` for temporary permission file names + +## \[2.0.0-beta.12] + +### New Features + +- [`36b4c1249`](https://www.github.com/tauri-apps/tauri/commit/36b4c12497fbe636066f4848c6877b3ab6cc892e)([#9331](https://www.github.com/tauri-apps/tauri/pull/9331)) Added support for `provides`, `conflicts` and `replaces` (`obsoletes` for RPM) options for `bundler > deb` and `bundler > rpm` configs. + +## \[2.0.0-beta.11] + +### New Features + +- [`259d84529`](https://www.github.com/tauri-apps/tauri/commit/259d845290dde40639537258b2810567910f47f3)([#9209](https://www.github.com/tauri-apps/tauri/pull/9209)) Added `preInstallScript`, `postInstallScript`, `preRemoveScript` and `postRemoveScript` options for `bundler > deb` and `bundler > rpm` configs. + +### Enhancements + +- [`7c334cb18`](https://www.github.com/tauri-apps/tauri/commit/7c334cb1851ab034a3cfb472dd99dfc61ad3ca7f)([#9327](https://www.github.com/tauri-apps/tauri/pull/9327)) Make the isolation pattern encrypt key unextractable. +- [`a804a70a7`](https://www.github.com/tauri-apps/tauri/commit/a804a70a7aa1dc40fa9043206ad2265c6a5a437b)([#9328](https://www.github.com/tauri-apps/tauri/pull/9328)) The isolation iframe script now removes itself after execution. + +### Breaking Changes + +- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names. + +## \[2.0.0-beta.10] + +### New Features + +- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Added the `plugin` module. + +### Enhancements + +- [`7213b9e47`](https://www.github.com/tauri-apps/tauri/commit/7213b9e47242bef814aa7257e0bf84631bf5fe7e)([#9124](https://www.github.com/tauri-apps/tauri/pull/9124)) Fallback to an empty permission set if the plugin did not define its `default` permissions. + +## \[2.0.0-beta.9] + +### Breaking Changes + +- [`490a6b424`](https://www.github.com/tauri-apps/tauri/commit/490a6b424e81714524150aef96fbf6cf7004b940)([#9147](https://www.github.com/tauri-apps/tauri/pull/9147)) Removed the `assets::Assets` trait which is now part of the `tauri` crate. + +## \[2.0.0-beta.8] + +### Enhancements + +- [`3e472d0af`](https://www.github.com/tauri-apps/tauri/commit/3e472d0afcd67545dd6d9f18d304580a3b2759a8)([#9115](https://www.github.com/tauri-apps/tauri/pull/9115)) Changed the permission and capability platforms to be optional. + +### Breaking Changes + +- [`4ef17d083`](https://www.github.com/tauri-apps/tauri/commit/4ef17d08336a2e0df4a7ef9adea746d7419710b6)([#9116](https://www.github.com/tauri-apps/tauri/pull/9116)) The ACL configuration for remote URLs now uses the URLPattern standard instead of glob patterns. + +## \[2.0.0-beta.7] + +### Bug Fixes + +- [`86fa339de`](https://www.github.com/tauri-apps/tauri/commit/86fa339de7b176efafa9b3e89f94dcad5fcd03da)([#9071](https://www.github.com/tauri-apps/tauri/pull/9071)) Fix compile time error in context generation when using `app.windows.windowEffects.color` +- [`6c0683224`](https://www.github.com/tauri-apps/tauri/commit/6c068322460300e9d56a4fac5b018d4c437daa9e)([#9068](https://www.github.com/tauri-apps/tauri/pull/9068)) Fixes scope resolution grouping scopes for all windows. +- [`c68218b36`](https://www.github.com/tauri-apps/tauri/commit/c68218b362c417b62e56c7a2b5b32c13fe035a83)([#8990](https://www.github.com/tauri-apps/tauri/pull/8990)) Fix `BundleTarget::to_vec` returning an empty vec for `BundleTarget::All` variant. +- [`c68218b36`](https://www.github.com/tauri-apps/tauri/commit/c68218b362c417b62e56c7a2b5b32c13fe035a83)([#8990](https://www.github.com/tauri-apps/tauri/pull/8990)) Add `BundleType::all` method to return all possible `BundleType` variants. + +### Breaking Changes + +- [`9aa0d6e95`](https://www.github.com/tauri-apps/tauri/commit/9aa0d6e959269a9d99ff474e7f12bd397ea75fcd)([#9069](https://www.github.com/tauri-apps/tauri/pull/9069)) Removed `debug_eprintln!` and `consume_unused_variable` macros. +- [`bb23511ea`](https://www.github.com/tauri-apps/tauri/commit/bb23511ea80bcaffbdebf057301e463fff268c90)([#9079](https://www.github.com/tauri-apps/tauri/pull/9079)) Changed `CapabiltyFile::List` enum variant to be a tuple-struct and added `CapabiltyFile::NamedList`. This allows more flexibility when parsing capabilties from JSON files. + +## \[2.0.0-beta.6] + +### New Features + +- [`d7f56fef`](https://www.github.com/tauri-apps/tauri/commit/d7f56fef85cac3af4e2dbac1eac40e5567b1f160)([#9014](https://www.github.com/tauri-apps/tauri/pull/9014)) Allow defining a permission that only applies to a set of target platforms via the `platforms` configuration option. + +### Enhancements + +- [`04440edc`](https://www.github.com/tauri-apps/tauri/commit/04440edce870f9d06055616034941d79443d5a87)([#9019](https://www.github.com/tauri-apps/tauri/pull/9019)) Changed plugin markdown docs generation to table format. + +### Breaking Changes + +- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Allow defining permissions for the application commands via `tauri_build::Attributes::app_manifest`. + +## \[2.0.0-beta.5] + +### Enhancements + +- [`bc5b5e67`](https://www.github.com/tauri-apps/tauri/commit/bc5b5e671a546512f823f1c157421b4c3311dfc0)([#8984](https://www.github.com/tauri-apps/tauri/pull/8984)) Do not include a CSP tag in the application HTML and rely on the custom protocol response header instead. + +## \[2.0.0-beta.4] + +### Breaking Changes + +- [`a76fb118`](https://www.github.com/tauri-apps/tauri/commit/a76fb118ce2de22e1bdb4216bf0ac01dfc3e5799)([#8950](https://www.github.com/tauri-apps/tauri/pull/8950)) Changed the capability format to allow configuring both `remote: { urls: Vec }` and `local: bool (default: true)` instead of choosing one on the `context` field. + +## \[2.0.0-beta.3] + +### Breaking Changes + +- [`361ec37f`](https://www.github.com/tauri-apps/tauri/commit/361ec37fd4a5caa5b6630b9563ef079f53c6c336)([#8932](https://www.github.com/tauri-apps/tauri/pull/8932)) Moved `ProgressBarState` from `tauri-utils` to the `tauri::window` module and removed the `unity_uri` field. + +## \[2.0.0-beta.2] + +### Enhancements + +- [`0cb0a15c`](https://www.github.com/tauri-apps/tauri/commit/0cb0a15ce22af3d649cf219ac04188c14c5f4905)([#8789](https://www.github.com/tauri-apps/tauri/pull/8789)) Add `webviews` array on the capability for usage on multiwebview contexts. +- [`83a68deb`](https://www.github.com/tauri-apps/tauri/commit/83a68deb5676d39cd4728d2e140f6b46d5f787ed)([#8797](https://www.github.com/tauri-apps/tauri/pull/8797)) Added a new configuration option `tauri.conf.json > app > security > capabilities` to reference existing capabilities and inline new ones. If it is empty, all capabilities are still included preserving the current behavior. +- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) The `Context` struct now includes the runtime authority instead of the resolved ACL. This does not impact most applications. +- [`28fb036c`](https://www.github.com/tauri-apps/tauri/commit/28fb036ce476c6f22815c35385f923135212c6f3)([#8852](https://www.github.com/tauri-apps/tauri/pull/8852)) Enhance resource directory resolution on development. +- [`dd7571a7`](https://www.github.com/tauri-apps/tauri/commit/dd7571a7808676c8063a4983b9c6687dfaf03a09)([#8815](https://www.github.com/tauri-apps/tauri/pull/8815)) Do not generate JSON schema and markdown reference file if the plugin does not define any permissions and delete those files if they exist. +- [`5618f6d2`](https://www.github.com/tauri-apps/tauri/commit/5618f6d2ffc9ebf40710145538b06bebfa55f878)([#8856](https://www.github.com/tauri-apps/tauri/pull/8856)) Relax requirements on plugin's identifiers to be alphanumeric and `-` instead of only lower alpha and `-`. +- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) Refactored the capability types and resolution algorithm. + +### Bug Fixes + +- [`ae0fe47c`](https://www.github.com/tauri-apps/tauri/commit/ae0fe47c4c35fa87c77acf42af32ef3f0615cb08)([#8774](https://www.github.com/tauri-apps/tauri/pull/8774)) Fix compile error when `tauri.conf.json` had `bundle > license` set. + +### Breaking Changes + +- [`f284f9c5`](https://www.github.com/tauri-apps/tauri/commit/f284f9c545deeb77d15b6e8b1d0d05f49c40634c)([#8898](https://www.github.com/tauri-apps/tauri/pull/8898)) Changed the capability `remote` configuration to take a list of `urls` instead of `domains` for more flexibility. + +## \[2.0.0-beta.1] + +### Enhancements + +- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Moved the capability JSON schema to the `src-tauri/gen` folder so it's easier to track changes on the `capabilities` folder. + +### Bug Fixes + +- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Rerun build script when a new permission is added. + +## \[2.0.0-beta.0] + +### New Features + +- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage. +- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Add `parent` option for window config. +- [`58fe2e81`](https://www.github.com/tauri-apps/tauri/commit/58fe2e812a85b9f4eba105286a63f271ea637836)([#8670](https://www.github.com/tauri-apps/tauri/pull/8670)) Add `WebviewUrl::CustomProtocol` enum variant. + +### What's Changed + +- [`6639a579`](https://www.github.com/tauri-apps/tauri/commit/6639a579c76d45210f33a72d37e21d4c5a9d334b)([#8441](https://www.github.com/tauri-apps/tauri/pull/8441)) Added the `WindowConfig::proxy_url` `WebviewBuilder::proxy_url() / WebviewWindowBuilder::proxy_url()` options when creating a webview. + +### Breaking Changes + +- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md): + + - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level. + - Removed `package` object. + - Renamed `tauri` object to `app`. + - Moved `tauri.bundle` object to the top-level. + - Renamed `build.distDir` field to `frontendDist`. + - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs. + - Moved `tauri.pattern` to `app.security.pattern`. + - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object. + - Moved `build.withGlobalTauri` to `app.withGlobalTauri`. + - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`. + - Moved `tauri.bundle.deb` object to `bundle.linux.deb`. + - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`. + - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`. + - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit. +- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Renamed `config::WindowUrl` to `config::WebviewUrl`. +- [`a093682d`](https://www.github.com/tauri-apps/tauri/commit/a093682d2df7169b024bb4f736c7f1fd2ea8b327)([#8621](https://www.github.com/tauri-apps/tauri/pull/8621)) Changed `error` field in `ConfigError::FormatToml` to be boxed `Box` to reduce the enum `ConfigError` size in memory. +- [`58fe2e81`](https://www.github.com/tauri-apps/tauri/commit/58fe2e812a85b9f4eba105286a63f271ea637836)([#8670](https://www.github.com/tauri-apps/tauri/pull/8670)) Changed `dist_dir` and `dev_path` config options to be optional. + +## \[2.0.0-alpha.13] + +### Bug Fixes + +- [`9b230de7`](https://www.github.com/tauri-apps/tauri/commit/9b230de7bc6690c2733f5324d50b999af1f7a6ef)([#8407](https://www.github.com/tauri-apps/tauri/pull/8407)) Fix compile error when parsing config that includes float values. + +## \[2.0.0-alpha.12] + +### New Features + +- [\`\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Add bundle DMG configuration options. + +## \[2.0.0-alpha.11] + +### Breaking Changes + +- [`5e84e92e`](https://www.github.com/tauri-apps/tauri/commit/5e84e92e99376f24b730f8eba002239379b593e1)([#8243](https://www.github.com/tauri-apps/tauri/pull/8243)) Changed `platform::windows_version` to return a `(u32, u32, u32)` instead of `Option<(u32, u32, u32)>` + +## \[2.0.0-alpha.10] + +### Enhancements + +- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release. + +### Dependencies + +- [`c7c2507d`](https://www.github.com/tauri-apps/tauri/commit/c7c2507da16a9beb71bf06745fe7ac1325ab7c2a)([#8035](https://www.github.com/tauri-apps/tauri/pull/8035)) Update `windows` to version `0.51` and `webview2-com` to version `0.27` + +## \[2.0.0-alpha.9] + +### New Features + +- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added `set_progress_bar` to `Window`. +- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added the `always_on_bottom` option to the window configuration. +- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70. +- [`ed32257d`](https://www.github.com/tauri-apps/tauri/commit/ed32257d044f90b5eb15053efd1667125def2d2b)([#7794](https://www.github.com/tauri-apps/tauri/pull/7794)) On Windows, add `WindowEffect::Tabbed`,`WindowEffect::TabbedDark` and `WindowEffect::TabbedLight` + +### Breaking Changes + +- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables. + +## \[2.0.0-alpha.8] + +### Enhancements + +- Add an option to specify `id` for the tray icon in the tauri configuration file. + +### Breaking Changes + +- [`100d9ede`](https://www.github.com/tauri-apps/tauri/commit/100d9ede35995d9db21d2087dd5606adfafb89a5)([#7802](https://www.github.com/tauri-apps/tauri/pull/7802)) Follow file name conventions set by desktop for mobile Tauri configuration files. Added `target` argument on most `config::parse` methods. + +## \[2.0.0-alpha.7] + +### New Features + +- [`4db363a0`](https://www.github.com/tauri-apps/tauri/commit/4db363a03c182349f8491f46ced258d84723b11f)([#6589](https://www.github.com/tauri-apps/tauri/pull/6589)) Added `visible_on_all_workspaces` configuration option to `WindowBuilder`, `Window`, and `WindowConfig`. +- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Add option to specify a tooltip text for the tray icon in the config. +- [`74b1f4fc`](https://www.github.com/tauri-apps/tauri/commit/74b1f4fc6625d5b4f9b86f70e4eebd6551c61809)([#7384](https://www.github.com/tauri-apps/tauri/pull/7384)) Add `WindowEffect::MicaDark` and `WindowEffect::MicaLight` +- [`3b98141a`](https://www.github.com/tauri-apps/tauri/commit/3b98141aa26f74c641a4090874247b97079bd58a)([#3736](https://www.github.com/tauri-apps/tauri/pull/3736)) Add a configuration object for file associations under `BundleConfig`. + +### Enhancements + +- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Use custom protocols on the IPC implementation to enhance performance. + +### Security fixes + +- [`43c6285e`](https://www.github.com/tauri-apps/tauri/commit/43c6285e9006fb84066461d57fe09ea8db76d636)([#7359](https://www.github.com/tauri-apps/tauri/pull/7359)) Changed HTML implementation from unmaintained `kuchiki` to `kuchikiki`. + +### Breaking Changes + +- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `systemTray` config option has been renamed to `trayIcon`. + +## \[2.0.0-alpha.6] + +### New Features + +- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `window_effects` option to the window configuration. + +## \[2.0.0-alpha.5] + +- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Remove `enable_tauri_api` from the IPC scope. +- [`09376af5`](https://www.github.com/tauri-apps/tauri/commit/09376af59424cc27803fa2820d2ac0d4cdc90a6d)([#6704](https://www.github.com/tauri-apps/tauri/pull/6704)) Moved the `cli` feature to its own plugin in the plugins-workspace repository. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the `protocol` scope configuration to the `asset_protocol` field in `SecurityConfig`. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`. +- [`b072daa3`](https://www.github.com/tauri-apps/tauri/commit/b072daa3bd3e38b808466666619ddb885052c5b2)([#6919](https://www.github.com/tauri-apps/tauri/pull/6919)) Moved the `updater` feature to its own plugin in the plugins-workspace repository. +- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65. +- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration. +- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Remove the updater's dialog option. + +## \[2.0.0-alpha.4] + +- Added `android` configuration object under `tauri > bundle`. + - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07 + +## \[2.0.0-alpha.3] + +- Pull changes from Tauri 1.3 release. + - [](https://www.github.com/tauri-apps/tauri/commit/undefined) on undefined + +## \[2.0.0-alpha.2] + +- Added the `shadow` option to the window configuration and `set_shadow` option to the `window` allow list. + - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08 + +## \[2.0.0-alpha.1] + +- Bump the MSRV to 1.64. + - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30 +- Added `crate_name` field on `PackageInfo`. + - [630a7f4b](https://www.github.com/tauri-apps/tauri/commit/630a7f4b18cef169bfd48673609306fec434e397) refactor: remove mobile log initialization, ref [#6049](https://www.github.com/tauri-apps/tauri/pull/6049) ([#6081](https://www.github.com/tauri-apps/tauri/pull/6081)) on 2023-01-17 + +## \[2.0.0-alpha.0] + +- Parse `android` and `ios` Tauri configuration files. + - [b3a3afc7](https://www.github.com/tauri-apps/tauri/commit/b3a3afc7de8de4021d73559288f5192732a706cf) feat(core): detect android and ios platform configuration files ([#4997](https://www.github.com/tauri-apps/tauri/pull/4997)) on 2022-08-22 +- First mobile alpha release! + - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08 + +## \[1.5.3] + +### New features + +- [`7aa30dec`](https://www.github.com/tauri-apps/tauri/commit/7aa30dec85a17c3d3faaf3841b93e10991b991b0)([#8620](https://www.github.com/tauri-apps/tauri/pull/8620)) Add `priority`, `section` and `changelog` options in Debian config. + +## \[1.5.2] + +### Bug Fixes + +- [`9b230de7`](https://www.github.com/tauri-apps/tauri/commit/9b230de7bc6690c2733f5324d50b999af1f7a6ef)([#8407](https://www.github.com/tauri-apps/tauri/pull/8407)) Fix compile error when parsing config that includes float values. + +## \[1.5.3] + +### New Features + +- [`b3e53e72`](https://www.github.com/tauri-apps/tauri/commit/b3e53e7243311a2659b7569dddc20c56ac9f9d8e)([#8288](https://www.github.com/tauri-apps/tauri/pull/8288)) Added `Assets::iter` to iterate on all embedded assets. + +## \[1.5.0] + +### New Features + +- [`4dd4893d`](https://www.github.com/tauri-apps/tauri/commit/4dd4893d7d166ac3a3b6dc2e3bd2540326352a78)([#5950](https://www.github.com/tauri-apps/tauri/pull/5950)) Allow specifying resources as a map specifying source and target paths. + +### Enhancements + +- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://.localhost/` for custom protocols on Windows. + +### Bug Fixes + +- [`a6b52e44`](https://www.github.com/tauri-apps/tauri/commit/a6b52e44f22844009e273fb0250368d7a463f095)([#6519](https://www.github.com/tauri-apps/tauri/pull/6519)) Fix `io::read_line` not including the new line character `\n`. + +### Security fixes + +- [`eeff1784`](https://www.github.com/tauri-apps/tauri/commit/eeff1784e1ffa568e4ba024e17dd611f8e086784)([#7367](https://www.github.com/tauri-apps/tauri/pull/7367)) Changed HTML implementation from unmaintained `kuchiki` to `kuchikiki`. + +## \[1.4.0] + +### New Features + +- [`acc36fe1`](https://www.github.com/tauri-apps/tauri/commit/acc36fe1176cc8aa9063bde932abeb94796c5c72)([#6158](https://www.github.com/tauri-apps/tauri/pull/6158)) Add option to configure `require_literal_leading_dot` on `fs` and `asset` protocol scopes. +- [`35cd751a`](https://www.github.com/tauri-apps/tauri/commit/35cd751adc6fef1f792696fa0cfb471b0bf99374)([#5176](https://www.github.com/tauri-apps/tauri/pull/5176)) Added the `desktop_template` option on `tauri.conf.json > tauri > bundle > deb`. +- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` options to the window configuration. +- [`3cb7a3e6`](https://www.github.com/tauri-apps/tauri/commit/3cb7a3e642bb10ee90dc1d24daa48b8c8c15c9ce)([#6997](https://www.github.com/tauri-apps/tauri/pull/6997)) Add `MimeType::parse_with_fallback` and `MimeType::parse_from_uri_with_fallback` +- [`29488205`](https://www.github.com/tauri-apps/tauri/commit/2948820579d20dfaa0861c2f0a58bd7737a7ffd1)([#6867](https://www.github.com/tauri-apps/tauri/pull/6867)) Allow specifying custom language files of Tauri's custom messages for the NSIS installer +- [`e092f799`](https://www.github.com/tauri-apps/tauri/commit/e092f799469ff32c7d1595d0f07d06fd2dab5c29)([#6887](https://www.github.com/tauri-apps/tauri/pull/6887)) Add `nsis > template` option to specify custom NSIS installer template. +- [`cd3846c8`](https://www.github.com/tauri-apps/tauri/commit/cd3846c8ce61ab2879b3911e831525e6242aaab2)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Add `WindowsUpdateInstallMode::nsis_args` +- [`85e77fb7`](https://www.github.com/tauri-apps/tauri/commit/85e77fb797ec17882f55d0944578d929fc6c9c1f)([#6762](https://www.github.com/tauri-apps/tauri/pull/6762)) Correctly determine MIME type of `.txt` files. + +## \[1.3.0] + +- Added the `additional_browser_args` option to the window configuration. + - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14 +- Added the `content_protected` option to the window configuration. + - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13 +- Correctly determine mime type of `.less`, `.sass` and `.styl` files. + - [5fdf8dcb](https://www.github.com/tauri-apps/tauri/commit/5fdf8dcb8ed171d06121dceb32078a7e4f86cc64) fix(core): mime type of .less, .sass and .styl files ([#6316](https://www.github.com/tauri-apps/tauri/pull/6316)) on 2023-02-19 +- Bump minimum supported Rust version to 1.60. + - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31 +- Add initial support for building `nsis` bundles on non-Windows platforms. + - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19 +- Add `nsis` bundle target + - [c94e1326](https://www.github.com/tauri-apps/tauri/commit/c94e1326a7c0767a13128a8b1d327a00156ece12) feat(bundler): add `nsis`, closes [#4450](https://www.github.com/tauri-apps/tauri/pull/4450), closes [#2319](https://www.github.com/tauri-apps/tauri/pull/2319) ([#4674](https://www.github.com/tauri-apps/tauri/pull/4674)) on 2023-01-03 +- Added configuration to specify remote URLs allowed to access the IPC. + - [ee71c31f](https://www.github.com/tauri-apps/tauri/commit/ee71c31fd09cc5427da6d29d37c003a914547696) feat(core): allow configuring remote domains with IPC access, closes [#5088](https://www.github.com/tauri-apps/tauri/pull/5088) ([#5918](https://www.github.com/tauri-apps/tauri/pull/5918)) on 2023-04-11 + +## \[1.2.1] + +- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`. + - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18 + +## \[1.2.0] + +- Validate `package > productName` in the tauri config and produce errors if it contains one of the following characters `/\:*?\"<>|` + - [b9316a64](https://www.github.com/tauri-apps/tauri/commit/b9316a64eaa9348c79efafb8b94960d9b4d5b27a) fix(cli): validate `productName` in config, closes [#5233](https://www.github.com/tauri-apps/tauri/pull/5233) ([#5262](https://www.github.com/tauri-apps/tauri/pull/5262)) on 2022-09-28 +- Properly serialize HTML template tags. + - [aec5537d](https://www.github.com/tauri-apps/tauri/commit/aec5537de0205f62b2ae5c89da04d21930a6fc2e) fix(codegen): serialize template tags, closes [#4410](https://www.github.com/tauri-apps/tauri/pull/4410) ([#5247](https://www.github.com/tauri-apps/tauri/pull/5247)) on 2022-09-28 +- `PatternKind::Isolation` is now defined even without the `isolation` feature. + - [a178f95d](https://www.github.com/tauri-apps/tauri/commit/a178f95d68b773779b40235a3a22115a5e36aa6a) feat: config schema generator ([#5193](https://www.github.com/tauri-apps/tauri/pull/5193)) on 2022-10-28 +- Added the `app` allowlist module. + - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03 +- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28 +- Add `tauri.conf.json > bundle > publisher` field to specify the app publisher. + - [628285c1](https://www.github.com/tauri-apps/tauri/commit/628285c1cf43f03ed62378f3b6cc0c991317526f) feat(bundler): add `publisher` field, closes [#5273](https://www.github.com/tauri-apps/tauri/pull/5273) ([#5283](https://www.github.com/tauri-apps/tauri/pull/5283)) on 2022-09-28 +- Canonicalize the return value of `platform::resource_dir`. + - [a06dc699](https://www.github.com/tauri-apps/tauri/commit/a06dc6993148f10ff7623c9dcc81f313dd960ad0) fix(core): canonicalize resource dir to fix scope check, closes [#5196](https://www.github.com/tauri-apps/tauri/pull/5196) ([#5218](https://www.github.com/tauri-apps/tauri/pull/5218)) on 2022-09-29 +- Added `title` option on the system tray configuration (macOS only). + - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30 +- Added the `user_agent` option to the window configuration. + - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02 +- Add `mime_type` module. + - [54c337e0](https://www.github.com/tauri-apps/tauri/commit/54c337e06f3bc624c4780cf002bc54790f446c90) feat(cli): hotreload support for frontend static files, closes [#2173](https://www.github.com/tauri-apps/tauri/pull/2173) ([#5256](https://www.github.com/tauri-apps/tauri/pull/5256)) on 2022-09-28 + +## \[1.1.1] + +- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs. + - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16 + +## \[1.1.0] + +- Allow adding `build > beforeBundleCommand` in tauri.conf.json to run a shell command before the bundling phase. + - [57ab9847](https://www.github.com/tauri-apps/tauri/commit/57ab9847eb2d8c9a5da584b873b7c072e9ee26bf) feat(cli): add `beforeBundleCommand`, closes [#4879](https://www.github.com/tauri-apps/tauri/pull/4879) ([#4893](https://www.github.com/tauri-apps/tauri/pull/4893)) on 2022-08-09 +- Change `before_dev_command` and `before_build_command` config value to allow configuring the current working directory. + - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02 +- Allow configuring the `before_dev_command` to force the CLI to wait for the command to finish before proceeding. + - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02 +- Added support to configuration files in TOML format (Tauri.toml file). + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Refactored the `config::parse` module. + - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02 +- Update windows to 0.39.0 and webview2-com to 0.19.1. + - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03 + +## \[1.0.3] + +- Added `menu_on_left_click: bool` to the `SystemTrayConfig`. + - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05 +- Added `config::parse::read_platform` and `config::parse::get_platform_config_filename`. + - [8e3e7fc6](https://www.github.com/tauri-apps/tauri/commit/8e3e7fc64641afc7a6833bc93205e6f525562545) feat(cli): improve bundle identifier validation, closes [#4589](https://www.github.com/tauri-apps/tauri/pull/4589) ([#4596](https://www.github.com/tauri-apps/tauri/pull/4596)) on 2022-07-05 + +## \[1.0.2] + +- Expose `platform::windows_version` function. + - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30 + +## \[1.0.1] + +- Changed the `BundleConfig::targets` to a `BundleTarget` enum to enhance generated documentation. + - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21 +- Added `platform::is_windows_7`. + - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28 +- Suppress unused variable warning in release builds. + - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22 +- Added webview install mode options. + - [2ca762d2](https://www.github.com/tauri-apps/tauri/commit/2ca762d207030a892a6d128b519e150e2d733468) feat(bundler): extend webview2 installation options, closes [#2882](https://www.github.com/tauri-apps/tauri/pull/2882) [#2452](https://www.github.com/tauri-apps/tauri/pull/2452) ([#4466](https://www.github.com/tauri-apps/tauri/pull/4466)) on 2022-06-26 + +## \[1.0.0] + +- Upgrade to `stable`! + - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15 + +## \[1.0.0-rc.9] + +- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`. + - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10 + +## \[1.0.0-rc.8] + +- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`. + - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25 + - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27 + +## \[1.0.0-rc.7] + +- Allow configuring the display options for the MSI execution allowing quieter updates. + - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15 + +## \[1.0.0-rc.6] + +- Added `$schema` support to `tauri.conf.json`. + - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03 +- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable. + - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02 + +## \[1.0.0-rc.5] + +- Added the `io` module with the `read_line` method. + - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22 +- **Breaking change:** Removed the `useBootstrapper` option. Use https://github.com/tauri-apps/fix-path-env-rs instead. + - [6a5ff08c](https://www.github.com/tauri-apps/tauri/commit/6a5ff08ce9052b656aa40accedfd4315825164a3) refactor: remove bootstrapper, closes [#3786](https://www.github.com/tauri-apps/tauri/pull/3786) ([#3832](https://www.github.com/tauri-apps/tauri/pull/3832)) on 2022-03-31 + +## \[1.0.0-rc.4] + +- Added an option to disable the CSP injection of distributable assets nonces and hashes. + - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28 + +- Use the default value for `MacConfig.minimumSystemVersion` if the value is set to an empty string. + - [c81534eb](https://www.github.com/tauri-apps/tauri/commit/c81534ebd873c358e0346c7949aeb171803149a5) feat(cli): use default macOS minimum system version when it is empty ([#3658](https://www.github.com/tauri-apps/tauri/pull/3658)) on 2022-03-13 + +- Replace multiple dependencies who's C code compiled concurrently and caused + the other ones to bloat compile time significantly. + +- `zstd` -> `brotli` + +- `blake3` -> a vendored version of the blake3 reference + +- `ring` -> `getrandom` + +See https://github.com/tauri-apps/tauri/pull/3773 for more information about +these specific choices. + +- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27 + +## \[1.0.0-rc.3] + +- Use `is_symlink` API compatible with Rust v1.57 instead of std/fs/struct.Metadata.html#method.is_symlink. + - [73388119](https://www.github.com/tauri-apps/tauri/commit/73388119e653e7902b19beef2ab6d7c5f8a7b83a) use older symlink check function ([#3579](https://www.github.com/tauri-apps/tauri/pull/3579)) on 2022-03-01 + +## \[1.0.0-rc.2] + +- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`. + - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17 + +## \[1.0.0-rc.1] + +- Change default value for the `freezePrototype` configuration to `false`. + - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12 + +## \[1.0.0-rc.0] + +- The `allowlist` configuration now includes a `clipboard` object, controlling the exposure of the `writeText` and `readText` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- The dialog allowlist now includes flags for the `message`, `ask` and `confirm` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- The `allowlist` configuration now includes a `process` object, controlling the exposure of the `relaunch` and `exit` APIs. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- The `window` allowlist now includes options to enable all window modification APIs: `center`, `close`, `create`, `hide`, `maximize`, `minimize`, `print`, `requestUserAttention`, `setAlwaysOnTop`, `setDecorations`, `setFocus`, `setFullscreen`, `setIcon`, `setMaxSize`, `setMinSize`, `setPosition`, `setResizable`, `setSize`, `setSkipTaskbar`, `setTitle`, `show`, `startDragging`, `unmaximize` and `unminimize`. + - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09 +- Added `asset` allowlist configuration, which enables the `asset` protocol and defines it access scope. + - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \[TRI-026] \[TRI-010] \[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09 +- Change `CliArg` numeric types from `u64` to `usize`. + - [1f988535](https://www.github.com/tauri-apps/tauri/commit/1f98853573a837dd0cfc2161b206a5033ec2da5e) chore(deps) Update Tauri Core ([#2480](https://www.github.com/tauri-apps/tauri/pull/2480)) on 2021-08-24 +- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives). + - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09 +- The path returned from `tauri::api::process::current_binary` is now cached when loading the binary. + - [7c3db7a3](https://www.github.com/tauri-apps/tauri/commit/7c3db7a3811fd4de3e71c78cfd00894fa51ab786) cache current binary path much sooner ([#45](https://www.github.com/tauri-apps/tauri/pull/45)) on 2022-02-01 +- Added `dev_csp` to the `security` configuration object. + - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09 +- Fixes resource directory resolution on Linux. + - [1a28904b](https://www.github.com/tauri-apps/tauri/commit/1a28904b8ebea92e143d5dc21ebd209e9edec531) fix(core): resource path resolution on Linux, closes [#2493](https://www.github.com/tauri-apps/tauri/pull/2493) on 2021-08-22 +- Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option. + - [85df94f2](https://www.github.com/tauri-apps/tauri/commit/85df94f2b0d40255812b42c5e32a70c4b45392df) feat(core): config for fixed webview2 runtime version path ([#27](https://www.github.com/tauri-apps/tauri/pull/27)) on 2021-11-02 +- The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command. + - [d95cc831](https://www.github.com/tauri-apps/tauri/commit/d95cc83105dda52df7514e30e54f3676cdb374ee) feat: enforce updater public key \[TRI-015] ([#42](https://www.github.com/tauri-apps/tauri/pull/42)) on 2022-01-09 +- Added the `isolation` pattern. + - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) Co-authored-by: Lucas Fernandes Nogueira on 2022-01-17 +- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension. + +Here is the logic flow that determines if JSON or JSON5 will be used to parse the config: + +1. Check if `tauri.conf.json` exists + a. Parse it with `serde_json` + b. Parse it with `json5` if `serde_json` fails + c. Return original `serde_json` error if all above steps failed +2. Check if `tauri.conf.json5` exists + a. Parse it with `json5` + b. Return error if all above steps failed +3. Return error if all above steps failed + +- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03 +- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development. + - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08 +- **Breaking change**\* Remove default webview window when `tauri.conf.json > tauri > windows` is not set. + - [c119060e](https://www.github.com/tauri-apps/tauri/commit/c119060e3d9a5a824639fb6b3c45a87e7a62e4e2) refactor(core): empty default value for config > tauri > windows ([#3380](https://www.github.com/tauri-apps/tauri/pull/3380)) on 2022-02-10 +- The minimum Rust version is now `1.56`. + - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22 +- Adds `scope` glob array config under `tauri > allowlist > fs`. + Adds `assetScope` glob array config under `tauri > allowlist > protocol`. + Adds `scope` URL array config under `tauri > allowlist > http`. + - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \[TRI-026] \[TRI-010] \[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09 + - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29 +- The `shell` allowlist now includes a `sidecar` flag, which enables the use of the `shell` API to execute sidecars. + - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24 +- Force updater endpoint URL to use `https` on release builds. + - [c077f449](https://www.github.com/tauri-apps/tauri/commit/c077f449270cffbf7956b1af81e1fb237ebf564a) feat: force endpoint URL to use https on release \[TRI-015] ([#41](https://www.github.com/tauri-apps/tauri/pull/41)) on 2022-01-09 + +## \[1.0.0-beta.3] + +- Fixes minimum window height being used as maximum height. + - [e3f99165](https://www.github.com/tauri-apps/tauri/commit/e3f9916526b226866137cb663e5cafab2b6a0e01) fix(core) minHeight being used as maxHeight ([#2247](https://www.github.com/tauri-apps/tauri/pull/2247)) on 2021-07-19 +- Implement `Debug` on public API structs and enums. + - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11 +- Keep original value on `config > package > productName` on Linux (previously converted to kebab-case). + - [3f039cb8](https://www.github.com/tauri-apps/tauri/commit/3f039cb8a308b0f18deaa37d7cfb1cc50d308d0e) fix: keep original `productName` for .desktop `Name` field, closes [#2295](https://www.github.com/tauri-apps/tauri/pull/2295) ([#2384](https://www.github.com/tauri-apps/tauri/pull/2384)) on 2021-08-10 +- Inject the invoke key on regular `` tags. + - [d0142e87](https://www.github.com/tauri-apps/tauri/commit/d0142e87ddf5231fd46e2cbe4769bb16f3fe01e9) fix(core): invoke key injection on regular JS scripts, closes [#2342](https://www.github.com/tauri-apps/tauri/pull/2342) ([#2344](https://www.github.com/tauri-apps/tauri/pull/2344)) on 2021-08-03 + +## \[1.0.0-beta.2] + +- Inject invoke key on `script` tags with `type="module"`. + - [f03eea9c](https://www.github.com/tauri-apps/tauri/commit/f03eea9c9b964709532afbc4d1dd343b3fd96081) feat(core): inject invoke key on `"#; + + let document = parse(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false)); + + assert_eq!( + String::from_utf8(serialize_node(&document)).unwrap(), + format!( + r#""# + ) + ); + } + + #[test] + fn inject_nonce_to_styles() { + let html = r#""#; + + let document = parse(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false)); + + assert_eq!( + String::from_utf8(serialize_node(&document)).unwrap(), + format!( + r#""# + ) + ); + } + + #[test] + fn inject_nonce_skips_existing() { + let html = r#""#; + + let document = parse(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false)); + + assert_eq!(String::from_utf8(serialize_node(&document)).unwrap(), html); + } + + #[test] + fn inject_nonce_respects_disabled_modification() { + let html = r#""#; + + let document = parse(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(true)); + + assert_eq!( + String::from_utf8(serialize_node(&document)).unwrap(), + r#""# + ); + } + + #[test] + fn inline_isolation_replaces_src_with_content() { + let temp_dir = tempfile::tempdir().unwrap(); + let mut file = tempfile::NamedTempFile::with_suffix_in(".js", &temp_dir).unwrap(); + file.write_all(b"console.log('test');").unwrap(); + let file_name = file.path().file_name().unwrap().to_str().unwrap(); + + let html = + format!(r#""#); + let document = parse(html); + inline_isolation(&document, temp_dir.path()); + + assert_eq!( + String::from_utf8(serialize_node(&document)).unwrap(), + r#""# + ); + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/html2.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/html2.rs new file mode 100644 index 0000000..7f591dc --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/html2.rs @@ -0,0 +1,335 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! The module to process HTML in Tauri. +//! +//! # Stability +//! +//! This is utility used in Tauri internally and not considered part of the stable API. +//! If you use it, note that it may include breaking changes in the future. + +use dom_query::NodeRef; + +use crate::{ + assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, + config::DisabledCspModificationKind, +}; + +/// # Stability +/// +/// This dependency might receive updates in minor releases. +pub use dom_query::Document; + +/// Serializes the document to HTML. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +pub fn serialize_doc(document: &Document) -> Vec { + document.html().as_bytes().to_vec() +} + +/// Parses the given HTML string. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +pub fn parse_doc(html: String) -> Document { + Document::from(html) +} + +fn ensure_head(document: &Document) -> NodeRef<'_> { + document.head().unwrap_or_else(|| { + let html = document.html_root(); + let head = document.tree.new_element("head"); + html.prepend_child(&head); + head + }) +} + +fn inject_nonce(document: &Document, selector: &str, token: &str) { + let elements = document.select(selector); + for elem in elements.nodes() { + // if the node already has the `nonce` attribute, skip it + if elem.attr("nonce").is_some() { + continue; + } + elem.set_attr("nonce", token); + } +} + +/// Inject nonce tokens to all scripts and styles. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +pub fn inject_nonce_token( + document: &Document, + dangerous_disable_asset_csp_modification: &DisabledCspModificationKind, +) { + if dangerous_disable_asset_csp_modification.can_modify("script-src") { + inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN); + } + if dangerous_disable_asset_csp_modification.can_modify("style-src") { + inject_nonce(document, "style", STYLE_NONCE_TOKEN); + } +} + +/// Injects a content security policy to the HTML. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +pub fn inject_csp(document: &Document, csp: &str) { + let head = ensure_head(document); + let meta_tag = document.tree.new_element("meta"); + meta_tag.set_attr("http-equiv", "Content-Security-Policy"); + meta_tag.set_attr("content", csp); + head.append_child(&meta_tag); +} + +/// Injects a content security policy to the HTML. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +pub fn append_script_to_head(document: &Document, script: &str) { + let head = ensure_head(document); + let script_tag = document.tree.new_element("script"); + script_tag.set_text(script); + head.prepend_child(&script_tag); +} + +/// Injects the Isolation JavaScript to a codegen time document. +/// +/// Note: This function is not considered part of the stable API. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +#[cfg(feature = "isolation")] +pub fn inject_codegen_isolation_script(document: &Document) { + use crate::pattern::isolation::IsolationJavascriptCodegen; + use serialize_to_javascript::DefaultTemplate; + + let head = ensure_head(document); + + let script_content = IsolationJavascriptCodegen {} + .render_default(&Default::default()) + .expect("unable to render codegen isolation script template") + .into_string(); + + let script_tag = document.tree.new_element("script"); + script_tag.set_attr("nonce", SCRIPT_NONCE_TOKEN); + script_tag.set_text(script_content); + + head.prepend_child(&script_tag); +} + +/// Temporary workaround for Windows not allowing requests +/// +/// Note: this does not prevent path traversal due to the isolation application expectation that it +/// is secure. +/// +/// # Stability +/// +/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases. +#[cfg(feature = "isolation")] +pub fn inline_isolation(document: &Document, dir: &std::path::Path) { + let scripts = document.select("script[src]"); + + for script in scripts.nodes() { + let src = match script.attr("src") { + Some(s) => s.to_string(), + None => continue, + }; + + let mut path = std::path::PathBuf::from(src); + if path.has_root() { + path = path + .strip_prefix("/") + .expect("Tauri \"Isolation\" Pattern only supports relative or absolute (`/`) paths.") + .into(); + } + + let file = std::fs::read_to_string(dir.join(path)).expect("unable to find isolation file"); + + script.set_text(file); + script.remove_attr("src"); + } +} + +// TODO: Verify this, this is not found in the HTML spec, see https://github.com/tauri-apps/tauri/pull/14265#discussion_r2415396842 +/// Normalize line endings in script content to match what the browser uses for CSP hashing. +/// +/// According to the HTML spec, browsers normalize: +/// - `\r\n` → `\n` +/// - `\r` → `\n` +pub fn normalize_script_for_csp(input: &[u8]) -> Vec { + let mut output = Vec::with_capacity(input.len()); + + let mut i = 0; + while i < input.len() { + match input[i] { + b'\r' => { + if i + 1 < input.len() && input[i + 1] == b'\n' { + // CRLF → LF + output.push(b'\n'); + i += 2; + } else { + // Lone CR → LF + output.push(b'\n'); + i += 1; + } + } + _ => { + output.push(input[i]); + i += 1; + } + } + } + + output +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, + config, + }; + + #[test] + fn csp() { + let htmls = vec![ + "".to_string(), + "".to_string(), + ]; + + for html in htmls { + let document = parse_doc(html); + let csp = "csp-string"; + inject_csp(&document, csp); + + assert_eq!( + String::from_utf8(serialize_doc(&document)).unwrap(), + format!( + r#""# + ) + ); + } + } + + #[test] + fn normalize_script_for_csp_test() { + let js = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\r// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\r\n\r\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\r\n return payload\r\n}\r\n"; + let expected = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n return payload\n}\n"; + + assert_eq!(normalize_script_for_csp(js.as_bytes()), expected.as_bytes()) + } + + #[test] + fn parse_and_serialize_roundtrips() { + let htmls = [ + "Test

Hello

", + "", + ]; + + for html in htmls { + let parsed = parse_doc(html.to_string()); + let serialized = serialize_doc(&parsed); + let result = String::from_utf8(serialized).unwrap(); + + assert_eq!(result, html); + } + } + + #[test] + fn inject_nonce_to_scripts() { + let html = r#""#; + + let document = parse_doc(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false)); + + assert_eq!( + String::from_utf8(serialize_doc(&document)).unwrap(), + format!( + r#""# + ) + ); + } + + #[test] + fn inject_nonce_to_styles() { + let html = r#""#; + + let document = parse_doc(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false)); + + assert_eq!( + String::from_utf8(serialize_doc(&document)).unwrap(), + format!( + r#""# + ) + ); + } + + #[test] + fn append_script_to_head_test() { + let html = r#""#; + + let document = parse_doc(html.to_string()); + append_script_to_head(&document, r#"console.log('Test')"#); + + assert_eq!( + String::from_utf8(serialize_doc(&document)).unwrap(), + format!(r#""#) + ); + } + + #[test] + fn inject_nonce_skips_existing() { + let html = r#""#; + + let document = parse_doc(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false)); + + assert_eq!(String::from_utf8(serialize_doc(&document)).unwrap(), html); + } + + #[test] + fn inject_nonce_respects_disabled_modification() { + let html = r#""#; + + let document = parse_doc(html.to_string()); + inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(true)); + + assert_eq!( + String::from_utf8(serialize_doc(&document)).unwrap(), + r#""# + ); + } + + #[test] + #[cfg(feature = "isolation")] + fn inline_isolation_replaces_src_with_content() { + use std::io::Write; + + let temp_dir = tempfile::tempdir().unwrap(); + let mut file = tempfile::NamedTempFile::with_suffix_in(".js", &temp_dir).unwrap(); + file.write_all(b"console.log('test');").unwrap(); + let file_name = file.path().file_name().unwrap().to_str().unwrap(); + + let html = + format!(r#""#); + let document = parse_doc(html); + inline_isolation(&document, temp_dir.path()); + + assert_eq!( + String::from_utf8(serialize_doc(&document)).unwrap(), + r#""# + ); + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/io.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/io.rs new file mode 100644 index 0000000..be4fd0d --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/io.rs @@ -0,0 +1,47 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! IO helpers. + +use std::io::BufRead; + +/// Read all bytes until a newline (the `0xA` byte) or a carriage return (`\r`) is reached, and append them to the provided buffer. +/// +/// Adapted from . +pub fn read_line(r: &mut R, buf: &mut Vec) -> std::io::Result { + let mut read = 0; + loop { + let (done, used) = { + let available = match r.fill_buf() { + Ok(n) => n, + Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + + Err(e) => return Err(e), + }; + match memchr::memchr(b'\n', available) { + Some(i) => { + let end = i + 1; + buf.extend_from_slice(&available[..end]); + (true, end) + } + None => match memchr::memchr(b'\r', available) { + Some(i) => { + let end = i + 1; + buf.extend_from_slice(&available[..end]); + (true, end) + } + None => { + buf.extend_from_slice(available); + (false, available.len()) + } + }, + } + }; + r.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/lib.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/lib.rs new file mode 100644 index 0000000..fb85d7d --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/lib.rs @@ -0,0 +1,404 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! This crate contains common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png" +)] +#![warn(missing_docs, rust_2018_idioms)] +#![allow(clippy::deprecated_semver)] + +use std::{ + ffi::OsString, + fmt::Display, + path::{Path, PathBuf}, +}; + +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +pub mod acl; +pub mod assets; +pub mod config; +pub mod config_v1; +#[cfg(feature = "html-manipulation")] +pub mod html; +#[cfg(feature = "html-manipulation-2")] +pub mod html2; +pub mod io; +pub mod mime_type; +pub mod platform; +pub mod plugin; +/// Prepare application resources and sidecars. +#[cfg(feature = "resources")] +pub mod resources; +#[cfg(any(feature = "build", feature = "build-2"))] +pub mod tokens; + +#[cfg(any(feature = "build", feature = "build-2"))] +pub mod build; + +/// Application pattern. +pub mod pattern; + +/// `tauri::App` package information. +#[derive(Debug, Clone)] +pub struct PackageInfo { + /// App name + pub name: String, + /// App version + pub version: Version, + /// The crate authors. + pub authors: &'static str, + /// The crate description. + pub description: &'static str, + /// The crate name. + pub crate_name: &'static str, +} + +#[allow(deprecated)] +mod window_effects { + use super::*; + + #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)] + #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] + #[serde(rename_all = "camelCase")] + /// Platform-specific window effects + pub enum WindowEffect { + /// A default material appropriate for the view's effectiveAppearance. **macOS 10.14-** + #[deprecated( + since = "macOS 10.14", + note = "You should instead choose an appropriate semantic material." + )] + AppearanceBased, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + Light, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + Dark, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + MediumLight, + /// **macOS 10.14-** + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + UltraDark, + /// **macOS 10.10+** + Titlebar, + /// **macOS 10.10+** + Selection, + /// **macOS 10.11+** + Menu, + /// **macOS 10.11+** + Popover, + /// **macOS 10.11+** + Sidebar, + /// **macOS 10.14+** + HeaderView, + /// **macOS 10.14+** + Sheet, + /// **macOS 10.14+** + WindowBackground, + /// **macOS 10.14+** + HudWindow, + /// **macOS 10.14+** + FullScreenUI, + /// **macOS 10.14+** + Tooltip, + /// **macOS 10.14+** + ContentBackground, + /// **macOS 10.14+** + UnderWindowBackground, + /// **macOS 10.14+** + UnderPageBackground, + /// Mica effect that matches the system dark preference **Windows 11 Only** + Mica, + /// Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only** + MicaDark, + /// Mica effect with light mode **Windows 11 Only** + MicaLight, + /// Tabbed effect that matches the system dark preference **Windows 11 Only** + Tabbed, + /// Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only** + TabbedDark, + /// Tabbed effect with light mode **Windows 11 Only** + TabbedLight, + /// **Windows 7/10/11(22H1) Only** + /// + /// ## Notes + /// + /// This effect has bad performance when resizing/dragging the window on Windows 11 build 22621. + Blur, + /// **Windows 10/11 Only** + /// + /// ## Notes + /// + /// This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000. + Acrylic, + } + + /// Window effect state **macOS only** + /// + /// + #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)] + #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] + #[serde(rename_all = "camelCase")] + pub enum WindowEffectState { + /// Make window effect state follow the window's active state + FollowsWindowActiveState, + /// Make window effect state always active + Active, + /// Make window effect state always inactive + Inactive, + } +} + +pub use window_effects::{WindowEffect, WindowEffectState}; + +/// How the window title bar should be displayed on macOS. +#[derive(Debug, Clone, PartialEq, Eq, Copy, Default)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[non_exhaustive] +pub enum TitleBarStyle { + /// A normal title bar. + #[default] + Visible, + /// Makes the title bar transparent, so the window background color is shown instead. + /// + /// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color. + Transparent, + /// Shows the title bar as a transparent overlay over the window's content. + /// + /// Keep in mind: + /// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect. + /// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus . + /// - The color of the window title depends on the system theme. + Overlay, +} + +impl Serialize for TitleBarStyle { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for TitleBarStyle { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "transparent" => Self::Transparent, + "overlay" => Self::Overlay, + _ => Self::Visible, + }) + } +} + +impl Display for TitleBarStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Visible => "Visible", + Self::Transparent => "Transparent", + Self::Overlay => "Overlay", + } + ) + } +} + +/// System theme. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[non_exhaustive] +pub enum Theme { + /// Light theme. + Light, + /// Dark theme. + Dark, +} + +impl Serialize for Theme { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for Theme { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "dark" => Self::Dark, + _ => Self::Light, + }) + } +} + +impl Display for Theme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Light => "light", + Self::Dark => "dark", + } + ) + } +} + +/// Information about environment variables. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Env { + /// The APPIMAGE environment variable. + #[cfg(target_os = "linux")] + pub appimage: Option, + /// The APPDIR environment variable. + #[cfg(target_os = "linux")] + pub appdir: Option, + /// The command line arguments of the current process. + pub args_os: Vec, +} + +#[allow(clippy::derivable_impls)] +impl Default for Env { + fn default() -> Self { + let args_os = std::env::args_os().collect(); + #[cfg(target_os = "linux")] + { + let env = Self { + #[cfg(target_os = "linux")] + appimage: std::env::var_os("APPIMAGE"), + #[cfg(target_os = "linux")] + appdir: std::env::var_os("APPDIR"), + args_os, + }; + if env.appimage.is_some() || env.appdir.is_some() { + // validate that we're actually running on an AppImage + // an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}` + // see + // note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage. + let is_temp = std::env::current_exe() + .map(|p| { + p.display() + .to_string() + .starts_with(&format!("{}/.mount_", std::env::temp_dir().display())) + }) + .unwrap_or(true); + + if !is_temp { + log::warn!( + "`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue." + ); + } + } + env + } + #[cfg(not(target_os = "linux"))] + { + Self { args_os } + } + } +} + +/// The result type of `tauri-utils`. +pub type Result = std::result::Result; + +/// The error type of `tauri-utils`. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + /// Target triple architecture error + #[error("Unable to determine target-architecture")] + Architecture, + /// Target triple OS error + #[error("Unable to determine target-os")] + Os, + /// Target triple environment error + #[error("Unable to determine target-environment")] + Environment, + /// Tried to get resource on an unsupported platform + #[error("Unsupported platform for reading resources")] + UnsupportedPlatform, + /// Get parent process error + #[error("Could not get parent process")] + ParentProcess, + /// Get parent process PID error + #[error("Could not get parent PID")] + ParentPid, + /// Get child process error + #[error("Could not get child process")] + ChildProcess, + /// IO error + #[error("{0}")] + Io(#[from] std::io::Error), + /// Invalid pattern. + #[error("invalid pattern `{0}`. Expected either `brownfield` or `isolation`.")] + InvalidPattern(String), + /// Invalid glob pattern. + #[cfg(feature = "resources")] + #[error("{0}")] + GlobPattern(#[from] glob::PatternError), + /// Failed to use glob pattern. + #[cfg(feature = "resources")] + #[error("`{0}`")] + Glob(#[from] glob::GlobError), + /// Glob pattern did not find any results. + #[cfg(feature = "resources")] + #[error("glob pattern {0} path not found or didn't match any files.")] + GlobPathNotFound(String), + /// Error walking directory. + #[cfg(feature = "resources")] + #[error("{0}")] + WalkdirError(#[from] walkdir::Error), + /// Not allowed to walk dir. + #[cfg(feature = "resources")] + #[error( + "could not walk directory `{0}`, try changing `allow_walk` to true on the `ResourcePaths` constructor." + )] + NotAllowedToWalkDir(std::path::PathBuf), + /// Resource path doesn't exist + #[cfg(feature = "resources")] + #[error("resource path `{0}` doesn't exist")] + ResourcePathNotFound(std::path::PathBuf), +} + +/// Reconstructs a path from its components using the platform separator then converts it to String and removes UNC prefixes on Windows if it exists. +pub fn display_path>(p: P) -> String { + dunce::simplified(&p.as_ref().components().collect::()) + .display() + .to_string() +} + +/// Write the file only if the content of the existing file (if any) is different. +/// +/// This will always write unless the file exists with identical content. +pub fn write_if_changed(path: P, content: C) -> std::io::Result<()> +where + P: AsRef, + C: AsRef<[u8]>, +{ + if let Ok(existing) = std::fs::read(&path) + && existing == content.as_ref() + { + return Ok(()); + } + + std::fs::write(path, content) +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/mime_type.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/mime_type.rs new file mode 100644 index 0000000..a4d001f --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/mime_type.rs @@ -0,0 +1,154 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Determine a mime type from a URI or file contents. + +use std::fmt; + +const MIMETYPE_PLAIN: &str = "text/plain"; + +/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers) +#[allow(missing_docs)] +pub enum MimeType { + Css, + Csv, + Html, + Ico, + Js, + Json, + Jsonld, + Mp4, + OctetStream, + Rtf, + Svg, + Txt, +} + +impl std::fmt::Display for MimeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mime = match self { + MimeType::Css => "text/css", + MimeType::Csv => "text/csv", + MimeType::Html => "text/html", + MimeType::Ico => "image/vnd.microsoft.icon", + MimeType::Js => "text/javascript", + MimeType::Json => "application/json", + MimeType::Jsonld => "application/ld+json", + MimeType::Mp4 => "video/mp4", + MimeType::OctetStream => "application/octet-stream", + MimeType::Rtf => "application/rtf", + MimeType::Svg => "image/svg+xml", + MimeType::Txt => MIMETYPE_PLAIN, + }; + write!(f, "{mime}") + } +} + +impl MimeType { + /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType. + pub fn parse_from_uri(uri: &str) -> MimeType { + Self::parse_from_uri_with_fallback(uri, Self::Html) + } + + /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType with specified fallback for unknown file extensions. + pub fn parse_from_uri_with_fallback(uri: &str, fallback: MimeType) -> MimeType { + let suffix = uri.split('.').next_back(); + match suffix { + Some("bin") => Self::OctetStream, + Some("css" | "less" | "sass" | "styl") => Self::Css, + Some("csv") => Self::Csv, + Some("html") => Self::Html, + Some("ico") => Self::Ico, + Some("js") => Self::Js, + Some("json") => Self::Json, + Some("jsonld") => Self::Jsonld, + Some("mjs") => Self::Js, + Some("mp4") => Self::Mp4, + Some("rtf") => Self::Rtf, + Some("svg") => Self::Svg, + Some("txt") => Self::Txt, + // Assume HTML when a TLD is found for eg. `wry:://tauri.app` | `wry://hello.com` + Some(_) => fallback, + // using octet stream according to this: + // + None => Self::OctetStream, + } + } + + /// infer mimetype from content (or) URI if needed. + pub fn parse(content: &[u8], uri: &str) -> String { + Self::parse_with_fallback(content, uri, Self::Html) + } + /// infer mimetype from content (or) URI if needed with specified fallback for unknown file extensions. + pub fn parse_with_fallback(content: &[u8], uri: &str, fallback: MimeType) -> String { + let mime = if uri.ends_with(".svg") { + // when reading svg, we can't use `infer` + None + } else { + infer::get(content).map(|info| info.mime_type()) + }; + + match mime { + Some(mime) if mime == MIMETYPE_PLAIN => { + Self::parse_from_uri_with_fallback(uri, fallback).to_string() + } + None => Self::parse_from_uri_with_fallback(uri, fallback).to_string(), + Some(mime) => mime.to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_parse_mimetype_from_uri() { + let css = MimeType::parse_from_uri( + "https://unpkg.com/browse/bootstrap@4.1.0/dist/css/bootstrap-grid.css", + ) + .to_string(); + assert_eq!(css, "text/css".to_string()); + + let csv: String = MimeType::parse_from_uri("https://example.com/random.csv").to_string(); + assert_eq!(csv, "text/csv".to_string()); + + let ico: String = + MimeType::parse_from_uri("https://icons.duckduckgo.com/ip3/microsoft.com.ico").to_string(); + assert_eq!(ico, String::from("image/vnd.microsoft.icon")); + + let html: String = MimeType::parse_from_uri("https://tauri.app/index.html").to_string(); + assert_eq!(html, String::from("text/html")); + + let js: String = + MimeType::parse_from_uri("https://unpkg.com/react@17.0.1/umd/react.production.min.js") + .to_string(); + assert_eq!(js, "text/javascript".to_string()); + + let json: String = + MimeType::parse_from_uri("https://unpkg.com/browse/react@17.0.1/build-info.json").to_string(); + assert_eq!(json, String::from("application/json")); + + let jsonld: String = MimeType::parse_from_uri("https:/example.com/hello.jsonld").to_string(); + assert_eq!(jsonld, String::from("application/ld+json")); + + let mjs: String = MimeType::parse_from_uri("https://example.com/bundled.mjs").to_string(); + assert_eq!(mjs, String::from("text/javascript")); + + let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string(); + assert_eq!(mp4, String::from("video/mp4")); + + let rtf: String = MimeType::parse_from_uri("https://example.com/document.rtf").to_string(); + assert_eq!(rtf, String::from("application/rtf")); + + let svg: String = MimeType::parse_from_uri("https://example.com/picture.svg").to_string(); + assert_eq!(svg, String::from("image/svg+xml")); + + let txt: String = MimeType::parse_from_uri("https://example.com/file.txt").to_string(); + assert_eq!(txt, String::from("text/plain")); + + let custom_scheme = MimeType::parse_from_uri("wry://tauri.app").to_string(); + assert_eq!(custom_scheme, String::from("text/html")); + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.js b/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.js new file mode 100644 index 0000000..586d080 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.js @@ -0,0 +1,154 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * IMPORTANT: See ipc.js for the main frame implementation. + * main frame -> isolation frame = isolation payload + * isolation frame -> main frame = isolation message + */ + +;(async function () { + /** + * Sends the message to the isolation frame. + * @param {any} message + */ + function sendMessage(message) { + window.parent.postMessage(message, '*') + } + + /** + * @type {string} - The main frame origin. + */ + const origin = __TEMPLATE_origin__ + + /** + * @type {Uint8Array} - Injected by Tauri during runtime + */ + const aesGcmKeyRaw = new Uint8Array(__TEMPLATE_runtime_aes_gcm_key__) + + /** + * @type {CryptoKey} + */ + const aesGcmKey = await window.crypto.subtle.importKey( + 'raw', + aesGcmKeyRaw, + 'AES-GCM', + false, + ['encrypt'] + ) + + /** + * @param {object} data + * @return {Promise<{nonce: number[], payload: number[]}>} + */ + async function encrypt(payload) { + const algorithm = Object.create(null) + algorithm.name = 'AES-GCM' + algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12)) + + const { contentType, data } = __RAW_process_ipc_message_fn__(payload) + + const message = + typeof data === 'string' + ? new TextEncoder().encode(data) + : ArrayBuffer.isView(data) || data instanceof ArrayBuffer + ? data + : new Uint8Array(data) + + return window.crypto.subtle + .encrypt(algorithm, aesGcmKey, message) + .then((payload) => { + const result = Object.create(null) + result.nonce = Array.from(new Uint8Array(algorithm.iv)) + result.payload = Array.from(new Uint8Array(payload)) + result.contentType = contentType + return result + }) + } + + /** + * Detects if a message event is a valid isolation message. + * + * @param {MessageEvent} event - a message event that is expected to be an isolation message + * @return {boolean} - if the event was a valid isolation message + */ + function isIsolationMessage(data) { + if (typeof data === 'object' && typeof data.payload === 'object') { + const keys = data.payload ? Object.keys(data.payload) : [] + return ( + keys.length > 0 + && keys.every( + (key) => key === 'nonce' || key === 'payload' || key === 'contentType' + ) + ) + } + return false + } + + /** + * Detect if a message event is a valid isolation payload. + * + * @param {MessageEvent} event - a message event that is expected to be an isolation payload + * @return boolean + */ + function isIsolationPayload(data) { + return ( + typeof data === 'object' + && 'callback' in data + && 'error' in data + && !isIsolationMessage(data) + ) + } + + /** + * Handle incoming payload events. + * @param {MessageEvent} event + */ + async function payloadHandler(event) { + if (event.origin !== origin || !isIsolationPayload(event.data)) { + return + } + + let data = event.data + + if (typeof window.__TAURI_ISOLATION_HOOK__ === 'function') { + // await even if it's not async so that we can support async ones + data = await window.__TAURI_ISOLATION_HOOK__(data) + } + + const message = Object.create(null) + message.cmd = data.cmd + message.callback = data.callback + message.error = data.error + message.options = data.options + message.payload = await encrypt(data.payload) + sendMessage(message) + } + + window.addEventListener('message', payloadHandler, false) + + /** + * @type {number} - How many milliseconds to wait between ready checks + */ + const readyIntervalMs = 50 + + /** + * Wait until this Isolation context is ready to receive messages, and let the main frame know. + */ + function waitUntilReady() { + // consider either a function or an explicitly set null value as the ready signal + if ( + typeof window.__TAURI_ISOLATION_HOOK__ === 'function' + || window.__TAURI_ISOLATION_HOOK__ === null + ) { + sendMessage('__TAURI_ISOLATION_READY__') + } else { + setTimeout(waitUntilReady, readyIntervalMs) + } + } + + setTimeout(waitUntilReady, readyIntervalMs) +})() + +document.currentScript?.remove() diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.rs new file mode 100644 index 0000000..223cca6 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.rs @@ -0,0 +1,171 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::array::TryFromSliceError; +use std::borrow::Cow; +use std::fmt::{Debug, Formatter}; +use std::string::FromUtf8Error; + +use aes_gcm::aead::Aead; +use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; +use getrandom::Error as CsprngError; +use serialize_to_javascript::{Template, default_template}; + +/// The style for the isolation iframe. +pub const IFRAME_STYLE: &str = "#__tauri_isolation__ { display: none !important }"; + +/// Errors that can occur during Isolation keys generation. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + /// Something went wrong with the CSPRNG. + #[error("CSPRNG error")] + Csprng(#[from] CsprngError), + + /// Something went wrong with decrypting an AES-GCM payload + #[error("AES-GCM")] + Aes, + + /// Nonce was not 96 bits + #[error("Nonce: {0}")] + NonceSize(#[from] TryFromSliceError), + + /// Payload was not valid utf8 + #[error("{0}")] + Utf8(#[from] FromUtf8Error), + + /// Invalid json format + #[error("{0}")] + Json(#[from] serde_json::Error), +} + +/// A formatted AES-GCM cipher instance along with the key used to initialize it. +#[derive(Clone)] +pub struct AesGcmPair { + raw: [u8; 32], + key: Aes256Gcm, +} + +impl Debug for AesGcmPair { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "AesGcmPair(...)") + } +} + +impl AesGcmPair { + fn new() -> Result { + let mut raw = [0u8; 32]; + getrandom::fill(&mut raw)?; + let key = aes_gcm::Key::::from_slice(&raw); + Ok(Self { + raw, + key: Aes256Gcm::new(key), + }) + } + + /// The raw value used to create the AES-GCM key + pub fn raw(&self) -> &[u8; 32] { + &self.raw + } + + /// The formatted AES-GCM key + pub fn key(&self) -> &Aes256Gcm { + &self.key + } + + #[doc(hidden)] + pub fn encrypt(&self, nonce: &[u8; 12], payload: &[u8]) -> Result, Error> { + self + .key + .encrypt(nonce.into(), payload) + .map_err(|_| self::Error::Aes) + } +} + +/// All cryptographic keys required for Isolation encryption +#[derive(Debug, Clone)] +pub struct Keys { + /// AES-GCM key + aes_gcm: AesGcmPair, +} + +impl Keys { + /// Securely generate required keys for Isolation encryption. + pub fn new() -> Result { + AesGcmPair::new().map(|aes_gcm| Self { aes_gcm }) + } + + /// The AES-GCM data (and raw data). + pub fn aes_gcm(&self) -> &AesGcmPair { + &self.aes_gcm + } + + /// Decrypts a message using the generated keys. + pub fn decrypt(&self, raw: RawIsolationPayload<'_>) -> Result, Error> { + let RawIsolationPayload { nonce, payload, .. } = raw; + let nonce: [u8; 12] = nonce.as_ref().try_into()?; + self + .aes_gcm + .key + .decrypt(Nonce::from_slice(&nonce), payload.as_ref()) + .map_err(|_| self::Error::Aes) + } +} + +/// Raw representation of +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RawIsolationPayload<'a> { + nonce: Cow<'a, [u8]>, + payload: Cow<'a, [u8]>, + content_type: Cow<'a, str>, +} + +impl<'a> RawIsolationPayload<'a> { + /// Content type of this payload. + pub fn content_type(&self) -> &Cow<'a, str> { + &self.content_type + } +} + +impl<'a> TryFrom<&'a Vec> for RawIsolationPayload<'a> { + type Error = Error; + + fn try_from(value: &'a Vec) -> Result { + serde_json::from_slice(value).map_err(Into::into) + } +} + +/// The Isolation JavaScript template meant to be injected during codegen. +/// +/// Note: This struct is not considered part of the stable API +#[derive(Template)] +#[default_template("isolation.js")] +pub struct IsolationJavascriptCodegen { + // this template intentionally does not include the runtime field +} + +/// The Isolation JavaScript template meant to be injected during runtime. +/// +/// Note: This struct is not considered part of the stable API +#[derive(Template)] +#[default_template("isolation.js")] +pub struct IsolationJavascriptRuntime<'a> { + /// The key used on the Rust backend and the Isolation Javascript + pub runtime_aes_gcm_key: &'a [u8; 32], + /// The origin the isolation application is expecting messages from. + pub origin: String, + /// The function that processes the IPC message. + #[raw] + pub process_ipc_message_fn: &'a str, +} + +#[cfg(test)] +mod test { + #[test] + fn create_keys() -> Result<(), Box> { + let _ = super::Keys::new()?; + Ok(()) + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/mod.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/mod.rs new file mode 100644 index 0000000..97ca495 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/// Handling the Tauri "Isolation" Pattern. +#[cfg(feature = "isolation")] +pub mod isolation; diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/platform.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/platform.rs new file mode 100644 index 0000000..708f968 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/platform.rs @@ -0,0 +1,434 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Platform helper functions. + +use std::{fmt::Display, path::PathBuf}; + +use serde::{Deserialize, Serialize}; + +use crate::{Env, PackageInfo, config::BundleType}; + +mod starting_binary; + +/// URI prefix of a Tauri asset. +/// +/// This is referenced in the Tauri Android library, +/// which resolves these assets to a file descriptor. +#[cfg(target_os = "android")] +pub const ANDROID_ASSET_PROTOCOL_URI_PREFIX: &str = "asset://localhost/"; + +/// Platform target. +#[derive(PartialEq, Eq, Copy, Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub enum Target { + /// MacOS. + #[serde(rename = "macOS")] + MacOS, + /// Windows. + Windows, + /// Linux. + Linux, + /// Android. + Android, + /// iOS. + #[serde(rename = "iOS")] + Ios, +} + +impl Display for Target { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::MacOS => "macOS", + Self::Windows => "windows", + Self::Linux => "linux", + Self::Android => "android", + Self::Ios => "iOS", + } + ) + } +} + +impl Target { + /// Parses the target from the given target triple. + pub fn from_triple(target: &str) -> Self { + if target.contains("darwin") { + Self::MacOS + } else if target.contains("windows") { + Self::Windows + } else if target.contains("android") { + Self::Android + } else if target.contains("ios") { + Self::Ios + } else { + Self::Linux + } + } + + /// Gets the current build target. + pub fn current() -> Self { + if cfg!(target_os = "macos") { + Self::MacOS + } else if cfg!(target_os = "windows") { + Self::Windows + } else if cfg!(target_os = "ios") { + Self::Ios + } else if cfg!(target_os = "android") { + Self::Android + } else { + Self::Linux + } + } + + /// Whether the target is mobile or not. + pub fn is_mobile(&self) -> bool { + matches!(self, Target::Android | Target::Ios) + } + + /// Whether the target is desktop or not. + pub fn is_desktop(&self) -> bool { + !self.is_mobile() + } +} + +/// Retrieves the currently running binary's path, taking into account security considerations. +/// +/// The path is cached as soon as possible (before even `main` runs) and that value is returned +/// repeatedly instead of fetching the path every time. It is possible for the path to not be found, +/// or explicitly disabled (see following macOS specific behavior). +/// +/// # Platform-specific behavior +/// +/// On `macOS`, this function will return an error if the original path contained any symlinks +/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the +/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*. +/// +/// # Security +/// +/// If the above platform-specific behavior does **not** take place, this function uses the +/// following resolution. +/// +/// We canonicalize the path we received from [`std::env::current_exe`] to resolve any soft links. +/// This avoids the usual issue of needing the file to exist at the passed path because a valid +/// current executable result for our purpose should always exist. Notably, +/// [`std::env::current_exe`] also has a security section that goes over a theoretical attack using +/// hard links. Let's cover some specific topics that relate to different ways an attacker might +/// try to trick this function into returning the wrong binary path. +/// +/// ## Symlinks ("Soft Links") +/// +/// [`std::path::Path::canonicalize`] is used to resolve symbolic links to the original path, +/// including nested symbolic links (`link2 -> link1 -> bin`). On macOS, any results that include +/// a symlink are rejected by default due to lesser symlink protections. This can be disabled, +/// **although discouraged**, with the `process-relaunch-dangerous-allow-symlink-macos` feature. +/// +/// ## Hard Links +/// +/// A [Hard Link] is a named entry that points to a file in the file system. +/// On most systems, this is what you would think of as a "file". The term is +/// used on filesystems that allow multiple entries to point to the same file. +/// The linked [Hard Link] Wikipedia page provides a decent overview. +/// +/// In short, unless the attacker was able to create the link with elevated +/// permissions, it should generally not be possible for them to hard link +/// to a file they do not have permissions to - with exception to possible +/// operating system exploits. +/// +/// There are also some platform-specific information about this below. +/// +/// ### Windows +/// +/// Windows requires a permission to be set for the user to create a symlink +/// or a hard link, regardless of ownership status of the target. Elevated +/// permissions users have the ability to create them. +/// +/// ### macOS +/// +/// macOS allows for the creation of symlinks and hard links to any file. +/// Accessing through those links will fail if the user who owns the links +/// does not have the proper permissions on the original file. +/// +/// ### Linux +/// +/// Linux allows for the creation of symlinks to any file. Accessing the +/// symlink will fail if the user who owns the symlink does not have the +/// proper permissions on the original file. +/// +/// Linux additionally provides a kernel hardening feature since version +/// 3.6 (30 September 2012). Most distributions since then have enabled +/// the protection (setting `fs.protected_hardlinks = 1`) by default, which +/// means that a vast majority of desktop Linux users should have it enabled. +/// **The feature prevents the creation of hardlinks that the user does not own +/// or have read/write access to.** [See the patch that enabled this]. +/// +/// [Hard Link]: https://en.wikipedia.org/wiki/Hard_link +/// [See the patch that enabled this]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7 +pub fn current_exe() -> std::io::Result { + self::starting_binary::STARTING_BINARY.cloned() +} + +/// Try to determine the current target triple. +/// +/// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an +/// `Error::Config` if the current config cannot be determined or is not some combination of the +/// following values: +/// `linux, mac, windows` -- `i686, x86, armv7` -- `gnu, musl, msvc` +/// +/// * Errors: +/// * Unexpected system config +pub fn target_triple() -> crate::Result { + let arch = if cfg!(target_arch = "x86") { + "i686" + } else if cfg!(target_arch = "x86_64") { + "x86_64" + } else if cfg!(target_arch = "arm") { + "armv7" + } else if cfg!(target_arch = "aarch64") { + "aarch64" + } else if cfg!(target_arch = "riscv64") { + "riscv64" + } else { + return Err(crate::Error::Architecture); + }; + + let os = if cfg!(target_os = "linux") { + "unknown-linux" + } else if cfg!(target_os = "macos") { + "apple-darwin" + } else if cfg!(target_os = "windows") { + "pc-windows" + } else if cfg!(target_os = "freebsd") { + "unknown-freebsd" + } else { + return Err(crate::Error::Os); + }; + + let os = if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") { + String::from(os) + } else { + let env = if cfg!(target_env = "gnu") { + "gnu" + } else if cfg!(target_env = "musl") { + "musl" + } else if cfg!(target_env = "msvc") { + "msvc" + } else { + return Err(crate::Error::Environment); + }; + + format!("{os}-{env}") + }; + + Ok(format!("{arch}-{os}")) +} + +#[cfg(all(not(test), not(target_os = "android")))] +fn is_cargo_output_directory(path: &std::path::Path) -> bool { + path.join(".cargo-lock").exists() +} + +#[cfg(test)] +const CARGO_OUTPUT_DIRECTORIES: &[&str] = &["debug", "release", "custom-profile"]; + +#[cfg(test)] +fn is_cargo_output_directory(path: &std::path::Path) -> bool { + let Some(last_component) = path.components().next_back() else { + return false; + }; + CARGO_OUTPUT_DIRECTORIES + .iter() + .any(|dirname| &last_component.as_os_str() == dirname) +} + +/// Computes the resource directory of the current environment. +/// +/// ## Platform-specific +/// +/// - **Windows:** Resolves to the directory that contains the main executable. +/// - **Linux:** When running in an AppImage, the `APPDIR` variable will be set to +/// the mounted location of the app, and the resource dir will be `${APPDIR}/usr/lib/${exe_name}`. +/// If not running in an AppImage, the path is `/usr/lib/${exe_name}`. +/// When running the app from `src-tauri/target/(debug|release)/`, the path is `${exe_dir}/../lib/${exe_name}`. +/// - **macOS:** Resolves to `${exe_dir}/../Resources` (inside .app). +/// - **iOS:** Resolves to `${exe_dir}/assets`. +/// - **Android:** Currently the resources are stored in the APK as assets so it's not a normal file system path, +/// we return a special URI prefix `asset://localhost/` here that can be used with the [file system plugin](https://tauri.app/plugin/file-system/), +/// with that, you can read the files through [`FsExt::fs`](https://docs.rs/tauri-plugin-fs/latest/tauri_plugin_fs/trait.FsExt.html#tymethod.fs) +/// like this: `app.fs().read_to_string(app.path().resource_dir().unwrap().join("resource"));` +pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result { + #[cfg(target_os = "android")] + return resource_dir_android(package_info, env); + #[cfg(not(target_os = "android"))] + { + let exe = current_exe()?; + resource_dir_from(exe, package_info, env) + } +} + +#[cfg(target_os = "android")] +fn resource_dir_android(_package_info: &PackageInfo, _env: &Env) -> crate::Result { + Ok(PathBuf::from(ANDROID_ASSET_PROTOCOL_URI_PREFIX)) +} + +#[cfg(not(target_os = "android"))] +#[allow(unused_variables)] +fn resource_dir_from>( + exe: P, + package_info: &PackageInfo, + env: &Env, +) -> crate::Result { + let exe_dir = exe.as_ref().parent().expect("failed to get exe directory"); + let curr_dir = exe_dir.display().to_string(); + + let parts: Vec<&str> = curr_dir.split(std::path::MAIN_SEPARATOR).collect(); + let len = parts.len(); + + // Check if running from the Cargo output directory, which means it's an executable in a development machine + // We check if the binary is inside a `target` folder which can be either `target/$profile` or `target/$triple/$profile` + // and see if there's a .cargo-lock file along the executable + // This ensures the check is safer so it doesn't affect apps in production + // Windows also includes the resources in the executable folder so we check that too + if cfg!(target_os = "windows") + || ((len >= 2 && parts[len - 2] == "target") || (len >= 3 && parts[len - 3] == "target")) + && is_cargo_output_directory(exe_dir) + { + return Ok(exe_dir.to_path_buf()); + } + + #[allow(unused_mut, unused_assignments)] + let mut res = Err(crate::Error::UnsupportedPlatform); + + #[cfg(target_os = "linux")] + { + // (canonicalize checks for existence, so there's no need for an extra check) + res = if let Ok(bundle_dir) = exe_dir + .join(format!("../lib/{}", package_info.name)) + .canonicalize() + { + Ok(bundle_dir) + } else if let Some(appdir) = &env.appdir { + let appdir: &std::path::Path = appdir.as_ref(); + Ok(PathBuf::from(format!( + "{}/usr/lib/{}", + appdir.display(), + package_info.name + ))) + } else { + // running bundle + Ok(PathBuf::from(format!("/usr/lib/{}", package_info.name))) + }; + } + + #[cfg(target_os = "macos")] + { + res = exe_dir + .join("../Resources") + .canonicalize() + .map_err(Into::into); + } + + #[cfg(target_os = "ios")] + { + res = exe_dir.join("assets").canonicalize().map_err(Into::into); + } + + res +} + +// Variable holding the type of bundle the executable is stored in. This is modified by binary +// patching during build +#[used] +// Marked as `mut` because it could get optimized away without it, +// see https://github.com/tauri-apps/tauri/pull/13812 +static mut __TAURI_BUNDLE_TYPE: &str = "__TAURI_BUNDLE_TYPE_VAR_UNK"; + +/// Get the type of the bundle current binary is packaged in. +/// If the bundle type is unknown, it returns [`Option::None`]. +pub fn bundle_type() -> Option { + unsafe { + match __TAURI_BUNDLE_TYPE { + "__TAURI_BUNDLE_TYPE_VAR_DEB" => Some(BundleType::Deb), + "__TAURI_BUNDLE_TYPE_VAR_RPM" => Some(BundleType::Rpm), + "__TAURI_BUNDLE_TYPE_VAR_APP" => Some(BundleType::AppImage), + "__TAURI_BUNDLE_TYPE_VAR_MSI" => Some(BundleType::Msi), + "__TAURI_BUNDLE_TYPE_VAR_NSS" => Some(BundleType::Nsis), + _ => { + if cfg!(target_os = "macos") { + Some(BundleType::App) + } else { + None + } + } + } + } +} + +#[cfg(any(feature = "build", feature = "build-2"))] +mod build { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt, quote}; + + use super::*; + + impl ToTokens for Target { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::platform::Target }; + + tokens.append_all(match self { + Self::MacOS => quote! { #prefix::MacOS }, + Self::Linux => quote! { #prefix::Linux }, + Self::Windows => quote! { #prefix::Windows }, + Self::Android => quote! { #prefix::Android }, + Self::Ios => quote! { #prefix::Ios }, + }); + } + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{Env, PackageInfo}; + + #[test] + #[cfg(not(target_os = "android"))] + fn resolve_resource_dir() { + let package_info = PackageInfo { + name: "MyApp".into(), + version: "1.0.0".parse().unwrap(), + authors: "", + description: "", + crate_name: "my-app", + }; + let env = Env::default(); + + let path = PathBuf::from("/path/to/target/aarch64-apple-darwin/debug/app"); + let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap(); + assert_eq!(resource_dir, path.parent().unwrap()); + + let path = PathBuf::from("/path/to/target/custom-profile/app"); + let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap(); + assert_eq!(resource_dir, path.parent().unwrap()); + + let path = PathBuf::from("/path/to/target/release/app"); + let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap(); + assert_eq!(resource_dir, path.parent().unwrap()); + + let path = PathBuf::from("/path/to/target/unknown-profile/app"); + #[allow(clippy::needless_borrows_for_generic_args)] + let resource_dir = super::resource_dir_from(&path, &package_info, &env); + #[cfg(target_os = "macos")] + assert!(resource_dir.is_err()); + #[cfg(target_os = "linux")] + assert_eq!(resource_dir.unwrap(), PathBuf::from("/usr/lib/MyApp")); + #[cfg(windows)] + assert_eq!(resource_dir.unwrap(), path.parent().unwrap()); + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/platform/starting_binary.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/platform/starting_binary.rs new file mode 100644 index 0000000..4e6d29c --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/platform/starting_binary.rs @@ -0,0 +1,83 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use ctor::ctor; +use std::{ + io::{Error, ErrorKind, Result}, + path::{Path, PathBuf}, +}; + +/// A cached version of the current binary using [`ctor`] to cache it before even `main` runs. +#[ctor] +#[used] +pub(super) static STARTING_BINARY: StartingBinary = unsafe { StartingBinary::new() }; + +/// Represents a binary path that was cached when the program was loaded. +pub(super) struct StartingBinary(std::io::Result); + +impl StartingBinary { + /// Find the starting executable as safely as possible. + fn new() -> Self { + // see notes on current_exe() for security implications + let dangerous_path = match std::env::current_exe() { + Ok(dangerous_path) => dangerous_path, + error @ Err(_) => return Self(error), + }; + + // note: this only checks symlinks on problematic platforms, see implementation below + if let Some(symlink) = Self::has_symlink(&dangerous_path) { + return Self(Err(Error::new( + ErrorKind::InvalidData, + format!( + "StartingBinary found current_exe() that contains a symlink on a non-allowed platform: {}", + symlink.display() + ), + ))); + } + + // we canonicalize the path to resolve any symlinks to the real exe path + Self(dangerous_path.canonicalize()) + } + + /// A clone of the [`PathBuf`] found to be the starting path. + /// + /// Because [`Error`] is not clone-able, it is recreated instead. + pub(super) fn cloned(&self) -> Result { + // false positive + #[allow(clippy::useless_asref)] + self + .0 + .as_ref() + .map(Clone::clone) + .map_err(|e| Error::new(e.kind(), e.to_string())) + } + + /// We only care about checking this on macOS currently, as it has the least symlink protections. + #[cfg(any( + not(target_os = "macos"), + feature = "process-relaunch-dangerous-allow-symlink-macos" + ))] + fn has_symlink(_: &Path) -> Option<&Path> { + None + } + + /// We only care about checking this on macOS currently, as it has the least symlink protections. + #[cfg(all( + target_os = "macos", + not(feature = "process-relaunch-dangerous-allow-symlink-macos") + ))] + fn has_symlink(path: &Path) -> Option<&Path> { + path.ancestors().find(|ancestor| { + matches!( + ancestor + .symlink_metadata() + .as_ref() + .map(std::fs::Metadata::file_type) + .as_ref() + .map(std::fs::FileType::is_symlink), + Ok(true) + ) + }) + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/plugin.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/plugin.rs new file mode 100644 index 0000000..9fe35be --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/plugin.rs @@ -0,0 +1,88 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Compile-time and runtime types for Tauri plugins. +#[cfg(any(feature = "build", feature = "build-2"))] +pub use build::*; + +#[cfg(any(feature = "build", feature = "build-2"))] +mod build { + use std::{ + env::vars_os, + fs, + path::{Path, PathBuf}, + }; + + const GLOBAL_API_SCRIPT_PATH_KEY: &str = "GLOBAL_API_SCRIPT_PATH"; + /// Known file name of the file that contains an array with the path of all API scripts defined with [`define_global_api_script_path`]. + pub const GLOBAL_API_SCRIPT_FILE_LIST_PATH: &str = "__global-api-script.js"; + + /// Defines the path to the global API script using Cargo instructions. + pub fn define_global_api_script_path(path: &Path) { + println!( + "cargo:{GLOBAL_API_SCRIPT_PATH_KEY}={}", + path + .canonicalize() + .expect("failed to canonicalize global API script path") + .display() + ) + } + + /// Collects the path of all the global API scripts defined with [`define_global_api_script_path`] + /// and saves them to the out dir with filename [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`]. + /// + /// `tauri_global_scripts` is only used in Tauri's monorepo for the examples to work + /// since they don't have a build script to run `tauri-build` and pull in the deps env vars + pub fn save_global_api_scripts_paths(out_dir: &Path, mut tauri_global_scripts: Option) { + let mut scripts = Vec::new(); + + for (key, value) in vars_os() { + let key = key.to_string_lossy(); + + if key == format!("DEP_TAURI_{GLOBAL_API_SCRIPT_PATH_KEY}") { + tauri_global_scripts = Some(PathBuf::from(value)); + } else if key.starts_with("DEP_") && key.ends_with(GLOBAL_API_SCRIPT_PATH_KEY) { + let script_path = PathBuf::from(value); + scripts.push(script_path); + } + } + + if let Some(tauri_global_scripts) = tauri_global_scripts { + scripts.insert(0, tauri_global_scripts); + } + + fs::write( + out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH), + serde_json::to_string(&scripts).expect("failed to serialize global API script paths"), + ) + .expect("failed to write global API script"); + } + + /// Read global api scripts from [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`] + pub fn read_global_api_scripts(out_dir: &Path) -> Option> { + let global_scripts_path = out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH); + if !global_scripts_path.exists() { + return None; + } + + let global_scripts_str = fs::read_to_string(global_scripts_path) + .expect("failed to read plugin global API script paths"); + let global_scripts = serde_json::from_str::>(&global_scripts_str) + .expect("failed to parse plugin global API script paths"); + + Some( + global_scripts + .into_iter() + .map(|p| { + fs::read_to_string(&p).unwrap_or_else(|e| { + panic!( + "failed to read plugin global API script {}: {e}", + p.display() + ) + }) + }) + .collect(), + ) + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/resources.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/resources.rs new file mode 100644 index 0000000..210374a --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/resources.rs @@ -0,0 +1,643 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + collections::HashMap, + path::{Component, Path, PathBuf}, +}; + +use walkdir::WalkDir; + +use crate::platform::Target as TargetPlatform; + +/// Given a path (absolute or relative) to a resource file, returns the +/// relative path from the bundle resources directory where that resource +/// should be stored. +pub fn resource_relpath(path: &Path) -> PathBuf { + let mut dest = PathBuf::new(); + for component in path.components() { + match component { + Component::Prefix(_) => {} + Component::RootDir => dest.push("_root_"), + Component::CurDir => {} + Component::ParentDir => dest.push("_up_"), + Component::Normal(string) => dest.push(string), + } + } + dest +} + +fn normalize(path: &Path) -> PathBuf { + let mut dest = PathBuf::new(); + for component in path.components() { + match component { + Component::Prefix(_) => {} + Component::RootDir => dest.push("/"), + Component::CurDir => {} + Component::ParentDir => dest.push(".."), + Component::Normal(string) => dest.push(string), + } + } + dest +} + +/// Parses the external binaries to bundle, adding the target triple suffix to each of them. +pub fn external_binaries( + external_binaries: &[String], + target_triple: &str, + target_platform: &TargetPlatform, +) -> Vec { + let mut paths = Vec::new(); + for curr_path in external_binaries { + let extension = if matches!(target_platform, TargetPlatform::Windows) { + ".exe" + } else { + "" + }; + paths.push(format!("{curr_path}-{target_triple}{extension}")); + } + paths +} + +/// Information for a resource. +#[derive(Debug)] +pub struct Resource { + path: PathBuf, + target: PathBuf, +} + +impl Resource { + /// The path of the resource. + pub fn path(&self) -> &Path { + &self.path + } + + /// The target location of the resource. + pub fn target(&self) -> &Path { + &self.target + } +} + +#[derive(Debug)] +enum PatternIter<'a> { + Slice(std::slice::Iter<'a, String>), + Map(std::collections::hash_map::Iter<'a, String, String>), +} + +/// A helper to iterate through resources. +pub struct ResourcePaths<'a> { + iter: ResourcePathsIter<'a>, +} + +impl<'a> ResourcePaths<'a> { + /// Creates a new ResourcePaths from a slice of patterns to iterate + pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> { + ResourcePaths { + iter: ResourcePathsIter { + pattern_iter: PatternIter::Slice(patterns.iter()), + allow_walk, + current_dest: None, + current_iter: None, + }, + } + } + + /// Creates a new ResourcePaths from a slice of patterns to iterate + pub fn from_map(patterns: &'a HashMap, allow_walk: bool) -> ResourcePaths<'a> { + ResourcePaths { + iter: ResourcePathsIter { + pattern_iter: PatternIter::Map(patterns.iter()), + allow_walk, + current_dest: None, + current_iter: None, + }, + } + } + + /// Returns the resource iterator that yields the source and target paths. + /// Needed when using [`Self::from_map`]. + pub fn iter(self) -> ResourcePathsIter<'a> { + self.iter + } +} + +/// Iterator of a [`ResourcePaths`]. +#[derive(Debug)] +pub struct ResourcePathsIter<'a> { + /// the patterns to iterate. + pattern_iter: PatternIter<'a>, + /// whether the resource paths allows directories or not. + allow_walk: bool, + + /// The value of map when [`Self::pattern_iter`] is a [`PatternIter::Map`], + /// used for determining [`Resource::target`] + current_dest: Option, + /// The iter for the current pattern. The cycle goes like this: + /// [`ResourcePaths::next`] -> [`Self::next`] -> [`Self::pattern_iter::next`] -> [`Self::current_iter::next`] + current_iter: Option, +} + +#[derive(Debug)] +enum ResourcePathsInnerIter { + Walk { + iter: walkdir::IntoIter, + /// The key of map when [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Map`], + /// used for determining [`Resource::target`] + current_pattern: Option, + }, + Glob { + iter: glob::Paths, + }, +} + +impl Iterator for ResourcePathsInnerIter { + type Item = crate::Result; + + fn next(&mut self) -> Option> { + match self { + ResourcePathsInnerIter::Walk { iter, .. } => Some( + iter + .next()? + .map(|entry| entry.into_path()) + .map_err(Into::into), + ), + ResourcePathsInnerIter::Glob { iter } => Some(iter.next()?.map_err(Into::into)), + } + } +} + +impl ResourcePathsIter<'_> { + fn next_current_iter(&mut self) -> Option> { + let current_iter = self.current_iter.as_mut().unwrap(); + let entry = current_iter.next()?; + + Some(match entry { + Ok(entry) => { + // Skip directories + if entry.is_dir() { + self.next_current_iter()? + } else { + self.resource_from_path(normalize(&entry)) + } + } + Err(error) => Err(error), + }) + } + + fn resource_from_path(&self, path: PathBuf) -> crate::Result { + if !path.exists() { + return Err(crate::Error::ResourcePathNotFound(path)); + } + + Ok(Resource { + target: if let Some(dest) = &self.current_dest { + match &self.current_iter { + Some(current_iter) => match current_iter { + // if processing a directory, preserve directory structure under current_dest + ResourcePathsInnerIter::Walk { + current_pattern, .. + } => { + if let Some(pattern) = current_pattern { + dest.join(path.strip_prefix(pattern).unwrap_or(&path)) + } else { + dest.join(&path) + } + } + // if processing a glob and current_dest is not empty + // we put all globbed paths under current_dest + // preserving the file name as it is + ResourcePathsInnerIter::Glob { .. } => dest.join(path.file_name().unwrap()), + }, + None => dest.clone(), + } + } else { + // If [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Slice`] + resource_relpath(&path) + }, + path, + }) + } + + fn next_pattern(&mut self) -> Option> { + self.current_dest = None; + + let pattern = match &mut self.pattern_iter { + PatternIter::Slice(iter) => iter.next()?, + PatternIter::Map(iter) => { + let (pattern, dest) = iter.next()?; + self.current_dest = Some(resource_relpath(Path::new(dest))); + pattern + } + }; + + if pattern.contains('*') { + self.current_iter = match glob::glob(pattern) { + Ok(glob) => Some(ResourcePathsInnerIter::Glob { iter: glob }), + Err(error) => return Some(Err(error.into())), + }; + match self.next_current_iter() { + Some(r) => return Some(r), + None => { + self.current_iter = None; + return Some(Err(crate::Error::GlobPathNotFound(pattern.clone()))); + } + } + } else { + let path = normalize(Path::new(pattern)); + if path.is_dir() { + if !self.allow_walk { + return Some(Err(crate::Error::NotAllowedToWalkDir(path))); + } + self.current_iter = Some(ResourcePathsInnerIter::Walk { + iter: WalkDir::new(&path).into_iter(), + current_pattern: if matches!(self.pattern_iter, PatternIter::Map(_)) { + Some(path) + } else { + None + }, + }); + } else { + return Some(self.resource_from_path(path)); + } + } + + self.next_current_iter() + } +} + +impl Iterator for ResourcePaths<'_> { + type Item = crate::Result; + + fn next(&mut self) -> Option> { + self.iter.next().map(|r| r.map(|res| res.path)) + } +} + +impl Iterator for ResourcePathsIter<'_> { + type Item = crate::Result; + + fn next(&mut self) -> Option> { + if self.current_iter.is_some() { + match self.next_current_iter() { + Some(r) => return Some(r), + None => self.current_iter = None, + } + } + + self.next_pattern() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use std::fs; + use std::path::Path; + + impl PartialEq for Resource { + fn eq(&self, other: &Self) -> bool { + self.path == other.path && self.target == other.target + } + } + + fn expected_resources(resources: &[(&str, &str)]) -> Vec { + resources + .iter() + .map(|(path, target)| Resource { + path: Path::new(path).components().collect(), + target: Path::new(target).components().collect(), + }) + .collect() + } + + fn setup_test_dirs() { + let mut random = [0; 1]; + getrandom::fill(&mut random).unwrap(); + + let temp = std::env::temp_dir(); + let temp = temp.join(format!("tauri_resource_paths_iter_test_{}", random[0])); + + let _ = fs::remove_dir_all(&temp); + fs::create_dir_all(&temp).unwrap(); + + std::env::set_current_dir(&temp).unwrap(); + + let paths = [ + "src-tauri/tauri.conf.json", + "src-tauri/some-other-json.json", + "src-tauri/Cargo.toml", + "src-tauri/Tauri.toml", + "src-tauri/build.rs", + "src-tauri/some-folder/some-file.txt", + "src/assets/javascript.svg", + "src/assets/tauri.svg", + "src/assets/rust.svg", + "src/assets/lang/en.json", + "src/assets/lang/ar.json", + "src/sounds/lang/es.wav", + "src/sounds/lang/fr.wav", + "src/textures/ground/earth.tex", + "src/textures/ground/sand.tex", + "src/textures/water.tex", + "src/textures/fire.tex", + "src/tiles/sky/grey.tile", + "src/tiles/sky/yellow.tile", + "src/tiles/grass.tile", + "src/tiles/stones.tile", + "src/index.html", + "src/style.css", + "src/script.js", + "src/dir/another-dir/file1.txt", + "src/dir/another-dir2/file2.txt", + ]; + + for path in paths { + let path = Path::new(path); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, "").unwrap(); + } + } + + fn resources_map(literal: &[(&str, &str)]) -> HashMap { + literal + .iter() + .map(|(from, to)| (from.to_string(), to.to_string())) + .collect() + } + + #[test] + #[serial_test::serial(resources)] + fn resource_paths_iter_slice_allow_walk() { + setup_test_dirs(); + + let dir = std::env::current_dir().unwrap().join("src-tauri"); + let _ = std::env::set_current_dir(dir); + + let resources = ResourcePaths::new( + &[ + "../src/script.js".into(), + "../src/assets".into(), + "../src/index.html".into(), + "../src/sounds".into(), + // Should be the same as `../src/textures/` or `../src/textures` + "../src/textures/**/*".into(), + "*.toml".into(), + "*.conf.json".into(), + ], + true, + ) + .iter() + .flatten() + .collect::>(); + + let expected = expected_resources(&[ + // From `../src/script.js` + ("../src/script.js", "_up_/src/script.js"), + // From `../src/assets` + ( + "../src/assets/javascript.svg", + "_up_/src/assets/javascript.svg", + ), + ("../src/assets/tauri.svg", "_up_/src/assets/tauri.svg"), + ("../src/assets/rust.svg", "_up_/src/assets/rust.svg"), + ("../src/assets/lang/en.json", "_up_/src/assets/lang/en.json"), + ("../src/assets/lang/ar.json", "_up_/src/assets/lang/ar.json"), + // From `../src/index.html` + ("../src/index.html", "_up_/src/index.html"), + // From `../src/sounds` + ("../src/sounds/lang/es.wav", "_up_/src/sounds/lang/es.wav"), + ("../src/sounds/lang/fr.wav", "_up_/src/sounds/lang/fr.wav"), + // From `../src/textures/**/*` + ( + "../src/textures/ground/earth.tex", + "_up_/src/textures/ground/earth.tex", + ), + ( + "../src/textures/ground/sand.tex", + "_up_/src/textures/ground/sand.tex", + ), + ("../src/textures/water.tex", "_up_/src/textures/water.tex"), + ("../src/textures/fire.tex", "_up_/src/textures/fire.tex"), + // From `*.toml` + ("Cargo.toml", "Cargo.toml"), + ("Tauri.toml", "Tauri.toml"), + // From `*.conf.json` + ("tauri.conf.json", "tauri.conf.json"), + ]); + + assert_eq!(resources.len(), expected.len()); + for resource in expected { + if !resources.contains(&resource) { + panic!("{resource:?} was expected but not found in {resources:?}"); + } + } + } + + #[test] + #[serial_test::serial(resources)] + fn resource_paths_iter_slice_no_walk() { + setup_test_dirs(); + + let dir = std::env::current_dir().unwrap().join("src-tauri"); + let _ = std::env::set_current_dir(dir); + + let resources = ResourcePaths::new( + &[ + "../src/script.js".into(), + "../src/assets".into(), + "../src/index.html".into(), + "../src/sounds".into(), + "*.toml".into(), + "*.conf.json".into(), + ], + false, + ) + .iter() + .flatten() + .collect::>(); + + let expected = expected_resources(&[ + ("../src/script.js", "_up_/src/script.js"), + ("../src/index.html", "_up_/src/index.html"), + ("Cargo.toml", "Cargo.toml"), + ("Tauri.toml", "Tauri.toml"), + ("tauri.conf.json", "tauri.conf.json"), + ]); + + assert_eq!(resources.len(), expected.len()); + for resource in expected { + if !resources.contains(&resource) { + panic!("{resource:?} was expected but not found in {resources:?}"); + } + } + } + + #[test] + #[serial_test::serial(resources)] + fn resource_paths_iter_map_allow_walk() { + setup_test_dirs(); + + let dir = std::env::current_dir().unwrap().join("src-tauri"); + let _ = std::env::set_current_dir(dir); + + let resources = ResourcePaths::from_map( + &resources_map(&[ + ("../src/script.js", "main.js"), + ("../src/assets", ""), + ("../src/index.html", "frontend/index.html"), + ("../src/sounds", "voices"), + ("../src/textures/*", "textures"), + ("../src/tiles/**/*", "tiles"), + ("*.toml", ""), + ("*.conf.json", "json"), + ("./some-folder/", "some-target-folder/"), + ("../non-existent-file", "asd"), // invalid case + ("../non/*", "asd"), // invalid case + ]), + true, + ) + .iter() + .flatten() + .collect::>(); + + let expected = expected_resources(&[ + ("../src/script.js", "main.js"), + ("../src/assets/javascript.svg", "javascript.svg"), + ("../src/assets/tauri.svg", "tauri.svg"), + ("../src/assets/rust.svg", "rust.svg"), + ("../src/assets/lang/en.json", "lang/en.json"), + ("../src/assets/lang/ar.json", "lang/ar.json"), + ("../src/index.html", "frontend/index.html"), + ("../src/sounds/lang/es.wav", "voices/lang/es.wav"), + ("../src/sounds/lang/fr.wav", "voices/lang/fr.wav"), + ("../src/textures/water.tex", "textures/water.tex"), + ("../src/textures/fire.tex", "textures/fire.tex"), + ("../src/tiles/grass.tile", "tiles/grass.tile"), + ("../src/tiles/stones.tile", "tiles/stones.tile"), + ("../src/tiles/sky/grey.tile", "tiles/grey.tile"), + ("../src/tiles/sky/yellow.tile", "tiles/yellow.tile"), + ("Cargo.toml", "Cargo.toml"), + ("Tauri.toml", "Tauri.toml"), + ("tauri.conf.json", "json/tauri.conf.json"), + ( + "some-folder/some-file.txt", + "some-target-folder/some-file.txt", + ), + ]); + + assert_eq!(resources.len(), expected.len()); + for resource in expected { + if !resources.contains(&resource) { + panic!("{resource:?} was expected but not found in {resources:?}"); + } + } + } + + #[test] + #[serial_test::serial(resources)] + fn resource_paths_iter_map_no_walk() { + setup_test_dirs(); + + let dir = std::env::current_dir().unwrap().join("src-tauri"); + let _ = std::env::set_current_dir(dir); + + let resources = ResourcePaths::from_map( + &resources_map(&[ + ("../src/script.js", "main.js"), + ("../src/assets", ""), + ("../src/index.html", "frontend/index.html"), + ("../src/sounds", "voices"), + ("*.toml", ""), + ("*.conf.json", "json"), + ]), + false, + ) + .iter() + .flatten() + .collect::>(); + + let expected = expected_resources(&[ + ("../src/script.js", "main.js"), + ("../src/index.html", "frontend/index.html"), + ("Cargo.toml", "Cargo.toml"), + ("Tauri.toml", "Tauri.toml"), + ("tauri.conf.json", "json/tauri.conf.json"), + ]); + + assert_eq!(resources.len(), expected.len()); + for resource in expected { + if !resources.contains(&resource) { + panic!("{resource:?} was expected but not found in {resources:?}"); + } + } + } + + #[test] + #[serial_test::serial(resources)] + fn resource_paths_errors() { + setup_test_dirs(); + + let dir = std::env::current_dir().unwrap().join("src-tauri"); + let _ = std::env::set_current_dir(dir); + + let resources = ResourcePaths::from_map( + &resources_map(&[ + ("../non-existent-file", "file"), + ("../non-existent-dir", "dir"), + // exists but not allowed to walk + ("../src", "dir2"), + // doesn't exist but it is a glob and will return an error + ("../non-existent-glob-dir/*", "glob"), + // exists but only contains directories and will not produce any values + ("../src/dir/*", "dir3"), + ]), + false, + ) + .iter() + .collect::>(); + + assert_eq!(resources.len(), 5); + + assert!(resources.iter().all(|r| r.is_err())); + + // hashmap order is not guaranteed so we check the error variant exists and how many + assert!( + resources + .iter() + .any(|r| matches!(r, Err(crate::Error::ResourcePathNotFound(_)))) + ); + assert_eq!( + resources + .iter() + .filter(|r| matches!(r, Err(crate::Error::ResourcePathNotFound(_)))) + .count(), + 2 + ); + assert!( + resources + .iter() + .any(|r| matches!(r, Err(crate::Error::NotAllowedToWalkDir(_)))) + ); + assert_eq!( + resources + .iter() + .filter(|r| matches!(r, Err(crate::Error::NotAllowedToWalkDir(_)))) + .count(), + 1 + ); + assert!( + resources + .iter() + .any(|r| matches!(r, Err(crate::Error::GlobPathNotFound(_)))) + ); + assert_eq!( + resources + .iter() + .filter(|r| matches!(r, Err(crate::Error::GlobPathNotFound(_)))) + .count(), + 2 + ); + } +} diff --git a/apps/tauri/src-tauri/vendor/tauri-utils/src/tokens.rs b/apps/tauri/src-tauri/vendor/tauri-utils/src/tokens.rs new file mode 100644 index 0000000..6558621 --- /dev/null +++ b/apps/tauri/src-tauri/vendor/tauri-utils/src/tokens.rs @@ -0,0 +1,175 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Utilities to implement [`ToTokens`] for a type. + +use std::path::Path; + +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; +use serde_json::Value as JsonValue; +use url::Url; + +/// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`. +/// +/// All fields must represent a binding of the same name that implements `ToTokens`. +#[macro_export] +macro_rules! literal_struct { + ($tokens:ident, $struct:path, $($field:ident),+) => { + $tokens.append_all(quote! { + $struct { + $($field: #$field),+ + } + }) + }; +} + +/// Create a `String` constructor `TokenStream`. +/// +/// e.g. `"Hello World"` -> `String::from("Hello World")`. +/// This takes a `&String` to reduce casting all the `&String` -> `&str` manually. +pub fn str_lit(s: impl AsRef) -> TokenStream { + let s = s.as_ref(); + quote! { #s.into() } +} + +/// Create an `Option` constructor `TokenStream`. +pub fn opt_lit(item: Option<&impl ToTokens>) -> TokenStream { + match item { + None => quote! { ::core::option::Option::None }, + Some(item) => quote! { ::core::option::Option::Some(#item) }, + } +} + +/// Create an `Option` constructor `TokenStream` over an owned [`ToTokens`] impl type. +pub fn opt_lit_owned(item: Option) -> TokenStream { + match item { + None => quote! { ::core::option::Option::None }, + Some(item) => quote! { ::core::option::Option::Some(#item) }, + } +} + +/// Helper function to combine an `opt_lit` with `str_lit`. +pub fn opt_str_lit(item: Option>) -> TokenStream { + opt_lit(item.map(str_lit).as_ref()) +} + +/// Helper function to combine an `opt_lit` with a list of `str_lit` +pub fn opt_vec_lit( + item: Option>, + map: impl Fn(Raw) -> Tokens, +) -> TokenStream +where + Tokens: ToTokens, +{ + opt_lit(item.map(|list| vec_lit(list, map)).as_ref()) +} + +/// Create a `Vec` constructor, mapping items with a function that spits out `TokenStream`s. +pub fn vec_lit( + list: impl IntoIterator, + map: impl Fn(Raw) -> Tokens, +) -> TokenStream +where + Tokens: ToTokens, +{ + let items = list.into_iter().map(map); + quote! { vec![#(#items),*] } +} + +/// Create a `PathBuf` constructor `TokenStream`. +/// +/// e.g. `"Hello World" -> String::from("Hello World"). +pub fn path_buf_lit(s: impl AsRef) -> TokenStream { + let s = s.as_ref().to_string_lossy().into_owned(); + quote! { ::std::path::PathBuf::from(#s) } +} + +/// Creates a `Url` constructor `TokenStream`. +pub fn url_lit(url: &Url) -> TokenStream { + let url = url.as_str(); + quote! { #url.parse().unwrap() } +} + +/// Create a map constructor, mapping keys and values with other `TokenStream`s. +/// +/// This function is pretty generic because the types of keys AND values get transformed. +pub fn map_lit( + map_type: TokenStream, + map: Map, + map_key: FuncKey, + map_value: FuncValue, +) -> TokenStream +where + ::IntoIter: ExactSizeIterator, + Map: IntoIterator, + TokenStreamKey: ToTokens, + TokenStreamValue: ToTokens, + FuncKey: Fn(Key) -> TokenStreamKey, + FuncValue: Fn(Value) -> TokenStreamValue, +{ + let ident = quote::format_ident!("map"); + let map = map.into_iter(); + + if map.len() > 0 { + let items = map.map(|(key, value)| { + let key = map_key(key); + let value = map_value(value); + quote! { #ident.insert(#key, #value); } + }); + + quote! {{ + let mut #ident = #map_type::new(); + #(#items)* + #ident + }} + } else { + quote! { #map_type::new() } + } +} + +/// Create a `serde_json::Value` variant `TokenStream` for a number +pub fn json_value_number_lit(num: &serde_json::Number) -> TokenStream { + // See for guarantees + let prefix = quote! { ::serde_json::Value }; + if num.is_u64() { + // guaranteed u64 + let num = num.as_u64().unwrap(); + quote! { #prefix::Number(#num.into()) } + } else if num.is_i64() { + // guaranteed i64 + let num = num.as_i64().unwrap(); + quote! { #prefix::Number(#num.into()) } + } else if num.is_f64() { + // guaranteed f64 + let num = num.as_f64().unwrap(); + quote! { #prefix::Number(::serde_json::Number::from_f64(#num).unwrap(/* safe to unwrap, guaranteed f64 */)) } + } else { + // invalid number + quote! { #prefix::Null } + } +} + +/// Create a `serde_json::Value` constructor `TokenStream` +pub fn json_value_lit(jv: &JsonValue) -> TokenStream { + let prefix = quote! { ::serde_json::Value }; + + match jv { + JsonValue::Null => quote! { #prefix::Null }, + JsonValue::Bool(bool) => quote! { #prefix::Bool(#bool) }, + JsonValue::Number(number) => json_value_number_lit(number), + JsonValue::String(str) => { + let s = str_lit(str); + quote! { #prefix::String(#s) } + } + JsonValue::Array(vec) => { + let items = vec.iter().map(json_value_lit); + quote! { #prefix::Array(vec![#(#items),*]) } + } + JsonValue::Object(map) => { + let map = map_lit(quote! { ::serde_json::Map }, map, str_lit, json_value_lit); + quote! { #prefix::Object(#map) } + } + } +} diff --git a/apps/web/package.json b/apps/web/package.json index a7c366d..5569834 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,10 +13,10 @@ }, "dependencies": { "@fontsource-variable/inter": "^5.2.8", - "@noble/curves": "^2.0.1", - "@tailwindcss/vite": "^4.2.1", - "@tanstack/react-router": "^1.0.0", - "@tanstack/react-virtual": "^3.0.0", + "@noble/curves": "^2.2.0", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-router": "^1.169.1", + "@tanstack/react-virtual": "^3.13.24", "@tauri-apps/api": "^2", "@tensamin/call": "workspace:*", "@tensamin/chat": "workspace:*", @@ -33,17 +33,16 @@ "clsx": "^2.1.1", "comlink": "^4.4.2", "framer-motion": "^12.38.0", - "lucide-react": "^0.564.0", + "lucide-react": "^1.14.0", "qrcode": "^1.5.4", "react": "^19.2.0", "react-dom": "^19.2.0", - "shadcn": "^4.0.7", - "sonner": "^1.0.0", + "shadcn": "^4.6.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tailwind-scrollbar-hide": "^4.0.0", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4.2.4", "tw-animate-css": "^1.4.0", - "vite-tsconfig-paths": "^6.1.1", "zod": "^4.3.6" }, "devDependencies": { @@ -51,11 +50,11 @@ "@types/qrcode": "^1.5.6", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", - "@vitejs/plugin-react": "^5.0.4", + "@vitejs/plugin-react": "^6.0.1", "eslint": "^10.0.3", "globals": "^17.4.0", - "typescript": "~5.9.3", + "typescript": "~6.0.3", "typescript-eslint": "^8.57.0", - "vite": "^7.3.1" + "vite": "^8.0.10" } } diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 3499547..1c2733b 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -86,7 +86,11 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {