Compare commits

...
10 Commits
Author SHA1 Message Date
swaits aeb5568727 manganis: load FFI classes from the activity classloader, not FindClass
JNIEnv::find_class resolves against the system classloader on a thread
attached from native code, which cannot see APK classes (#5629). Route
the generated #[manganis::ffi] type lookup through
load_class_from_classloader, and reimplement that helper to ask the
activity's own ClassLoader.loadClass instead of Class.forName.
2026-09-05 20:34:33 -06:00
nico.burns 07dddd569e CLI: configurable Android versionCode and iOS/macOS CFBundleVersion 2026-09-05 20:29:43 -06:00
nicoburns 6adf7ba465 Fix leak in generated iOS FFI wrappers by holding Retained<AnyObject> 2026-09-05 20:29:37 -06:00
Nico Burns 57d6794ad6 release v0.7.10 2026-07-30 15:33:31 -07:00
Nico Burns fda083bb65 Fix hotpatching when the bin target's crate name differs from the package name (#5719) (#5720)
(cherry picked from commit 4c51c12bc19d78983cb2992f319ebe996acb2893)

Signed-off-by: Nico Burns <nico@nicoburns.com>
2026-07-29 16:55:16 -07:00
Jonathan Kelley 3e43ffa6cf release v0.7.9 2026-05-07 17:09:43 -07:00
Jonathan Kelley bfcc111817 fix: git sha for cli 2026-05-07 16:59:46 -07:00
Jonathan Kelley d1ead872b1 fix: make instantiate async 2026-05-07 16:44:37 -07:00
Jonathan Kelley f5be0ea510 release v0.7.8 2026-05-07 15:28:20 -07:00
f1a0124227 backport iframe fix to v0.7 (#5535)
* feat:Add opt‑in navigation handler for external URLs (#5531)

* add navigation handler config option

code from https://github.com/DioxusLabs/dioxus/pull/5231

* Re-order branches

The branches should be reorder so that the branch that checks for
the index and assets is always taken first. Otherwise, they will
fail to load for users who forget to allow them in their custom
handlers, which will be a crappy experience and hard to debug.

* remove return

* Change behavior when handler not set

---------

Co-authored-by: not a cow <agielow@proton.me>

* fix: allow navigations but disallow external nav

---------

Co-authored-by: Martin Martinez Rivera <mrtnz.rvr@gmail.com>
Co-authored-by: not a cow <agielow@proton.me>
2026-05-07 15:11:47 -07:00
24 changed files with 512 additions and 188 deletions

No files matched your search

+12
View File
@@ -70,6 +70,18 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
with:
# Build from the tag we're releasing, not the branch the workflow
# was dispatched from. Without this the binary's Cargo.toml version
# and embedded SHA reflect whatever was on the dispatch branch.
ref: refs/tags/${{ github.event.inputs.post }}
- name: Resolve release commit SHA
shell: bash
run: |
sha=$(git rev-parse HEAD)
echo "DIOXUS_CLI_GIT_SHA=$sha" >> "$GITHUB_ENV"
echo "DIOXUS_CLI_GIT_SHA_SHORT=${sha:0:7}" >> "$GITHUB_ENV"
- name: Install openssl on macos
if: matrix.platform.os == 'macos-latest'
Generated
+56 -56
View File
@@ -3110,7 +3110,7 @@ dependencies = [
[[package]]
name = "depinfo"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"thiserror 2.0.18",
]
@@ -3246,7 +3246,7 @@ dependencies = [
[[package]]
name = "dioxus"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"criterion",
"dioxus",
@@ -3288,7 +3288,7 @@ dependencies = [
[[package]]
name = "dioxus-asset-resolver"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-cli-config",
@@ -3309,7 +3309,7 @@ dependencies = [
[[package]]
name = "dioxus-autofmt"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus-rsx",
"pretty_assertions",
@@ -3323,7 +3323,7 @@ dependencies = [
[[package]]
name = "dioxus-cli"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"ansi-to-html",
"ansi-to-tui",
@@ -3451,7 +3451,7 @@ dependencies = [
[[package]]
name = "dioxus-cli-config"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"wasm-bindgen",
]
@@ -3467,7 +3467,7 @@ dependencies = [
[[package]]
name = "dioxus-cli-telemetry"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"chrono",
"dirs",
@@ -3479,7 +3479,7 @@ dependencies = [
[[package]]
name = "dioxus-component-manifest"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"schemars 1.2.1",
"serde",
@@ -3488,7 +3488,7 @@ dependencies = [
[[package]]
name = "dioxus-config-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"proc-macro2",
"quote",
@@ -3496,11 +3496,11 @@ dependencies = [
[[package]]
name = "dioxus-config-macros"
version = "0.7.7"
version = "0.7.10"
[[package]]
name = "dioxus-core"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyhow",
"const_format",
@@ -3531,7 +3531,7 @@ dependencies = [
[[package]]
name = "dioxus-core-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"convert_case 0.8.0",
"dioxus",
@@ -3547,11 +3547,11 @@ dependencies = [
[[package]]
name = "dioxus-core-types"
version = "0.7.7"
version = "0.7.10"
[[package]]
name = "dioxus-desktop"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyhow",
"async-trait",
@@ -3612,7 +3612,7 @@ dependencies = [
[[package]]
name = "dioxus-devtools"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus-cli-config",
"dioxus-core",
@@ -3631,7 +3631,7 @@ dependencies = [
[[package]]
name = "dioxus-devtools-types"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus-core",
"serde",
@@ -3640,7 +3640,7 @@ dependencies = [
[[package]]
name = "dioxus-document"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-core",
@@ -3658,7 +3658,7 @@ dependencies = [
[[package]]
name = "dioxus-dx-wire-format"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"cargo_metadata",
"manganis-core",
@@ -3707,7 +3707,7 @@ dependencies = [
[[package]]
name = "dioxus-ext"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus-autofmt",
"dioxus-rsx-rosetta",
@@ -3718,7 +3718,7 @@ dependencies = [
[[package]]
name = "dioxus-fullstack"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyhow",
"async-stream",
@@ -3785,7 +3785,7 @@ dependencies = [
[[package]]
name = "dioxus-fullstack-core"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyhow",
"axum-core 0.5.6",
@@ -3813,7 +3813,7 @@ dependencies = [
[[package]]
name = "dioxus-fullstack-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"axum 0.8.9",
"const_format",
@@ -3829,7 +3829,7 @@ dependencies = [
[[package]]
name = "dioxus-history"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-core",
@@ -3838,7 +3838,7 @@ dependencies = [
[[package]]
name = "dioxus-hooks"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-core",
@@ -3856,7 +3856,7 @@ dependencies = [
[[package]]
name = "dioxus-html"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"async-trait",
"bytes",
@@ -3887,7 +3887,7 @@ dependencies = [
[[package]]
name = "dioxus-html-internal-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"convert_case 0.8.0",
"proc-macro2",
@@ -3898,7 +3898,7 @@ dependencies = [
[[package]]
name = "dioxus-interpreter-js"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus-core",
"dioxus-core-types",
@@ -3916,7 +3916,7 @@ dependencies = [
[[package]]
name = "dioxus-liveview"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"axum 0.8.9",
"dioxus",
@@ -3944,7 +3944,7 @@ dependencies = [
[[package]]
name = "dioxus-logger"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-cli-config",
@@ -3955,7 +3955,7 @@ dependencies = [
[[package]]
name = "dioxus-native"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyrender",
"anyrender_vello",
@@ -3990,7 +3990,7 @@ dependencies = [
[[package]]
name = "dioxus-native-dom"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"blitz-dom",
"blitz-traits",
@@ -4115,7 +4115,7 @@ dependencies = [
[[package]]
name = "dioxus-router"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"axum 0.8.9",
"base64 0.22.1",
@@ -4143,7 +4143,7 @@ dependencies = [
[[package]]
name = "dioxus-router-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"base16",
"digest",
@@ -4157,7 +4157,7 @@ dependencies = [
[[package]]
name = "dioxus-rsx"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"prettier-please",
"prettyplease",
@@ -4170,7 +4170,7 @@ dependencies = [
[[package]]
name = "dioxus-rsx-hotreload"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus-core",
"dioxus-core-types",
@@ -4185,7 +4185,7 @@ dependencies = [
[[package]]
name = "dioxus-rsx-rosetta"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"convert_case 0.8.0",
"dioxus-autofmt",
@@ -4201,7 +4201,7 @@ dependencies = [
[[package]]
name = "dioxus-server"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyhow",
"async-trait",
@@ -4263,7 +4263,7 @@ dependencies = [
[[package]]
name = "dioxus-signals"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-core",
@@ -4283,7 +4283,7 @@ dependencies = [
[[package]]
name = "dioxus-ssr"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"askama_escape",
"dioxus",
@@ -4294,7 +4294,7 @@ dependencies = [
[[package]]
name = "dioxus-stores"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
"dioxus-core",
@@ -4305,7 +4305,7 @@ dependencies = [
[[package]]
name = "dioxus-stores-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"convert_case 0.8.0",
"dioxus",
@@ -4325,7 +4325,7 @@ dependencies = [
[[package]]
name = "dioxus-web"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"ciborium",
"dioxus",
@@ -5468,7 +5468,7 @@ dependencies = [
[[package]]
name = "generational-box"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"criterion",
"parking_lot",
@@ -7885,7 +7885,7 @@ dependencies = [
[[package]]
name = "lazy-js-bundle"
version = "0.7.7"
version = "0.7.10"
[[package]]
name = "lazy_static"
@@ -8303,7 +8303,7 @@ dependencies = [
[[package]]
name = "manganis"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"const-serialize 0.7.2",
"const-serialize 0.8.0-alpha.0",
@@ -8317,7 +8317,7 @@ dependencies = [
[[package]]
name = "manganis-core"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"const-serialize 0.7.2",
"const-serialize 0.8.0-alpha.0",
@@ -8338,7 +8338,7 @@ dependencies = [
[[package]]
name = "manganis-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dunce",
"macro-string",
@@ -12547,7 +12547,7 @@ dependencies = [
[[package]]
name = "ssr-only"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
]
@@ -12815,7 +12815,7 @@ dependencies = [
[[package]]
name = "subsecond"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"js-sys",
"libc",
@@ -12841,7 +12841,7 @@ dependencies = [
[[package]]
name = "subsecond-types"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"serde",
]
@@ -12873,7 +12873,7 @@ dependencies = [
[[package]]
name = "suspense-carousel"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"async-std",
"dioxus",
@@ -14521,7 +14521,7 @@ dependencies = [
[[package]]
name = "wasm-split-cli"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"anyhow",
"clap",
@@ -14554,7 +14554,7 @@ dependencies = [
[[package]]
name = "wasm-split-macro"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"base16",
"digest",
@@ -14566,7 +14566,7 @@ dependencies = [
[[package]]
name = "wasm-splitter"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"async-once-cell",
"wasm-split-macro",
@@ -14587,7 +14587,7 @@ dependencies = [
[[package]]
name = "wasm-used"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"id-arena",
"tracing",
@@ -15351,7 +15351,7 @@ dependencies = [
[[package]]
name = "windows-headless"
version = "0.7.7"
version = "0.7.10"
dependencies = [
"dioxus",
]
+52 -52
View File
@@ -114,71 +114,71 @@ members = [
[workspace.package]
version = "0.7.7"
version = "0.7.10"
# dependencies that are shared across packages
[workspace.dependencies]
dioxus = { path = "packages/dioxus", version = "0.7.7" }
dioxus-core = { path = "packages/core", version = "0.7.7" }
dioxus-core-types = { path = "packages/core-types", version = "0.7.7" }
dioxus-core-macro = { path = "packages/core-macro", version = "0.7.7" }
dioxus-config-macro = { path = "packages/config-macro", version = "0.7.7" }
dioxus-router = { path = "packages/router", version = "0.7.7" }
dioxus-router-macro = { path = "packages/router-macro", version = "0.7.7" }
dioxus-document = { path = "packages/document", version = "0.7.7", default-features = false }
dioxus-history = { path = "packages/history", version = "0.7.7", default-features = false }
dioxus-html = { path = "packages/html", version = "0.7.7", default-features = false }
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.7.7" }
dioxus-hooks = { path = "packages/hooks", version = "0.7.7" }
dioxus-web = { path = "packages/web", version = "0.7.7", default-features = false }
dioxus-ssr = { path = "packages/ssr", version = "0.7.7", default-features = false }
dioxus-desktop = { path = "packages/desktop", version = "0.7.7", default-features = false }
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.7.7" }
dioxus-liveview = { path = "packages/liveview", version = "0.7.7" }
dioxus-autofmt = { path = "packages/autofmt", version = "0.7.7" }
dioxus-rsx = { path = "packages/rsx", version = "0.7.7" }
dioxus-rsx-hotreload = { path = "packages/rsx-hotreload", version = "0.7.7" }
dioxus-rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.7.7" }
dioxus-signals = { path = "packages/signals", version = "0.7.7" }
dioxus-stores = { path = "packages/stores", version = "0.7.7" }
dioxus-stores-macro = { path = "packages/stores-macro", version = "0.7.7" }
dioxus-devtools = { path = "packages/devtools", version = "0.7.7" }
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.7.7" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.7.7", default-features = false }
dioxus-fullstack-core = { path = "packages/fullstack-core", version = "0.7.7", default-features = false }
dioxus-fullstack-macro = { path = "packages/fullstack-macro", version = "0.7.7", default-features = false }
dioxus-server = { path = "packages/fullstack-server", version = "0.7.7" }
dioxus-dx-wire-format = { path = "packages/dx-wire-format", version = "0.7.7" }
dioxus-logger = { path = "packages/logger", version = "0.7.7" }
dioxus-native = { path = "packages/native", version = "0.7.7" }
dioxus-native-dom = { path = "packages/native-dom", version = "0.7.7" }
dioxus-asset-resolver = { path = "packages/asset-resolver", version = "0.7.7" }
dioxus-config-macros = { path = "packages/config-macros", version = "0.7.7" }
dioxus-component-manifest = { path = "packages/component-manifest", version = "0.7.7" }
generational-box = { path = "packages/generational-box", version = "0.7.7" }
lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.7.7" }
dioxus = { path = "packages/dioxus", version = "0.7.10" }
dioxus-core = { path = "packages/core", version = "0.7.10" }
dioxus-core-types = { path = "packages/core-types", version = "0.7.10" }
dioxus-core-macro = { path = "packages/core-macro", version = "0.7.10" }
dioxus-config-macro = { path = "packages/config-macro", version = "0.7.10" }
dioxus-router = { path = "packages/router", version = "0.7.10" }
dioxus-router-macro = { path = "packages/router-macro", version = "0.7.10" }
dioxus-document = { path = "packages/document", version = "0.7.10", default-features = false }
dioxus-history = { path = "packages/history", version = "0.7.10", default-features = false }
dioxus-html = { path = "packages/html", version = "0.7.10", default-features = false }
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.7.10" }
dioxus-hooks = { path = "packages/hooks", version = "0.7.10" }
dioxus-web = { path = "packages/web", version = "0.7.10", default-features = false }
dioxus-ssr = { path = "packages/ssr", version = "0.7.10", default-features = false }
dioxus-desktop = { path = "packages/desktop", version = "0.7.10", default-features = false }
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.7.10" }
dioxus-liveview = { path = "packages/liveview", version = "0.7.10" }
dioxus-autofmt = { path = "packages/autofmt", version = "0.7.10" }
dioxus-rsx = { path = "packages/rsx", version = "0.7.10" }
dioxus-rsx-hotreload = { path = "packages/rsx-hotreload", version = "0.7.10" }
dioxus-rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.7.10" }
dioxus-signals = { path = "packages/signals", version = "0.7.10" }
dioxus-stores = { path = "packages/stores", version = "0.7.10" }
dioxus-stores-macro = { path = "packages/stores-macro", version = "0.7.10" }
dioxus-devtools = { path = "packages/devtools", version = "0.7.10" }
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.7.10" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.7.10", default-features = false }
dioxus-fullstack-core = { path = "packages/fullstack-core", version = "0.7.10", default-features = false }
dioxus-fullstack-macro = { path = "packages/fullstack-macro", version = "0.7.10", default-features = false }
dioxus-server = { path = "packages/fullstack-server", version = "0.7.10" }
dioxus-dx-wire-format = { path = "packages/dx-wire-format", version = "0.7.10" }
dioxus-logger = { path = "packages/logger", version = "0.7.10" }
dioxus-native = { path = "packages/native", version = "0.7.10" }
dioxus-native-dom = { path = "packages/native-dom", version = "0.7.10" }
dioxus-asset-resolver = { path = "packages/asset-resolver", version = "0.7.10" }
dioxus-config-macros = { path = "packages/config-macros", version = "0.7.10" }
dioxus-component-manifest = { path = "packages/component-manifest", version = "0.7.10" }
generational-box = { path = "packages/generational-box", version = "0.7.10" }
lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.7.10" }
# cli
dioxus-cli-telemetry = { path = "packages/cli-telemetry", version = "0.7.7" }
dioxus-cli-config = { path = "packages/cli-config", version = "0.7.7" }
dioxus-cli-telemetry = { path = "packages/cli-telemetry", version = "0.7.10" }
dioxus-cli-config = { path = "packages/cli-config", version = "0.7.10" }
# subsecond
subsecond-types = { path = "packages/subsecond/subsecond-types", version = "0.7.7" }
subsecond = { path = "packages/subsecond/subsecond", version = "0.7.7" }
subsecond-types = { path = "packages/subsecond/subsecond-types", version = "0.7.10" }
subsecond = { path = "packages/subsecond/subsecond", version = "0.7.10" }
# wasm-split
wasm-splitter = { path = "packages/wasm-split/wasm-split", version = "0.7.7" }
wasm-split-macro = { path = "packages/wasm-split/wasm-split-macro", version = "0.7.7" }
wasm-split-cli = { path = "packages/wasm-split/wasm-split-cli", version = "0.7.7" }
wasm-used = { path = "packages/wasm-split/wasm-used", version = "0.7.7" }
wasm-splitter = { path = "packages/wasm-split/wasm-split", version = "0.7.10" }
wasm-split-macro = { path = "packages/wasm-split/wasm-split-macro", version = "0.7.10" }
wasm-split-cli = { path = "packages/wasm-split/wasm-split-cli", version = "0.7.10" }
wasm-used = { path = "packages/wasm-split/wasm-used", version = "0.7.10" }
# our dep-info parsing crate
depinfo = { path = "packages/depinfo", version = "0.7.7" }
depinfo = { path = "packages/depinfo", version = "0.7.10" }
# manganis
manganis = { path = "packages/manganis/manganis", version = "0.7.7" }
manganis-core = { path = "packages/manganis/manganis-core", version = "0.7.7" }
manganis-macro = { path = "packages/manganis/manganis-macro", version = "0.7.7" }
manganis = { path = "packages/manganis/manganis", version = "0.7.10" }
manganis-core = { path = "packages/manganis/manganis-core", version = "0.7.10" }
manganis-macro = { path = "packages/manganis/manganis-macro", version = "0.7.10" }
manganis-core-07 = { path = "packages/manganis/manganis-07", version = "0.7.2" }
# const-serialize.
+11 -4
View File
@@ -98,12 +98,19 @@
);
in
{
packages.dioxus-cli = (
rustPackage "dioxus-cli" {
packages.dioxus-cli =
(rustPackage "dioxus-cli" {
binary = "dx";
features = [ "no-downloads" ];
}
);
}).overrideAttrs
(_: {
# The nix sandbox has no .git and no .cargo_vcs_info.json, so
# build.rs has nothing to derive the commit from. Pass the
# flake's own input rev through the env-var override path.
# `self.rev` is set on a clean tree; `dirtyRev` on a dirty one
# (and includes a `-dirty` suffix, which build.rs accepts).
DIOXUS_CLI_GIT_SHA = inputs.self.rev or inputs.self.dirtyRev or "";
});
packages.default = self'.packages.dioxus-cli;
checks.dioxus-cli = self'.packages.dioxus-cli;
+1 -1
View File
@@ -27,7 +27,7 @@ dioxus-cli-telemetry = { workspace = true }
dioxus-component-manifest = { workspace = true }
bytes = { workspace = true }
clap = { workspace = true, features = ["derive", "cargo"] }
clap = { workspace = true, features = ["derive", "cargo", "env"] }
convert_case = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true, features = ["v4", "v5", "v7"] }
@@ -13,7 +13,7 @@ android {
applicationId = "{{ application_id }}"
minSdk = {{ min_sdk }}
targetSdk = {{ target_sdk }}
versionCode = 1
versionCode = {{ version_code }}
versionName = "{{ version }}"
}
{{#if android_bundle}}
+1 -1
View File
@@ -15,7 +15,7 @@
<string>{{ bundle_name }}</string>
<key>CFBundleVersion</key>
<string>{{ version }}</string>
<string>{{ bundle_version }}</string>
<key>CFBundleShortVersionString</key>
<string>{{ version }}</string>
<key>CFBundleDevelopmentRegion</key>
+1 -1
View File
@@ -27,7 +27,7 @@
<string>{{ version }}</string>
<key>CFBundleVersion</key>
<string>{{ version }}</string>
<string>{{ bundle_version }}</string>
<key>LSMinimumSystemVersion</key>
<string>{{ minimum_system_version }}</string>
+128 -21
View File
@@ -1,38 +1,145 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA");
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA_SHORT");
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/refs");
let full_hash = std::env::var("DIOXUS_CLI_GIT_SHA").ok().or_else(|| {
let output = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let git_dir = find_git_dir(&manifest_dir);
if !output.status.success() {
return None;
// Track the files that determine the current commit. Absolute paths are
// required because the build script runs from `packages/cli/`, not the
// workspace root where `.git` lives.
if let Some(git_dir) = git_dir.as_deref() {
emit_rerun(&git_dir.join("HEAD"));
emit_rerun(&git_dir.join("packed-refs"));
if let Some(target) = head_ref_target(git_dir) {
emit_rerun(&git_dir.join(target));
}
}
emit_rerun(&manifest_dir.join(".cargo_vcs_info.json"));
let hash = String::from_utf8(output.stdout).ok()?;
let hash = hash.trim().to_string();
if hash.is_empty() {
None
} else {
Some(hash)
}
});
let full_hash = env_nonempty("DIOXUS_CLI_GIT_SHA")
.or_else(|| git_dir.as_deref().and_then(read_head_sha))
.or_else(|| read_cargo_vcs_info(&manifest_dir))
.or_else(|| git_cli_sha(&manifest_dir));
if let Some(full_hash) = full_hash {
let full_hash = full_hash.trim();
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA={full_hash}");
let short_hash = std::env::var("DIOXUS_CLI_GIT_SHA_SHORT")
.ok()
let short_hash = env_nonempty("DIOXUS_CLI_GIT_SHA_SHORT")
.unwrap_or_else(|| full_hash.chars().take(7).collect());
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA_SHORT={short_hash}");
}
}
fn env_nonempty(key: &str) -> Option<String> {
env::var(key).ok().filter(|s| !s.trim().is_empty())
}
fn emit_rerun(path: &Path) {
println!("cargo:rerun-if-changed={}", path.display());
}
/// Walk up from `start` looking for `.git`. Handles the file form used by
/// git worktrees and submodules, where `.git` contains `gitdir: <path>`.
fn find_git_dir(start: &Path) -> Option<PathBuf> {
for dir in start.ancestors() {
let candidate = dir.join(".git");
let meta = match fs::metadata(&candidate) {
Ok(m) => m,
Err(_) => continue,
};
if meta.is_dir() {
return Some(candidate);
}
if meta.is_file() {
let contents = fs::read_to_string(&candidate).ok()?;
let pointer = contents
.lines()
.find_map(|l| l.strip_prefix("gitdir:"))?
.trim();
let resolved = if Path::new(pointer).is_absolute() {
PathBuf::from(pointer)
} else {
dir.join(pointer)
};
return Some(resolved);
}
}
None
}
/// If HEAD is a symbolic ref (`ref: refs/heads/foo`), return the target path.
fn head_ref_target(git_dir: &Path) -> Option<String> {
let head = fs::read_to_string(git_dir.join("HEAD")).ok()?;
head.strip_prefix("ref:").map(|s| s.trim().to_owned())
}
fn read_head_sha(git_dir: &Path) -> Option<String> {
let head = fs::read_to_string(git_dir.join("HEAD")).ok()?;
let head = head.trim();
let Some(target) = head.strip_prefix("ref:").map(str::trim) else {
// Detached HEAD: the file holds the SHA directly.
return is_sha(head).then(|| head.to_owned());
};
// Loose ref file.
if let Ok(sha) = fs::read_to_string(git_dir.join(target)) {
let sha = sha.trim();
if is_sha(sha) {
return Some(sha.to_owned());
}
}
// Packed refs (after `git gc` / shallow clones, refs only live here).
let packed = fs::read_to_string(git_dir.join("packed-refs")).ok()?;
for line in packed.lines() {
if line.starts_with('#') || line.starts_with('^') || line.is_empty() {
continue;
}
let (sha, name) = line.split_once(' ')?;
if name == target && is_sha(sha) {
return Some(sha.to_owned());
}
}
None
}
/// Cargo writes `.cargo_vcs_info.json` next to `Cargo.toml` when publishing,
/// recording the source SHA at publish time. This is how `cargo install`
/// from crates.io can still report a real commit despite the `.crate`
/// tarball having no `.git/`.
fn read_cargo_vcs_info(manifest_dir: &Path) -> Option<String> {
let contents = fs::read_to_string(manifest_dir.join(".cargo_vcs_info.json")).ok()?;
// Cheap targeted parse so we don't pull serde into the build script.
// The file is generated by cargo and the field name is stable.
let after = contents.split("\"sha1\"").nth(1)?;
let sha = after.split('"').nth(1)?.trim();
is_sha(sha).then(|| sha.to_owned())
}
fn git_cli_sha(manifest_dir: &Path) -> Option<String> {
// Scope `git` to the manifest dir so we never accidentally pick up a
// SHA from a parent repo (e.g. someone's `~` is a git repo).
let output = Command::new("git")
.arg("-C")
.arg(manifest_dir)
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let sha = String::from_utf8(output.stdout).ok()?.trim().to_owned();
is_sha(&sha).then_some(sha)
}
fn is_sha(s: &str) -> bool {
matches!(s.len(), 40 | 64) && s.chars().all(|c| c.is_ascii_hexdigit())
}
+16
View File
@@ -267,6 +267,15 @@
"items": {
"type": "string"
}
},
"version_code": {
"description": "The version code used for `versionCode` in the generated Gradle project.\n\nMust be between 1 and 2100000000 (the Google Play Store maximum).\n\nCan be overridden per-build with `--version-code` or the `DX_ANDROID_VERSION_CODE`\nenvironment variable. If unset, it is derived from the crate version as\n`major * 1000000 + minor * 1000 + patch`.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0
}
}
},
@@ -964,6 +973,13 @@
"type": "string"
}
},
"bundle_version": {
"description": "The bundle version string (CFBundleVersion).\n\nThis is the machine-readable build number that must increase with every upload\nto App Store Connect. Can be overridden per-build with `--bundle-version` or the\n`DX_BUNDLE_VERSION` environment variable. Defaults to the crate version.",
"type": [
"string",
"null"
]
},
"category": {
"description": "App category. Overrides `bundle.category` for iOS builds.",
"type": [
+2
View File
@@ -120,6 +120,7 @@ impl BuildRequest {
application_id: String,
app_name: String,
version: String,
version_code: u32,
android_bundle: Option<crate::AndroidSettings>,
/// Android SDK version settings
min_sdk: u32,
@@ -180,6 +181,7 @@ impl BuildRequest {
application_id: self.bundle_identifier(),
app_name: self.bundled_app_name(),
version: self.crate_version(),
version_code: self.android_version_code()?,
android_bundle: self.config.bundle.android.clone(),
min_sdk: self.config.android.min_sdk.unwrap_or(24),
target_sdk: self.config.android.target_sdk.unwrap_or(34),
+4
View File
@@ -160,6 +160,8 @@ impl BuildRequest {
pub executable_name: String,
/// App version string (from Cargo.toml)
pub version: String,
/// Bundle version string (CFBundleVersion)
pub bundle_version: String,
/// Permission usage descriptions
pub permissions: Vec<PlistPermission>,
/// Additional plist entries as raw XML
@@ -231,6 +233,7 @@ impl BuildRequest {
executable_name: self.platform_exe_name(),
bundle_identifier: self.bundle_identifier(),
version: self.crate_version(),
bundle_version: self.apple_bundle_version(),
permissions,
plist_entries,
raw_plist,
@@ -265,6 +268,7 @@ impl BuildRequest {
executable_name: self.platform_exe_name(),
bundle_identifier: self.bundle_identifier(),
version: self.crate_version(),
bundle_version: self.apple_bundle_version(),
permissions,
plist_entries,
raw_plist,
+3 -1
View File
@@ -408,7 +408,9 @@ impl AppBuilder {
// patch (starting from changed_crates + cascade). That serves a different purpose — it only
// compiles what changed now, not everything ever modified. Both use workspace_dependents_of
// for the BFS, so they stay in sync automatically.
let tip_crate_name = self.build.main_target.replace('-', "_");
// Track the tip by *package* name — that's the identity used by the file→crate
// mapping and the workspace dependents graph (the bin target name can differ).
let tip_crate_name = self.build.tip_package_name();
self.modified_crates.insert(tip_crate_name.clone());
// Add changed crates and their transitive workspace dependents (cascade).
+3 -1
View File
@@ -614,7 +614,9 @@ impl BuildRequest {
modified_crates: &HashSet<String>,
) -> Result<Vec<String>> {
// Exclude the tip crate — it's compiled separately via cargo_build after replay.
let tip = self.tip_crate_name();
// Modified crates are tracked by *package* name, which can differ from the bin
// target name (`tip_crate_name()`), e.g. package `browser` with `[[bin]] name = "blitz"`.
let tip = self.tip_package_name();
let crates: HashSet<&String> = modified_crates
.iter()
.filter(|name| **name != tip)
+73 -1
View File
@@ -269,6 +269,8 @@ pub(crate) struct BuildRequest {
pub(crate) using_dioxus_explicitly: bool,
pub(crate) apple_entitlements: Option<PathBuf>,
pub(crate) apple_team_id: Option<String>,
pub(crate) version_code: Option<u32>,
pub(crate) bundle_version: Option<String>,
pub(crate) session_cache_dir: PathBuf,
pub(crate) raw_json_diagnostics: bool,
pub(crate) windows_subsystem: Option<String>,
@@ -905,6 +907,8 @@ impl BuildRequest {
inject_loading_scripts: args.inject_loading_scripts,
apple_entitlements: args.apple_entitlements.clone(),
apple_team_id: args.apple_team_id.clone(),
version_code: args.version_code,
bundle_version: args.bundle_version.clone(),
raw_json_diagnostics: args.raw_json_diagnostics,
windows_subsystem: args.windows_subsystem.clone(),
})
@@ -2581,6 +2585,16 @@ impl BuildRequest {
self.main_target.replace('-', "_")
}
/// The workspace package name of the tip crate (hyphens replaced with underscores).
///
/// This can differ from `tip_crate_name()` when the binary target is named differently
/// than its package (e.g. package `browser` with `[[bin]] name = "blitz"`). Use this for
/// workspace-graph lookups (which are keyed by package name) and `tip_crate_name()` for
/// rustc `--crate-name` keys like `{name}.bin`.
pub(crate) fn tip_package_name(&self) -> String {
self.package().name.replace('-', "_")
}
/// Stderr captured from the linker during the last build. Written by the linker
/// interception in `rustcwrapper` and read back to surface warnings/errors to the user.
fn link_err_file(&self) -> PathBuf {
@@ -2675,6 +2689,64 @@ impl BuildRequest {
.to_string()
}
/// Get the Android `versionCode` for the generated Gradle project.
///
/// Resolution order: `--version-code` / `DX_ANDROID_VERSION_CODE`, then
/// `[android] version_code` in Dioxus.toml, then a value derived from the crate
/// version as `major * 1000000 + minor * 1000 + patch`.
pub(crate) fn android_version_code(&self) -> Result<u32> {
/// The maximum value accepted by the Google Play Store.
const MAX_VERSION_CODE: u32 = 2_100_000_000;
let code = match self.version_code.or(self.config.android.version_code) {
Some(code) => code,
None => {
let version = &self.workspace.krates[self.crate_package].version;
if version.major > 2099 || version.minor > 999 || version.patch > 999 {
bail!(
"Cannot derive an Android versionCode from crate version `{version}`: each component must fit `major <= 2099`, `minor <= 999`, `patch <= 999`. Set an explicit version code via `[android] version_code` in Dioxus.toml or the `--version-code` flag."
);
}
if !version.pre.is_empty() {
tracing::warn!(
"Pre-release identifier of crate version `{version}` is ignored when deriving the Android versionCode"
);
}
let code =
(version.major * 1_000_000 + version.minor * 1_000 + version.patch) as u32;
// Google Play requires versionCode >= 1, so bump 0.0.0 up to 1.
code.max(1)
}
};
if !(1..=MAX_VERSION_CODE).contains(&code) {
bail!(
"Android versionCode must be between 1 and {MAX_VERSION_CODE}, got {code}. See https://developer.android.com/studio/publish/versioning"
);
}
Ok(code)
}
/// The explicitly-configured `CFBundleVersion` for iOS/macOS bundles, if any.
///
/// Resolution order: `--bundle-version` / `DX_BUNDLE_VERSION`, then the platform's
/// `bundle_version` in Dioxus.toml (`[ios]` or `[macos]`).
pub(crate) fn apple_bundle_version_override(&self) -> Option<String> {
self.bundle_version.clone().or_else(|| match self.bundle {
BundleFormat::Ios => self.config.ios.bundle_version.clone(),
BundleFormat::MacOS => self.config.macos.bundle_version.clone(),
_ => None,
})
}
/// The `CFBundleVersion` for iOS/macOS bundles, falling back to the crate version.
pub(crate) fn apple_bundle_version(&self) -> String {
self.apple_bundle_version_override()
.unwrap_or_else(|| self.crate_version())
}
pub(crate) fn bundle_identifier(&self) -> String {
use crate::config::BundlePlatform;
@@ -3095,7 +3167,7 @@ impl BuildRequest {
})
.collect();
let tip = self.tip_crate_name();
let tip = self.tip_package_name();
let Some(tip_nid) = krates.workspace_members().find_map(|m| match m {
krates::Node::Krate { id, krate, .. } if krate.name.replace('-', "_") == tip => {
krates.nid_for_kid(id)
+5 -1
View File
@@ -482,7 +482,11 @@ impl BundleContext<'_> {
);
let version = self.version_string();
let bundle_version = macos_settings.bundle_version.as_deref().unwrap_or(&version);
let bundle_version = self
.build
.apple_bundle_version_override()
.or_else(|| macos_settings.bundle_version.clone())
.unwrap_or_else(|| version.clone());
dict.insert(
"CFBundleShortVersionString".into(),
+18
View File
@@ -162,6 +162,22 @@ pub(crate) struct TargetArgs {
#[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)]
pub(crate) wasm_js_cfg: bool,
/// The Android `versionCode` to stamp into the generated Gradle project.
///
/// Must be between 1 and 2100000000 (the Google Play Store maximum). Overrides
/// `[android] version_code` in Dioxus.toml. If neither is set, the version code is
/// derived from the crate version as `major * 1000000 + minor * 1000 + patch`.
#[clap(long, env = "DX_ANDROID_VERSION_CODE", help_heading = HELP_HEADING)]
pub(crate) version_code: Option<u32>,
/// The bundle version (CFBundleVersion) to stamp into the Info.plist of iOS and macOS bundles.
///
/// This is the machine-readable build number that must increase with every upload to
/// App Store Connect. Overrides `[ios] bundle_version` / `[macos] bundle_version` in
/// Dioxus.toml. Defaults to the crate version.
#[clap(long, env = "DX_BUNDLE_VERSION", help_heading = HELP_HEADING)]
pub(crate) bundle_version: Option<String>,
/// The Windows subsystem to use when building for Windows targets. This can be either `CONSOLE` or `WINDOWS`.
///
/// By default, DX uses `WINDOWS` since it assumes a GUI application, but you can override this behavior with this flag.
@@ -200,6 +216,8 @@ impl Anonymized for TargetArgs {
"debug_symbols": self.debug_symbols,
"keep_names": self.keep_names,
"device": self.device,
"version_code": self.version_code,
"bundle_version": self.bundle_version.is_some(),
"base_path": self.base_path.is_some(),
"cargo_args": self.cargo_args.is_some(),
"rustc_args": self.rustc_args.is_some(),
+18
View File
@@ -326,6 +326,14 @@ pub struct IosConfig {
pub long_description: Option<String>,
// === iOS-specific settings ===
/// The bundle version string (CFBundleVersion).
///
/// This is the machine-readable build number that must increase with every upload
/// to App Store Connect. Can be overridden per-build with `--bundle-version` or the
/// `DX_BUNDLE_VERSION` environment variable. Defaults to the crate version.
#[serde(default)]
pub bundle_version: Option<String>,
/// Minimum iOS deployment target (e.g., "15.0").
#[serde(default)]
pub deployment_target: Option<String>,
@@ -590,6 +598,16 @@ pub struct AndroidConfig {
pub signing: Option<AndroidSigningConfig>,
// === Android-specific settings ===
/// The version code used for `versionCode` in the generated Gradle project.
///
/// Must be between 1 and 2100000000 (the Google Play Store maximum).
///
/// Can be overridden per-build with `--version-code` or the `DX_ANDROID_VERSION_CODE`
/// environment variable. If unset, it is derived from the crate version as
/// `major * 1000000 + minor * 1000 + patch`.
#[serde(default)]
pub version_code: Option<u32>,
/// Minimum SDK version.
#[serde(default)]
pub min_sdk: Option<u32>,
+1 -1
View File
@@ -1187,7 +1187,7 @@ impl AppServer {
/// Uses BFS from the tip crate through its workspace dependencies to find the path.
/// If the changed crate IS the tip crate, returns just `[tip]`.
pub(crate) fn workspace_dep_chain(&self, changed_crate: &str) -> Vec<String> {
let tip_name = self.client.build.main_target.replace('-', "_");
let tip_name = self.client.build.tip_package_name();
// If the changed crate is the tip, no chain needed
if changed_crate == tip_name {
+13
View File
@@ -20,6 +20,10 @@ type CustomEventHandler = Box<
),
>;
/// A function taking a URL and returning whether the webview should navigate to it or open it in
/// the browser. If missing in the config, all URLs will be allowed.
type NavigationHandler = Box<dyn Fn(&str) -> bool + 'static>;
/// The closing behaviour of specific application window.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
@@ -70,6 +74,7 @@ pub struct Config {
pub(crate) disable_dma_buf_on_wayland: bool,
pub(crate) additional_windows_args: Option<String>,
pub(crate) tray_icon_show_window_on_click: bool,
pub(crate) navigation_handler: Option<NavigationHandler>,
#[allow(clippy::type_complexity)]
pub(crate) on_window: Option<Box<dyn FnMut(Arc<Window>, &mut VirtualDom) + 'static>>,
@@ -124,6 +129,7 @@ impl Config {
on_window: None,
additional_windows_args: None,
tray_icon_show_window_on_click: true,
navigation_handler: None,
}
}
@@ -350,6 +356,13 @@ impl Config {
self.tray_icon_show_window_on_click = show;
self
}
/// Set a custom navigation handler for non-dioxus URLs.
/// Return true to allow navigation inside the webview, false to block.
pub fn with_navigation_handler(mut self, f: impl Fn(&str) -> bool + 'static) -> Self {
self.navigation_handler = Some(Box::new(f));
self
}
}
impl Default for Config {
+17 -12
View File
@@ -353,6 +353,7 @@ impl WebviewInstance {
}
};
let navigation_handler = cfg.navigation_handler.take();
let page_loaded = AtomicBool::new(false);
let mut webview = WebViewBuilder::new_with_web_context(&mut web_context)
@@ -367,25 +368,29 @@ impl WebviewInstance {
.with_url("dioxus://index.html/")
.with_ipc_handler(ipc_handler)
.with_navigation_handler(move |var| {
// We don't want to allow any navigation
// We only want to serve the index file and assets
// Serve the index and assets.
if var.starts_with("dioxus://")
|| var.starts_with("http://dioxus.")
|| var.starts_with("https://dioxus.")
{
// After the page has loaded once, don't allow any more navigation
let page_loaded = page_loaded.swap(true, std::sync::atomic::Ordering::SeqCst);
!page_loaded
} else {
if var.starts_with("http://")
|| var.starts_with("https://")
|| var.starts_with("mailto:")
{
_ = webbrowser::open(&var);
}
false
return !page_loaded;
}
}) // prevent all navigations
// External links always open somewhere else. Prevents the webview from navigating
if var.starts_with("http://")
|| var.starts_with("https://")
|| var.starts_with("mailto:")
{
_ = webbrowser::open(&var);
return false;
}
// By default, external links are allowed. This keeps things like iframes working.
// However, users can customize this to allow/disallow domains/routes/patterns.
navigation_handler.as_ref().map(|f| f(&var)).unwrap_or(true)
})
.with_asynchronous_custom_protocol(String::from("dioxus"), request_handler);
// Enable https scheme on android, needed for secure context API, like the geolocation API
+19 -10
View File
@@ -398,7 +398,7 @@ impl FfiBridgeParser {
// Use with_activity which returns Option<R>, wrapping our Result in the Option
let inner_result: Option<Result<Self, String>> = manganis::android::with_activity(|mut env, activity| {
// Find the class
let class_result = env.find_class(#class_name_lit);
let class_result = manganis::android::load_class_from_classloader(env, activity, #class_name_lit);
let class = match class_result {
Ok(c) => c,
Err(e) => return Some(Err(format!("Failed to find class {}: {:?}", #class_name_lit, e))),
@@ -724,10 +724,10 @@ impl FfiBridgeParser {
let class_name_bytes = format!("{}\0", name);
let type_def = quote! {
/// Opaque wrapper around an Objective-C object pointer
/// Opaque wrapper around a retained Objective-C object
/// The actual Swift class is looked up dynamically at runtime after dx links everything
pub struct #name {
inner: *mut manganis::objc2::runtime::AnyObject,
inner: manganis::objc2::rc::Retained<manganis::objc2::runtime::AnyObject>,
}
unsafe impl Send for #name {}
@@ -827,17 +827,26 @@ impl FfiBridgeParser {
let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![class, alloc];
let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![instance, init];
if instance.is_null() {
return Err("Failed to initialize instance");
}
// `alloc` + `init` returns a +1 retained object; `Retained::from_raw`
// takes over that reference and sends `release` on drop.
let inner = manganis::objc2::rc::Retained::from_raw(instance)
.ok_or("Failed to initialize instance")?;
Ok(Self { inner: instance })
Ok(Self { inner })
}
}
/// Create from an existing object pointer
/// Create from an existing object pointer.
///
/// # Safety
///
/// `ptr` must be a valid, non-null pointer to an Objective-C object,
/// and the caller must transfer ownership of one retain count (+1)
/// to the returned wrapper, which sends `release` on drop.
pub unsafe fn from_raw(ptr: *mut manganis::objc2::runtime::AnyObject) -> Self {
Self { inner: ptr }
let inner = manganis::objc2::rc::Retained::from_raw(ptr)
.expect("from_raw called with a null pointer");
Self { inner }
}
}
};
@@ -960,7 +969,7 @@ impl FfiBridgeParser {
// Build the msg_send call
let this_expr = if func.receiver.is_some() {
quote! { this.inner }
quote! { manganis::objc2::rc::Retained::as_ptr(&this.inner) as *mut manganis::objc2::runtime::AnyObject }
} else {
// For static methods, we'd need the class
quote! { class }
+24 -5
View File
@@ -111,15 +111,34 @@ pub fn request_permissions_via_helper(
Ok(())
}
/// Load a Java class from the APK's classloader
/// Load a Java class from the APK's classloader.
///
/// `JNIEnv::find_class` resolves against the *system* classloader on a thread that
/// was attached from native code (`JavaVM::attach_current_thread`), which cannot see
/// classes packaged in the APK, so it raises `ClassNotFoundException`. The activity's
/// own classloader can, so we ask the activity for it and call `loadClass` on it.
///
/// `class_name` may be given in either JNI form (`com/example/Foo`) or binary form
/// (`com.example.Foo`); `loadClass` requires the binary form, so slashes are converted.
pub fn load_class_from_classloader<'env>(
env: &mut JNIEnv<'env>,
activity: &JObject<'_>,
class_name: &str,
) -> Result<JClass<'env>> {
let class_name_jstring = new_string(env, class_name)?;
let class = env.call_static_method(
"java/lang/Class",
"forName",
let binary_name = class_name.replace('/', ".");
let activity_class = env.get_object_class(activity)?;
let loader = env
.call_method(
&activity_class,
"getClassLoader",
"()Ljava/lang/ClassLoader;",
&[],
)?
.l()?;
let class_name_jstring = new_string(env, &binary_name)?;
let class = env.call_method(
&loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::Object(&class_name_jstring)],
)?;
+33 -19
View File
@@ -594,24 +594,31 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> {
.unwrap()
.unchecked_into();
// ── SYNC SECTION — no `.await` past this point ────────────────
// ── HOST-STATE-MUTATING SECTION ───────────────────────────────
//
// Everything below mutates shared JS state (linear memory, the
// indirect function table, the patch's data segment) and the
// jump-table pointer. We must not yield, because:
// Below we grow shared linear memory and the indirect function
// table, then async-instantiate the patch into them and commit
// the new jump table. There IS one `.await` for the instantiate
// (we can't avoid it: Chrome disallows synchronous
// `new WebAssembly.Instance` on the main thread for modules
// larger than 8MB, and patches routinely cross that), but it's
// safe — the original race we fixed wasn't about yielding here:
//
// * `memory.grow` detaches the previous `ArrayBuffer`. The old
// code captured `memory.buffer()` BEFORE the awaits and then
// read its `byteLength` to compute `__memory_base`; if any
// other task ran a `memory.grow` during the awaits (e.g. an
// allocator call from a concurrent render), the snapshot was
// detached, `byteLength` returned 0, and the patch's data
// segment ended up loaded over the host's stack/rodata.
// * Two concurrent `apply_patch` tasks must not interleave
// between memory/table grow and `commit_patch`. Doing all
// side-effecting work in one synchronous turn guarantees that.
// * If the future is cancelled before this point, host state
// is untouched.
// * `memory.grow` and `funcs.grow` each return their PRIOR
// length atomically. Concurrent `apply_patch` tasks therefore
// each get a unique, non-overlapping `memory_base` /
// `table_base`, so two patches can't land on the same region.
// * Host code can't observe the half-instantiated patch: the
// new memory pages are zero, the new table slots are null,
// and the jump table isn't committed until the very end of
// this block, so nothing redirects through the new slots.
// * The original bug — using `memory.buffer().byteLength()`
// captured before the awaits, which returned 0 if the buffer
// had been detached by a concurrent grow — is gone because
// we derive `memory_base` from `memory.grow()`'s return
// value instead.
// * Cancellation between grow and `commit_patch` leaks memory
// pages and table slots, but doesn't corrupt anything.
const PAGE_SIZE: u32 = 64 * 1024;
let patch_pages = (dl_bytes.byte_length() as f64 / PAGE_SIZE as f64).ceil() as u32 + 1;
@@ -646,9 +653,16 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> {
let imports = Object::new();
Reflect::set(&imports, &"env".into(), &env).unwrap();
// Synchronous instantiation of the precompiled module — writes
// the patch's data segment into memory at `memory_base`.
let instance = Instance::new(&module, &imports).unwrap();
// Async instantiation of the precompiled module. We use the
// (Module, imports) form of `WebAssembly.instantiate`, which
// resolves directly to an `Instance` (not `{module, instance}`).
// This is the no-size-limit path; the synchronous
// `new WebAssembly.Instance` constructor is capped at 8MB on
// Chrome's main thread.
let instance: Instance = JsFuture::from(WebAssembly::instantiate_module(&module, &imports))
.await
.unwrap()
.unchecked_into();
let inst_exports: Object = instance.exports();
// Run the patch's relocation thunks and constructors. Order