1use std::cell::{Cell, Ref, RefCell};
8use std::collections::HashMap;
9use std::collections::hash_map::Entry;
10use std::rc::Rc;
11
12use crossbeam_channel::{Receiver, Sender, unbounded};
13use euclid::Rect;
14#[cfg(all(
15 feature = "gamepad",
16 not(any(target_os = "android", target_env = "ohos"))
17))]
18use gilrs::Event;
19use image::{DynamicImage, ImageFormat, RgbaImage};
20#[cfg(all(
21 any(coverage, llvm_pgo),
22 any(target_os = "android", target_env = "ohos")
23))]
24use libc::c_char;
25use log::{error, info, warn};
26#[cfg(all(
27 feature = "gamepad",
28 not(any(target_os = "android", target_env = "ohos"))
29))]
30use servo::GamepadIndex;
31use servo::{
32 AllowOrDenyRequest, AuthenticationRequest, BluetoothDeviceSelectionRequest, CSSPixel,
33 ConsoleLogLevel, CreateNewWebViewRequest, DeviceIntPoint, DeviceIntSize, EmbedderControl,
34 EmbedderControlId, EventLoopWaker, GenericSender, InputEvent, InputEventId, InputEventResult,
35 JSValue, LoadStatus, MediaSessionEvent, PermissionRequest, PrefValue, Preferences,
36 ScreenshotCaptureError, Servo, ServoDelegate, ServoError, TraversalId, UserContentManager,
37 WebDriverCommandMsg, WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand,
38 WebDriverSenders, WebView, WebViewDelegate, WebViewId,
39};
40use url::Url;
41
42#[cfg(all(
43 feature = "gamepad",
44 not(any(target_os = "android", target_env = "ohos"))
45))]
46pub(crate) use crate::desktop::gamepad::ServoshellGamepadDelegate;
47use crate::prefs::{EXPERIMENTAL_PREFS, ServoShellPreferences};
48use crate::webdriver::WebDriverEmbedderControls;
49use crate::window::{
50 PlatformWindow, ServoShellWindow, ServoShellWindowId, TopLevelWebViewCreationRequest,
51};
52
53#[cfg(all(
54 any(coverage, llvm_pgo),
55 any(target_os = "android", target_env = "ohos")
56))]
57unsafe extern "C" {
58 fn __llvm_profile_set_filename(file: *const c_char);
59 fn __llvm_profile_write_file();
60}
61
62#[derive(Default)]
63pub struct WebViewCollection {
64 webviews: HashMap<WebViewId, WebView>,
68
69 pub(crate) creation_order: Vec<WebViewId>,
71
72 active_webview_id: Option<WebViewId>,
75
76 has_system_focus: bool,
78}
79
80impl WebViewCollection {
81 pub fn add(&mut self, webview: WebView) {
82 let id = webview.id();
83 self.creation_order.push(id);
84 self.webviews.insert(id, webview);
85 }
86
87 pub fn remove(&mut self, id: WebViewId) -> Option<WebView> {
90 self.creation_order.retain(|&webview_id| webview_id != id);
91 let removed_webview = self.webviews.remove(&id);
92
93 if self.active_webview_id == Some(id) {
94 self.active_webview_id = None;
95 if let Some(newest) = self.creation_order.last() {
96 self.activate_webview(*newest);
97 }
98 }
99
100 removed_webview
101 }
102
103 pub fn get(&self, id: WebViewId) -> Option<&WebView> {
104 self.webviews.get(&id)
105 }
106
107 pub fn active(&self) -> Option<&WebView> {
108 self.active_webview_id.and_then(|id| self.webviews.get(&id))
109 }
110
111 pub fn active_id(&self) -> Option<WebViewId> {
112 self.active_webview_id
113 }
114
115 pub fn newest(&self) -> Option<&WebView> {
117 self.creation_order
118 .last()
119 .and_then(|id| self.webviews.get(id))
120 }
121
122 pub fn all_in_creation_order(&self) -> impl Iterator<Item = (WebViewId, &WebView)> {
123 self.creation_order
124 .iter()
125 .filter_map(move |id| self.webviews.get(id).map(|webview| (*id, webview)))
126 }
127
128 pub fn values(&self) -> impl Iterator<Item = &WebView> {
130 self.webviews.values()
131 }
132
133 pub fn is_empty(&self) -> bool {
135 self.webviews.is_empty()
136 }
137
138 pub(crate) fn activate_webview(&mut self, id_to_activate: WebViewId) {
139 assert!(self.creation_order.contains(&id_to_activate));
140
141 self.active_webview_id = Some(id_to_activate);
142 for (webview_id, webview) in self.all_in_creation_order() {
143 if id_to_activate == webview_id {
144 if self.has_system_focus {
145 webview.set_focused(true);
146 }
147 webview.show();
148 } else {
149 if self.has_system_focus {
150 webview.set_focused(false);
151 }
152 webview.hide();
153 }
154 }
155 }
156
157 pub(crate) fn activate_webview_by_index(&mut self, index: usize) {
158 let Some(webview_id) = self.creation_order.get(index) else {
159 return;
162 };
163 self.activate_webview(*webview_id);
164 }
165
166 pub(crate) fn set_focused(&mut self, focused: bool) {
167 self.has_system_focus = focused;
168 if let Some(active) = self.active() {
169 active.set_focused(focused);
170 }
171 }
172}
173
174#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
176pub(crate) enum UserInterfaceCommand {
177 Go(String),
178 Back,
179 Forward,
180 Reload,
181 ReloadAll,
182 NewWebView,
183 CloseWebView(WebViewId),
184 NewWindow(TopLevelWebViewCreationRequest),
185}
186
187pub(crate) struct RunningAppState {
188 #[cfg(all(
191 feature = "gamepad",
192 not(any(target_os = "android", target_env = "ohos"))
193 ))]
194 gamepad_delegate: Option<Rc<ServoshellGamepadDelegate>>,
195
196 pub(crate) webdriver_senders: RefCell<WebDriverSenders>,
198
199 pub(crate) webdriver_embedder_controls: WebDriverEmbedderControls,
203
204 pub(crate) pending_webdriver_events: RefCell<HashMap<InputEventId, (WebViewId, Sender<()>)>>,
208
209 pub(crate) webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
212
213 pub(crate) servoshell_preferences: ServoShellPreferences,
215
216 pub(crate) servo: Servo,
218
219 pub(crate) achieved_stable_image: Rc<Cell<bool>>,
222
223 pub(crate) user_content_manager: Rc<UserContentManager>,
225
226 exit_scheduled: Cell<bool>,
229
230 experimental_preferences_enabled: Cell<bool>,
232
233 windows: RefCell<Vec<Rc<ServoShellWindow>>>,
240
241 focused_window: RefCell<Option<Rc<ServoShellWindow>>>,
243
244 accessibility_active: Cell<bool>,
250}
251
252impl RunningAppState {
253 pub(crate) fn new(
254 servo: Servo,
255 servoshell_preferences: ServoShellPreferences,
256 event_loop_waker: Box<dyn EventLoopWaker>,
257 user_content_manager: Rc<UserContentManager>,
258 default_preferences: Preferences,
259 #[cfg(all(
260 feature = "gamepad",
261 not(any(target_os = "android", target_env = "ohos"))
262 ))]
263 gamepad_delegate: Option<Rc<ServoshellGamepadDelegate>>,
264 ) -> Self {
265 servo.set_delegate(Rc::new(ServoShellServoDelegate));
266
267 let webdriver_receiver = servoshell_preferences.webdriver_port.get().map(|port| {
268 let (embedder_sender, embedder_receiver) = unbounded();
269 webdriver_server::start_server(
270 port,
271 embedder_sender,
272 event_loop_waker,
273 default_preferences,
274 );
275 embedder_receiver
276 });
277
278 let experimental_preferences_enabled =
279 Cell::new(servoshell_preferences.experimental_preferences_enabled);
280
281 Self {
282 windows: Default::default(),
283 focused_window: Default::default(),
284 #[cfg(all(
285 feature = "gamepad",
286 not(any(target_os = "android", target_env = "ohos"))
287 ))]
288 gamepad_delegate,
289 webdriver_senders: RefCell::default(),
290 webdriver_embedder_controls: Default::default(),
291 pending_webdriver_events: Default::default(),
292 webdriver_receiver,
293 servoshell_preferences,
294 servo,
295 achieved_stable_image: Default::default(),
296 exit_scheduled: Default::default(),
297 user_content_manager,
298 experimental_preferences_enabled,
299 accessibility_active: Cell::new(false),
300 }
301 }
302
303 pub(crate) fn open_window(
304 self: &Rc<Self>,
305 platform_window: Rc<dyn PlatformWindow>,
306 creation_request: TopLevelWebViewCreationRequest,
307 ) -> Rc<ServoShellWindow> {
308 let window = Rc::new(ServoShellWindow::new(platform_window.clone()));
309 self.windows.borrow_mut().push(window.clone());
310 window.create_and_activate_toplevel_webview(self.clone(), creation_request);
311
312 if platform_window.has_platform_focus() {
314 self.set_window_has_focus(window.clone(), true);
315 }
316
317 window
318 }
319
320 pub(crate) fn windows<'a>(&'a self) -> Ref<'a, Vec<Rc<ServoShellWindow>>> {
321 self.windows.borrow()
322 }
323
324 pub(crate) fn focused_window(&self) -> Option<Rc<ServoShellWindow>> {
325 self.focused_window.borrow().clone()
326 }
327
328 pub(crate) fn set_window_has_focus(&self, window: Rc<ServoShellWindow>, has_focus: bool) {
329 let mut focused_window = self.focused_window.borrow_mut();
330
331 if let Some(previously_focused_window) = &*focused_window &&
334 !Rc::ptr_eq(previously_focused_window, &window) &&
335 has_focus
336 {
337 previously_focused_window
338 .webview_collection
339 .borrow_mut()
340 .set_focused(false);
341 }
342
343 window
344 .webview_collection
345 .borrow_mut()
346 .set_focused(has_focus);
347
348 if let Some(previously_focused_window) = &*focused_window &&
349 Rc::ptr_eq(previously_focused_window, &window) &&
350 !has_focus
351 {
352 *focused_window = None;
353 } else if has_focus {
354 *focused_window = Some(window);
355 }
356 }
357
358 #[cfg_attr(target_os = "android", expect(dead_code))]
359 pub(crate) fn window(&self, id: ServoShellWindowId) -> Option<Rc<ServoShellWindow>> {
360 self.windows
361 .borrow()
362 .iter()
363 .find(|window| window.id() == id)
364 .cloned()
365 }
366
367 pub(crate) fn webview_by_id(&self, webview_id: WebViewId) -> Option<WebView> {
368 self.windows()
369 .iter()
370 .find_map(|window| window.webview_by_id(webview_id))
371 }
372
373 pub(crate) fn webdriver_receiver(&self) -> Option<&Receiver<WebDriverCommandMsg>> {
374 self.webdriver_receiver.as_ref()
375 }
376
377 pub(crate) fn servo(&self) -> &Servo {
378 &self.servo
379 }
380
381 #[cfg(all(
382 feature = "gamepad",
383 not(any(target_os = "android", target_env = "ohos"))
384 ))]
385 pub(crate) fn gamepad_delegate(&self) -> Option<Rc<ServoshellGamepadDelegate>> {
386 self.gamepad_delegate.clone()
387 }
388
389 pub(crate) fn schedule_exit(&self) {
390 self.servoshell_preferences.webdriver_port.set(None);
396 self.exit_scheduled.set(true);
397
398 #[cfg(all(
399 any(coverage, llvm_pgo),
400 any(target_os = "android", target_env = "ohos")
401 ))]
402 {
403 use std::ffi::CString;
404
405 use crate::prefs::default_config_dir;
406
407 let mut profile_path = default_config_dir().expect("Need a config dir");
408 profile_path.push("profiles/");
409 let filename = format!(
410 "{}/profile-%h-%p.profraw",
411 profile_path.to_str().expect("Should be unicode")
412 );
413 let c_filename = CString::new(filename).expect("Need a valid cstring");
414 unsafe {
415 __llvm_profile_set_filename(c_filename.as_ptr() as *const c_char);
416 __llvm_profile_write_file()
417 }
418 }
419 }
420
421 #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
422 pub(crate) fn experimental_preferences_enabled(&self) -> bool {
423 self.experimental_preferences_enabled.get()
424 }
425
426 #[cfg_attr(target_env = "ohos", expect(dead_code))]
427 pub(crate) fn set_experimental_preferences_enabled(&self, new_value: bool) {
428 let old_value = self.experimental_preferences_enabled.replace(new_value);
429 if old_value == new_value {
430 return;
431 }
432 for pref in EXPERIMENTAL_PREFS {
433 self.servo.set_preference(pref, PrefValue::Bool(new_value));
434 }
435 }
436
437 fn close_empty_windows(&self) {
439 let mut move_focus = false;
440 self.windows.borrow_mut().retain(|window| {
441 if !self.exit_scheduled.get() && !window.should_close() {
442 return true;
443 }
444
445 if let Some(focused_window) = self.focused_window() &&
446 Rc::ptr_eq(window, &focused_window)
447 {
448 *self.focused_window.borrow_mut() = None;
449 move_focus = !focused_window.platform_window().platform_manages_focus();
450 }
451 false
452 });
453
454 if move_focus &&
457 let Some(newly_focused_window) = self
458 .windows()
459 .iter()
460 .rev()
461 .find(|window| !window.platform_window().platform_manages_focus())
462 .cloned()
463 {
464 self.set_window_has_focus(newly_focused_window, true);
465 }
466 }
467
468 pub(crate) fn spin_event_loop(
476 self: &Rc<Self>,
477 create_platform_window: Option<&dyn Fn(Url) -> Rc<dyn PlatformWindow>>,
478 ) -> bool {
479 self.handle_webdriver_messages(create_platform_window);
480
481 self.servo.spin_event_loop();
482
483 for window in self.windows.borrow().iter() {
484 window.update_and_request_repaint_if_necessary(self);
485 }
486
487 let windows: Vec<_> = self.windows.borrow().iter().cloned().collect();
489 for window in windows {
490 window.handle_interface_commands(self, create_platform_window);
491 }
492
493 if self.servoshell_preferences.exit_after_stable_image && self.achieved_stable_image.get() {
494 self.schedule_exit();
495 }
496
497 self.close_empty_windows();
498
499 if self.servoshell_preferences.webdriver_port.get().is_none() &&
502 self.windows.borrow().is_empty()
503 {
504 self.schedule_exit()
505 }
506
507 !self.exit_scheduled.get()
508 }
509
510 fn maybe_window_for_webview(&self, webview: &WebView) -> Option<Rc<ServoShellWindow>> {
511 let rendering_context = webview.rendering_context();
515 self.windows()
516 .iter()
517 .find(|window| {
518 Rc::ptr_eq(
519 &window.platform_window().rendering_context(),
520 &rendering_context,
521 )
522 })
523 .cloned()
524 }
525
526 pub(crate) fn window_for_webview(&self, webview: &WebView) -> Rc<ServoShellWindow> {
527 self.maybe_window_for_webview(webview)
528 .unwrap_or_else(|| panic!("Looking for unexpected WebView: {:?}", webview.id()))
529 }
530
531 pub(crate) fn platform_window_for_webview(&self, webview: &WebView) -> Rc<dyn PlatformWindow> {
532 self.window_for_webview(webview).platform_window()
533 }
534
535 fn maybe_request_screenshot(&self, webview: WebView) {
538 let output_path = self.servoshell_preferences.output_image_path.clone();
539 if !self.servoshell_preferences.exit_after_stable_image && output_path.is_none() {
540 return;
541 }
542
543 let achieved_stable_image = self.achieved_stable_image.clone();
545 if achieved_stable_image.get() {
546 return;
547 }
548
549 webview.take_screenshot(None, move |image| {
550 achieved_stable_image.set(true);
551
552 let Some(output_path) = output_path else {
553 return;
554 };
555
556 let image = match image {
557 Ok(image) => image,
558 Err(error) => {
559 error!("Could not take screenshot: {error:?}");
560 return;
561 },
562 };
563
564 let image_format = ImageFormat::from_path(&output_path).unwrap_or(ImageFormat::Png);
565 if let Err(error) =
566 DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format)
567 {
568 error!("Failed to save screenshot: {error}.");
569 }
570 });
571 }
572
573 pub(crate) fn set_pending_traversal(
574 &self,
575 traversal_id: TraversalId,
576 sender: GenericSender<WebDriverLoadStatus>,
577 ) {
578 self.webdriver_senders
579 .borrow_mut()
580 .pending_traversals
581 .insert(traversal_id, sender);
582 }
583
584 pub(crate) fn set_load_status_sender(
585 &self,
586 webview_id: WebViewId,
587 sender: GenericSender<WebDriverLoadStatus>,
588 ) {
589 self.webdriver_senders
590 .borrow_mut()
591 .load_status_senders
592 .insert(webview_id, sender);
593 }
594
595 fn remove_load_status_sender(&self, webview_id: WebViewId) {
596 self.webdriver_senders
597 .borrow_mut()
598 .load_status_senders
599 .remove(&webview_id);
600 }
601
602 fn set_script_command_interrupt_sender(
603 &self,
604 sender: Option<GenericSender<WebDriverJSResult>>,
605 ) {
606 self.webdriver_senders
607 .borrow_mut()
608 .script_evaluation_interrupt_sender = sender;
609 }
610
611 pub(crate) fn handle_webdriver_input_event(
612 &self,
613 webview_id: WebViewId,
614 input_event: InputEvent,
615 response_sender: Option<Sender<()>>,
616 ) {
617 if let Some(webview) = self.webview_by_id(webview_id) {
618 let event_id = webview.notify_input_event(input_event);
619 if let Some(response_sender) = response_sender {
620 self.pending_webdriver_events
621 .borrow_mut()
622 .insert(event_id, (webview_id, response_sender));
623 }
624 } else {
625 error!("Could not find WebView ({webview_id:?}) for WebDriver event: {input_event:?}");
626 };
627 }
628
629 pub(crate) fn handle_webdriver_screenshot(
630 &self,
631 webview_id: WebViewId,
632 rect: Option<Rect<f32, CSSPixel>>,
633 result_sender: Sender<Result<RgbaImage, ScreenshotCaptureError>>,
634 ) {
635 if let Some(webview) = self.webview_by_id(webview_id) {
636 let rect = rect.map(|rect| rect.to_box2d().into());
637 webview.take_screenshot(rect, move |result| {
638 if let Err(error) = result_sender.send(result) {
639 warn!("Failed to send response to TakeScreenshot: {error}");
640 }
641 });
642 } else if let Err(error) =
643 result_sender.send(Err(ScreenshotCaptureError::WebViewDoesNotExist))
644 {
645 error!("Failed to send response to TakeScreenshot: {error}");
646 }
647 }
648
649 pub(crate) fn handle_webdriver_script_command(&self, script_command: &WebDriverScriptCommand) {
650 match script_command {
651 WebDriverScriptCommand::ExecuteScriptWithCallback(_webview_id, response_sender) => {
652 self.set_script_command_interrupt_sender(Some(response_sender.clone()));
656 },
657 WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
658 self.set_load_status_sender(*webview_id, load_status_sender.clone());
659 },
660 WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
661 self.remove_load_status_sender(*webview_id);
662 },
663 _ => {
664 self.set_script_command_interrupt_sender(None);
665 },
666 }
667 }
668
669 pub(crate) fn handle_webdriver_load_url(
670 &self,
671 webview_id: WebViewId,
672 url: Url,
673 load_status_sender: GenericSender<WebDriverLoadStatus>,
674 ) {
675 let Some(webview) = self.webview_by_id(webview_id) else {
676 return;
677 };
678
679 self.platform_window_for_webview(&webview)
680 .dismiss_embedder_controls_for_webview(webview_id);
681
682 info!("Loading URL in webview {}: {}", webview_id, url);
683 self.set_load_status_sender(webview_id, load_status_sender);
684 webview.load(url);
685 }
686
687 #[cfg(all(
688 feature = "gamepad",
689 not(any(target_os = "android", target_env = "ohos"))
690 ))]
691 pub(crate) fn handle_gamepad_events(
692 &self,
693 event: Event,
694 gamepad_name: String,
695 gamepad_index: GamepadIndex,
696 ) {
697 let Some(gamepad_delegate) = self.gamepad_delegate.as_ref() else {
698 return;
699 };
700 let Some(active_webview) = self
701 .focused_window()
702 .and_then(|window| window.active_webview())
703 else {
704 return;
705 };
706 gamepad_delegate.handle_gamepad_events(event, gamepad_name, gamepad_index, active_webview);
707 }
708
709 fn interrupt_webdriver_script_evaluation(&self) {
719 if let Some(sender) = &self
720 .webdriver_senders
721 .borrow()
722 .script_evaluation_interrupt_sender
723 {
724 sender.send(Ok(JSValue::Null)).unwrap_or_else(|err| {
725 info!(
726 "Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"
727 );
728 });
729 }
730 }
731
732 #[cfg(not(any(target_os = "android", target_env = "ohos")))]
733 pub(crate) fn set_accessibility_active(&self, active: bool) {
734 let was_active = self.accessibility_active.replace(active);
735 if active == was_active {
736 return;
737 }
738
739 for window in self.windows().iter() {
740 for (_, webview) in window.webviews() {
741 webview.set_accessibility_active(active);
744 }
745 }
746 }
747
748 pub(crate) fn accessibility_active(&self) -> bool {
749 self.accessibility_active.get()
750 }
751}
752
753impl WebViewDelegate for RunningAppState {
754 fn screen_geometry(&self, webview: WebView) -> Option<servo::ScreenGeometry> {
755 Some(self.platform_window_for_webview(&webview).screen_geometry())
756 }
757
758 fn notify_status_text_changed(&self, webview: WebView, _status: Option<String>) {
759 self.window_for_webview(&webview).set_needs_update();
760 }
761
762 fn notify_history_changed(&self, webview: WebView, _entries: Vec<Url>, _current: usize) {
763 self.window_for_webview(&webview).set_needs_update();
764 }
765
766 fn notify_page_title_changed(&self, webview: WebView, _: Option<String>) {
767 self.window_for_webview(&webview).set_needs_update();
768 }
769
770 fn notify_traversal_complete(&self, _webview: WebView, traversal_id: TraversalId) {
771 let mut webdriver_state = self.webdriver_senders.borrow_mut();
772 if let Entry::Occupied(entry) = webdriver_state.pending_traversals.entry(traversal_id) {
773 let sender = entry.remove();
774 let _ = sender.send(WebDriverLoadStatus::Complete);
775 }
776 }
777
778 fn request_move_to(&self, webview: WebView, new_position: DeviceIntPoint) {
779 self.platform_window_for_webview(&webview)
780 .set_position(new_position);
781 }
782
783 fn request_resize_to(&self, webview: WebView, requested_outer_size: DeviceIntSize) {
784 self.platform_window_for_webview(&webview)
785 .request_resize(&webview, requested_outer_size);
786 }
787
788 fn request_authentication(
789 &self,
790 webview: WebView,
791 authentication_request: AuthenticationRequest,
792 ) {
793 self.platform_window_for_webview(&webview)
794 .show_http_authentication_dialog(webview.id(), authentication_request);
795 }
796
797 fn request_create_new(&self, parent_webview: WebView, request: CreateNewWebViewRequest) {
798 let window = self.window_for_webview(&parent_webview);
799
800 if self.servoshell_preferences.webdriver_port.get().is_some() {
804 window.queue_user_interface_command(UserInterfaceCommand::NewWindow(
805 TopLevelWebViewCreationRequest::WithCreateRequest(request),
806 ));
807 return;
808 }
809
810 let platform_window = window.platform_window();
811 let webview = request
812 .builder(platform_window.rendering_context())
813 .hidpi_scale_factor(platform_window.hidpi_scale_factor())
814 .delegate(parent_webview.delegate())
815 .build();
816
817 window.add_webview(webview.clone());
818 window.activate_webview(webview.id());
819 }
820
821 fn notify_closed(&self, webview: WebView) {
822 let webview_id = webview.id();
823 self.pending_webdriver_events
824 .borrow_mut()
825 .retain(|_, (pending_webview_id, _)| *pending_webview_id != webview_id);
826 self.window_for_webview(&webview).close_webview(webview_id)
827 }
828
829 fn notify_input_event_handled(
830 &self,
831 webview: WebView,
832 id: InputEventId,
833 result: InputEventResult,
834 ) {
835 self.platform_window_for_webview(&webview)
836 .notify_input_event_handled(&webview, id, result);
837
838 self.pending_webdriver_events.borrow_mut().remove(&id);
839 }
840
841 fn notify_cursor_changed(&self, webview: WebView, cursor: servo::Cursor) {
842 self.platform_window_for_webview(&webview)
843 .set_cursor(cursor);
844 }
845
846 fn notify_load_status_changed(&self, webview: WebView, status: LoadStatus) {
847 self.window_for_webview(&webview).set_needs_update();
848
849 if status == LoadStatus::Complete {
850 if let Some(sender) = self
851 .webdriver_senders
852 .borrow_mut()
853 .load_status_senders
854 .remove(&webview.id())
855 {
856 let _ = sender.send(WebDriverLoadStatus::Complete);
857 }
858 self.maybe_request_screenshot(webview);
859 }
860 }
861
862 fn notify_fullscreen_state_changed(&self, webview: WebView, fullscreen_state: bool) {
863 self.platform_window_for_webview(&webview)
864 .set_fullscreen(fullscreen_state);
865 }
866
867 fn show_bluetooth_device_dialog(
868 &self,
869 webview: WebView,
870 request: BluetoothDeviceSelectionRequest,
871 ) {
872 self.platform_window_for_webview(&webview)
873 .show_bluetooth_device_dialog(webview.id(), request);
874 }
875
876 fn request_permission(&self, webview: WebView, permission_request: PermissionRequest) {
877 self.platform_window_for_webview(&webview)
878 .show_permission_dialog(webview.id(), permission_request);
879 }
880
881 fn notify_new_frame_ready(&self, webview: WebView) {
882 self.window_for_webview(&webview).set_needs_repaint();
883 }
884
885 fn show_embedder_control(&self, webview: WebView, embedder_control: EmbedderControl) {
886 if self.servoshell_preferences.webdriver_port.get().is_some() {
887 if matches!(&embedder_control, EmbedderControl::SimpleDialog(..)) {
888 self.interrupt_webdriver_script_evaluation();
889
890 if let Some(sender) = self
892 .webdriver_senders
893 .borrow_mut()
894 .load_status_senders
895 .get(&webview.id())
896 {
897 let _ = sender.send(WebDriverLoadStatus::Blocked);
898 };
899 }
900
901 self.webdriver_embedder_controls
902 .show_embedder_control(webview.id(), embedder_control);
903 return;
904 }
905
906 self.window_for_webview(&webview)
907 .show_embedder_control(webview, embedder_control);
908 }
909
910 fn hide_embedder_control(&self, webview: WebView, embedder_control_id: EmbedderControlId) {
911 if self.servoshell_preferences.webdriver_port.get().is_some() {
912 self.webdriver_embedder_controls
913 .hide_embedder_control(webview.id(), embedder_control_id);
914 return;
915 }
916
917 self.window_for_webview(&webview)
918 .hide_embedder_control(webview, embedder_control_id);
919 }
920
921 fn notify_favicon_changed(&self, webview: WebView) {
922 self.window_for_webview(&webview)
923 .notify_favicon_changed(webview);
924 }
925
926 fn notify_media_session_event(&self, webview: WebView, event: MediaSessionEvent) {
927 self.platform_window_for_webview(&webview)
928 .notify_media_session_event(event);
929 }
930
931 fn notify_crashed(&self, webview: WebView, reason: String, backtrace: Option<String>) {
932 self.platform_window_for_webview(&webview)
933 .notify_crashed(webview, reason, backtrace);
934 }
935
936 fn show_console_message(&self, webview: WebView, level: ConsoleLogLevel, message: String) {
937 self.platform_window_for_webview(&webview)
938 .show_console_message(level, &message);
939 }
940
941 fn notify_accessibility_tree_update(
942 &self,
943 webview: WebView,
944 tree_update: accesskit::TreeUpdate,
945 ) {
946 self.platform_window_for_webview(&webview)
947 .notify_accessibility_tree_update(webview, tree_update);
948 }
949}
950
951struct ServoShellServoDelegate;
952impl ServoDelegate for ServoShellServoDelegate {
953 fn notify_devtools_server_started(&self, port: u16, _token: String) {
954 info!("Devtools Server running on port {port}");
955 }
956
957 fn request_devtools_connection(&self, request: AllowOrDenyRequest) {
958 request.allow();
959 }
960
961 fn notify_error(&self, error: ServoError) {
962 error!("Saw Servo error: {error:?}!");
963 }
964
965 fn show_console_message(&self, level: ConsoleLogLevel, message: String) {
966 #[cfg(not(any(target_os = "android", target_env = "ohos")))]
968 println!("{message}");
969 log::log!(level.into(), "{message}");
970 }
971}