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