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