libservo: Allow embedders to execute JavaScript scripts via the API (#35720)

This change adds a new `WebView` API `evaluate_javascript()`, which
allows embedders to
execute JavaScript code and wait for a reply asynchronously. Ongoing
script execution is
tracked by a libservo `JavaScriptEvaluator` struct, which maps an id to
the callback passed
to the `evaluate_javascript()` method. The id is used to track the
script and its execution
through the other parts of Servo.

Testing: This changes includes `WebView` unit tests.

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Narfinger 2025-05-13 14:54:18 +02:00 committed by GitHub
parent 91c4c7b998
commit 991be359a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 391 additions and 18 deletions

View file

@ -11,12 +11,14 @@
mod common;
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use anyhow::ensure;
use common::{ServoTest, run_api_tests};
use servo::{WebViewBuilder, WebViewDelegate};
use servo::{
JSValue, JavaScriptEvaluationError, LoadStatus, WebView, WebViewBuilder, WebViewDelegate,
};
#[derive(Default)]
struct WebViewDelegateImpl {
@ -44,6 +46,81 @@ fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
Ok(())
}
fn evaluate_javascript(
servo_test: &ServoTest,
webview: WebView,
script: impl ToString,
) -> Result<JSValue, JavaScriptEvaluationError> {
let load_webview = webview.clone();
let _ = servo_test.spin(move || Ok(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 || Ok(spin_result.borrow().is_none()));
(*saved_result.borrow())
.clone()
.expect("Should have waited until value available")
}
fn test_evaluate_javascript_basic(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
let delegate = Rc::new(WebViewDelegateImpl::default());
let webview = WebViewBuilder::new(servo_test.servo())
.delegate(delegate.clone())
.build();
let result = evaluate_javascript(servo_test, webview.clone(), "undefined");
ensure!(result == Ok(JSValue::Undefined));
let result = evaluate_javascript(servo_test, webview.clone(), "null");
ensure!(result == Ok(JSValue::Null));
let result = evaluate_javascript(servo_test, webview.clone(), "42");
ensure!(result == Ok(JSValue::Number(42.0)));
let result = evaluate_javascript(servo_test, webview.clone(), "3 + 4");
ensure!(result == Ok(JSValue::Number(7.0)));
let result = evaluate_javascript(servo_test, webview.clone(), "'abc' + 'def'");
ensure!(result == Ok(JSValue::String("abcdef".into())));
let result = evaluate_javascript(servo_test, webview.clone(), "let foo = {blah: 123}; foo");
ensure!(matches!(result, Ok(JSValue::Object(_))));
if let Ok(JSValue::Object(values)) = result {
ensure!(values.len() == 1);
ensure!(values.get("blah") == Some(&JSValue::Number(123.0)));
}
let result = evaluate_javascript(servo_test, webview.clone(), "[1, 2, 3, 4]");
let expected = JSValue::Array(vec![
JSValue::Number(1.0),
JSValue::Number(2.0),
JSValue::Number(3.0),
JSValue::Number(4.0),
]);
ensure!(result == Ok(expected));
let result = evaluate_javascript(servo_test, webview.clone(), "window");
ensure!(matches!(result, Ok(JSValue::Window(..))));
let result = evaluate_javascript(servo_test, webview.clone(), "document.body");
ensure!(matches!(result, Ok(JSValue::Element(..))));
let result = evaluate_javascript(
servo_test,
webview.clone(),
"document.body.innerHTML += '<iframe>'; frames[0]",
);
ensure!(matches!(result, Ok(JSValue::Frame(..))));
Ok(())
}
fn test_create_webview_and_immediately_drop_webview_before_shutdown(
servo_test: &ServoTest,
) -> Result<(), anyhow::Error> {
@ -54,6 +131,7 @@ fn test_create_webview_and_immediately_drop_webview_before_shutdown(
fn main() {
run_api_tests!(
test_create_webview,
test_evaluate_javascript_basic,
// This test needs to be last, as it tests creating and dropping
// a WebView right before shutdown.
test_create_webview_and_immediately_drop_webview_before_shutdown