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