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