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, PermissionRequest, PrefValue, Preferences, ScreenshotCaptureError, Servo,
36    ServoDelegate, ServoError, TraversalId, UserContentManager, WebDriverCommandMsg,
37    WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand, WebDriverSenders, WebView,
38    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    pub(crate) fn window(&self, id: ServoShellWindowId) -> Option<Rc<ServoShellWindow>> {
359        self.windows
360            .borrow()
361            .iter()
362            .find(|window| window.id() == id)
363            .cloned()
364    }
365
366    pub(crate) fn webview_by_id(&self, webview_id: WebViewId) -> Option<WebView> {
367        self.windows()
368            .iter()
369            .find_map(|window| window.webview_by_id(webview_id))
370    }
371
372    pub(crate) fn webdriver_receiver(&self) -> Option<&Receiver<WebDriverCommandMsg>> {
373        self.webdriver_receiver.as_ref()
374    }
375
376    pub(crate) fn servo(&self) -> &Servo {
377        &self.servo
378    }
379
380    #[cfg(all(
381        feature = "gamepad",
382        not(any(target_os = "android", target_env = "ohos"))
383    ))]
384    pub(crate) fn gamepad_delegate(&self) -> Option<Rc<ServoshellGamepadDelegate>> {
385        self.gamepad_delegate.clone()
386    }
387
388    pub(crate) fn schedule_exit(&self) {
389        // When explicitly required to shutdown, unset webdriver port
390        // which allows normal shutdown.
391        // Note that when not explicitly required to shutdown, we still keep Servo alive
392        // when all tabs are closed when `webdriver_port` enabled, which is necessary
393        // to run wpt test using servodriver.
394        self.servoshell_preferences.webdriver_port.set(None);
395        self.exit_scheduled.set(true);
396
397        #[cfg(all(
398            any(coverage, llvm_pgo),
399            any(target_os = "android", target_env = "ohos")
400        ))]
401        {
402            use std::ffi::CString;
403
404            use crate::prefs::default_config_dir;
405
406            let mut profile_path = default_config_dir().expect("Need a config dir");
407            profile_path.push("profiles/");
408            let filename = format!(
409                "{}/profile-%h-%p.profraw",
410                profile_path.to_str().expect("Should be unicode")
411            );
412            let c_filename = CString::new(filename).expect("Need a valid cstring");
413            unsafe {
414                __llvm_profile_set_filename(c_filename.as_ptr() as *const c_char);
415                __llvm_profile_write_file()
416            }
417        }
418    }
419
420    #[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
421    pub(crate) fn experimental_preferences_enabled(&self) -> bool {
422        self.experimental_preferences_enabled.get()
423    }
424
425    #[cfg_attr(target_env = "ohos", expect(dead_code))]
426    pub(crate) fn set_experimental_preferences_enabled(&self, new_value: bool) {
427        let old_value = self.experimental_preferences_enabled.replace(new_value);
428        if old_value == new_value {
429            return;
430        }
431        for pref in EXPERIMENTAL_PREFS {
432            self.servo.set_preference(pref, PrefValue::Bool(new_value));
433        }
434    }
435
436    /// Close any [`ServoShellWindow`] that doesn't have an open [`WebView`].
437    fn close_empty_windows(&self) {
438        let mut move_focus = false;
439        self.windows.borrow_mut().retain(|window| {
440            if !self.exit_scheduled.get() && !window.should_close() {
441                return true;
442            }
443
444            if let Some(focused_window) = self.focused_window() &&
445                Rc::ptr_eq(window, &focused_window)
446            {
447                *self.focused_window.borrow_mut() = None;
448                move_focus = !focused_window.platform_window().platform_manages_focus();
449            }
450            false
451        });
452
453        // If the platform doesn't manage focus, then focus the most recently opened
454        // window.
455        if move_focus &&
456            let Some(newly_focused_window) = self
457                .windows()
458                .iter()
459                .rev()
460                .find(|window| !window.platform_window().platform_manages_focus())
461                .cloned()
462        {
463            self.set_window_has_focus(newly_focused_window, true);
464        }
465    }
466
467    /// Spins the internal application event loop.
468    ///
469    /// - Notifies Servo about incoming gamepad events
470    /// - Spin the Servo event loop, which will update Servo's embedding layer and trigger
471    ///   delegate methods.
472    ///
473    /// Returns true if the event loop should continue spinning and false if it should exit.
474    pub(crate) fn spin_event_loop(
475        self: &Rc<Self>,
476        create_platform_window: Option<&dyn Fn(Url) -> Rc<dyn PlatformWindow>>,
477    ) -> bool {
478        self.handle_webdriver_messages(create_platform_window);
479
480        self.servo.spin_event_loop();
481
482        for window in self.windows.borrow().iter() {
483            window.update_and_request_repaint_if_necessary(self);
484        }
485
486        // We clone here to avoid a double borrow. User interface commands can update the list of windows.
487        let windows: Vec<_> = self.windows.borrow().iter().cloned().collect();
488        for window in windows {
489            window.handle_interface_commands(self, create_platform_window);
490        }
491
492        if self.servoshell_preferences.exit_after_stable_image && self.achieved_stable_image.get() {
493            self.schedule_exit();
494        }
495
496        self.close_empty_windows();
497
498        // When no more windows are open, exit the application. Do not do this when
499        // running WebDriver, which expects to keep running with no WebView open.
500        if self.servoshell_preferences.webdriver_port.get().is_none() &&
501            self.windows.borrow().is_empty()
502        {
503            self.schedule_exit()
504        }
505
506        !self.exit_scheduled.get()
507    }
508
509    fn maybe_window_for_webview(&self, webview: &WebView) -> Option<Rc<ServoShellWindow>> {
510        // Look up the ServoShellWindow by RenderingContext. This method can be called while a
511        // WebView is being constructed, which means that it may not fully be associated with a
512        // ServoShellWindow yet.
513        let rendering_context = webview.rendering_context();
514        self.windows()
515            .iter()
516            .find(|window| {
517                Rc::ptr_eq(
518                    &window.platform_window().rendering_context(),
519                    &rendering_context,
520                )
521            })
522            .cloned()
523    }
524
525    pub(crate) fn window_for_webview(&self, webview: &WebView) -> Rc<ServoShellWindow> {
526        self.maybe_window_for_webview(webview)
527            .unwrap_or_else(|| panic!("Looking for unexpected WebView: {:?}", webview.id()))
528    }
529
530    pub(crate) fn platform_window_for_webview(&self, webview: &WebView) -> Rc<dyn PlatformWindow> {
531        self.window_for_webview(webview).platform_window()
532    }
533
534    /// If we are exiting after achieving a stable image or we want to save the display of the
535    /// [`WebView`] to an image file, request a screenshot of the [`WebView`].
536    fn maybe_request_screenshot(&self, webview: WebView) {
537        let output_path = self.servoshell_preferences.output_image_path.clone();
538        if !self.servoshell_preferences.exit_after_stable_image && output_path.is_none() {
539            return;
540        }
541
542        // Never request more than a single screenshot for now.
543        let achieved_stable_image = self.achieved_stable_image.clone();
544        if achieved_stable_image.get() {
545            return;
546        }
547
548        webview.take_screenshot(None, move |image| {
549            achieved_stable_image.set(true);
550
551            let Some(output_path) = output_path else {
552                return;
553            };
554
555            let image = match image {
556                Ok(image) => image,
557                Err(error) => {
558                    error!("Could not take screenshot: {error:?}");
559                    return;
560                },
561            };
562
563            let image_format = ImageFormat::from_path(&output_path).unwrap_or(ImageFormat::Png);
564            if let Err(error) =
565                DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format)
566            {
567                error!("Failed to save screenshot: {error}.");
568            }
569        });
570    }
571
572    pub(crate) fn set_pending_traversal(
573        &self,
574        traversal_id: TraversalId,
575        sender: GenericSender<WebDriverLoadStatus>,
576    ) {
577        self.webdriver_senders
578            .borrow_mut()
579            .pending_traversals
580            .insert(traversal_id, sender);
581    }
582
583    pub(crate) fn set_load_status_sender(
584        &self,
585        webview_id: WebViewId,
586        sender: GenericSender<WebDriverLoadStatus>,
587    ) {
588        self.webdriver_senders
589            .borrow_mut()
590            .load_status_senders
591            .insert(webview_id, sender);
592    }
593
594    fn remove_load_status_sender(&self, webview_id: WebViewId) {
595        self.webdriver_senders
596            .borrow_mut()
597            .load_status_senders
598            .remove(&webview_id);
599    }
600
601    fn set_script_command_interrupt_sender(
602        &self,
603        sender: Option<GenericSender<WebDriverJSResult>>,
604    ) {
605        self.webdriver_senders
606            .borrow_mut()
607            .script_evaluation_interrupt_sender = sender;
608    }
609
610    pub(crate) fn handle_webdriver_input_event(
611        &self,
612        webview_id: WebViewId,
613        input_event: InputEvent,
614        response_sender: Option<Sender<()>>,
615    ) {
616        if let Some(webview) = self.webview_by_id(webview_id) {
617            let event_id = webview.notify_input_event(input_event);
618            if let Some(response_sender) = response_sender {
619                self.pending_webdriver_events
620                    .borrow_mut()
621                    .insert(event_id, (webview_id, response_sender));
622            }
623        } else {
624            error!("Could not find WebView ({webview_id:?}) for WebDriver event: {input_event:?}");
625        };
626    }
627
628    pub(crate) fn handle_webdriver_screenshot(
629        &self,
630        webview_id: WebViewId,
631        rect: Option<Rect<f32, CSSPixel>>,
632        result_sender: Sender<Result<RgbaImage, ScreenshotCaptureError>>,
633    ) {
634        if let Some(webview) = self.webview_by_id(webview_id) {
635            let rect = rect.map(|rect| rect.to_box2d().into());
636            webview.take_screenshot(rect, move |result| {
637                if let Err(error) = result_sender.send(result) {
638                    warn!("Failed to send response to TakeScreenshot: {error}");
639                }
640            });
641        } else if let Err(error) =
642            result_sender.send(Err(ScreenshotCaptureError::WebViewDoesNotExist))
643        {
644            error!("Failed to send response to TakeScreenshot: {error}");
645        }
646    }
647
648    pub(crate) fn handle_webdriver_script_command(&self, script_command: &WebDriverScriptCommand) {
649        match script_command {
650            WebDriverScriptCommand::ExecuteScriptWithCallback(_webview_id, response_sender) => {
651                // Give embedder a chance to interrupt the script command.
652                // Webdriver only handles 1 script command at a time, so we can
653                // safely set a new interrupt sender and remove the previous one here.
654                self.set_script_command_interrupt_sender(Some(response_sender.clone()));
655            },
656            WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
657                self.set_load_status_sender(*webview_id, load_status_sender.clone());
658            },
659            WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
660                self.remove_load_status_sender(*webview_id);
661            },
662            _ => {
663                self.set_script_command_interrupt_sender(None);
664            },
665        }
666    }
667
668    pub(crate) fn handle_webdriver_load_url(
669        &self,
670        webview_id: WebViewId,
671        url: Url,
672        load_status_sender: GenericSender<WebDriverLoadStatus>,
673    ) {
674        let Some(webview) = self.webview_by_id(webview_id) else {
675            return;
676        };
677
678        self.platform_window_for_webview(&webview)
679            .dismiss_embedder_controls_for_webview(webview_id);
680
681        info!("Loading URL in webview {}: {}", webview_id, url);
682        self.set_load_status_sender(webview_id, load_status_sender);
683        webview.load(url);
684    }
685
686    #[cfg(all(
687        feature = "gamepad",
688        not(any(target_os = "android", target_env = "ohos"))
689    ))]
690    pub(crate) fn handle_gamepad_events(
691        &self,
692        event: Event,
693        gamepad_name: String,
694        gamepad_index: GamepadIndex,
695    ) {
696        let Some(gamepad_delegate) = self.gamepad_delegate.as_ref() else {
697            return;
698        };
699        let Some(active_webview) = self
700            .focused_window()
701            .and_then(|window| window.active_webview())
702        else {
703            return;
704        };
705        gamepad_delegate.handle_gamepad_events(event, gamepad_name, gamepad_index, active_webview);
706    }
707
708    /// Interrupt any ongoing WebDriver-based script evaluation.
709    ///
710    /// From <https://w3c.github.io/webdriver/#dfn-execute-a-function-body>:
711    /// > The rules to execute a function body are as follows. The algorithm returns
712    /// > an ECMAScript completion record.
713    /// >
714    /// > If at any point during the algorithm a user prompt appears, immediately return
715    /// > Completion { Type: normal, Value: null, Target: empty }, but continue to run the
716    /// >  other steps of this algorithm in parallel.
717    fn interrupt_webdriver_script_evaluation(&self) {
718        if let Some(sender) = &self
719            .webdriver_senders
720            .borrow()
721            .script_evaluation_interrupt_sender
722        {
723            sender.send(Ok(JSValue::Null)).unwrap_or_else(|err| {
724                info!(
725                    "Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"
726                );
727            });
728        }
729    }
730
731    #[cfg(not(any(target_os = "android", target_env = "ohos")))]
732    pub(crate) fn set_accessibility_active(&self, active: bool) {
733        let was_active = self.accessibility_active.replace(active);
734        if active == was_active {
735            return;
736        }
737
738        for window in self.windows().iter() {
739            for (_, webview) in window.webviews() {
740                // Activate accessibility in the WebView.
741                // There are two sites like this; this is the a11y activation site.
742                webview.set_accessibility_active(active);
743            }
744        }
745    }
746
747    pub(crate) fn accessibility_active(&self) -> bool {
748        self.accessibility_active.get()
749    }
750}
751
752impl WebViewDelegate for RunningAppState {
753    fn screen_geometry(&self, webview: WebView) -> Option<servo::ScreenGeometry> {
754        Some(self.platform_window_for_webview(&webview).screen_geometry())
755    }
756
757    fn notify_status_text_changed(&self, webview: WebView, _status: Option<String>) {
758        self.window_for_webview(&webview).set_needs_update();
759    }
760
761    fn notify_history_changed(&self, webview: WebView, _entries: Vec<Url>, _current: usize) {
762        self.window_for_webview(&webview).set_needs_update();
763    }
764
765    fn notify_page_title_changed(&self, webview: WebView, _: Option<String>) {
766        self.window_for_webview(&webview).set_needs_update();
767    }
768
769    fn notify_traversal_complete(&self, _webview: WebView, traversal_id: TraversalId) {
770        let mut webdriver_state = self.webdriver_senders.borrow_mut();
771        if let Entry::Occupied(entry) = webdriver_state.pending_traversals.entry(traversal_id) {
772            let sender = entry.remove();
773            let _ = sender.send(WebDriverLoadStatus::Complete);
774        }
775    }
776
777    fn request_move_to(&self, webview: WebView, new_position: DeviceIntPoint) {
778        self.platform_window_for_webview(&webview)
779            .set_position(new_position);
780    }
781
782    fn request_resize_to(&self, webview: WebView, requested_outer_size: DeviceIntSize) {
783        self.platform_window_for_webview(&webview)
784            .request_resize(&webview, requested_outer_size);
785    }
786
787    fn request_authentication(
788        &self,
789        webview: WebView,
790        authentication_request: AuthenticationRequest,
791    ) {
792        self.platform_window_for_webview(&webview)
793            .show_http_authentication_dialog(webview.id(), authentication_request);
794    }
795
796    fn request_create_new(&self, parent_webview: WebView, request: CreateNewWebViewRequest) {
797        let window = self.window_for_webview(&parent_webview);
798
799        // When WebDriver wants to open a new WebView, do this in a new window. Normally, servoshell
800        // throttles WebViews that are hidden, but WebDriver expects all opened WebViews to stay
801        // active. Using a separate window ensures this.
802        if self.servoshell_preferences.webdriver_port.get().is_some() {
803            window.queue_user_interface_command(UserInterfaceCommand::NewWindow(
804                TopLevelWebViewCreationRequest::WithCreateRequest(request),
805            ));
806            return;
807        }
808
809        let platform_window = window.platform_window();
810        let webview = request
811            .builder(platform_window.rendering_context())
812            .hidpi_scale_factor(platform_window.hidpi_scale_factor())
813            .delegate(parent_webview.delegate())
814            .build();
815
816        window.add_webview(webview.clone());
817        window.activate_webview(webview.id());
818    }
819
820    fn notify_closed(&self, webview: WebView) {
821        let webview_id = webview.id();
822        self.pending_webdriver_events
823            .borrow_mut()
824            .retain(|_, (pending_webview_id, _)| *pending_webview_id != webview_id);
825        self.window_for_webview(&webview).close_webview(webview_id)
826    }
827
828    fn notify_input_event_handled(
829        &self,
830        webview: WebView,
831        id: InputEventId,
832        result: InputEventResult,
833    ) {
834        self.platform_window_for_webview(&webview)
835            .notify_input_event_handled(&webview, id, result);
836
837        self.pending_webdriver_events.borrow_mut().remove(&id);
838    }
839
840    fn notify_cursor_changed(&self, webview: WebView, cursor: servo::Cursor) {
841        self.platform_window_for_webview(&webview)
842            .set_cursor(cursor);
843    }
844
845    fn notify_load_status_changed(&self, webview: WebView, status: LoadStatus) {
846        self.window_for_webview(&webview).set_needs_update();
847
848        let webdriver_status = match status {
849            LoadStatus::Complete => WebDriverLoadStatus::Complete,
850            LoadStatus::Stopped => WebDriverLoadStatus::NavigationStop,
851            _ => return,
852        };
853
854        if let Some(sender) = self
855            .webdriver_senders
856            .borrow_mut()
857            .load_status_senders
858            .remove(&webview.id())
859        {
860            let _ = sender.send(webdriver_status);
861        }
862        self.maybe_request_screenshot(webview);
863    }
864
865    fn notify_fullscreen_state_changed(&self, webview: WebView, fullscreen_state: bool) {
866        self.platform_window_for_webview(&webview)
867            .set_fullscreen(fullscreen_state);
868    }
869
870    fn show_bluetooth_device_dialog(
871        &self,
872        webview: WebView,
873        request: BluetoothDeviceSelectionRequest,
874    ) {
875        self.platform_window_for_webview(&webview)
876            .show_bluetooth_device_dialog(webview.id(), request);
877    }
878
879    fn request_permission(&self, webview: WebView, permission_request: PermissionRequest) {
880        self.platform_window_for_webview(&webview)
881            .show_permission_dialog(webview.id(), permission_request);
882    }
883
884    fn notify_new_frame_ready(&self, webview: WebView) {
885        self.window_for_webview(&webview).set_needs_repaint();
886    }
887
888    fn show_embedder_control(&self, webview: WebView, embedder_control: EmbedderControl) {
889        if self.servoshell_preferences.webdriver_port.get().is_some() {
890            if matches!(&embedder_control, EmbedderControl::SimpleDialog(..)) {
891                self.interrupt_webdriver_script_evaluation();
892
893                // Dialogs block the page load, so need need to notify WebDriver
894                if let Some(sender) = self
895                    .webdriver_senders
896                    .borrow_mut()
897                    .load_status_senders
898                    .get(&webview.id())
899                {
900                    let _ = sender.send(WebDriverLoadStatus::Blocked);
901                };
902            }
903
904            self.webdriver_embedder_controls
905                .show_embedder_control(webview.id(), embedder_control);
906            return;
907        }
908
909        self.window_for_webview(&webview)
910            .show_embedder_control(webview, embedder_control);
911    }
912
913    fn hide_embedder_control(&self, webview: WebView, embedder_control_id: EmbedderControlId) {
914        if self.servoshell_preferences.webdriver_port.get().is_some() {
915            self.webdriver_embedder_controls
916                .hide_embedder_control(webview.id(), embedder_control_id);
917            return;
918        }
919
920        self.window_for_webview(&webview)
921            .hide_embedder_control(webview, embedder_control_id);
922    }
923
924    fn notify_favicon_changed(&self, webview: WebView) {
925        self.window_for_webview(&webview)
926            .notify_favicon_changed(webview);
927    }
928
929    fn notify_crashed(&self, webview: WebView, reason: String, backtrace: Option<String>) {
930        self.platform_window_for_webview(&webview)
931            .notify_crashed(webview, reason, backtrace);
932    }
933
934    fn show_console_message(&self, webview: WebView, level: ConsoleLogLevel, message: String) {
935        self.platform_window_for_webview(&webview)
936            .show_console_message(level, &message);
937    }
938
939    fn notify_accessibility_tree_update(
940        &self,
941        webview: WebView,
942        tree_update: accesskit::TreeUpdate,
943    ) {
944        self.platform_window_for_webview(&webview)
945            .notify_accessibility_tree_update(webview, tree_update);
946    }
947}
948
949struct ServoShellServoDelegate;
950impl ServoDelegate for ServoShellServoDelegate {
951    fn notify_devtools_server_started(&self, port: u16, _token: String) {
952        info!("Devtools Server running on port {port}");
953    }
954
955    fn request_devtools_connection(&self, request: AllowOrDenyRequest) {
956        request.allow();
957    }
958
959    fn notify_error(&self, error: ServoError) {
960        error!("Saw Servo error: {error:?}!");
961    }
962
963    fn show_console_message(&self, level: ConsoleLogLevel, message: String) {
964        // For messages without a WebView context, apply platform-specific behavior
965        #[cfg(not(any(target_os = "android", target_env = "ohos")))]
966        println!("{message}");
967        log::log!(level.into(), "{message}");
968    }
969}