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, RefCell};
8use std::collections::HashMap;
9use std::rc::Rc;
10
11use crossbeam_channel::{Receiver, Sender};
12use euclid::Rect;
13use image::{DynamicImage, ImageFormat, RgbaImage};
14use log::{error, info, warn};
15use servo::base::generic_channel::GenericSender;
16use servo::base::id::WebViewId;
17use servo::ipc_channel::ipc::IpcSender;
18use servo::style_traits::CSSPixel;
19use servo::{
20    InputEvent, InputEventId, ScreenshotCaptureError, Servo, TraversalId, WebDriverCommandMsg,
21    WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand, WebDriverSenders, WebView,
22};
23use url::Url;
24
25use crate::prefs::ServoShellPreferences;
26
27pub struct RunningAppStateBase {
28    pub(crate) webdriver_senders: RefCell<WebDriverSenders>,
29
30    /// A [`HashMap`] of pending WebDriver events. It is the WebDriver embedder's responsibility
31    /// to inform the WebDriver server when the event has been fully handled. This map is used
32    /// to report back to WebDriver when that happens.
33    pub(crate) pending_webdriver_events: RefCell<HashMap<InputEventId, Sender<()>>>,
34
35    /// A [`Receiver`] for receiving commands from a running WebDriver server, if WebDriver
36    /// was enabled.
37    pub(crate) webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
38
39    /// servoshell specific preferences created during startup of the application.
40    pub(crate) servoshell_preferences: ServoShellPreferences,
41
42    /// A handle to the Servo instance.
43    pub(crate) servo: Servo,
44
45    /// Whether or not the application has achieved stable image output. This is used
46    /// for the `exit_after_stable_image` option.
47    pub(crate) achieved_stable_image: Rc<Cell<bool>>,
48}
49
50impl RunningAppStateBase {
51    pub fn new(
52        servoshell_preferences: ServoShellPreferences,
53        servo: Servo,
54        webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
55    ) -> Self {
56        Self {
57            webdriver_senders: RefCell::default(),
58            pending_webdriver_events: Default::default(),
59            webdriver_receiver,
60            servoshell_preferences,
61            servo,
62            achieved_stable_image: Default::default(),
63        }
64    }
65}
66
67pub trait RunningAppStateTrait {
68    fn base(&self) -> &RunningAppStateBase;
69
70    #[allow(dead_code)]
71    fn base_mut(&mut self) -> &mut RunningAppStateBase;
72
73    fn webview_by_id(&self, id: WebViewId) -> Option<WebView>;
74
75    fn servoshell_preferences(&self) -> &ServoShellPreferences {
76        &self.base().servoshell_preferences
77    }
78
79    fn servo(&self) -> &Servo {
80        &self.base().servo
81    }
82
83    fn webdriver_receiver(&self) -> Option<&Receiver<WebDriverCommandMsg>> {
84        self.base().webdriver_receiver.as_ref()
85    }
86
87    fn set_pending_traversal(
88        &self,
89        traversal_id: TraversalId,
90        sender: GenericSender<WebDriverLoadStatus>,
91    ) {
92        self.base()
93            .webdriver_senders
94            .borrow_mut()
95            .pending_traversals
96            .insert(traversal_id, sender);
97    }
98
99    fn set_load_status_sender(
100        &self,
101        webview_id: WebViewId,
102        sender: GenericSender<WebDriverLoadStatus>,
103    ) {
104        self.base()
105            .webdriver_senders
106            .borrow_mut()
107            .load_status_senders
108            .insert(webview_id, sender);
109    }
110
111    fn remove_load_status_sender(&self, webview_id: WebViewId) {
112        self.base()
113            .webdriver_senders
114            .borrow_mut()
115            .load_status_senders
116            .remove(&webview_id);
117    }
118
119    fn set_script_command_interrupt_sender(&self, sender: Option<IpcSender<WebDriverJSResult>>) {
120        self.base()
121            .webdriver_senders
122            .borrow_mut()
123            .script_evaluation_interrupt_sender = sender;
124    }
125
126    fn handle_webdriver_input_event(
127        &self,
128        webview_id: WebViewId,
129        input_event: InputEvent,
130        response_sender: Option<Sender<()>>,
131    ) {
132        if let Some(webview) = self.webview_by_id(webview_id) {
133            let event_id = webview.notify_input_event(input_event);
134            if let Some(response_sender) = response_sender {
135                self.base()
136                    .pending_webdriver_events
137                    .borrow_mut()
138                    .insert(event_id, response_sender);
139            }
140        } else {
141            error!("Could not find WebView ({webview_id:?}) for WebDriver event: {input_event:?}");
142        };
143    }
144
145    fn handle_webdriver_screenshot(
146        &self,
147        webview_id: WebViewId,
148        rect: Option<Rect<f32, CSSPixel>>,
149        result_sender: Sender<Result<RgbaImage, ScreenshotCaptureError>>,
150    ) {
151        if let Some(webview) = self.webview_by_id(webview_id) {
152            let rect = rect.map(|rect| rect.to_box2d().into());
153            webview.take_screenshot(rect, move |result| {
154                if let Err(error) = result_sender.send(result) {
155                    warn!("Failed to send response to TakeScreenshot: {error}");
156                }
157            });
158        } else if let Err(error) =
159            result_sender.send(Err(ScreenshotCaptureError::WebViewDoesNotExist))
160        {
161            error!("Failed to send response to TakeScreenshot: {error}");
162        }
163    }
164
165    fn handle_webdriver_script_command(&self, script_command: &WebDriverScriptCommand) {
166        match script_command {
167            WebDriverScriptCommand::ExecuteScript(_webview_id, response_sender) |
168            WebDriverScriptCommand::ExecuteAsyncScript(_webview_id, response_sender) => {
169                // Give embedder a chance to interrupt the script command.
170                // Webdriver only handles 1 script command at a time, so we can
171                // safely set a new interrupt sender and remove the previous one here.
172                self.set_script_command_interrupt_sender(Some(response_sender.clone()));
173            },
174            WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
175                self.set_load_status_sender(*webview_id, load_status_sender.clone());
176            },
177            WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
178                self.remove_load_status_sender(*webview_id);
179            },
180            _ => {
181                self.set_script_command_interrupt_sender(None);
182            },
183        }
184    }
185
186    fn handle_webdriver_load_url(
187        &self,
188        webview_id: WebViewId,
189        url: Url,
190        load_status_sender: GenericSender<WebDriverLoadStatus>,
191    ) {
192        if let Some(webview) = self.webview_by_id(webview_id) {
193            info!("Loading URL in webview {}: {}", webview_id, url);
194            self.set_load_status_sender(webview_id, load_status_sender);
195            webview.load(url);
196        }
197    }
198
199    /// If we are exiting after achieving a stable image or we want to save the display of the
200    /// [`WebView`] to an image file, request a screenshot of the [`WebView`].
201    fn maybe_request_screenshot(&self, webview: WebView) {
202        let output_path = self.servoshell_preferences().output_image_path.clone();
203        if !self.servoshell_preferences().exit_after_stable_image && output_path.is_none() {
204            return;
205        }
206
207        // Never request more than a single screenshot for now.
208        let achieved_stable_image = self.base().achieved_stable_image.clone();
209        if achieved_stable_image.get() {
210            return;
211        }
212
213        webview.take_screenshot(None, move |image| {
214            achieved_stable_image.set(true);
215
216            let Some(output_path) = output_path else {
217                return;
218            };
219
220            let image = match image {
221                Ok(image) => image,
222                Err(error) => {
223                    error!("Could not take screenshot: {error:?}");
224                    return;
225                },
226            };
227
228            let image_format = ImageFormat::from_path(&output_path).unwrap_or(ImageFormat::Png);
229            if let Err(error) =
230                DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format)
231            {
232                error!("Failed to save screenshot: {error}.");
233            }
234        });
235    }
236}