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::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    /// List of top-level browsing contexts.
47    /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
48    /// and we exit if it ever becomes empty.
49    webviews: HashMap<WebViewId, WebView>,
50
51    /// The order in which the webviews were created.
52    creation_order: Vec<WebViewId>,
53
54    /// The webview that is currently focused.
55    /// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
56    focused_webview_id: Option<WebViewId>,
57
58    /// The current set of open dialogs.
59    dialogs: HashMap<WebViewId, Vec<Dialog>>,
60
61    /// A handle to the Window that Servo is rendering in -- either headed or headless.
62    window: Rc<dyn WindowPortsMethods>,
63
64    /// Gamepad support, which may be `None` if it failed to initialize.
65    gamepad_support: Option<GamepadSupport>,
66
67    /// Whether or not the application interface needs to be updated.
68    need_update: bool,
69
70    /// Whether or not Servo needs to repaint its display. Currently this is global
71    /// because every `WebView` shares a `RenderingContext`.
72    need_repaint: bool,
73
74    /// Whether or not the amount of dialogs on the currently rendered webview
75    /// has just changed.
76    dialog_amount_changed: bool,
77
78    /// List of webviews that have favicon textures which are not yet uploaded
79    /// to the GPU by egui.
80    pending_favicon_loads: Vec<WebViewId>,
81
82    /// A list of showing [`InputMethod`] interfaces.
83    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    /// Repaint the Servo view is necessary, returning true if anything was actually
171    /// painted or false otherwise. Something may not be painted if Servo is waiting
172    /// for a stable image to paint.
173    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    /// Spins the internal application event loop.
189    ///
190    /// - Notifies Servo about incoming gamepad events
191    /// - Spin the Servo event loop, which will run the compositor and trigger delegate methods.
192    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        // Delegate handlers may have asked us to present or update compositor contents.
202        // Currently, egui-file-dialog dialogs need to be constantly redrawn or animations aren't fluid.
203        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 a dialog is open, clear any Servo cursor. TODO: This should restore the
245        // cursor too, when all dialogs close. In general, we need a better cursor
246        // management strategy.
247        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        // This can happen because we can trigger a close with a UI action and then get the
266        // close event from Servo later.
267        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                // For WebDriver, don't shut down when last webview closed
293                // https://github.com/servo/servo/issues/37408
294            },
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    // Returns the webviews in the creation order.
305    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        // Dialogs block the page load, so need need to notify WebDriver
413        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            // TODO: Avoid copying this from the default trait impl?
428            // Return the DOM-specified default value for when we **cannot show simple dialogs**.
429            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    /// Interrupt any ongoing WebDriver-based script evaluation.
454    ///
455    /// From <https://w3c.github.io/webdriver/#dfn-execute-a-function-body>:
456    /// > The rules to execute a function body are as follows. The algorithm returns
457    /// > an ECMAScript completion record.
458    /// >
459    /// > If at any point during the algorithm a user prompt appears, immediately return
460    /// > Completion { Type: normal, Value: null, Target: empty }, but continue to run the
461    /// >  other steps of this algorithm in parallel.
462    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    /// Return a list of all webviews that have favicons that have not yet been loaded by egui.
478    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        // We need to update compositor's view later as we not sure about resizing result.
533        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        // When WebDriver is enabled, do not focus and raise the WebView to the top,
566        // as that is what the specification expects. Otherwise, we would like `window.open()`
567        // to create a new foreground tab
568        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                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
704                // But if the toolbar height changes while the dialog is open then the position won't be updated
705                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                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
710                // But if the toolbar height changes while the dialog is open then the position won't be updated
711                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}