Rewild Your Web
web browser dweb
at main 395 lines 16 kB view raw
1// SPDX-License-Identifier: AGPL-3.0-or-later 2 3//! Manages a single "OS level" window state. 4 5use std::cell::{Cell, RefCell}; 6use std::collections::{BTreeMap, HashMap}; 7use std::hash::Hash; 8use std::rc::Rc; 9 10use euclid::{Point2D, Rect, Scale}; 11use servo::{ 12 DevicePixel, DevicePoint, InputEvent, InputEventId, InputEventResult, KeyboardEvent, 13 MouseButton as ServoMouseButton, MouseButtonAction, MouseButtonEvent, MouseLeftViewportEvent, 14 MouseMoveEvent, RenderingContext, Theme, TouchEvent, TouchEventType, TouchId, WebView, 15 WebViewId, WheelDelta, WheelEvent, WheelMode, WindowRenderingContext, 16}; 17use url::Url; 18use winit::dpi::PhysicalPosition; 19use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; 20use winit::keyboard::ModifiersState; 21use winit::window::Window; 22 23use crate::keyutils::keyboard_event_from_winit; 24use crate::touch_event_simulator::TouchEventSimulator; 25 26/// Commands that the shell can queue for later processing. 27#[derive(Clone, Debug)] 28pub(crate) enum UserInterfaceCommand { 29 /// Open a new browser window. 30 NewWindow(Option<Url>, Option<String>), 31} 32 33/// A unique identifier for a browser window, derived from winit's WindowId. 34#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 35pub(crate) struct BrowserWindowId(u64); 36 37impl From<winit::window::WindowId> for BrowserWindowId { 38 fn from(id: winit::window::WindowId) -> Self { 39 Self(id.into()) 40 } 41} 42 43/// A keyboard event sent to system webview, pending forwarding decision. 44struct PendingKeyboardEvent { 45 /// The original keyboard event for potential forwarding. 46 keyboard_event: KeyboardEvent, 47 /// The focused webview at time of event (target for forwarding). 48 target_webview_id: Option<WebViewId>, 49} 50 51/// Per-window state. Each OS-level window has its own BrowserWindow instance. 52pub(crate) struct BrowserWindow { 53 pub(crate) window: Window, 54 pub(crate) rendering_context: Rc<WindowRenderingContext>, 55 pub(crate) webviews: RefCell<BTreeMap<WebViewId, WebView>>, 56 webview_relative_mouse_point: Cell<Point2D<f32, DevicePixel>>, 57 modifiers_state: Cell<ModifiersState>, 58 /// The WebViewId of the currently focused webview (for keyboard input routing). 59 /// Updated via notify_focus_changed delegate callback. 60 pub(crate) focused_webview_id: Cell<Option<WebViewId>>, 61 /// The WebViewId of the system UI 62 pub(crate) system_webview_id: Cell<Option<WebViewId>>, 63 /// Commands queued for later processing. 64 pending_commands: RefCell<Vec<UserInterfaceCommand>>, 65 /// Keyboard events awaiting callback from system webview to decide on forwarding. 66 pending_keyboard_events: RefCell<HashMap<InputEventId, PendingKeyboardEvent>>, 67 /// Simulates touch events from mouse when mobile simulation is enabled. 68 touch_event_simulator: Option<TouchEventSimulator>, 69} 70 71impl BrowserWindow { 72 pub(crate) fn new( 73 window: Window, 74 rendering_context: Rc<WindowRenderingContext>, 75 simulate_touch: bool, 76 ) -> Self { 77 Self { 78 window, 79 rendering_context, 80 webviews: Default::default(), 81 webview_relative_mouse_point: Cell::new(Point2D::zero()), 82 modifiers_state: Cell::new(ModifiersState::empty()), 83 focused_webview_id: Cell::new(None), 84 system_webview_id: Cell::new(None), 85 pending_commands: Default::default(), 86 pending_keyboard_events: Default::default(), 87 touch_event_simulator: simulate_touch.then(Default::default), 88 } 89 } 90 91 pub(crate) fn id(&self) -> BrowserWindowId { 92 self.window.id().into() 93 } 94 95 /// Get the focused webview, or fall back to the first webview. 96 pub(crate) fn focused_or_first_webview(&self) -> Option<WebView> { 97 let webviews = self.webviews.borrow(); 98 let result = if let Some(focused_id) = self.focused_webview_id.get() { 99 webviews.get(&focused_id) 100 } else { 101 // Fall back to first webview 102 webviews.values().next() 103 }; 104 result.cloned() 105 } 106 107 pub(crate) fn first_webview(&self) -> Option<WebView> { 108 let webviews = self.webviews.borrow(); 109 webviews.values().next().cloned() 110 } 111 112 /// Get the system webview (browserhtml shell UI). 113 pub(crate) fn system_webview(&self) -> Option<WebView> { 114 let system_id = self.system_webview_id.get()?; 115 self.webviews.borrow().get(&system_id).cloned() 116 } 117 118 /// Get a webview by its ID. 119 pub(crate) fn webview(&self, id: WebViewId) -> Option<WebView> { 120 self.webviews.borrow().get(&id).cloned() 121 } 122 123 /// Helper function to handle a click 124 pub(crate) fn handle_mouse_button_event( 125 &self, 126 webview: &WebView, 127 button: MouseButton, 128 action: ElementState, 129 ) { 130 // `point` can be outside viewport, such as at toolbar with negative y-coordinate. 131 let point = self.webview_relative_mouse_point.get(); 132 let webview_rect: Rect<_, _> = webview.size().into(); 133 if !webview_rect.contains(point) { 134 return; 135 } 136 137 // Check for touch simulation first 138 if self 139 .touch_event_simulator 140 .as_ref() 141 .is_some_and(|sim| sim.maybe_consume_mouse_button_event(webview, button, action, point)) 142 { 143 return; 144 } 145 146 let mouse_button = match &button { 147 MouseButton::Left => ServoMouseButton::Left, 148 MouseButton::Right => ServoMouseButton::Right, 149 MouseButton::Middle => ServoMouseButton::Middle, 150 MouseButton::Back => ServoMouseButton::Back, 151 MouseButton::Forward => ServoMouseButton::Forward, 152 MouseButton::Other(value) => ServoMouseButton::Other(*value), 153 }; 154 155 let action = match action { 156 ElementState::Pressed => MouseButtonAction::Down, 157 ElementState::Released => MouseButtonAction::Up, 158 }; 159 160 webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new( 161 action, 162 mouse_button, 163 point.into(), 164 ))); 165 } 166 167 /// Helper function to handle mouse move events. 168 pub(crate) fn handle_mouse_move_event( 169 &self, 170 webview: &WebView, 171 position: PhysicalPosition<f64>, 172 ) { 173 let point = winit_position_to_euclid_point(position).to_f32(); 174 175 let previous_point = self.webview_relative_mouse_point.get(); 176 self.webview_relative_mouse_point.set(point); 177 178 let webview_rect: Rect<_, _> = webview.size().into(); 179 if !webview_rect.contains(point) { 180 if webview_rect.contains(previous_point) { 181 webview.notify_input_event(InputEvent::MouseLeftViewport( 182 MouseLeftViewportEvent::default(), 183 )); 184 } 185 return; 186 } 187 188 // Check for touch simulation first 189 if self 190 .touch_event_simulator 191 .as_ref() 192 .is_some_and(|sim| sim.maybe_consume_mouse_move_event(webview, point)) 193 { 194 return; 195 } 196 197 webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point.into()))); 198 } 199 200 /// Queue a command for later processing. 201 pub(crate) fn queue_command(&self, command: UserInterfaceCommand) { 202 self.pending_commands.borrow_mut().push(command); 203 } 204 205 /// Take all pending commands. 206 pub(crate) fn take_pending_commands(&self) -> Vec<UserInterfaceCommand> { 207 self.pending_commands.borrow_mut().drain(..).collect() 208 } 209 210 /// Handle a keyboard event result callback. If the event was sent to the system 211 /// webview for interception, this will forward it to the target webview if not prevented. 212 pub(crate) fn handle_keyboard_event_result( 213 &self, 214 event_id: InputEventId, 215 result: InputEventResult, 216 ) { 217 let pending = self.pending_keyboard_events.borrow_mut().remove(&event_id); 218 let Some(pending_event) = pending else { 219 return; 220 }; 221 222 // If system webview prevented or consumed the event, don't forward 223 if result.intersects(InputEventResult::DefaultPrevented | InputEventResult::Consumed) { 224 return; 225 } 226 227 // Forward to target webview if it exists and differs from system 228 let Some(target_id) = pending_event.target_webview_id else { 229 return; 230 }; 231 232 if Some(target_id) == self.system_webview_id.get() { 233 return; 234 } 235 236 if let Some(target_webview) = self.webview(target_id) { 237 target_webview.notify_input_event(InputEvent::Keyboard(pending_event.keyboard_event)); 238 } 239 } 240 241 pub(crate) fn contains_webview(&self, id: WebViewId) -> bool { 242 self.webviews.borrow().contains_key(&id) 243 } 244 245 pub(crate) fn window_event(&self, event: WindowEvent) { 246 match event { 247 WindowEvent::ScaleFactorChanged { scale_factor, .. } => { 248 let scale = Scale::new(scale_factor as _); 249 for webview in self.webviews.borrow().values() { 250 webview.set_hidpi_scale_factor(scale); 251 } 252 }, 253 WindowEvent::RedrawRequested => { 254 if let Some(webview) = self.first_webview() { 255 webview.paint(); 256 self.rendering_context.present(); 257 } 258 }, 259 WindowEvent::MouseInput { state, button, .. } => { 260 // Send mouse events to the system webview (browserhtml shell) first. 261 // The shell's DOM hit testing will determine if events should be 262 // forwarded to embedded webviews. 263 if let Some(webview) = self.system_webview().or_else(|| self.first_webview()) { 264 self.handle_mouse_button_event(&webview, button, state); 265 } 266 }, 267 WindowEvent::CursorMoved { position, .. } => { 268 // Send mouse events to the system webview (browserhtml shell) first. 269 // The shell's DOM hit testing will determine if events should be 270 // forwarded to embedded webviews. 271 if let Some(webview) = self.system_webview().or_else(|| self.first_webview()) { 272 self.handle_mouse_move_event(&webview, position); 273 } 274 }, 275 WindowEvent::CursorLeft { .. } => { 276 if let Some(webview) = self.system_webview().or_else(|| self.first_webview()) { 277 let webview_rect: Rect<_, _> = webview.size().into(); 278 if webview_rect.contains(self.webview_relative_mouse_point.get()) { 279 webview.notify_input_event(InputEvent::MouseLeftViewport( 280 MouseLeftViewportEvent::default(), 281 )); 282 } 283 } 284 }, 285 WindowEvent::MouseWheel { delta, .. } => { 286 // Send wheel events to the system webview (browserhtml shell) first. 287 // The shell's DOM hit testing will determine if events should be 288 // forwarded to embedded webviews. 289 if let Some(webview) = self.system_webview().or_else(|| self.first_webview()) { 290 let (delta_x, delta_y, mode) = match delta { 291 MouseScrollDelta::LineDelta(dx, dy) => ( 292 (dx * 76.0) as f64, 293 (dy * 76.0) as f64, 294 WheelMode::DeltaPixel, 295 ), 296 MouseScrollDelta::PixelDelta(delta) => { 297 (delta.x * 6.0, delta.y * 6.0, WheelMode::DeltaPixel) 298 }, 299 }; 300 301 let point = self.webview_relative_mouse_point.get(); 302 webview.notify_input_event(InputEvent::Wheel(WheelEvent::new( 303 WheelDelta { 304 x: delta_x, 305 y: delta_y, 306 z: 0.0, 307 mode, 308 }, 309 point.into(), 310 ))); 311 } 312 }, 313 WindowEvent::Touch(touch) => { 314 // Send touch events to the system webview (browserhtml shell) first. 315 // The shell's DOM hit testing will determine if events should be 316 // forwarded to embedded webviews. 317 if let Some(webview) = self.system_webview().or_else(|| self.first_webview()) { 318 webview.notify_input_event(InputEvent::Touch(TouchEvent::new( 319 winit_phase_to_touch_event_type(touch.phase), 320 TouchId(touch.id as i32), 321 DevicePoint::new(touch.location.x as f32, touch.location.y as f32).into(), 322 ))); 323 } 324 }, 325 WindowEvent::PinchGesture { delta, .. } => { 326 if let Some(webview) = self.first_webview() { 327 webview.pinch_zoom(delta as f32 + 1.0, self.webview_relative_mouse_point.get()); 328 } 329 }, 330 WindowEvent::ThemeChanged(theme) => { 331 for webview in self.webviews.borrow().values() { 332 webview.notify_theme_change(match theme { 333 winit::window::Theme::Light => Theme::Light, 334 winit::window::Theme::Dark => Theme::Dark, 335 }); 336 } 337 }, 338 WindowEvent::Resized(new_size) => { 339 if let Some(webview) = self.first_webview() { 340 webview.resize(new_size); 341 } 342 }, 343 WindowEvent::ModifiersChanged(modifiers) => { 344 self.modifiers_state.set(modifiers.state()); 345 }, 346 WindowEvent::KeyboardInput { event, .. } => { 347 let keyboard_event = keyboard_event_from_winit(&event, self.modifiers_state.get()); 348 349 let focused_webview_id = self.focused_webview_id.get(); 350 let system_webview_id = self.system_webview_id.get(); 351 352 // Check if we need parent-first forwarding flow 353 let needs_forwarding = match (focused_webview_id, system_webview_id) { 354 (Some(focused), Some(system)) => focused != system, 355 _ => false, 356 }; 357 358 if needs_forwarding { 359 // Send to system webview first, store for potential forwarding 360 if let Some(system_webview) = self.system_webview() { 361 let event_id = system_webview 362 .notify_input_event(InputEvent::Keyboard(keyboard_event.clone())); 363 364 self.pending_keyboard_events.borrow_mut().insert( 365 event_id, 366 PendingKeyboardEvent { 367 keyboard_event, 368 target_webview_id: focused_webview_id, 369 }, 370 ); 371 } 372 } else { 373 // No forwarding needed - send directly to focused/first webview 374 if let Some(webview) = self.focused_or_first_webview() { 375 webview.notify_input_event(InputEvent::Keyboard(keyboard_event)); 376 } 377 } 378 }, 379 _ => (), 380 } 381 } 382} 383 384fn winit_position_to_euclid_point<T>(position: PhysicalPosition<T>) -> Point2D<T, DevicePixel> { 385 Point2D::new(position.x, position.y) 386} 387 388fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType { 389 match phase { 390 TouchPhase::Started => TouchEventType::Down, 391 TouchPhase::Moved => TouchEventType::Move, 392 TouchPhase::Ended => TouchEventType::Up, 393 TouchPhase::Cancelled => TouchEventType::Cancel, 394 } 395}