mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
Use setTimeout()
trick instead of Window.requestIdleCallback()
(#3044)
This commit is contained in:
parent
68ef9f707e
commit
48abf52aac
|
@ -9,6 +9,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- Fix window size sometimes being invalid when resizing on macOS.
|
- Fix window size sometimes being invalid when resizing on macOS.
|
||||||
|
- On Web, `ControlFlow::Poll` and `ControlFlow::WaitUntil` are now using the Prioritized Task Scheduling API. `setTimeout()` with a trick to circumvent throttling to 4ms is used as a fallback.
|
||||||
|
|
||||||
# 0.29.1-beta
|
# 0.29.1-beta
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,8 @@ redox_syscall = "0.3"
|
||||||
package = "web-sys"
|
package = "web-sys"
|
||||||
version = "0.3.64"
|
version = "0.3.64"
|
||||||
features = [
|
features = [
|
||||||
|
'AbortController',
|
||||||
|
'AbortSignal',
|
||||||
'console',
|
'console',
|
||||||
'CssStyleDeclaration',
|
'CssStyleDeclaration',
|
||||||
'Document',
|
'Document',
|
||||||
|
@ -179,6 +181,8 @@ features = [
|
||||||
'IntersectionObserverEntry',
|
'IntersectionObserverEntry',
|
||||||
'KeyboardEvent',
|
'KeyboardEvent',
|
||||||
'MediaQueryList',
|
'MediaQueryList',
|
||||||
|
'MessageChannel',
|
||||||
|
'MessagePort',
|
||||||
'Node',
|
'Node',
|
||||||
'PageTransitionEvent',
|
'PageTransitionEvent',
|
||||||
'PointerEvent',
|
'PointerEvent',
|
||||||
|
|
|
@ -627,9 +627,11 @@ impl<T: 'static> Shared<T> {
|
||||||
ControlFlow::Poll => {
|
ControlFlow::Poll => {
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
State::Poll {
|
State::Poll {
|
||||||
request: backend::IdleCallback::new(self.window().clone(), move || {
|
request: backend::Schedule::new(
|
||||||
cloned.poll()
|
self.window().clone(),
|
||||||
}),
|
move || cloned.poll(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlFlow::Wait => State::Wait {
|
ControlFlow::Wait => State::Wait {
|
||||||
|
@ -649,10 +651,10 @@ impl<T: 'static> Shared<T> {
|
||||||
State::WaitUntil {
|
State::WaitUntil {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
timeout: backend::Timeout::new(
|
timeout: backend::Schedule::new(
|
||||||
self.window().clone(),
|
self.window().clone(),
|
||||||
move || cloned.resume_time_reached(start, end),
|
move || cloned.resume_time_reached(start, end),
|
||||||
delay,
|
Some(delay),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use web_time::Instant;
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Init,
|
Init,
|
||||||
WaitUntil {
|
WaitUntil {
|
||||||
timeout: backend::Timeout,
|
timeout: backend::Schedule,
|
||||||
start: Instant,
|
start: Instant,
|
||||||
end: Instant,
|
end: Instant,
|
||||||
},
|
},
|
||||||
|
@ -15,7 +15,7 @@ pub enum State {
|
||||||
start: Instant,
|
start: Instant,
|
||||||
},
|
},
|
||||||
Poll {
|
Poll {
|
||||||
request: backend::IdleCallback,
|
request: backend::Schedule,
|
||||||
},
|
},
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,13 @@ mod intersection_handle;
|
||||||
mod media_query_handle;
|
mod media_query_handle;
|
||||||
mod pointer;
|
mod pointer;
|
||||||
mod resize_scaling;
|
mod resize_scaling;
|
||||||
mod timeout;
|
mod schedule;
|
||||||
|
|
||||||
pub use self::canvas::Canvas;
|
pub use self::canvas::Canvas;
|
||||||
pub use self::event::ButtonsState;
|
pub use self::event::ButtonsState;
|
||||||
pub use self::event_handle::EventListenerHandle;
|
pub use self::event_handle::EventListenerHandle;
|
||||||
pub use self::resize_scaling::ResizeScaleHandle;
|
pub use self::resize_scaling::ResizeScaleHandle;
|
||||||
pub use self::timeout::{IdleCallback, Timeout};
|
pub use self::schedule::Schedule;
|
||||||
|
|
||||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
use wasm_bindgen::closure::Closure;
|
use wasm_bindgen::closure::Closure;
|
||||||
|
|
182
src/platform_impl/web/web_sys/schedule.rs
Normal file
182
src/platform_impl/web/web_sys/schedule.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
use js_sys::{Function, Object, Promise, Reflect};
|
||||||
|
use once_cell::unsync::{Lazy, OnceCell};
|
||||||
|
use std::time::Duration;
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
|
use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Schedule(Inner);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Inner {
|
||||||
|
Scheduler {
|
||||||
|
controller: AbortController,
|
||||||
|
_closure: Closure<dyn FnMut()>,
|
||||||
|
},
|
||||||
|
Timeout {
|
||||||
|
window: web_sys::Window,
|
||||||
|
handle: i32,
|
||||||
|
port: MessagePort,
|
||||||
|
_message_closure: Closure<dyn FnMut()>,
|
||||||
|
_timeout_closure: Closure<dyn FnMut()>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schedule {
|
||||||
|
pub fn new<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Schedule
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
if has_scheduler_support(&window) {
|
||||||
|
Self::new_scheduler(window, f, duration)
|
||||||
|
} else {
|
||||||
|
Self::new_timeout(window, f, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_scheduler<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Schedule
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
let window: WindowSupportExt = window.unchecked_into();
|
||||||
|
let scheduler = window.scheduler();
|
||||||
|
|
||||||
|
let closure = Closure::new(f);
|
||||||
|
let mut options = SchedulerPostTaskOptions::new();
|
||||||
|
let controller = AbortController::new().expect("Failed to create `AbortController`");
|
||||||
|
options.signal(&controller.signal());
|
||||||
|
|
||||||
|
if let Some(duration) = duration {
|
||||||
|
options.delay(duration.as_millis() as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static REJECT_HANDLER: Lazy<Closure<dyn FnMut(JsValue)>> = Lazy::new(|| Closure::new(|_| ()));
|
||||||
|
}
|
||||||
|
REJECT_HANDLER.with(|handler| {
|
||||||
|
let _ = scheduler
|
||||||
|
.post_task_with_options(closure.as_ref().unchecked_ref(), &options)
|
||||||
|
.catch(handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schedule(Inner::Scheduler {
|
||||||
|
controller,
|
||||||
|
_closure: closure,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_timeout<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Schedule
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
let channel = MessageChannel::new().unwrap();
|
||||||
|
let message_closure = Closure::new(f);
|
||||||
|
let port_1 = channel.port1();
|
||||||
|
port_1
|
||||||
|
.add_event_listener_with_callback("message", message_closure.as_ref().unchecked_ref())
|
||||||
|
.expect("Failed to set message handler");
|
||||||
|
port_1.start();
|
||||||
|
|
||||||
|
let port_2 = channel.port2();
|
||||||
|
let timeout_closure = Closure::new(move || {
|
||||||
|
port_2
|
||||||
|
.post_message(&JsValue::UNDEFINED)
|
||||||
|
.expect("Failed to send message")
|
||||||
|
});
|
||||||
|
let handle = if let Some(duration) = duration {
|
||||||
|
window.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||||
|
timeout_closure.as_ref().unchecked_ref(),
|
||||||
|
duration.as_millis() as i32,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
window.set_timeout_with_callback(timeout_closure.as_ref().unchecked_ref())
|
||||||
|
}
|
||||||
|
.expect("Failed to set timeout");
|
||||||
|
|
||||||
|
Schedule(Inner::Timeout {
|
||||||
|
window,
|
||||||
|
handle,
|
||||||
|
port: port_1,
|
||||||
|
_message_closure: message_closure,
|
||||||
|
_timeout_closure: timeout_closure,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Schedule {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
match &self.0 {
|
||||||
|
Inner::Scheduler { controller, .. } => controller.abort(),
|
||||||
|
Inner::Timeout {
|
||||||
|
window,
|
||||||
|
handle,
|
||||||
|
port,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
window.clear_timeout_with_handle(*handle);
|
||||||
|
port.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_scheduler_support(window: &web_sys::Window) -> bool {
|
||||||
|
thread_local! {
|
||||||
|
static SCHEDULER_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
SCHEDULER_SUPPORT.with(|support| {
|
||||||
|
*support.get_or_init(|| {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
type SchedulerSupport;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter, js_name = scheduler)]
|
||||||
|
fn has_scheduler(this: &SchedulerSupport) -> JsValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let support: &SchedulerSupport = window.unchecked_ref();
|
||||||
|
|
||||||
|
!support.has_scheduler().is_undefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
type WindowSupportExt;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn scheduler(this: &WindowSupportExt) -> Scheduler;
|
||||||
|
|
||||||
|
type Scheduler;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, js_name = postTask)]
|
||||||
|
fn post_task_with_options(
|
||||||
|
this: &Scheduler,
|
||||||
|
callback: &Function,
|
||||||
|
options: &SchedulerPostTaskOptions,
|
||||||
|
) -> Promise;
|
||||||
|
|
||||||
|
type SchedulerPostTaskOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchedulerPostTaskOptions {
|
||||||
|
fn new() -> Self {
|
||||||
|
Object::new().unchecked_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delay(&mut self, val: f64) -> &mut Self {
|
||||||
|
let r = Reflect::set(self, &JsValue::from("delay"), &val.into());
|
||||||
|
debug_assert!(r.is_ok(), "Failed to set `delay` property");
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signal(&mut self, val: &AbortSignal) -> &mut Self {
|
||||||
|
let r = Reflect::set(self, &JsValue::from("signal"), &val.into());
|
||||||
|
debug_assert!(r.is_ok(), "Failed to set `signal` property");
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,124 +0,0 @@
|
||||||
use once_cell::unsync::OnceCell;
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use wasm_bindgen::closure::Closure;
|
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Timeout {
|
|
||||||
window: web_sys::Window,
|
|
||||||
handle: i32,
|
|
||||||
_closure: Closure<dyn FnMut()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timeout {
|
|
||||||
pub fn new<F>(window: web_sys::Window, f: F, duration: Duration) -> Timeout
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(),
|
|
||||||
{
|
|
||||||
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
|
|
||||||
|
|
||||||
let handle = window
|
|
||||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
|
||||||
closure.as_ref().unchecked_ref(),
|
|
||||||
duration.as_millis() as i32,
|
|
||||||
)
|
|
||||||
.expect("Failed to set timeout");
|
|
||||||
|
|
||||||
Timeout {
|
|
||||||
window,
|
|
||||||
handle,
|
|
||||||
_closure: closure,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Timeout {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.window.clear_timeout_with_handle(self.handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct IdleCallback {
|
|
||||||
window: web_sys::Window,
|
|
||||||
handle: Handle,
|
|
||||||
fired: Rc<Cell<bool>>,
|
|
||||||
_closure: Closure<dyn FnMut()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
enum Handle {
|
|
||||||
IdleCallback(u32),
|
|
||||||
Timeout(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IdleCallback {
|
|
||||||
pub fn new<F>(window: web_sys::Window, mut f: F) -> IdleCallback
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(),
|
|
||||||
{
|
|
||||||
let fired = Rc::new(Cell::new(false));
|
|
||||||
let c_fired = fired.clone();
|
|
||||||
let closure = Closure::wrap(Box::new(move || {
|
|
||||||
(*c_fired).set(true);
|
|
||||||
f();
|
|
||||||
}) as Box<dyn FnMut()>);
|
|
||||||
|
|
||||||
let handle = if has_idle_callback_support(&window) {
|
|
||||||
Handle::IdleCallback(
|
|
||||||
window
|
|
||||||
.request_idle_callback(closure.as_ref().unchecked_ref())
|
|
||||||
.expect("Failed to request idle callback"),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Handle::Timeout(
|
|
||||||
window
|
|
||||||
.set_timeout_with_callback(closure.as_ref().unchecked_ref())
|
|
||||||
.expect("Failed to set timeout"),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
IdleCallback {
|
|
||||||
window,
|
|
||||||
handle,
|
|
||||||
fired,
|
|
||||||
_closure: closure,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for IdleCallback {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if !(*self.fired).get() {
|
|
||||||
match self.handle {
|
|
||||||
Handle::IdleCallback(handle) => self.window.cancel_idle_callback(handle),
|
|
||||||
Handle::Timeout(handle) => self.window.clear_timeout_with_handle(handle),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_idle_callback_support(window: &web_sys::Window) -> bool {
|
|
||||||
thread_local! {
|
|
||||||
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = OnceCell::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDLE_CALLBACK_SUPPORT.with(|support| {
|
|
||||||
*support.get_or_init(|| {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
extern "C" {
|
|
||||||
type IdleCallbackSupport;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter, js_name = requestIdleCallback)]
|
|
||||||
fn has_request_idle_callback(this: &IdleCallbackSupport) -> JsValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let support: &IdleCallbackSupport = window.unchecked_ref();
|
|
||||||
!support.has_request_idle_callback().is_undefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in a new issue