1use std::cell::{Ref, RefCell, RefMut};
6use std::collections::HashMap;
7use std::collections::hash_map::Entry;
8use std::mem;
9use std::rc::Rc;
10
11use crossbeam_channel::Receiver;
12use log::{error, info};
13use servo::base::generic_channel::GenericSender;
14use servo::base::id::WebViewId;
15use servo::config::pref;
16use servo::ipc_channel::ipc::IpcSender;
17use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize};
18use servo::{
19 AllowOrDenyRequest, AuthenticationRequest, Cursor, EmbedderControl, EmbedderControlId,
20 GamepadHapticEffectType, InputEventId, InputEventResult, JSValue, LoadStatus,
21 PermissionRequest, Servo, ServoDelegate, ServoError, SimpleDialog, TraversalId,
22 WebDriverCommandMsg, WebDriverLoadStatus, WebDriverUserPrompt, WebView, WebViewBuilder,
23 WebViewDelegate,
24};
25use url::Url;
26
27use super::app::PumpResult;
28use super::dialog::Dialog;
29use super::gamepad::GamepadSupport;
30use super::window_trait::WindowPortsMethods;
31use crate::prefs::ServoShellPreferences;
32use crate::running_app_state::{RunningAppStateBase, RunningAppStateTrait};
33
34pub(crate) enum AppState {
35 Initializing,
36 Running(Rc<RunningAppState>),
37 ShuttingDown,
38}
39
40pub(crate) struct RunningAppState {
41 base: RunningAppStateBase,
42 inner: RefCell<RunningAppStateInner>,
43}
44
45pub struct RunningAppStateInner {
46 webviews: HashMap<WebViewId, WebView>,
50
51 creation_order: Vec<WebViewId>,
53
54 focused_webview_id: Option<WebViewId>,
57
58 dialogs: HashMap<WebViewId, Vec<Dialog>>,
60
61 window: Rc<dyn WindowPortsMethods>,
63
64 gamepad_support: Option<GamepadSupport>,
66
67 need_update: bool,
69
70 need_repaint: bool,
73
74 dialog_amount_changed: bool,
77
78 pending_favicon_loads: Vec<WebViewId>,
81
82 visible_input_methods: Vec<EmbedderControlId>,
84}
85
86impl Drop for RunningAppState {
87 fn drop(&mut self) {
88 self.servo().deinit();
89 }
90}
91
92impl RunningAppStateTrait for RunningAppState {
93 fn base(&self) -> &RunningAppStateBase {
94 &self.base
95 }
96
97 fn base_mut(&mut self) -> &mut RunningAppStateBase {
98 &mut self.base
99 }
100
101 fn webview_by_id(&self, id: WebViewId) -> Option<WebView> {
102 self.inner().webviews.get(&id).cloned()
103 }
104}
105
106impl RunningAppState {
107 pub fn new(
108 servo: Servo,
109 window: Rc<dyn WindowPortsMethods>,
110 servoshell_preferences: ServoShellPreferences,
111 webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
112 ) -> RunningAppState {
113 servo.set_delegate(Rc::new(ServoShellServoDelegate));
114 let gamepad_support = if pref!(dom_gamepad_enabled) {
115 GamepadSupport::maybe_new()
116 } else {
117 None
118 };
119 RunningAppState {
120 base: RunningAppStateBase::new(servoshell_preferences, servo, webdriver_receiver),
121 inner: RefCell::new(RunningAppStateInner {
122 webviews: HashMap::default(),
123 creation_order: Default::default(),
124 focused_webview_id: None,
125 dialogs: Default::default(),
126 window,
127 gamepad_support,
128 need_update: false,
129 need_repaint: false,
130 dialog_amount_changed: false,
131 pending_favicon_loads: Default::default(),
132 visible_input_methods: Default::default(),
133 }),
134 }
135 }
136
137 pub(crate) fn create_and_focus_toplevel_webview(self: &Rc<Self>, url: Url) {
138 let webview = self.create_toplevel_webview(url);
139 webview.focus_and_raise_to_top(true);
140 }
141
142 pub(crate) fn create_toplevel_webview(self: &Rc<Self>, url: Url) -> WebView {
143 let webview = WebViewBuilder::new(self.servo())
144 .url(url)
145 .hidpi_scale_factor(self.inner().window.hidpi_scale_factor())
146 .delegate(self.clone())
147 .build();
148
149 webview.notify_theme_change(self.inner().window.theme());
150 self.add(webview.clone());
151 webview
152 }
153
154 pub(crate) fn inner(&self) -> Ref<'_, RunningAppStateInner> {
155 self.inner.borrow()
156 }
157
158 pub(crate) fn inner_mut(&self) -> RefMut<'_, RunningAppStateInner> {
159 self.inner.borrow_mut()
160 }
161
162 pub(crate) fn hidpi_scale_factor_changed(&self) {
163 let inner = self.inner();
164 let new_scale_factor = inner.window.hidpi_scale_factor();
165 for webview in inner.webviews.values() {
166 webview.set_hidpi_scale_factor(new_scale_factor);
167 }
168 }
169
170 pub(crate) fn repaint_servo_if_necessary(&self) {
174 if !self.inner().need_repaint {
175 return;
176 }
177 let Some(webview) = self.focused_webview() else {
178 return;
179 };
180
181 webview.paint();
182
183 let mut inner_mut = self.inner_mut();
184 inner_mut.window.rendering_context().present();
185 inner_mut.need_repaint = false;
186 }
187
188 pub(crate) fn pump_event_loop(&self) -> PumpResult {
193 if pref!(dom_gamepad_enabled) {
194 self.handle_gamepad_events();
195 }
196
197 if !self.servo().spin_event_loop() {
198 return PumpResult::Shutdown;
199 }
200
201 let need_window_redraw = self.inner().need_repaint ||
204 self.has_active_dialog() ||
205 self.inner().dialog_amount_changed;
206 let need_update = std::mem::replace(&mut self.inner_mut().need_update, false);
207
208 self.inner_mut().dialog_amount_changed = false;
209
210 if self.servoshell_preferences().exit_after_stable_image &&
211 self.base().achieved_stable_image.get()
212 {
213 self.servo().start_shutting_down();
214 }
215
216 PumpResult::Continue {
217 need_update,
218 need_window_redraw,
219 }
220 }
221
222 pub(crate) fn add(&self, webview: WebView) {
223 self.inner_mut().creation_order.push(webview.id());
224 self.inner_mut().webviews.insert(webview.id(), webview);
225 }
226
227 pub(crate) fn shutdown(&self) {
228 self.inner_mut().webviews.clear();
229 }
230
231 pub(crate) fn for_each_active_dialog(&self, callback: impl Fn(&mut Dialog) -> bool) {
232 let last_created_webview_id = self.inner().creation_order.last().cloned();
233 let Some(webview_id) = self
234 .focused_webview()
235 .as_ref()
236 .map(WebView::id)
237 .or(last_created_webview_id)
238 else {
239 return;
240 };
241
242 let mut inner = self.inner_mut();
243
244 if inner
248 .dialogs
249 .get(&webview_id)
250 .is_some_and(|dialogs| !dialogs.is_empty())
251 {
252 inner.window.set_cursor(Cursor::Default);
253 }
254
255 if let Some(dialogs) = inner.dialogs.get_mut(&webview_id) {
256 let length = dialogs.len();
257 dialogs.retain_mut(callback);
258 if length != dialogs.len() {
259 inner.dialog_amount_changed = true;
260 }
261 }
262 }
263
264 pub fn close_webview(&self, webview_id: WebViewId) {
265 let mut inner = self.inner_mut();
268 if !inner.webviews.contains_key(&webview_id) {
269 return;
270 }
271
272 inner.webviews.retain(|&id, _| id != webview_id);
273 inner.creation_order.retain(|&id| id != webview_id);
274 inner.dialogs.remove(&webview_id);
275 if Some(webview_id) == inner.focused_webview_id {
276 inner.focused_webview_id = None;
277 }
278
279 let last_created = inner
280 .creation_order
281 .last()
282 .and_then(|id| inner.webviews.get(id));
283
284 match last_created {
285 Some(last_created_webview) => {
286 last_created_webview.focus();
287 },
288 None if self.servoshell_preferences().webdriver_port.is_none() => {
289 self.servo().start_shutting_down()
290 },
291 None => {
292 },
295 }
296 }
297
298 pub fn focused_webview(&self) -> Option<WebView> {
299 self.inner()
300 .focused_webview_id
301 .and_then(|id| self.inner().webviews.get(&id).cloned())
302 }
303
304 pub fn webviews(&self) -> Vec<(WebViewId, WebView)> {
306 let inner = self.inner();
307 inner
308 .creation_order
309 .iter()
310 .map(|id| (*id, inner.webviews.get(id).unwrap().clone()))
311 .collect()
312 }
313
314 pub fn handle_gamepad_events(&self) {
315 let Some(active_webview) = self.focused_webview() else {
316 return;
317 };
318 if let Some(gamepad_support) = self.inner_mut().gamepad_support.as_mut() {
319 gamepad_support.handle_gamepad_events(active_webview);
320 }
321 }
322
323 pub(crate) fn focus_webview_by_index(&self, index: usize) {
324 if let Some((_, webview)) = self.webviews().get(index) {
325 webview.focus();
326 }
327 }
328
329 fn add_dialog(&self, webview: servo::WebView, dialog: Dialog) {
330 let mut inner_mut = self.inner_mut();
331 inner_mut
332 .dialogs
333 .entry(webview.id())
334 .or_default()
335 .push(dialog);
336 inner_mut.need_update = true;
337 }
338
339 pub(crate) fn has_active_dialog(&self) -> bool {
340 let last_created_webview_id = self.inner().creation_order.last().cloned();
341 let Some(webview_id) = self
342 .focused_webview()
343 .as_ref()
344 .map(WebView::id)
345 .or(last_created_webview_id)
346 else {
347 return false;
348 };
349
350 let inner = self.inner();
351 inner
352 .dialogs
353 .get(&webview_id)
354 .is_some_and(|dialogs| !dialogs.is_empty())
355 }
356
357 pub(crate) fn webview_has_active_dialog(&self, webview_id: WebViewId) -> bool {
358 self.inner()
359 .dialogs
360 .get(&webview_id)
361 .is_some_and(|dialogs| !dialogs.is_empty())
362 }
363
364 pub(crate) fn get_current_active_dialog_webdriver_type(
365 &self,
366 webview_id: WebViewId,
367 ) -> Option<WebDriverUserPrompt> {
368 let inner = self.inner();
369 let dialogs = inner.dialogs.get(&webview_id)?;
370 dialogs
371 .iter()
372 .rev()
373 .filter_map(|dialog| dialog.webdriver_dialog_type())
374 .nth(0)
375 }
376
377 pub(crate) fn accept_active_dialogs(&self, webview_id: WebViewId) {
378 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview_id) {
379 dialogs.drain(..).for_each(|dialog| {
380 dialog.accept();
381 });
382 }
383 }
384
385 pub(crate) fn dismiss_active_dialogs(&self, webview_id: WebViewId) {
386 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview_id) {
387 dialogs.drain(..).for_each(|dialog| {
388 dialog.dismiss();
389 });
390 }
391 }
392
393 pub(crate) fn alert_text_of_newest_dialog(&self, webview_id: WebViewId) -> Option<String> {
394 self.inner()
395 .dialogs
396 .get(&webview_id)
397 .and_then(|dialogs| dialogs.last())
398 .and_then(|dialog| dialog.message())
399 }
400
401 pub(crate) fn set_alert_text_of_newest_dialog(&self, webview_id: WebViewId, text: String) {
402 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview_id) {
403 if let Some(dialog) = dialogs.last_mut() {
404 dialog.set_message(text);
405 }
406 }
407 }
408
409 fn show_simple_dialog(&self, webview: servo::WebView, dialog: SimpleDialog) {
410 self.interrupt_webdriver_script_evaluation();
411
412 let webview_id = webview.id();
414 if let Some(sender) = self
415 .base()
416 .webdriver_senders
417 .borrow_mut()
418 .load_status_senders
419 .get(&webview_id)
420 {
421 let _ = sender.send(WebDriverLoadStatus::Blocked);
422 };
423
424 if self.servoshell_preferences().headless &&
425 self.servoshell_preferences().webdriver_port.is_none()
426 {
427 let _ = match dialog {
430 SimpleDialog::Alert {
431 response_sender, ..
432 } => response_sender.send(Default::default()),
433 SimpleDialog::Confirm {
434 response_sender, ..
435 } => response_sender.send(Default::default()),
436 SimpleDialog::Prompt {
437 response_sender, ..
438 } => response_sender.send(Default::default()),
439 };
440 return;
441 }
442 let dialog = Dialog::new_simple_dialog(dialog);
443 self.add_dialog(webview, dialog);
444 }
445
446 pub(crate) fn get_focused_webview_index(&self) -> Option<usize> {
447 let focused_id = self.inner().focused_webview_id?;
448 self.webviews()
449 .iter()
450 .position(|webview| webview.0 == focused_id)
451 }
452
453 fn interrupt_webdriver_script_evaluation(&self) {
463 if let Some(sender) = &self
464 .base()
465 .webdriver_senders
466 .borrow()
467 .script_evaluation_interrupt_sender
468 {
469 sender.send(Ok(JSValue::Null)).unwrap_or_else(|err| {
470 info!(
471 "Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"
472 );
473 });
474 }
475 }
476
477 pub(crate) fn take_pending_favicon_loads(&self) -> Vec<WebViewId> {
479 mem::take(&mut self.inner_mut().pending_favicon_loads)
480 }
481}
482
483struct ServoShellServoDelegate;
484impl ServoDelegate for ServoShellServoDelegate {
485 fn notify_devtools_server_started(&self, _servo: &Servo, port: u16, _token: String) {
486 info!("Devtools Server running on port {port}");
487 }
488
489 fn request_devtools_connection(&self, _servo: &Servo, request: AllowOrDenyRequest) {
490 request.allow();
491 }
492
493 fn notify_error(&self, _servo: &Servo, error: ServoError) {
494 error!("Saw Servo error: {error:?}!");
495 }
496}
497
498impl WebViewDelegate for RunningAppState {
499 fn screen_geometry(&self, _webview: WebView) -> Option<servo::ScreenGeometry> {
500 Some(self.inner().window.screen_geometry())
501 }
502
503 fn notify_status_text_changed(&self, _webview: servo::WebView, _status: Option<String>) {
504 self.inner_mut().need_update = true;
505 }
506
507 fn notify_history_changed(&self, _webview: WebView, _entries: Vec<Url>, _current: usize) {
508 self.inner_mut().need_update = true;
509 }
510
511 fn notify_page_title_changed(&self, webview: servo::WebView, title: Option<String>) {
512 if webview.focused() {
513 let window_title = format!("{} - Servo", title.clone().unwrap_or_default());
514 self.inner().window.set_title(&window_title);
515 self.inner_mut().need_update = true;
516 }
517 }
518
519 fn notify_traversal_complete(&self, _webview: servo::WebView, traversal_id: TraversalId) {
520 let mut webdriver_state = self.base().webdriver_senders.borrow_mut();
521 if let Entry::Occupied(entry) = webdriver_state.pending_traversals.entry(traversal_id) {
522 let sender = entry.remove();
523 let _ = sender.send(WebDriverLoadStatus::Complete);
524 }
525 }
526
527 fn request_move_to(&self, _: servo::WebView, new_position: DeviceIntPoint) {
528 self.inner().window.set_position(new_position);
529 }
530
531 fn request_resize_to(&self, webview: servo::WebView, requested_outer_size: DeviceIntSize) {
532 self.inner()
534 .window
535 .request_resize(&webview, requested_outer_size);
536 }
537
538 fn request_authentication(
539 &self,
540 webview: WebView,
541 authentication_request: AuthenticationRequest,
542 ) {
543 if self.servoshell_preferences().headless &&
544 self.servoshell_preferences().webdriver_port.is_none()
545 {
546 return;
547 }
548
549 self.add_dialog(
550 webview,
551 Dialog::new_authentication_dialog(authentication_request),
552 );
553 }
554
555 fn request_open_auxiliary_webview(
556 &self,
557 parent_webview: servo::WebView,
558 ) -> Option<servo::WebView> {
559 let webview = WebViewBuilder::new_auxiliary(self.servo())
560 .hidpi_scale_factor(self.inner().window.hidpi_scale_factor())
561 .delegate(parent_webview.delegate())
562 .build();
563
564 webview.notify_theme_change(self.inner().window.theme());
565 if self.servoshell_preferences().webdriver_port.is_none() {
569 webview.focus_and_raise_to_top(true);
570 }
571 self.add(webview.clone());
572 Some(webview)
573 }
574
575 fn notify_closed(&self, webview: servo::WebView) {
576 self.close_webview(webview.id());
577 }
578
579 fn notify_focus_changed(&self, webview: servo::WebView, focused: bool) {
580 let mut inner_mut = self.inner_mut();
581 if focused {
582 webview.show(true);
583 inner_mut.need_update = true;
584 inner_mut.focused_webview_id = Some(webview.id());
585 } else if inner_mut.focused_webview_id == Some(webview.id()) {
586 inner_mut.focused_webview_id = None;
587 }
588 }
589
590 fn notify_input_event_handled(
591 &self,
592 webview: WebView,
593 id: InputEventId,
594 result: InputEventResult,
595 ) {
596 self.inner()
597 .window
598 .notify_input_event_handled(&webview, id, result);
599
600 if let Some(response_sender) = self
601 .base()
602 .pending_webdriver_events
603 .borrow_mut()
604 .remove(&id)
605 {
606 let _ = response_sender.send(());
607 }
608 }
609
610 fn notify_cursor_changed(&self, _webview: servo::WebView, cursor: servo::Cursor) {
611 self.inner().window.set_cursor(cursor);
612 }
613
614 fn notify_load_status_changed(&self, webview: servo::WebView, status: LoadStatus) {
615 self.inner_mut().need_update = true;
616
617 if status == LoadStatus::Complete {
618 if let Some(sender) = self
619 .base()
620 .webdriver_senders
621 .borrow_mut()
622 .load_status_senders
623 .remove(&webview.id())
624 {
625 let _ = sender.send(WebDriverLoadStatus::Complete);
626 }
627 self.maybe_request_screenshot(webview);
628 }
629 }
630
631 fn notify_fullscreen_state_changed(&self, _webview: servo::WebView, fullscreen_state: bool) {
632 self.inner().window.set_fullscreen(fullscreen_state);
633 }
634
635 fn show_bluetooth_device_dialog(
636 &self,
637 webview: servo::WebView,
638 devices: Vec<String>,
639 response_sender: GenericSender<Option<String>>,
640 ) {
641 self.add_dialog(
642 webview,
643 Dialog::new_device_selection_dialog(devices, response_sender),
644 );
645 }
646
647 fn request_permission(&self, webview: servo::WebView, permission_request: PermissionRequest) {
648 if self.servoshell_preferences().headless &&
649 self.servoshell_preferences().webdriver_port.is_none()
650 {
651 permission_request.deny();
652 return;
653 }
654
655 let permission_dialog = Dialog::new_permission_request_dialog(permission_request);
656 self.add_dialog(webview, permission_dialog);
657 }
658
659 fn notify_new_frame_ready(&self, _webview: servo::WebView) {
660 self.inner_mut().need_repaint = true;
661 }
662
663 fn play_gamepad_haptic_effect(
664 &self,
665 _webview: servo::WebView,
666 index: usize,
667 effect_type: GamepadHapticEffectType,
668 effect_complete_sender: IpcSender<bool>,
669 ) {
670 match self.inner_mut().gamepad_support.as_mut() {
671 Some(gamepad_support) => {
672 gamepad_support.play_haptic_effect(index, effect_type, effect_complete_sender);
673 },
674 None => {
675 let _ = effect_complete_sender.send(false);
676 },
677 }
678 }
679
680 fn stop_gamepad_haptic_effect(
681 &self,
682 _webview: servo::WebView,
683 index: usize,
684 haptic_stop_sender: IpcSender<bool>,
685 ) {
686 let stopped = match self.inner_mut().gamepad_support.as_mut() {
687 Some(gamepad_support) => gamepad_support.stop_haptic_effect(index),
688 None => false,
689 };
690 let _ = haptic_stop_sender.send(stopped);
691 }
692
693 fn show_embedder_control(&self, webview: WebView, embedder_control: EmbedderControl) {
694 if self.servoshell_preferences().headless &&
695 self.servoshell_preferences().webdriver_port.is_none()
696 {
697 return;
698 }
699
700 let control_id = embedder_control.id();
701 match embedder_control {
702 EmbedderControl::SelectElement(prompt) => {
703 let offset = self.inner().window.toolbar_height();
706 self.add_dialog(webview, Dialog::new_select_element_dialog(prompt, offset));
707 },
708 EmbedderControl::ColorPicker(color_picker) => {
709 let offset = self.inner().window.toolbar_height();
712 self.add_dialog(
713 webview,
714 Dialog::new_color_picker_dialog(color_picker, offset),
715 );
716 },
717 EmbedderControl::InputMethod(input_method_control) => {
718 self.inner_mut().visible_input_methods.push(control_id);
719 self.inner().window.show_ime(input_method_control);
720 },
721 EmbedderControl::FilePicker(file_picker) => {
722 self.add_dialog(webview, Dialog::new_file_dialog(file_picker));
723 },
724 EmbedderControl::SimpleDialog(simple_dialog) => {
725 self.show_simple_dialog(webview, simple_dialog);
726 },
727 EmbedderControl::ContextMenu(prompt) => {
728 let offset = self.inner().window.toolbar_height();
729 self.add_dialog(webview, Dialog::new_context_menu(prompt, offset));
730 },
731 }
732 }
733
734 fn hide_embedder_control(&self, webview: WebView, control_id: EmbedderControlId) {
735 {
736 let mut inner_mut = self.inner_mut();
737 if let Some(index) = inner_mut
738 .visible_input_methods
739 .iter()
740 .position(|visible_id| *visible_id == control_id)
741 {
742 inner_mut.visible_input_methods.remove(index);
743 inner_mut.window.hide_ime();
744 }
745 }
746
747 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview.id()) {
748 dialogs.retain(|dialog| dialog.embedder_control_id() != Some(control_id));
749 }
750 }
751
752 fn notify_favicon_changed(&self, webview: WebView) {
753 let mut inner = self.inner_mut();
754 inner.pending_favicon_loads.push(webview.id());
755 inner.need_repaint = true;
756 }
757}