Web: use raw data in DeviceEvent::MouseMotion (#3803)

Only supported on Chrome MacOS and Windows.
This commit is contained in:
daxpedda 2024-07-23 17:05:55 +02:00 committed by GitHub
parent ef580b817d
commit 2e97ab3d4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 190 additions and 29 deletions

View file

@ -8,7 +8,7 @@ use std::rc::Rc;
use web_sys::Element;
use super::super::monitor::MonitorHandle;
use super::super::KeyEventExtra;
use super::super::{lock, KeyEventExtra};
use super::device::DeviceId;
use super::runner::{EventWrapper, WeakShared};
use super::window::WindowId;
@ -652,6 +652,10 @@ impl ActiveEventLoop {
self.runner.wait_until_strategy()
}
pub(crate) fn is_cursor_lock_raw(&self) -> bool {
lock::is_cursor_lock_raw(self.runner.window(), self.runner.document())
}
pub(crate) fn waker(&self) -> Waker<WeakShared> {
self.runner.waker()
}

View file

@ -0,0 +1,82 @@
use std::cell::OnceCell;
use js_sys::{Object, Promise};
use tracing::error;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{console, Document, DomException, Element, Window};
pub(crate) fn is_cursor_lock_raw(window: &Window, document: &Document) -> bool {
thread_local! {
static IS_CURSOR_LOCK_RAW: OnceCell<bool> = const { OnceCell::new() };
}
IS_CURSOR_LOCK_RAW.with(|cell| {
*cell.get_or_init(|| {
// TODO: Remove when Chrome can better advertise that they don't support unaccelerated
// movement on Linux.
// See <https://issues.chromium.org/issues/40833850>.
if super::web_sys::chrome_linux(window) {
return false;
}
let element: ElementExt = document.create_element("div").unwrap().unchecked_into();
let promise = element.request_pointer_lock();
if promise.is_undefined() {
false
} else {
thread_local! {
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
}
let promise: Promise = promise.unchecked_into();
let _ = REJECT_HANDLER.with(|handler| promise.catch(handler));
true
}
})
})
}
pub(crate) fn request_pointer_lock(window: &Window, document: &Document, element: &Element) {
if is_cursor_lock_raw(window, document) {
thread_local! {
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|error: JsValue| {
if let Some(error) = error.dyn_ref::<DomException>() {
error!("Failed to lock pointer. {}: {}", error.name(), error.message());
} else {
console::error_1(&error);
error!("Failed to lock pointer");
}
});
}
let element: &ElementExt = element.unchecked_ref();
let options: PointerLockOptions = Object::new().unchecked_into();
options.set_unadjusted_movement(true);
let _ = REJECT_HANDLER
.with(|handler| element.request_pointer_lock_with_options(&options).catch(handler));
} else {
element.request_pointer_lock();
}
}
#[wasm_bindgen]
extern "C" {
type ElementExt;
#[wasm_bindgen(method, js_name = requestPointerLock)]
fn request_pointer_lock(this: &ElementExt) -> JsValue;
#[wasm_bindgen(method, js_name = requestPointerLock)]
fn request_pointer_lock_with_options(
this: &ElementExt,
options: &PointerLockOptions,
) -> Promise;
type PointerLockOptions;
#[wasm_bindgen(method, setter, js_name = unadjustedMovement)]
fn set_unadjusted_movement(this: &PointerLockOptions, value: bool);
}

View file

@ -26,6 +26,7 @@ mod device;
mod error;
mod event_loop;
mod keyboard;
mod lock;
mod main_thread;
mod monitor;
mod web_sys;

View file

