servoshell/desktop/
app_state.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// A handle to the Servo instance of the [`RunningAppState`]. This is not stored inside
47    /// `inner` so that we can keep a reference to Servo in order to spin the event loop,
48    /// which will in turn call delegates doing a mutable borrow on `inner`.
49    servo: Servo,
50    /// The preferences for this run of servoshell. This is not mutable, so doesn't need to
51    /// be stored inside the [`RunningAppStateInner`].
52    servoshell_preferences: ServoShellPreferences,
53    /// A [`Receiver`] for receiving commands from a running WebDriver server, if WebDriver
54    /// was enabled.
55    webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
56    webdriver_senders: RefCell<WebDriverSenders>,
57    inner: RefCell<RunningAppStateInner>,
58}
59
60pub struct RunningAppStateInner {
61    /// List of top-level browsing contexts.
62    /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
63    /// and we exit if it ever becomes empty.
64    webviews: HashMap<WebViewId, WebView>,
65
66    /// The order in which the webviews were created.
67    creation_order: Vec<WebViewId>,
68
69    /// The webview that is currently focused.
70    /// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
71    focused_webview_id: Option<WebViewId>,
72
73    /// The current set of open dialogs.
74    dialogs: HashMap<WebViewId, Vec<Dialog>>,
75
76    /// A handle to the Window that Servo is rendering in -- either headed or headless.
77    window: Rc<dyn WindowPortsMethods>,
78
79    /// Gamepad support, which may be `None` if it failed to initialize.
80    gamepad_support: Option<GamepadSupport>,
81
82    /// Whether or not the application interface needs to be updated.
83    need_update: bool,
84
85    /// Whether or not Servo needs to repaint its display. Currently this is global
86    /// because every `WebView` shares a `RenderingContext`.
87    need_repaint: bool,
88
89    /// Whether or not the amount of dialogs on the currently rendered webview
90    /// has just changed.
91    dialog_amount_changed: bool,
92
93    /// List of webviews that have favicon textures which are not yet uploaded
94    /// to the GPU by egui.
95    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    /// Repaint the Servo view is necessary, returning true if anything was actually
174    /// painted or false otherwise. Something may not be painted if Servo is waiting
175    /// for a stable image to paint.
176    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        // This needs to be done before presenting(), because `ReneringContext::read_to_image` reads
188        // from the back buffer.
189        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    /// Spins the internal application event loop.
204    ///
205    /// - Notifies Servo about incoming gamepad events
206    /// - Spin the Servo event loop, which will run the compositor and trigger delegate methods.
207    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        // Delegate handlers may have asked us to present or update compositor contents.
217        // Currently, egui-file-dialog dialogs need to be constantly redrawn or animations aren't fluid.
218        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        // This can happen because we can trigger a close with a UI action and then get the
263        // close event from Servo later.
264        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                // For WebDriver, don't shut down when last webview closed
290                // https://github.com/servo/servo/issues/37408
291            },
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    // Returns the webviews in the creation order.
302    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    /// Handle servoshell key bindings that may have been prevented by the page in the focused webview.
416    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    /// Interrupt any ongoing WebDriver-based script evaluation.
508    ///
509    /// From <https://w3c.github.io/webdriver/#dfn-execute-a-function-body>:
510    /// > The rules to execute a function body are as follows. The algorithm returns
511    /// > an ECMAScript completion record.
512    /// >
513    /// > If at any point during the algorithm a user prompt appears, immediately return
514    /// > Completion { Type: normal, Value: null, Target: empty }, but continue to run the
515    /// >  other steps of this algorithm in parallel.
516    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    /// Return a list of all webviews that have favicons that have not yet been loaded by egui.
538    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        // We need to update compositor's view later as we not sure about resizing result.
589        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        // Dialogs block the page load, so need need to notify WebDriver
598        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            // TODO: Avoid copying this from the default trait impl?
612            // Return the DOM-specified default value for when we **cannot show simple dialogs**.
613            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        // When WebDriver is enabled, do not focus and raise the WebView to the top,
658        // as that is what the specification expects. Otherwise, we would like `window.open()`
659        // to create a new foreground tab
660        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                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
813                // But if the toolbar height changes while the dialog is open then the position won't be updated
814                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                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
819                // But if the toolbar height changes while the dialog is open then the position won't be updated
820                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}