servoshell/
running_app_state.rs1use 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 pub(crate) pending_webdriver_events: RefCell<HashMap<InputEventId, Sender<()>>>,
34
35 pub(crate) webdriver_receiver: Option<Receiver<WebDriverCommandMsg>>,
38
39 pub(crate) servoshell_preferences: ServoShellPreferences,
41
42 pub(crate) servo: Servo,
44
45 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 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 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 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}