@ -176,15 +176,6 @@ impl Canvas {
})
}
pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> {
if lock {
self.raw().request_pointer_lock();
} else {
self.common.document.exit_pointer_lock();
}
Ok(())
}
pub fn set_attribute(&self, attribute: &str, value: &str) {
self.common
.raw

View file

@ -9,7 +9,7 @@ mod pointer;
mod resize_scaling;
mod schedule;
use std::sync::OnceLock;
use std::cell::OnceCell;
use js_sys::Array;
use wasm_bindgen::closure::Closure;
@ -173,9 +173,24 @@ pub enum Engine {
WebKit,
}
pub fn engine(window: &Window) -> Option<Engine> {
static ENGINE: OnceLock<Option<Engine>> = OnceLock::new();
struct UserAgentData {
engine: Option<Engine>,
chrome_linux: bool,
}
thread_local! {
static USER_AGENT_DATA: OnceCell<UserAgentData> = const { OnceCell::new() };
}
pub fn chrome_linux(window: &Window) -> bool {
USER_AGENT_DATA.with(|data| data.get_or_init(|| user_agent(window)).chrome_linux)
}
pub fn engine(window: &Window) -> Option<Engine> {
USER_AGENT_DATA.with(|data| data.get_or_init(|| user_agent(window)).engine)
}
fn user_agent(window: &Window) -> UserAgentData {
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Navigator)]
@ -189,16 +204,19 @@ pub fn engine(window: &Window) -> Option<Engine> {
#[wasm_bindgen(method, getter)]
fn brands(this: &NavigatorUaData) -> Array;
#[wasm_bindgen(method, getter)]
fn platform(this: &NavigatorUaData) -> String;
type NavigatorUaBrandVersion;
#[wasm_bindgen(method, getter)]
fn brand(this: &NavigatorUaBrandVersion) -> String;
}
*ENGINE.get_or_init(|| {
let navigator: NavigatorExt = window.navigator().unchecked_into();
let navigator: NavigatorExt = window.navigator().unchecked_into();
if let Some(data) = navigator.user_agent_data() {
if let Some(data) = navigator.user_agent_data() {
let engine = 'engine: {
for brand in data
.brands()
.iter()
@ -206,18 +224,28 @@ pub fn engine(window: &Window) -> Option<Engine> {
.map(|brand| brand.brand())
{
match brand.as_str() {
"Chromium" => return Some(Engine::Chromium),
"Chromium" => break 'engine Some(Engine::Chromium),
// TODO: verify when Firefox actually implements it.
"Gecko" => return Some(Engine::Gecko),
"Gecko" => break 'engine Some(Engine::Gecko),
// TODO: verify when Safari actually implements it.
"WebKit" => return Some(Engine::WebKit),
"WebKit" => break 'engine Some(Engine::WebKit),
_ => (),
}
}
None
} else {
let data = navigator.user_agent().ok()?;
};
let chrome_linux = matches!(engine, Some(Engine::Chromium))
.then(|| data.platform() == "Linux")
.unwrap_or(false);
UserAgentData { engine, chrome_linux }
} else {
let engine = 'engine: {
let Ok(data) = navigator.user_agent() else {
break 'engine None;
};
if data.contains("Chrome/") {
Some(Engine::Chromium)
@ -228,6 +256,8 @@ pub fn engine(window: &Window) -> Option<Engine> {
} else {
None
}
}
})
};
UserAgentData { engine, chrome_linux: false }
}
}

View file

@ -8,7 +8,7 @@ use web_sys::HtmlCanvasElement;
use super::main_thread::{MainThreadMarker, MainThreadSafe};
use super::monitor::MonitorHandle;
use super::r#async::Dispatcher;
use super::{backend, ActiveEventLoop, Fullscreen};
use super::{backend, lock, ActiveEventLoop, Fullscreen};
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::icon::Icon;
@ -78,6 +78,12 @@ impl Window {
self.inner.dispatch(move |inner| inner.canvas.prevent_default.set(prevent_default))
}
pub(crate) fn is_cursor_lock_raw(&self) -> bool {
self.inner.queue(move |inner| {
lock::is_cursor_lock_raw(inner.canvas.window(), inner.canvas.document())
})
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
@ -235,15 +241,19 @@ impl Inner {
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let lock = match mode {
CursorGrabMode::None => false,
CursorGrabMode::Locked => true,
match mode {
CursorGrabMode::None => self.canvas.document().exit_pointer_lock(),
CursorGrabMode::Locked => lock::request_pointer_lock(
self.canvas.window(),
self.canvas.document(),
self.canvas.raw(),
),
CursorGrabMode::Confined => {
return Err(ExternalError::NotSupported(NotSupportedError::new()))
},
};
}
self.canvas.set_cursor_lock(lock).map_err(ExternalError::Os)
Ok(())
}
#[inline]