Repository: iced-rs/iced (language: rust)

Issue:
Using `time::every` displays an empty page on WASM 
### Is your issue REALLY a bug?

- [X] My issue is indeed a bug!
- [X] I am not crazy! I will not fill out this form just to ask a question or request a feature. Pinky promise.

### Is there an existing issue for this?

- [X] I have searched the existing issues.

### Is this issue related to iced?

- [X] My hardware is compatible and my graphics drivers are up-to-date.

### What happened?

Running a program that has a subscription that runs in intervals with time::every in WASM displays a blank page
![image](https://github.com/user-attachments/assets/5313ed98-af95-4f87-a30d-7b8587476264)
Commenting out the subscription, or returning Subscription::none() 
![image](https://github.com/user-attachments/assets/941cec8b-4a59-403c-a0b3-0db5f514a0d6)

## SSCCE

`src/main.rs` 
```rust
use iced::{widget::text, Element, Renderer, Theme};

fn main() -> iced::Result {
    iced::application("Subscription Bug", App::update, App::view)
        .subscription(App::subscription)
        .run()
}

#[derive(Debug)]
pub enum Message {
    Increment
}

#[derive(Default)]
pub struct App {
    counter: usize
}

impl App {
    fn subscription(&self) -> iced::Subscription<Message> {
        iced::time::every(std::time::Duration::from_millis(1000)).map(|_| Message::Increment)
    }
    
    fn update(&mut self, event: Message) {
        match event {
            Message::Increment => {
                self.counter += 1;
            }
        }
    }
    
    fn view(&self) -> Element<Message, Theme, Renderer> {
        text(format!("Counter: {}", self.counter)).size(50).into()
    }
}
```

`index.html`
```
<!DOCTYPE html>
<html lang="en">
<head>  
    <meta charset="utf-8" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <base data-trunk-public-url />
</head>
<body>
    <link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" />
</body>
</html>
```

Ran with: `trunk serve`

### What is the expected behavior?

The page should display the counter and increment it every second

### Version

crates.io release

### Operating System

Windows

### Do you have any log output?

```shell
DevTools console (not sure if relevant):
Uncaught TypeError: Failed to resolve module specifier "env". Relative references must start with either "/", "./", or "../".
```



Discussion / hints:
Possibly related to #352 
I have investigated a bit and it seems this is caused by the `instant` crate trying to pull in something that it can't. It's a transitive dependency of `wasm_timer`, which is used in `iced-futures`. This problem has been known for quite some time, but neither wasm_timer nor instant have been updated in the last 4 years. There is a fork of wasm_time called `wasmtimer` that also hasn't received an update in a year, but seems to have solved the problem.
Considering the `instant` crate has by now been marked as "unmaintained" by RUSTSEC (https://rustsec.org/advisories/RUSTSEC-2024-0384.html), replacing it in some form or another seems like something that probably should be looked into.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index c71d6d8df0..f38c1efa14 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2535,7 +2535,7 @@ dependencies = [
  "smol",
  "tokio",
  "wasm-bindgen-futures",
- "wasm-timer",
+ "wasmtimer",
 ]
 
 [[package]]
@@ -4112,17 +4112,6 @@ version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
 
-[[package]]
-name = "parking_lot"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
-dependencies = [
- "instant",
- "lock_api",
- "parking_lot_core 0.8.6",
-]
-
 [[package]]
 name = "parking_lot"
 version = "0.12.3"
@@ -4130,21 +4119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
 dependencies = [
  "lock_api",
- "parking_lot_core 0.9.10",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
-dependencies = [
- "cfg-if",
- "instant",
- "libc",
- "redox_syscall 0.2.16",
- "smallvec",
- "winapi",
+ "parking_lot_core",
 ]
 
 [[package]]
@@ -4665,15 +4640,6 @@ dependencies = [
  "font-types",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
-dependencies = [
- "bitflags 1.3.2",
-]
-
 [[package]]
 name = "redox_syscall"
 version = "0.4.1"
@@ -5813,7 +5779,7 @@ dependencies = [
  "serde_json",
  "tracing-subscriber",
  "uuid",
- "wasm-timer",
+ "wasmtimer",
  "web-sys",
 ]
 
@@ -6459,18 +6425,17 @@ dependencies = [
 ]
 
 [[package]]
-name = "wasm-timer"
-version = "0.2.5"
+name = "wasmtimer"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
+checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23"
 dependencies = [
  "futures",
  "js-sys",
- "parking_lot 0.11.2",
+ "parking_lot",
  "pin-utils",
+ "slab",
  "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
 ]
 
 [[package]]
@@ -6657,7 +6622,7 @@ dependencies = [
  "js-sys",
  "log",
  "naga",
- "parking_lot 0.12.3",
+ "parking_lot",
  "profiling",
  "raw-window-handle 0.6.2",
  "smallvec",
@@ -6685,7 +6650,7 @@ dependencies = [
  "log",
  "naga",
  "once_cell",
- "parking_lot 0.12.3",
+ "parking_lot",
  "profiling",
  "raw-window-handle 0.6.2",
  "rustc-hash 1.1.0",
@@ -6725,7 +6690,7 @@ dependencies = [
  "ndk-sys 0.5.0+25.2.9519653",
  "objc",
  "once_cell",
- "parking_lot 0.12.3",
+ "parking_lot",
  "profiling",
  "range-alloc",
  "raw-window-handle 0.6.2",
diff --git a/Cargo.toml b/Cargo.toml
index d365e14696..4d6531e18d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -184,7 +184,7 @@ tracing = "0.1"
 unicode-segmentation = "1.0"
 url = "2.5"
 wasm-bindgen-futures = "0.4"
-wasm-timer = "0.2"
+wasmtimer = "0.4.1"
 web-sys = "0.3.69"
 web-time = "1.1"
 wgpu = "23.0"
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index 5d42a88d27..2b49e4e94c 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -24,7 +24,7 @@ iced.features = ["debug", "webgl", "fira-sans"]
 
 uuid = { version = "1.0", features = ["js"] }
 web-sys = { workspace = true, features = ["Window", "Storage"] }
-wasm-timer.workspace = true
+wasmtimer.workspace = true
 
 [dev-dependencies]
 iced_test.workspace = true
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 7faf742e30..74c4c46493 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -578,7 +578,8 @@ impl SavedState {
             .set_item("state", &json)
             .map_err(|_| SaveError::Write)?;
 
-        let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await;
+        let _ =
+            wasmtimer::tokio::sleep(std::time::Duration::from_secs(2)).await;
 
         Ok(())
     }
diff --git a/futures/Cargo.toml b/futures/Cargo.toml
index a6fcfde13b..3984ce833d 100644
--- a/futures/Cargo.toml
+++ b/futures/Cargo.toml
@@ -41,4 +41,4 @@ tokio.features = ["rt", "rt-multi-thread", "time"]
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 wasm-bindgen-futures.workspace = true
-wasm-timer.workspace = true
+wasmtimer.workspace = true
diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs
index f7846c0129..4811e7f412 100644
--- a/futures/src/backend/wasm/wasm_bindgen.rs
+++ b/futures/src/backend/wasm/wasm_bindgen.rs
@@ -16,41 +16,30 @@ impl crate::Executor for Executor {
 
 pub mod time {
     //! Listen and react to time.
-    use crate::subscription::{self, Hasher, Subscription};
-    use crate::BoxStream;
+    use crate::subscription::Subscription;
+
+    use wasmtimer::std::Instant;
 
     /// Returns a [`Subscription`] that produces messages at a set interval.
     ///
     /// The first message is produced after a `duration`, and then continues to
     /// produce more messages every `duration` after that.
-    pub fn every(
-        duration: std::time::Duration,
-    ) -> Subscription<wasm_timer::Instant> {
-        subscription::from_recipe(Every(duration))
-    }
-
-    #[derive(Debug)]
-    struct Every(std::time::Duration);
-
-    impl subscription::Recipe for Every {
-        type Output = wasm_timer::Instant;
-
-        fn hash(&self, state: &mut Hasher) {
-            use std::hash::Hash;
+    pub fn every(duration: std::time::Duration) -> Subscription<Instant> {
+        Subscription::run_with(duration, |duration| {
+            use futures::stream::StreamExt;
 
-            std::any::TypeId::of::<Self>().hash(state);
-            self.0.hash(state);
-        }
+            let mut interval = wasmtimer::tokio::interval(*duration);
+            interval.set_missed_tick_behavior(
+                wasmtimer::tokio::MissedTickBehavior::Skip,
+            );
 
-        fn stream(
-            self: Box<Self>,
-            _input: subscription::EventStream,
-        ) -> BoxStream<Self::Output> {
-            use futures::stream::StreamExt;
+            let stream = {
+                futures::stream::unfold(interval, |mut interval| async move {
+                    Some((interval.tick().await, interval))
+                })
+            };
 
-            wasm_timer::Interval::new(self.0)
-                .map(|_| wasm_timer::Instant::now())
-                .boxed_local()
-        }
+            stream.boxed()
+        })
     }
 }


Test patch: