Skip to main content

servoshell/
running_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
5//! Shared state and methods for desktop and EGL implementations.
6
7use std::cell::{Cell, Ref, RefCell};
8use std::collections::HashMap;
9use std::collections::hash_map::Entry;
10use std::rc::Rc;
11
12use crossbeam_channel::{Receiver, Sender, unbounded};
13use euclid::Rect;
14#[cfg(all(
15    feature = "gamepad",
16    not(any(target_os = "android", target_env = "ohos"))
17))]
18use gilrs::Event;
19use image::{DynamicImage, ImageFormat, RgbaImage};
20#[cfg(all(
21    any(coverage, llvm_pgo),
22    any(target_os = "android", target_env = "ohos")
23))]
24use libc::c_char;
25use log::{error, info, warn};
26#[cfg(all(
27    feature = "gamepad",
28    not(any(target_os = "android", target_env = "ohos"))
29))]
30use servo::GamepadIndex;
31use servo::{
32    AllowOrDenyRequest, AuthenticationRequest, BluetoothDeviceSelectionRequest, CSSPixel,
33    ConsoleLogLevel, CreateNewWebViewRequest, DeviceIntPoint, DeviceIntSize, EmbedderControl,
34    EmbedderControlId, EventLoopWaker, GenericSender, InputEvent, InputEventId, InputEventResult,
35    JSValue, LoadStatus, MediaSessionEvent, PermissionRequest, PrefValue, Preferences,
36    ScreenshotCaptureError, Servo, ServoDelegate, ServoError, TraversalId, UserContentManager,
37    WebDriverCommandMsg, WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand,
38    WebDriverSenders, WebView, WebViewDelegate, WebViewId,
39};
40use url::Url;
41
42#[cfg(all(
43    feature = "gamepad",
44    not(any(target_os = "android", target_env = "ohos"))
45))]
46pub(crate) use crate::desktop::gamepad::ServoshellGamepadDelegate;
47use crate::prefs::{EXPERIMENTAL_PREFS, ServoShellPreferences};
48use crate::webdriver::WebDriverEmbedderControls;
49use crate::window::{
50    PlatformWindow, ServoShellWindow, ServoShellWindowId, TopLevelWebViewCreationRequest,
51};
52
53#[cfg(all(
54    any(coverage, llvm_pgo),
55    any(target_os = "android", target_env = "ohos")
56))]
57unsafe extern "C" {
58    fn __llvm_profile_set_filename(file: *const c_char);
59    fn __llvm_profile_write_file();
60}
61
62#[derive(Default)]
63pub struct WebViewCollection {
64    /// List of top-level browsing contexts.
65    /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
66    /// and we exit if it ever becomes empty.
67    webviews: HashMap<WebViewId, WebView>,
68
69    /// The order in which the webviews were created.
70    pub(crate) creation_order: Vec<WebViewId>,
71
72    /// The [`WebView`] that is currently active. This is the [`WebView`] that is shown and has
73    /// input focus.
74    active_webview_id: Option<WebViewId>,
75
76    /// Whether or not the active WebView in this collection has system focus.
77    has_system_focus: bool,
78}
79
80impl WebViewCollection {
81    pub fn add(&mut self, webview: WebView) {
82        let id = webview.id();
83        self.creation_order.push(id);
84        self.webviews.insert(id, webview);
85    }
86
87    /// Removes a webview from the collection by [`WebViewId`]. If the removed [`WebView`] was the active
88    /// [`WebView`] then the next newest [`WebView`] will be activated.
89    pub fn remove(&mut self, id: WebViewId) -> Option<WebView> {
90        self.creation_order.retain(|&webview_id| webview_id != id);
91        let removed_webview = self.webviews.remove(&id);
92
93        if self.active_webview_id == Some(id) {
94            self.active_webview_id = None;
95            if let Some(newest) = self.creation_order.last() {
96                self.activate_webview(*newest);
97            }
98        }
99
100        removed_webview
101    }
102
103    pub fn get(&self, id: WebViewId) -> Option<&WebView> {
104        self.webviews.get(&id)
105    }
106
107    pub fn active(&self) -> Option<&WebView> {
108        self.active_webview_id.and_then(|id| self.webviews.get(&id))
109    }
110
111    pub fn active_id(&self) -> Option<WebViewId> {
112        self.active_webview_id
113    }
114
115    /// Gets a reference to the most recently created webview, if any.
116    pub fn newest(&self) -> Option<&WebView> {
117        self.creation_order
118            .last()
119            .and_then(|id| self.webviews.get(id))
120    }
121
122    pub fn all_in_creation_order(&self) -> impl Iterator<Item = (WebViewId, &WebView)> {
123        self.creation_order
124            .iter()
125            .filter_map(move |id| self.webviews.get(id).map(|webview| (*id, webview)))
126    }
127
128    /// Returns an iterator over all webview references (in arbitrary order).
129    pub fn values(&self) -> impl Iterator<Item = &WebView> {
130        self.webviews.values()
131    }
132
133    /// Returns true if the collection contains no webviews.
134    pub fn is_empty(&self) -> bool {
135        self.webviews.is_empty()
136    }
137
138    pub(crate) fn activate_webview(&mut self, id_to_activate: WebViewId) {
139        assert!(self.creation_order.contains(&id_to_activate));
140
141        self.active_webview_id = Some(id_to_activate);
142        for (webview_id, webview) in self.all_in_creation_order() {
143            if id_to_activate == webview_id {
144                if self.has_system_focus {
145                    webview.set_focused(true);
146                }
147                webview.show();
148            } else {
149                if self.has_system_focus {
150                    webview.set_focused(false);
151                }
152                webview.hide();
153            }
154        }
155    }
156
157    pub(crate) fn activate_webview_by_index(&mut self, index: usize) {
158        let Some(webview_id) = self.creation_order.get(index) else {
159            // Just ignore requests to activate unknown WebViews. This can happen by pressing
160            // keyboard shortcuts in the interface.
161            return;
162        };
163        self.activate_webview(*webview_id);
164    }
165
166    pub(crate) fn set_focused(&mut self, focused: bool) {
167        self.has_system_focus = focused;
168        if let Some(active) = self.active() {
169            active.set_focused(focused);
170        }
171    }
172}
173
174/// A command received via the user interacting with the user interface.
175#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
176pub(crate) enum UserInterfaceCommand {
177    Go(String),
178    Back,
179    Forward,
180    Reload,
181    ReloadAll,
182    NewWebView,
183    CloseWebView(WebViewId),
184    NewWindow(TopLevelWebViewCreationRequest),
185}
186
187pub(crate) struct RunningAppState {
188    /// The gamepad provider, used for handling gamepad events and set on each WebView.
189    /// May be `None` if gamepad support is disabled or failed to initialize.
190    #[cfg(all(
191        feature = "gamepad",
192        not(any(target_os = "android", target_env = "ohos"))
193    ))]
194    gamepad_delegate: Option<Rc<ServoshellGamepadDelegate>>,
195
196    /// The [`WebDriverSenders`] used to reply to pending WebDriver requests.
197    pub(crate) webdriver_senders: RefCell<WebDriverSenders>,
198
199    /// When running in WebDriver mode, [`WebDriverEmbedderControls`] is a virtual container
200    /// for all embedder controls. This overrides the normal behavior where these controls
201    /// are shown in the GUI or not processed at all in headless mode.
202    pub(crate) webdriver_embedder_controls: WebDriverEmbedderControls,
203
204    /// A [`HashMap`] of pending WebDriver events. It is the WebDriver embedder's responsibility
205    /// to inform the WebDriver server when the event has been fully handled. This map is used
206    /// to report back to WebDriver when that happens.
207    pub(crate) pending_webdriver_events: RefCell<HashMap<InputEventId, (WebViewId, Sender<()>)>>,
208
209    /// A [`Receiver`] for receiving commands from a running WebDriver server, if WebDriver
210    /// was enabled.
211    pub(crate) webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
212
213    /// servoshell specific preferences created during startup of the application.
214    pub(crate) servoshell_preferences: ServoShellPreferences,
215
216    /// A handle to the Servo instance.
217    pub(crate) servo: Servo,
218
219    /// Whether or not the application has achieved stable image output. This is used
220    /// for the `exit_after_stable_image` option.
221    pub(crate) achieved_stable_image: Rc<Cell<bool>>,
222
223    /// The [`UserContentManager`] for all `WebView`s created.
224    pub(crate) user_content_manager: Rc<UserContentManager>,
225
226    /// Whether or not program exit has been triggered. This means that all windows
227    /// will be destroyed and shutdown will start at the end of the current event loop.
228    exit_scheduled: Cell<bool>,
229
230    /// Whether the user has enabled experimental preferences.
231    experimental_preferences_enabled: Cell<bool>,
232
233    /// The set of [`ServoShellWindow`]s that currently exist for this instance of servoshell.
234    ///
235    /// These are stored in creation order to ensure consistent focus behavior.
236    // This is the last field of the struct to ensure that windows are dropped *after* all
237    // other references to the relevant rendering contexts have been destroyed.
238    // See https://github.com/servo/servo/issues/36711.
239    windows: RefCell<Vec<Rc<ServoShellWindow>>>,
240
241    /// The currently focused [`ServoShellWindow`], if one is focused.
242    focused_window: RefCell<Option<Rc<ServoShellWindow>>>,
243
244    /// Whether accessibility is active in servoshell.
245    ///
246    /// Set by the platform via AccessKit, and forwarded to existing and new WebViews via
247    /// [`WebView::set_accessibility_active()`], in [`Self::set_accessibility_active()`] and
248    /// and [`ServoShellWindow::create_toplevel_webview()`].
249    accessibility_active: Cell<bool>,
250}
251
252impl RunningAppState {
253    pub(crate) fn new(
254        servo: Servo,
255        servoshell_preferences: ServoShellPreferences,
256        event_loop_waker: Box<dyn EventLoopWaker>,
257        user_content_manager: Rc<UserContentManager>,
258        default_preferences: Preferences,
259        #[cfg(all(
260            feature = "gamepad",
261            not(any(target_os = "android", target_env = "ohos"))
262        ))]
263        gamepad_delegate: Option<Rc<ServoshellGamepadDelegate>>,
264    ) -> Self {
265        servo.set_delegate(Rc::new(ServoShellServoDelegate));
266
267        let webdriver_receiver = servoshell_preferences.webdriver_port.get().map(|port| {
268            let (embedder_sender, embedder_receiver) = unbounded();
269            webdriver_server::start_server(
270                port,
271                embedder_sender,
272                event_loop_waker,
273                default_preferences,
274            );
275            embedder_receiver
276        });
277
278        let experimental_preferences_enabled =
279            Cell::new(servoshell_preferences.experimental_preferences_enabled);
280
281        Self {
282            windows: Default::default(),
283            focused_window: Default::default(),
284            #[cfg(all(
285                feature = "gamepad",
286                not(any(target_os = "android", target_env = "ohos"))
287            ))]
288            gamepad_delegate,
289            webdriver_senders: RefCell::default(),
290            webdriver_embedder_controls: Default::default(),
291            pending_webdriver_events: Default::default(),
292            webdriver_receiver,
293            servoshell_preferences,
294            servo,
295            achieved_stable_image: Default::default(),
296            exit_scheduled: Default::default(),
297            user_content_manager,
298            experimental_preferences_enabled,
299            accessibility_active: Cell::new(false),
300        }
301    }
302
303    pub(crate) fn open_window(
304        self: &Rc<Self>,
305        platform_window: Rc<dyn PlatformWindow>,
306        creation_request: TopLevelWebViewCreationRequest,
307    ) -> Rc<ServoShellWindow> {
308        let window = Rc::new(ServoShellWindow::new(platform_window.clone()));
309        self.windows.borrow_mut().push(window.clone());
310        window.create_and_activate_toplevel_webview(self.clone(), creation_request);
311
312        // If the window already has platform focus, mark it as focused in our application state.
313        if platform_window.has_platform_focus() {
314            self.set_window_has_focus(window.clone(), true);
315        }
316
317        window
318    }
319
320    pub(crate) fn windows<'a>(&'a self) -> Ref<'a, Vec<Rc<ServoShellWindow>>> {
321        self.windows.borrow()
322    }
323
324    pub(crate) fn focused_window(&self) -> Option<Rc<ServoShellWindow>> {
325        self.focused_window.borrow().clone()
326    }
327
328    pub(crate) fn set_window_has_focus(&self, window: Rc<ServoShellWindow>, has_focus: bool) {
329        let mut focused_window = self.focused_window.borrow_mut();
330
331        // If another window had focus before and this new window is gaining it, then
332        // ensure that the old window is explicitly unfocused.
333        if let Some(previously_focused_window) = &*focused_window &&
334            !Rc::ptr_eq(previously_focused_window, &window) &&
335            has_focus
336        {
337            previously_focused_window
338                .webview_collection
339                .borrow_mut()
340                .set_focused(false);
341        }
342
343        window
344            .webview_collection
345            .borrow_mut()
346            .set_focused(has_focus);
347
348        if let Some(previously_focused_window) = &*focused_window &&
349            Rc::ptr_eq(previously_focused_window, &window) &&
350            !has_focus
351        {
352            *focused_window = None;
353        } else if has_focus {
354            *focused_window = Some(window);
355        }
356    }
357
358    #[cfg_attr(target_os = "android", expect(dead_code))]
359    pub(crate) fn window(&self, id: ServoShellWindowId) -> Option<Rc<ServoShellWindow>> {
360        self.windows
361            .borrow()
362            .iter()
363            .find(|window| window.id() == id)
364            .cloned()
365    }
366
367    pub(crate) fn webview_by_id(&self, webview_id: WebViewId) -> Option<WebView> {
368        self.windows()
369            .iter()
370            .find_map(|window| window.webview_by_id(webview_id))
371    }
372
373    pub(crate) fn webdriver_receiver(&self) -> Option<&Receiver<WebDriverCommandMsg>> {
374        self.webdriver_receiver.as_ref()
375    }
376
377    pub(crate) fn servo(&self) -> &Servo {
378        &self.servo
379    }
380
381    #[cfg(all(
382        feature = "gamepad",
383        not(any(target_os = "android", target_env = "ohos"))
384    ))]
385    pub(crate) fn gamepad_delegate(&self) -> Option<Rc<ServoshellGamepadDelegate>> {
386        self.gamepad_delegate.clone()
387    }
388
389    pub(crate) fn schedule_exit(&self) {
390        // When explicitly required to shutdown, unset webdriver port
391        // which allows normal shutdown.
392        // Note that when not explicitly required to shutdown, we still keep Servo alive
393        // when all tabs are closed when `webdriver_port` enabled, which is necessary
394        // to run wpt test using servodriver.
395        self.servoshell_preferences.webdriver_port.set(None);
396        self.exit_scheduled.set(true);
397
398        #[cfg(all(
399            any(coverage, llvm_pgo),
400            any(target_os = "android", target_env = "ohos")
401        ))]
402        {
403            use std::ffi::CString;
404
405            use crate::prefs::default_config_dir;
406
407            let mut profile_path = default_config_dir().expect("Need a config dir");
408            profile_path.push("profiles/");
409            let filename = format!(
410                "{}/profile-%h-%p.profraw",
411                profile_path.to_str().expect("Should be unicode")
412            );
413            let c_filename = CString::new(filename).expect("Need a valid cstring");
414            unsafe {
415                __llvm_profile_set_filename(c_filename.as_ptr() as *const c_char);
416                __llvm_profile_write_file()
417            }
418        }
419    }
420
421    #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
422    pub(crate) fn experimental_preferences_enabled(&self) -> bool {
423        self.experimental_preferences_enabled.get()
424    }
425
426    #[cfg_attr(target_env = "ohos", expect(dead_code))]
427    pub(crate) fn set_experimental_preferences_enabled(&self, new_value: bool) {
428        let old_value = self.experimental_preferences_enabled.replace(new_value);
429        if old_value == new_value {
430            return;
431        }
432        for pref in EXPERIMENTAL_PREFS {
433            self.servo.set_preference(pref, PrefValue::Bool(new_value));
434        }
435    }
436
437    /// Close any [`ServoShellWindow`] that doesn't have an open [`WebView`].
438    fn close_empty_windows(&self) {
439        let mut move_focus = false;
440        self.windows.borrow_mut().retain(|window| {
441            if !self.exit_scheduled.get() && !window.should_close() {
442                return true;
443            }
444
445            if let Some(focused_window) = self.focused_window() &&
446                Rc::ptr_eq(window, &focused_window)
447            {
448                *self.focused_window.borrow_mut() = None;
449                move_focus = !focused_window.platform_window().platform_manages_focus();
450            }
451            false
452        });
453
454        // If the platform doesn't manage focus, then focus the most recently opened
455        // window.
456        if move_focus &&
457            let Some(newly_focused_window) = self
458                .windows()
459                .iter()
460                .rev()
461                .find(|window| !window.platform_window().platform_manages_focus())
462                .cloned()
463        {
464            self.set_window_has_focus(newly_focused_window, true);
465        }
466    }
467
468    /// Spins the internal application event loop.
469    ///
470    /// - Notifies Servo about incoming gamepad events
471    /// - Spin the Servo event loop, which will update Servo's embedding layer and trigger
472    ///   delegate methods.
473    ///
474    /// Returns true if the event loop should continue spinning and false if it should exit.
475    pub(crate) fn spin_event_loop(
476        self: &Rc<Self>,
477        create_platform_window: Option<&dyn Fn(Url) -> Rc<dyn PlatformWindow>>,
478    ) -> bool {
479        self.handle_webdriver_messages(create_platform_window);
480
481        self.servo.spin_event_loop();
482
483        for window in self.windows.borrow().iter() {
484            window.update_and_request_repaint_if_necessary(self);
485        }
486
487        // We clone here to avoid a double borrow. User interface commands can update the list of windows.
488        let windows: Vec<_> = self.windows.borrow().iter().cloned().collect();
489        for window in windows {
490            window.handle_interface_commands(self, create_platform_window);
491        }
492
493        if self.servoshell_preferences.exit_after_stable_image && self.achieved_stable_image.get() {
494            self.schedule_exit();
495        }
496
497        self.close_empty_windows();
498
499        // When no more windows are open, exit the application. Do not do this when
500        // running WebDriver, which expects to keep running with no WebView open.
501        if self.servoshell_preferences.webdriver_port.get().is_none() &&
502            self.windows.borrow().is_empty()
503        {
504            self.schedule_exit()
505        }
506
507        !self.exit_scheduled.get()
508    }
509
510    fn maybe_window_for_webview(&self, webview: &WebView) -> Option<Rc<ServoShellWindow>> {
511        // Look up the ServoShellWindow by RenderingContext. This method can be called while a
512        // WebView is being constructed, which means that it may not fully be associated with a
513        // ServoShellWindow yet.
514        let rendering_context = webview.rendering_context();
515        self.windows()
516            .iter()
517            .find(|window| {
518                Rc::ptr_eq(
519                    &window.platform_window().rendering_context(),
520                    &rendering_context,
521                )
522            })
523            .cloned()
524    }
525
526    pub(crate) fn window_for_webview(&self, webview: &WebView) -> Rc<ServoShellWindow> {
527        self.maybe_window_for_webview(webview)
528            .unwrap_or_else(|| panic!("Looking for unexpected WebView: {:?}", webview.id()))
529    }
530
531    pub(crate) fn platform_window_for_webview(&self, webview: &WebView) -> Rc<dyn PlatformWindow> {
532        self.window_for_webview(webview).platform_window()
533    }
534
535    /// If we are exiting after achieving a stable image or we want to save the display of the
536    /// [`WebView`] to an image file, request a screenshot of the [`WebView`].
537    fn maybe_request_screenshot(&self, webview: WebView) {
538        let output_path = self.servoshell_preferences.output_image_path.clone();
539        if !self.servoshell_preferences.exit_after_stable_image && output_path.is_none() {
540            return;
541        }
542
543        // Never request more than a single screenshot for now.
544        let achieved_stable_image = self.achieved_stable_image.clone();
545        if achieved_stable_image.get() {
546            return;
547        }
548
549        webview.take_screenshot(None, move |image| {
550            achieved_stable_image.set(true);
551
552            let Some(output_path) = output_path else {
553                return;
554            };
555
556            let image = match image {
557                Ok(image) => image,
558                Err(error) => {
559                    error!("Could not take screenshot: {error:?}");
560                    return;
561                },
562            };
563
564            let image_format = ImageFormat::from_path(&output_path).unwrap_or(ImageFormat::Png);
565            if let Err(error) =
566                DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format)
567            {
568                error!("Failed to save screenshot: {error}.");
569            }
570        });
571    }
572
573    pub(crate) fn set_pending_traversal(
574        &self,
575        traversal_id: TraversalId,
576        sender: GenericSender<WebDriverLoadStatus>,
577    ) {
578        self.webdriver_senders
579            .borrow_mut()
580            .pending_traversals
581            .insert(traversal_id, sender);
582    }
583
584    pub(crate) fn set_load_status_sender(
585        &self,
586        webview_id: WebViewId,
587        sender: GenericSender<WebDriverLoadStatus>,
588    ) {
589        self.webdriver_senders
590            .borrow_mut()
591            .load_status_senders
592            .insert(webview_id, sender);
593    }
594
595    fn remove_load_status_sender(&self, webview_id: WebViewId) {
596        self.webdriver_senders
597            .borrow_mut()
598            .load_status_senders
599            .remove(&webview_id);
600    }
601
602    fn set_script_command_interrupt_sender(
603        &self,
604        sender: Option<GenericSender<WebDriverJSResult>>,
605    ) {
606        self.webdriver_senders
607            .borrow_mut()
608            .script_evaluation_interrupt_sender = sender;
609    }
610
611    pub(crate) fn handle_webdriver_input_event(
612        &self,
613        webview_id: WebViewId,
614        input_event: InputEvent,
615        response_sender: Option<Sender<()>>,
616    ) {
617        if let Some(webview) = self.webview_by_id(webview_id) {
618            let event_id = webview.notify_input_event(input_event);
619            if let Some(response_sender) = response_sender {
620                self.pending_webdriver_events
621                    .borrow_mut()
622                    .insert(event_id, (webview_id, response_sender));
623            }
624        } else {
625            error!("Could not find WebView ({webview_id:?}) for WebDriver event: {input_event:?}");
626        };
627    }
628
629    pub(crate) fn handle_webdriver_screenshot(
630        &self,
631        webview_id: WebViewId,
632        rect: Option<Rect<f32, CSSPixel>>,
633        result_sender: Sender<Result<RgbaImage, ScreenshotCaptureError>>,
634    ) {
635        if let Some(webview) = self.webview_by_id(webview_id) {
636            let rect = rect.map(|rect| rect.to_box2d().into());
637            webview.take_screenshot(rect, move |result| {
638                if let Err(error) = result_sender.send(result) {
639                    warn!("Failed to send response to TakeScreenshot: {error}");
640                }
641            });
642        } else if let Err(error) =
643            result_sender.send(Err(ScreenshotCaptureError::WebViewDoesNotExist))
644        {
645            error!("Failed to send response to TakeScreenshot: {error}");
646        }
647    }
648
649    pub(crate) fn handle_webdriver_script_command(&self, script_command: &WebDriverScriptCommand) {
650        match script_command {
651            WebDriverScriptCommand::ExecuteScriptWithCallback(_webview_id, response_sender) => {
652                // Give embedder a chance to interrupt the script command.
653                // Webdriver only handles 1 script command at a time, so we can
654                // safely set a new interrupt sender and remove the previous one here.
655                self.set_script_command_interrupt_sender(Some(response_sender.clone()));
656            },
657            WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
658                self.set_load_status_sender(*webview_id, load_status_sender.clone());
659            },
660            WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
661                self.remove_load_status_sender(*webview_id);
662            },
663            _ => {
664                self.set_script_command_interrupt_sender(None);
665            },
666        }
667    }
668
669    pub(crate) fn handle_webdriver_load_url(
670        &self,
671        webview_id: WebViewId,
672        url: Url,
673        load_status_sender: GenericSender<WebDriverLoadStatus>,
674    ) {
675        let Some(webview) = self.webview_by_id(webview_id) else {
676            return;
677        };
678
679        self.platform_window_for_webview(&webview)
680            .dismiss_embedder_controls_for_webview(webview_id);
681
682        info!("Loading URL in webview {}: {}", webview_id, url);
683        self.set_load_status_sender(webview_id, load_status_sender);
684        webview.load(url);
685    }
686
687    #[cfg(all(
688        feature = "gamepad",
689        not(any(target_os = "android", target_env = "ohos"))
690    ))]
691    pub(crate) fn handle_gamepad_events(
692        &self,
693        event: Event,
694        gamepad_name: String,
695        gamepad_index: GamepadIndex,
696    ) {
697        let Some(gamepad_delegate) = self.gamepad_delegate.as_ref() else {
698            return;
699        };
700        let Some(active_webview) = self
701            .focused_window()
702            .and_then(|window| window.active_webview())
703        else {
704            return;
705        };
706        gamepad_delegate.handle_gamepad_events(event, gamepad_name, gamepad_index, active_webview);
707    }
708
709    /// Interrupt any ongoing WebDriver-based script evaluation.
710    ///
711    /// From <https://w3c.github.io/webdriver/#dfn-execute-a-function-body>:
712    /// > The rules to execute a function body are as follows. The algorithm returns
713    /// > an ECMAScript completion record.
714    /// >
715    /// > If at any point during the algorithm a user prompt appears, immediately return
716    /// > Completion { Type: normal, Value: null, Target: empty }, but continue to run the
717    /// >  other steps of this algorithm in parallel.
718    fn interrupt_webdriver_script_evaluation(&self) {
719        if let Some(sender) = &self
720            .webdriver_senders
721            .borrow()
722            .script_evaluation_interrupt_sender
723        {
724            sender.send(Ok(JSValue::Null)).unwrap_or_else(|err| {
725                info!(
726                    "Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"
727                );
728            });
729        }
730    }
731
732    #[cfg(not(any(target_os = "android", target_env = "ohos")))]
733    pub(crate) fn set_accessibility_active(&self, active: bool) {
734        let was_active = self.accessibility_active.replace(active);
735        if active == was_active {
736            return;
737        }
738
739        for window in self.windows().iter() {
740            for (_, webview) in window.webviews() {
741                // Activate accessibility in the WebView.
742                // There are two sites like this; this is the a11y activation site.
743                webview.set_accessibility_active(active);
744            }
745        }
746    }
747
748    pub(crate) fn accessibility_active(&self) -> bool {
749        self.accessibility_active.get()
750    }
751}
752
753impl WebViewDelegate for RunningAppState {
754    fn screen_geometry(&self, webview: WebView) -> Option<servo::ScreenGeometry> {
755        Some(self.platform_window_for_webview(&webview).screen_geometry())
756    }
757
758    fn notify_status_text_changed(&self, webview: WebView, _status: Option<String>) {
759        self.window_for_webview(&webview).set_needs_update();
760    }
761
762    fn notify_history_changed(&self, webview: WebView, _entries: Vec<Url>, _current: usize) {
763        self.window_for_webview(&webview).set_needs_update();
764    }
765
766    fn notify_page_title_changed(&self, webview: WebView, _: Option<String>) {
767        self.window_for_webview(&webview).set_needs_update();
768    }
769
770    fn notify_traversal_complete(&self, _webview: WebView, traversal_id: TraversalId) {
771        let mut webdriver_state = self.webdriver_senders.borrow_mut();
772        if let Entry::Occupied(entry) = webdriver_state.pending_traversals.entry(traversal_id) {
773            let sender = entry.remove();
774            let _ = sender.send(WebDriverLoadStatus::Complete);
775        }
776    }
777
778    fn request_move_to(&self, webview: WebView, new_position: DeviceIntPoint) {
779        self.platform_window_for_webview(&webview)
780            .set_position(new_position);
781    }
782
783    fn request_resize_to(&self, webview: WebView, requested_outer_size: DeviceIntSize) {
784        self.platform_window_for_webview(&webview)
785            .request_resize(&webview, requested_outer_size);
786    }
787
788    fn request_authentication(
789        &self,
790        webview: WebView,
791        authentication_request: AuthenticationRequest,
792    ) {
793        self.platform_window_for_webview(&webview)
794            .show_http_authentication_dialog(webview.id(), authentication_request);
795    }
796
797    fn request_create_new(&self, parent_webview: WebView, request: CreateNewWebViewRequest) {
798        let window = self.window_for_webview(&parent_webview);
799
800        // When WebDriver wants to open a new WebView, do this in a new window. Normally, servoshell
801        // throttles WebViews that are hidden, but WebDriver expects all opened WebViews to stay
802        // active. Using a separate window ensures this.
803        if self.servoshell_preferences.webdriver_port.get().is_some() {
804            window.queue_user_interface_command(UserInterfaceCommand::NewWindow(
805                TopLevelWebViewCreationRequest::WithCreateRequest(request),
806            ));
807            return;
808        }
809
810        let platform_window = window.platform_window();
811        let webview = request
812            .builder(platform_window.rendering_context())
813            .hidpi_scale_factor(platform_window.hidpi_scale_factor())
814            .delegate(parent_webview.delegate())
815            .build();
816
817        window.add_webview(webview.clone());
818        window.activate_webview(webview.id());
819    }
820
821    fn notify_closed(&self, webview: WebView) {
822        let webview_id = webview.id();
823        self.pending_webdriver_events
824            .borrow_mut()
825            .retain(|_, (pending_webview_id, _)| *pending_webview_id != webview_id);
826        self.window_for_webview(&webview).close_webview(webview_id)
827    }
828
829    fn notify_input_event_handled(
830        &self,
831        webview: WebView,
832        id: InputEventId,
833        result: InputEventResult,
834    ) {
835        self.platform_window_for_webview(&webview)
836            .notify_input_event_handled(&webview, id, result);
837
838        self.pending_webdriver_events.borrow_mut().remove(&id);
839    }
840
841    fn notify_cursor_changed(&self, webview: WebView, cursor: servo::Cursor) {
842        self.platform_window_for_webview(&webview)
843            .set_cursor(cursor);
844    }
845
846    fn notify_load_status_changed(&self, webview: WebView, status: LoadStatus) {
847        self.window_for_webview(&webview).set_needs_update();
848
849        if status == LoadStatus::Complete {
850            if let Some(sender) = self
851                .webdriver_senders
852                .borrow_mut()
853                .load_status_senders
854                .remove(&webview.id())
855            {
856                let _ = sender.send(WebDriverLoadStatus::Complete);
857            }
858            self.maybe_request_screenshot(webview);
859        }
860    }
861
862    fn notify_fullscreen_state_changed(&self, webview: WebView, fullscreen_state: bool) {
863        self.platform_window_for_webview(&webview)
864            .set_fullscreen(fullscreen_state);
865    }
866
867    fn show_bluetooth_device_dialog(
868        &self,
869        webview: WebView,
870        request: BluetoothDeviceSelectionRequest,
871    ) {
872        self.platform_window_for_webview(&webview)
873            .show_bluetooth_device_dialog(webview.id(), request);
874    }
875
876    fn request_permission(&self, webview: WebView, permission_request: PermissionRequest) {
877        self.platform_window_for_webview(&webview)
878            .show_permission_dialog(webview.id(), permission_request);
879    }
880
881    fn notify_new_frame_ready(&self, webview: WebView) {
882        self.window_for_webview(&webview).set_needs_repaint();
883    }
884
885    fn show_embedder_control(&self, webview: WebView, embedder_control: EmbedderControl) {
886        if self.servoshell_preferences.webdriver_port.get().is_some() {
887            if matches!(&embedder_control, EmbedderControl::SimpleDialog(..)) {
888                self.interrupt_webdriver_script_evaluation();
889
890                // Dialogs block the page load, so need need to notify WebDriver
891                if let Some(sender) = self
892                    .webdriver_senders
893                    .borrow_mut()
894                    .load_status_senders
895                    .get(&webview.id())
896                {
897                    let _ = sender.send(WebDriverLoadStatus::Blocked);
898                };
899            }
900
901            self.webdriver_embedder_controls
902                .show_embedder_control(webview.id(), embedder_control);
903            return;
904        }
905
906        self.window_for_webview(&webview)
907            .show_embedder_control(webview, embedder_control);
908    }
909
910    fn hide_embedder_control(&self, webview: WebView, embedder_control_id: EmbedderControlId) {
911        if self.servoshell_preferences.webdriver_port.get().is_some() {
912            self.webdriver_embedder_controls
913                .hide_embedder_control(webview.id(), embedder_control_id);
914            return;
915        }
916
917        self.window_for_webview(&webview)
918            .hide_embedder_control(webview, embedder_control_id);
919    }
920
921    fn notify_favicon_changed(&self, webview: WebView) {
922        self.window_for_webview(&webview)
923            .notify_favicon_changed(webview);
924    }
925
926    fn notify_media_session_event(&self, webview: WebView, event: MediaSessionEvent) {
927        self.platform_window_for_webview(&webview)
928            .notify_media_session_event(event);
929    }
930
931    fn notify_crashed(&self, webview: WebView, reason: String, backtrace: Option<String>) {
932        self.platform_window_for_webview(&webview)
933            .notify_crashed(webview, reason, backtrace);
934    }
935
936    fn show_console_message(&self, webview: WebView, level: ConsoleLogLevel, message: String) {
937        self.platform_window_for_webview(&webview)
938            .show_console_message(level, &message);
939    }
940
941    fn notify_accessibility_tree_update(
942        &self,
943        webview: WebView,
944        tree_update: accesskit::TreeUpdate,
945    ) {
946        self.platform_window_for_webview(&webview)
947            .notify_accessibility_tree_update(webview, tree_update);
948    }
949}
950
951struct ServoShellServoDelegate;
952impl ServoDelegate for ServoShellServoDelegate {
953    fn notify_devtools_server_started(&self, port: u16, _token: String) {
954        info!("Devtools Server running on port {port}");
955    }
956
957    fn request_devtools_connection(&self, request: AllowOrDenyRequest) {
958        request.allow();
959    }
960
961    fn notify_error(&self, error: ServoError) {
962        error!("Saw Servo error: {error:?}!");
963    }
964
965    fn show_console_message(&self, level: ConsoleLogLevel, message: String) {
966        // For messages without a WebView context, apply platform-specific behavior
967        #[cfg(not(any(target_os = "android", target_env = "ohos")))]
968        println!("{message}");
969        log::log!(level.into(), "{message}");
970    }
971}