use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use dpi::PhysicalSize;
use embedder_traits::EventLoopWaker;
use paint_api::rendering_context::{RenderingContext, SoftwareRenderingContext};
use servo::{
EmbedderControl, InputEvent, JSValue, JavaScriptEvaluationError, LoadStatus, MouseButton,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Preferences, Servo, ServoBuilder,
SimpleDialog, WebView, WebViewDelegate,
};
use webrender_api::units::DevicePoint;
pub struct ServoTest {
pub servo: Servo,
pub rendering_context: Rc<dyn RenderingContext>,
}
impl ServoTest {
#[allow(dead_code)] pub(crate) fn new() -> Self {
Self::new_with_builder(|builder| builder)
}
pub(crate) fn new_with_builder<F>(customize: F) -> Self
where
F: FnOnce(ServoBuilder) -> ServoBuilder,
{
let rendering_context = Rc::new(
SoftwareRenderingContext::new(PhysicalSize {
width: 500,
height: 500,
})
.expect("Could not create SoftwareRenderingContext"),
);
assert!(rendering_context.make_current().is_ok());
#[derive(Clone)]
struct EventLoopWakerImpl(Arc<AtomicBool>);
impl EventLoopWaker for EventLoopWakerImpl {
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
Box::new(self.clone())
}
fn wake(&self) {
self.0.store(true, Ordering::Relaxed);
}
}
let user_event_triggered = Arc::new(AtomicBool::new(false));
let mut preferences = Preferences::default();
preferences.network_http_proxy_uri = String::new();
preferences.network_https_proxy_uri = String::new();
let builder = ServoBuilder::default()
.preferences(preferences)
.event_loop_waker(Box::new(EventLoopWakerImpl(user_event_triggered)));
let builder = customize(builder);
Self {
servo: builder.build(),
rendering_context,
}
}
pub fn servo(&self) -> &Servo {
&self.servo
}
pub fn spin(&self, callback: impl Fn() -> bool + 'static) {
while callback() {
self.servo.spin_event_loop();
std::thread::sleep(Duration::from_millis(1));
}
}
}
#[derive(Default)]
pub(crate) struct WebViewDelegateImpl {
pub(crate) url_changed: Cell<bool>,
pub(crate) cursor_changed: Cell<bool>,
pub(crate) new_frame_ready: Cell<bool>,
pub(crate) load_status_changed: Cell<bool>,
pub(crate) controls_shown: RefCell<Vec<EmbedderControl>>,
pub(crate) active_dialog: RefCell<Option<SimpleDialog>>,
pub(crate) number_of_controls_shown: Cell<usize>,
pub(crate) number_of_controls_hidden: Cell<usize>,
pub(crate) last_accesskit_tree_updates: RefCell<Vec<accesskit::TreeUpdate>>,
}
#[allow(dead_code)] impl WebViewDelegateImpl {
pub(crate) fn reset(&self) {
self.url_changed.set(false);
self.cursor_changed.set(false);
self.new_frame_ready.set(false);
self.controls_shown.borrow_mut().clear();
self.number_of_controls_shown.set(0);
self.number_of_controls_hidden.set(0);
self.last_accesskit_tree_updates.borrow_mut().clear();
}
}
impl WebViewDelegate for WebViewDelegateImpl {
fn notify_url_changed(&self, _webview: servo::WebView, _url: url::Url) {
self.url_changed.set(true);
}
fn notify_cursor_changed(&self, _webview: WebView, _: servo::Cursor) {
self.cursor_changed.set(true);
}
fn notify_new_frame_ready(&self, webview: WebView) {
self.new_frame_ready.set(true);
webview.paint();
}
fn notify_load_status_changed(&self, _webview: WebView, status: LoadStatus) {
if status == LoadStatus::Complete {
self.load_status_changed.set(true);
}
}
fn show_embedder_control(&self, _: WebView, embedder_control: EmbedderControl) {
if let EmbedderControl::SimpleDialog(simple_dialog) = embedder_control {
let previous_dialog = self.active_dialog.borrow_mut().replace(simple_dialog);
assert!(previous_dialog.is_none());
return;
}
self.controls_shown.borrow_mut().push(embedder_control);
self.number_of_controls_shown
.set(self.number_of_controls_shown.get() + 1);
}
fn hide_embedder_control(&self, _webview: WebView, _control_id: servo::EmbedderControlId) {
self.number_of_controls_hidden
.set(self.number_of_controls_hidden.get() + 1);
}
fn notify_accessibility_tree_update(
&self,
_webview: WebView,
tree_update: accesskit::TreeUpdate,
) {
self.last_accesskit_tree_updates
.borrow_mut()
.push(tree_update);
}
}
#[allow(dead_code)]
pub(crate) fn click_at_point(webview: &WebView, point: DevicePoint) {
let point = point.into();
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Down,
MouseButton::Left,
point,
)));
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Up,
MouseButton::Left,
point,
)));
}
#[allow(dead_code)]
pub(crate) fn evaluate_javascript(
servo_test: &ServoTest,
webview: WebView,
script: impl ToString,
) -> Result<JSValue, JavaScriptEvaluationError> {
let load_webview = webview.clone();
let _ = servo_test.spin(move || load_webview.load_status() != LoadStatus::Complete);
let saved_result = Rc::new(RefCell::new(None));
let callback_result = saved_result.clone();
webview.evaluate_javascript(script, move |result| {
*callback_result.borrow_mut() = Some(result)
});
let spin_result = saved_result.clone();
let _ = servo_test.spin(move || spin_result.borrow().is_none());
(*saved_result.borrow())
.clone()
.expect("Should have waited until value available")
}
#[allow(dead_code)]
pub(crate) fn show_webview_and_wait_for_rendering_to_be_ready(
servo_test: &ServoTest,
webview: &WebView,
delegate: &Rc<WebViewDelegateImpl>,
) {
let load_webview = webview.clone();
servo_test.spin(move || load_webview.load_status() != LoadStatus::Complete);
delegate.reset();
let _ = evaluate_javascript(
&servo_test,
webview.clone(),
"requestAnimationFrame(() => { \
document.body.style.background = 'red'; \
document.body.style.background = 'green'; \
});",
);
let captured_delegate = delegate.clone();
servo_test.spin(move || !captured_delegate.new_frame_ready.get());
}