1use std::cell::{Ref, RefCell, RefMut};
6use std::collections::HashMap;
7use std::collections::hash_map::Entry;
8use std::mem;
9use std::path::PathBuf;
10use std::rc::Rc;
11
12use crossbeam_channel::Receiver;
13use embedder_traits::webdriver::WebDriverSenders;
14use euclid::Vector2D;
15use keyboard_types::{Key, Modifiers, NamedKey, ShortcutMatcher};
16use log::{error, info};
17use servo::base::generic_channel::GenericSender;
18use servo::base::id::WebViewId;
19use servo::config::pref;
20use servo::ipc_channel::ipc::IpcSender;
21use servo::webrender_api::ScrollLocation;
22use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize};
23use servo::{
24 AllowOrDenyRequest, AuthenticationRequest, FilterPattern, FocusId, FormControl,
25 GamepadHapticEffectType, JSValue, KeyboardEvent, LoadStatus, PermissionRequest, Servo,
26 ServoDelegate, ServoError, SimpleDialog, TraversalId, WebDriverCommandMsg, WebDriverJSResult,
27 WebDriverLoadStatus, WebDriverUserPrompt, WebView, WebViewBuilder, WebViewDelegate,
28};
29use url::Url;
30
31use super::app::PumpResult;
32use super::dialog::Dialog;
33use super::gamepad::GamepadSupport;
34use super::keyutils::CMD_OR_CONTROL;
35use super::window_trait::{LINE_HEIGHT, LINE_WIDTH, WindowPortsMethods};
36use crate::output_image::save_output_image_if_necessary;
37use crate::prefs::ServoShellPreferences;
38
39pub(crate) enum AppState {
40 Initializing,
41 Running(Rc<RunningAppState>),
42 ShuttingDown,
43}
44
45pub(crate) struct RunningAppState {
46 servo: Servo,
50 servoshell_preferences: ServoShellPreferences,
53 webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
56 webdriver_senders: RefCell<WebDriverSenders>,
57 inner: RefCell<RunningAppStateInner>,
58}
59
60pub struct RunningAppStateInner {
61 webviews: HashMap<WebViewId, WebView>,
65
66 creation_order: Vec<WebViewId>,
68
69 focused_webview_id: Option<WebViewId>,
72
73 dialogs: HashMap<WebViewId, Vec<Dialog>>,
75
76 window: Rc<dyn WindowPortsMethods>,
78
79 gamepad_support: Option<GamepadSupport>,
81
82 need_update: bool,
84
85 need_repaint: bool,
88
89 dialog_amount_changed: bool,
92
93 pending_favicon_loads: Vec<WebViewId>,
96}
97
98impl Drop for RunningAppState {
99 fn drop(&mut self) {
100 self.servo.deinit();
101 }
102}
103
104impl RunningAppState {
105 pub fn new(
106 servo: Servo,
107 window: Rc<dyn WindowPortsMethods>,
108 servoshell_preferences: ServoShellPreferences,
109 webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
110 ) -> RunningAppState {
111 servo.set_delegate(Rc::new(ServoShellServoDelegate));
112 RunningAppState {
113 servo,
114 servoshell_preferences,
115 webdriver_receiver,
116 webdriver_senders: RefCell::default(),
117 inner: RefCell::new(RunningAppStateInner {
118 webviews: HashMap::default(),
119 creation_order: Default::default(),
120 focused_webview_id: None,
121 dialogs: Default::default(),
122 window,
123 gamepad_support: GamepadSupport::maybe_new(),
124 need_update: false,
125 need_repaint: false,
126 dialog_amount_changed: false,
127 pending_favicon_loads: Default::default(),
128 }),
129 }
130 }
131
132 pub(crate) fn create_and_focus_toplevel_webview(self: &Rc<Self>, url: Url) {
133 let webview = self.create_toplevel_webview(url);
134 webview.focus_and_raise_to_top(true);
135 }
136
137 pub(crate) fn create_toplevel_webview(self: &Rc<Self>, url: Url) -> WebView {
138 let webview = WebViewBuilder::new(self.servo())
139 .url(url)
140 .hidpi_scale_factor(self.inner().window.hidpi_scale_factor())
141 .delegate(self.clone())
142 .build();
143
144 webview.notify_theme_change(self.inner().window.theme());
145 self.add(webview.clone());
146 webview
147 }
148
149 pub(crate) fn inner(&self) -> Ref<'_, RunningAppStateInner> {
150 self.inner.borrow()
151 }
152
153 pub(crate) fn inner_mut(&self) -> RefMut<'_, RunningAppStateInner> {
154 self.inner.borrow_mut()
155 }
156
157 pub(crate) fn servo(&self) -> &Servo {
158 &self.servo
159 }
160
161 pub(crate) fn webdriver_receiver(&self) -> Option<&Receiver<WebDriverCommandMsg>> {
162 self.webdriver_receiver.as_ref()
163 }
164
165 pub(crate) fn hidpi_scale_factor_changed(&self) {
166 let inner = self.inner();
167 let new_scale_factor = inner.window.hidpi_scale_factor();
168 for webview in inner.webviews.values() {
169 webview.set_hidpi_scale_factor(new_scale_factor);
170 }
171 }
172
173 pub(crate) fn repaint_servo_if_necessary(&self) {
177 if !self.inner().need_repaint {
178 return;
179 }
180 let Some(webview) = self.focused_webview() else {
181 return;
182 };
183 if !webview.paint() {
184 return;
185 }
186
187 save_output_image_if_necessary(
190 &self.servoshell_preferences,
191 &self.inner().window.rendering_context(),
192 );
193
194 let mut inner_mut = self.inner_mut();
195 inner_mut.window.rendering_context().present();
196 inner_mut.need_repaint = false;
197
198 if self.servoshell_preferences.exit_after_stable_image {
199 self.servo().start_shutting_down();
200 }
201 }
202
203 pub(crate) fn pump_event_loop(&self) -> PumpResult {
208 if pref!(dom_gamepad_enabled) {
209 self.handle_gamepad_events();
210 }
211
212 if !self.servo().spin_event_loop() {
213 return PumpResult::Shutdown;
214 }
215
216 let need_window_redraw = self.inner().need_repaint ||
219 self.has_active_dialog() ||
220 self.inner().dialog_amount_changed;
221 let need_update = std::mem::replace(&mut self.inner_mut().need_update, false);
222
223 self.inner_mut().dialog_amount_changed = false;
224
225 PumpResult::Continue {
226 need_update,
227 need_window_redraw,
228 }
229 }
230
231 pub(crate) fn add(&self, webview: WebView) {
232 self.inner_mut().creation_order.push(webview.id());
233 self.inner_mut().webviews.insert(webview.id(), webview);
234 }
235
236 pub(crate) fn shutdown(&self) {
237 self.inner_mut().webviews.clear();
238 }
239
240 pub(crate) fn for_each_active_dialog(&self, callback: impl Fn(&mut Dialog) -> bool) {
241 let last_created_webview_id = self.inner().creation_order.last().cloned();
242 let Some(webview_id) = self
243 .focused_webview()
244 .as_ref()
245 .map(WebView::id)
246 .or(last_created_webview_id)
247 else {
248 return;
249 };
250
251 let mut inner = self.inner_mut();
252 if let Some(dialogs) = inner.dialogs.get_mut(&webview_id) {
253 let length = dialogs.len();
254 dialogs.retain_mut(callback);
255 if length != dialogs.len() {
256 inner.dialog_amount_changed = true;
257 }
258 }
259 }
260
261 pub fn close_webview(&self, webview_id: WebViewId) {
262 let mut inner = self.inner_mut();
265 if !inner.webviews.contains_key(&webview_id) {
266 return;
267 }
268
269 inner.webviews.retain(|&id, _| id != webview_id);
270 inner.creation_order.retain(|&id| id != webview_id);
271 inner.dialogs.remove(&webview_id);
272 if Some(webview_id) == inner.focused_webview_id {
273 inner.focused_webview_id = None;
274 }
275
276 let last_created = inner
277 .creation_order
278 .last()
279 .and_then(|id| inner.webviews.get(id));
280
281 match last_created {
282 Some(last_created_webview) => {
283 last_created_webview.focus();
284 },
285 None if self.servoshell_preferences.webdriver_port.is_none() => {
286 self.servo.start_shutting_down()
287 },
288 None => {
289 },
292 }
293 }
294
295 pub fn focused_webview(&self) -> Option<WebView> {
296 self.inner()
297 .focused_webview_id
298 .and_then(|id| self.inner().webviews.get(&id).cloned())
299 }
300
301 pub fn webviews(&self) -> Vec<(WebViewId, WebView)> {
303 let inner = self.inner();
304 inner
305 .creation_order
306 .iter()
307 .map(|id| (*id, inner.webviews.get(id).unwrap().clone()))
308 .collect()
309 }
310
311 pub fn webview_by_id(&self, id: WebViewId) -> Option<WebView> {
312 self.inner().webviews.get(&id).cloned()
313 }
314
315 pub fn handle_gamepad_events(&self) {
316 let Some(active_webview) = self.focused_webview() else {
317 return;
318 };
319 if let Some(gamepad_support) = self.inner_mut().gamepad_support.as_mut() {
320 gamepad_support.handle_gamepad_events(active_webview);
321 }
322 }
323
324 pub(crate) fn focus_webview_by_index(&self, index: usize) {
325 if let Some((_, webview)) = self.webviews().get(index) {
326 webview.focus();
327 }
328 }
329
330 fn add_dialog(&self, webview: servo::WebView, dialog: Dialog) {
331 let mut inner_mut = self.inner_mut();
332 inner_mut
333 .dialogs
334 .entry(webview.id())
335 .or_default()
336 .push(dialog);
337 inner_mut.need_update = true;
338 }
339
340 pub(crate) fn has_active_dialog(&self) -> bool {
341 let last_created_webview_id = self.inner().creation_order.last().cloned();
342 let Some(webview_id) = self
343 .focused_webview()
344 .as_ref()
345 .map(WebView::id)
346 .or(last_created_webview_id)
347 else {
348 return false;
349 };
350
351 let inner = self.inner();
352 inner
353 .dialogs
354 .get(&webview_id)
355 .is_some_and(|dialogs| !dialogs.is_empty())
356 }
357
358 pub(crate) fn webview_has_active_dialog(&self, webview_id: WebViewId) -> bool {
359 self.inner()
360 .dialogs
361 .get(&webview_id)
362 .is_some_and(|dialogs| !dialogs.is_empty())
363 }
364
365 pub(crate) fn get_current_active_dialog_webdriver_type(
366 &self,
367 webview_id: WebViewId,
368 ) -> Option<WebDriverUserPrompt> {
369 self.inner()
370 .dialogs
371 .get(&webview_id)
372 .and_then(|dialogs| dialogs.last())
373 .map(|dialog| dialog.webdriver_diaglog_type())
374 }
375
376 pub(crate) fn accept_active_dialogs(&self, webview_id: WebViewId) {
377 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview_id) {
378 dialogs.drain(..).for_each(|dialog| {
379 dialog.accept();
380 });
381 }
382 }
383
384 pub(crate) fn dismiss_active_dialogs(&self, webview_id: WebViewId) {
385 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview_id) {
386 dialogs.drain(..).for_each(|dialog| {
387 dialog.dismiss();
388 });
389 }
390 }
391
392 pub(crate) fn alert_text_of_newest_dialog(&self, webview_id: WebViewId) -> Option<String> {
393 self.inner()
394 .dialogs
395 .get(&webview_id)
396 .and_then(|dialogs| dialogs.last())
397 .and_then(|dialog| dialog.message())
398 }
399
400 pub(crate) fn set_alert_text_of_newest_dialog(&self, webview_id: WebViewId, text: String) {
401 if let Some(dialogs) = self.inner_mut().dialogs.get_mut(&webview_id) {
402 if let Some(dialog) = dialogs.last_mut() {
403 dialog.set_message(text);
404 }
405 }
406 }
407
408 pub(crate) fn get_focused_webview_index(&self) -> Option<usize> {
409 let focused_id = self.inner().focused_webview_id?;
410 self.webviews()
411 .iter()
412 .position(|webview| webview.0 == focused_id)
413 }
414
415 fn handle_overridable_key_bindings(&self, webview: ::servo::WebView, event: KeyboardEvent) {
417 let origin = webview.rect().min.ceil().to_i32();
418 ShortcutMatcher::from_event(event.event)
419 .shortcut(CMD_OR_CONTROL, '=', || {
420 webview.set_zoom(1.1);
421 })
422 .shortcut(CMD_OR_CONTROL, '+', || {
423 webview.set_zoom(1.1);
424 })
425 .shortcut(CMD_OR_CONTROL, '-', || {
426 webview.set_zoom(1.0 / 1.1);
427 })
428 .shortcut(CMD_OR_CONTROL, '0', || {
429 webview.reset_zoom();
430 })
431 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
432 let scroll_location = ScrollLocation::Delta(Vector2D::new(
433 0.0,
434 self.inner().window.page_height() - 2.0 * LINE_HEIGHT,
435 ));
436 webview.notify_scroll_event(scroll_location, origin);
437 })
438 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
439 let scroll_location = ScrollLocation::Delta(Vector2D::new(
440 0.0,
441 -self.inner().window.page_height() + 2.0 * LINE_HEIGHT,
442 ));
443 webview.notify_scroll_event(scroll_location, origin);
444 })
445 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Home), || {
446 webview.notify_scroll_event(ScrollLocation::Start, origin);
447 })
448 .shortcut(Modifiers::empty(), Key::Named(NamedKey::End), || {
449 webview.notify_scroll_event(ScrollLocation::End, origin);
450 })
451 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
452 let location = ScrollLocation::Delta(Vector2D::new(0.0, -LINE_HEIGHT));
453 webview.notify_scroll_event(location, origin);
454 })
455 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
456 let location = ScrollLocation::Delta(Vector2D::new(0.0, LINE_HEIGHT));
457 webview.notify_scroll_event(location, origin);
458 })
459 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
460 let location = ScrollLocation::Delta(Vector2D::new(-LINE_WIDTH, 0.0));
461 webview.notify_scroll_event(location, origin);
462 })
463 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
464 let location = ScrollLocation::Delta(Vector2D::new(LINE_WIDTH, 0.0));
465 webview.notify_scroll_event(location, origin);
466 });
467 }
468
469 pub(crate) fn set_pending_focus(&self, focus_id: FocusId, sender: IpcSender<bool>) {
470 self.webdriver_senders
471 .borrow_mut()
472 .pending_focus
473 .insert(focus_id, sender);
474 }
475
476 pub(crate) fn set_pending_traversal(
477 &self,
478 traversal_id: TraversalId,
479 sender: GenericSender<WebDriverLoadStatus>,
480 ) {
481 self.webdriver_senders
482 .borrow_mut()
483 .pending_traversals
484 .insert(traversal_id, sender);
485 }
486
487 pub(crate) fn set_load_status_sender(
488 &self,
489 webview_id: WebViewId,
490 sender: GenericSender<WebDriverLoadStatus>,
491 ) {
492 self.webdriver_senders
493 .borrow_mut()
494 .load_status_senders
495 .insert(webview_id, sender);
496 }
497
498 pub(crate) fn set_script_command_interrupt_sender(
499 &self,
500 sender: Option<IpcSender<WebDriverJSResult>>,
501 ) {
502 self.webdriver_senders
503 .borrow_mut()
504 .script_evaluation_interrupt_sender = sender;
505 }
506
507 fn interrupt_webdriver_script_evaluation(&self) {
517 if let Some(sender) = &self
518 .webdriver_senders
519 .borrow()
520 .script_evaluation_interrupt_sender
521 {
522 sender.send(Ok(JSValue::Null)).unwrap_or_else(|err| {
523 info!(
524 "Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"
525 );
526 });
527 }
528 }
529
530 pub(crate) fn remove_load_status_sender(&self, webview_id: WebViewId) {
531 self.webdriver_senders
532 .borrow_mut()
533 .load_status_senders
534 .remove(&webview_id);
535 }
536
537 pub(crate) fn take_pending_favicon_loads(&self) -> Vec<WebViewId> {
539 mem::take(&mut self.inner_mut().pending_favicon_loads)
540 }
541}
542
543struct ServoShellServoDelegate;
544impl ServoDelegate for ServoShellServoDelegate {
545 fn notify_devtools_server_started(&self, _servo: &Servo, port: u16, _token: String) {
546 info!("Devtools Server running on port {port}");
547 }
548
549 fn request_devtools_connection(&self, _servo: &Servo, request: AllowOrDenyRequest) {
550 request.allow();
551 }
552
553 fn notify_error(&self, _servo: &Servo, error: ServoError) {
554 error!("Saw Servo error: {error:?}!");
555 }
556}
557
558impl WebViewDelegate for RunningAppState {
559 fn screen_geometry(&self, _webview: WebView) -> Option<servo::ScreenGeometry> {
560 Some(self.inner().window.screen_geometry())
561 }
562
563 fn notify_status_text_changed(&self, _webview: servo::WebView, _status: Option<String>) {
564 self.inner_mut().need_update = true;
565 }
566
567 fn notify_page_title_changed(&self, webview: servo::WebView, title: Option<String>) {
568 if webview.focused() {
569 let window_title = format!("{} - Servo", title.clone().unwrap_or_default());
570 self.inner().window.set_title(&window_title);
571 self.inner_mut().need_update = true;
572 }
573 }
574
575 fn notify_traversal_complete(&self, _webview: servo::WebView, traversal_id: TraversalId) {
576 let mut webdriver_state = self.webdriver_senders.borrow_mut();
577 if let Entry::Occupied(entry) = webdriver_state.pending_traversals.entry(traversal_id) {
578 let sender = entry.remove();
579 let _ = sender.send(WebDriverLoadStatus::Complete);
580 }
581 }
582
583 fn request_move_to(&self, _: servo::WebView, new_position: DeviceIntPoint) {
584 self.inner().window.set_position(new_position);
585 }
586
587 fn request_resize_to(&self, webview: servo::WebView, requested_outer_size: DeviceIntSize) {
588 self.inner()
590 .window
591 .request_resize(&webview, requested_outer_size);
592 }
593
594 fn show_simple_dialog(&self, webview: servo::WebView, dialog: SimpleDialog) {
595 self.interrupt_webdriver_script_evaluation();
596
597 let webview_id = webview.id();
599 if let Some(sender) = self
600 .webdriver_senders
601 .borrow_mut()
602 .load_status_senders
603 .get(&webview_id)
604 {
605 let _ = sender.send(WebDriverLoadStatus::Blocked);
606 };
607
608 if self.servoshell_preferences.headless &&
609 self.servoshell_preferences.webdriver_port.is_none()
610 {
611 let _ = match dialog {
614 SimpleDialog::Alert {
615 response_sender, ..
616 } => response_sender.send(Default::default()),
617 SimpleDialog::Confirm {
618 response_sender, ..
619 } => response_sender.send(Default::default()),
620 SimpleDialog::Prompt {
621 response_sender, ..
622 } => response_sender.send(Default::default()),
623 };
624 return;
625 }
626 let dialog = Dialog::new_simple_dialog(dialog);
627 self.add_dialog(webview, dialog);
628 }
629
630 fn request_authentication(
631 &self,
632 webview: WebView,
633 authentication_request: AuthenticationRequest,
634 ) {
635 if self.servoshell_preferences.headless &&
636 self.servoshell_preferences.webdriver_port.is_none()
637 {
638 return;
639 }
640
641 self.add_dialog(
642 webview,
643 Dialog::new_authentication_dialog(authentication_request),
644 );
645 }
646
647 fn request_open_auxiliary_webview(
648 &self,
649 parent_webview: servo::WebView,
650 ) -> Option<servo::WebView> {
651 let webview = WebViewBuilder::new_auxiliary(&self.servo)
652 .hidpi_scale_factor(self.inner().window.hidpi_scale_factor())
653 .delegate(parent_webview.delegate())
654 .build();
655
656 webview.notify_theme_change(self.inner().window.theme());
657 if self.servoshell_preferences.webdriver_port.is_none() {
661 webview.focus_and_raise_to_top(true);
662 }
663 self.add(webview.clone());
664 Some(webview)
665 }
666
667 fn notify_closed(&self, webview: servo::WebView) {
668 self.close_webview(webview.id());
669 }
670
671 fn notify_focus_complete(&self, webview: servo::WebView, focus_id: FocusId) {
672 let mut webdriver_state = self.webdriver_senders.borrow_mut();
673 if let Entry::Occupied(entry) = webdriver_state.pending_focus.entry(focus_id) {
674 let sender = entry.remove();
675 let _ = sender.send(webview.focused());
676 }
677 }
678
679 fn notify_focus_changed(&self, webview: servo::WebView, focused: bool) {
680 let mut inner_mut = self.inner_mut();
681 if focused {
682 webview.show(true);
683 inner_mut.need_update = true;
684 inner_mut.focused_webview_id = Some(webview.id());
685 } else if inner_mut.focused_webview_id == Some(webview.id()) {
686 inner_mut.focused_webview_id = None;
687 }
688 }
689
690 fn notify_keyboard_event(&self, webview: servo::WebView, keyboard_event: KeyboardEvent) {
691 self.handle_overridable_key_bindings(webview, keyboard_event);
692 }
693
694 fn notify_cursor_changed(&self, _webview: servo::WebView, cursor: servo::Cursor) {
695 self.inner().window.set_cursor(cursor);
696 }
697
698 fn notify_load_status_changed(&self, webview: servo::WebView, status: LoadStatus) {
699 self.inner_mut().need_update = true;
700
701 if status == LoadStatus::Complete {
702 if let Some(sender) = self
703 .webdriver_senders
704 .borrow_mut()
705 .load_status_senders
706 .remove(&webview.id())
707 {
708 let _ = sender.send(WebDriverLoadStatus::Complete);
709 }
710 }
711 }
712
713 fn notify_fullscreen_state_changed(&self, _webview: servo::WebView, fullscreen_state: bool) {
714 self.inner().window.set_fullscreen(fullscreen_state);
715 }
716
717 fn show_bluetooth_device_dialog(
718 &self,
719 webview: servo::WebView,
720 devices: Vec<String>,
721 response_sender: GenericSender<Option<String>>,
722 ) {
723 self.add_dialog(
724 webview,
725 Dialog::new_device_selection_dialog(devices, response_sender),
726 );
727 }
728
729 fn show_file_selection_dialog(
730 &self,
731 webview: servo::WebView,
732 filter_pattern: Vec<FilterPattern>,
733 allow_select_mutiple: bool,
734 response_sender: GenericSender<Option<Vec<PathBuf>>>,
735 ) {
736 let file_dialog =
737 Dialog::new_file_dialog(allow_select_mutiple, response_sender, filter_pattern);
738 self.add_dialog(webview, file_dialog);
739 }
740
741 fn request_permission(&self, webview: servo::WebView, permission_request: PermissionRequest) {
742 if self.servoshell_preferences.headless &&
743 self.servoshell_preferences.webdriver_port.is_none()
744 {
745 permission_request.deny();
746 return;
747 }
748
749 let permission_dialog = Dialog::new_permission_request_dialog(permission_request);
750 self.add_dialog(webview, permission_dialog);
751 }
752
753 fn notify_new_frame_ready(&self, _webview: servo::WebView) {
754 self.inner_mut().need_repaint = true;
755 }
756
757 fn play_gamepad_haptic_effect(
758 &self,
759 _webview: servo::WebView,
760 index: usize,
761 effect_type: GamepadHapticEffectType,
762 effect_complete_sender: IpcSender<bool>,
763 ) {
764 match self.inner_mut().gamepad_support.as_mut() {
765 Some(gamepad_support) => {
766 gamepad_support.play_haptic_effect(index, effect_type, effect_complete_sender);
767 },
768 None => {
769 let _ = effect_complete_sender.send(false);
770 },
771 }
772 }
773
774 fn stop_gamepad_haptic_effect(
775 &self,
776 _webview: servo::WebView,
777 index: usize,
778 haptic_stop_sender: IpcSender<bool>,
779 ) {
780 let stopped = match self.inner_mut().gamepad_support.as_mut() {
781 Some(gamepad_support) => gamepad_support.stop_haptic_effect(index),
782 None => false,
783 };
784 let _ = haptic_stop_sender.send(stopped);
785 }
786 fn show_ime(
787 &self,
788 _webview: WebView,
789 input_type: servo::InputMethodType,
790 text: Option<(String, i32)>,
791 multiline: bool,
792 position: servo::webrender_api::units::DeviceIntRect,
793 ) {
794 self.inner()
795 .window
796 .show_ime(input_type, text, multiline, position);
797 }
798
799 fn hide_ime(&self, _webview: WebView) {
800 self.inner().window.hide_ime();
801 }
802
803 fn show_form_control(&self, webview: WebView, form_control: FormControl) {
804 if self.servoshell_preferences.headless &&
805 self.servoshell_preferences.webdriver_port.is_none()
806 {
807 return;
808 }
809
810 match form_control {
811 FormControl::SelectElement(prompt) => {
812 let offset = self.inner().window.toolbar_height();
815 self.add_dialog(webview, Dialog::new_select_element_dialog(prompt, offset));
816 },
817 FormControl::ColorPicker(color_picker) => {
818 let offset = self.inner().window.toolbar_height();
821 self.add_dialog(
822 webview,
823 Dialog::new_color_picker_dialog(color_picker, offset),
824 );
825 },
826 }
827 }
828
829 fn notify_favicon_changed(&self, webview: WebView) {
830 let mut inner = self.inner_mut();
831 inner.pending_favicon_loads.push(webview.id());
832 inner.need_repaint = true;
833 }
834}