Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
run: cargo test --target=x86_64-unknown-linux-gnu

- name: Install wasm-pack
run: curl https://drager.github.io/wasm-pack/installer/init.sh -sSf | bash
run: curl https://wasm-bindgen.github.io/wasm-pack/installer/init.sh -sSf | bash

- name: Test on Node
run: wasm-pack test --node -- ${{ matrix.unstable-flags }}
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ features = [
wasm-bindgen-test = "0.3.58"
tokio = { version = "^1", features = ["macros", "rt"] }
pin-project = "^1"
gloo-timers = { version = "^0.3.0", features = ["futures"] }
gloo-timers = { version = "^0.4.0", features = ["futures"] }

[dev-dependencies.web-sys]
version = "^0.3.85"
Expand All @@ -70,3 +70,7 @@ features = [
[package.metadata.docs.rs]
# https://blog.rust-lang.org/2020/03/15/docs-rs-opt-into-fewer-targets.html
targets = ["x86_64-unknown-linux-gnu"]

# Pending gloo PR https://github.com/rustwasm/gloo/pull/562 with unwind safety
[patch.crates-io]
gloo-timers = { git = "https://github.com/guybedford/gloo", branch = "fix-gloo-timers-unwind-safety" }
12 changes: 11 additions & 1 deletion src/readable/into_underlying_byte_source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::panic::AssertUnwindSafe;
use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
use std::pin::Pin;
use std::rc::Rc;

Expand All @@ -15,12 +15,22 @@

#[wasm_bindgen]
pub(crate) struct IntoUnderlyingByteSource {
inner: Rc<RefCell<Inner>>,

Check warning on line 18 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

fields `inner`, `default_buffer_len`, and `controller` are never read

Check warning on line 18 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

fields `inner`, `default_buffer_len`, and `controller` are never read

Check warning on line 18 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

fields `inner`, `default_buffer_len`, and `controller` are never read
default_buffer_len: usize,
controller: Option<sys::ReadableByteStreamController>,
pull_handle: Option<AbortHandle>,
}

// SAFETY: `Inner` holds an `Option<Pin<Box<dyn AsyncRead>>>` and uses the
// take-and-replace pattern across every fallible await point in `Inner::pull`.
// On panic, the inner async_read is already taken out of the `Option`, leaving
// the cell in a clean `None` state. The `Rc<RefCell<Inner>>` interior mutability
// therefore cannot expose torn invariants to a subsequent call after a caught
// panic, satisfying the logical unwind-safety contract enforced by
// `#[wasm_bindgen]` exports under `panic = "unwind"`.
impl UnwindSafe for IntoUnderlyingByteSource {}
impl RefUnwindSafe for IntoUnderlyingByteSource {}

impl IntoUnderlyingByteSource {
pub fn new(async_read: Box<dyn AsyncRead>, default_buffer_len: usize) -> Self {
IntoUnderlyingByteSource {
Expand All @@ -36,7 +46,7 @@
#[wasm_bindgen]
impl IntoUnderlyingByteSource {
#[wasm_bindgen(getter, js_name = type)]
pub fn type_(&self) -> sys::ReadableStreamType {

Check warning on line 49 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

methods `type_`, `auto_allocate_chunk_size`, `start`, `pull`, and `cancel` are never used

Check warning on line 49 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

methods `type_`, `auto_allocate_chunk_size`, `start`, `pull`, and `cancel` are never used

Check warning on line 49 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

methods `type_`, `auto_allocate_chunk_size`, `start`, `pull`, and `cancel` are never used
sys::ReadableStreamType::Bytes
}

Expand Down Expand Up @@ -87,7 +97,7 @@
}

struct Inner {
async_read: Option<Pin<Box<dyn AsyncRead>>>,

Check warning on line 100 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

fields `async_read` and `buffer` are never read

Check warning on line 100 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

fields `async_read` and `buffer` are never read

Check warning on line 100 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

fields `async_read` and `buffer` are never read
buffer: Vec<u8>,
}

Expand All @@ -99,7 +109,7 @@
}
}

async fn pull(

Check warning on line 112 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

method `pull` is never used

Check warning on line 112 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

method `pull` is never used

Check warning on line 112 in src/readable/into_underlying_byte_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

method `pull` is never used
&mut self,
controller: sys::ReadableByteStreamController,
) -> Result<JsValue, JsValue> {
Expand Down
11 changes: 10 additions & 1 deletion src/readable/into_underlying_source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::panic::AssertUnwindSafe;
use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
use std::pin::Pin;
use std::rc::Rc;

Expand All @@ -15,10 +15,19 @@

#[wasm_bindgen]
pub(crate) struct IntoUnderlyingSource {
inner: Rc<RefCell<Inner>>,

Check warning on line 18 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

field `inner` is never read

Check warning on line 18 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

field `inner` is never read

Check warning on line 18 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

field `inner` is never read
pull_handle: Option<AbortHandle>,
}

// SAFETY: `Inner` holds an `Option<Pin<Box<JsValueStream>>>` and uses the
// take-and-replace pattern around every fallible await in `Inner::pull`. On
// panic, the stream is already taken out of the `Option`, leaving the cell in
// a clean `None` state. Subsequent calls fail cleanly via `unwrap_throw`
// rather than observing a torn intermediate state, which upholds the logical
// unwind-safety contract `#[wasm_bindgen]` enforces for exported types.
impl UnwindSafe for IntoUnderlyingSource {}
impl RefUnwindSafe for IntoUnderlyingSource {}

impl IntoUnderlyingSource {
pub fn new(stream: Box<JsValueStream>) -> Self {
IntoUnderlyingSource {
Expand All @@ -31,7 +40,7 @@
#[allow(clippy::await_holding_refcell_ref)]
#[wasm_bindgen]
impl IntoUnderlyingSource {
pub fn pull(&mut self, controller: sys::ReadableStreamDefaultController) -> Promise {

Check warning on line 43 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

methods `pull` and `cancel` are never used

Check warning on line 43 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

methods `pull` and `cancel` are never used

Check warning on line 43 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

methods `pull` and `cancel` are never used
let inner = self.inner.clone();
let fut = async move {
// This mutable borrow can never panic, since the ReadableStream always queues
Expand Down Expand Up @@ -69,7 +78,7 @@
}

struct Inner {
stream: Option<Pin<Box<JsValueStream>>>,

Check warning on line 81 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

field `stream` is never read

Check warning on line 81 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

field `stream` is never read

Check warning on line 81 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

field `stream` is never read
}

impl Inner {
Expand All @@ -79,7 +88,7 @@
}
}

async fn pull(

Check warning on line 91 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on stable

method `pull` is never used

Check warning on line 91 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly-2026-01-28 with -Cpanic=unwind

method `pull` is never used

Check warning on line 91 in src/readable/into_underlying_source.rs

View workflow job for this annotation

GitHub Actions / Test on nightly

method `pull` is never used
&mut self,
controller: sys::ReadableStreamDefaultController,
) -> Result<JsValue, JsValue> {
Expand Down
12 changes: 11 additions & 1 deletion src/writable/into_underlying_sink.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::panic::AssertUnwindSafe;
use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
use std::pin::Pin;
use std::rc::Rc;

Expand All @@ -13,6 +13,16 @@ pub(crate) struct IntoUnderlyingSink {
inner: Rc<RefCell<Inner>>,
}

// SAFETY: `Inner` holds an `Option<Pin<Box<dyn Sink<...>>>>` and uses the
// take-and-replace pattern around every fallible await in `Inner::write` /
// `Inner::close` / `Inner::abort`. On panic the sink is already taken out of
// the `Option`, leaving the cell in a clean `None` state. The
// `Rc<RefCell<Inner>>` interior mutability therefore cannot expose torn
// invariants after a caught panic, upholding the logical unwind-safety
// contract enforced by `#[wasm_bindgen]` exports under `panic = "unwind"`.
impl UnwindSafe for IntoUnderlyingSink {}
impl RefUnwindSafe for IntoUnderlyingSink {}

impl IntoUnderlyingSink {
pub fn new(sink: Box<dyn Sink<JsValue, Error = JsValue>>) -> Self {
IntoUnderlyingSink {
Expand Down
2 changes: 1 addition & 1 deletion tests/tests/fetch_as_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use web_sys::{Response, Window};
#[wasm_bindgen_test]
async fn test_fetch_as_stream() {
// Make a fetch request
let url = "https://drager.github.io/wasm-pack/public/img/wasm-ferris.png";
let url = "https://wasm-bindgen.github.io/wasm-pack/public/img/wasm-ferris.png";
let window = global().unchecked_into::<Window>(); // hack to also support Node.js
let resp_value = JsFuture::from(window.fetch_with_str(url))
.await
Expand Down
14 changes: 9 additions & 5 deletions tests/util/unhandled_error_guard.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::cell::RefCell;
use std::panic::AssertUnwindSafe;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
Expand All @@ -16,12 +17,15 @@ impl UnhandledErrorGuard {
// Add a listener that collects any errors
let errors = Rc::new(RefCell::new(vec![]));
let listener = {
let errors = errors.clone();
let errors = AssertUnwindSafe(errors.clone());
Closure::<dyn FnMut(_)>::new(move |event: JsValue| {
if let Some(event) = event.dyn_ref::<ErrorEvent>() {
errors.borrow_mut().push(event.error());
} else if let Some(event) = event.dyn_ref::<PromiseRejectionEvent>() {
errors.borrow_mut().push(event.reason());
let err = if let Some(event) = event.dyn_ref::<ErrorEvent>() {
Some(event.error())
} else {
event.dyn_ref::<PromiseRejectionEvent>().map(|e| e.reason())
};
if let Some(err) = err {
errors.borrow_mut().push(err);
}
})
};
Expand Down
Loading