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

@ -13,8 +13,10 @@ pub mod resources;
pub mod user_content_manager;
mod webdriver;
use std::collections::HashMap;
use std::ffi::c_void;
use std::fmt::{Debug, Display, Error, Formatter};
use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
@ -372,6 +374,12 @@ pub enum EmbedderMsg {
DeviceIntRect,
IpcSender<Option<usize>>,
),
/// Inform the embedding layer that a JavaScript evaluation has
/// finished with the given result.
FinishJavaScriptEvaluation(
JavaScriptEvaluationId,
Result<JSValue, JavaScriptEvaluationError>,
),
}
impl Debug for EmbedderMsg {
@ -857,3 +865,59 @@ impl Display for FocusSequenceNumber {
Display::fmt(&self.0, f)
}
}
/// An identifier for a particular JavaScript evaluation that is used to track the
/// evaluation from the embedding layer to the script layer and then back.
#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct JavaScriptEvaluationId(pub usize);
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum JSValue {
Undefined,
Null,
Boolean(bool),
Number(f64),
String(String),
Element(String),
Frame(String),
Window(String),
Array(Vec<JSValue>),
Object(HashMap<String, JSValue>),
}
impl From<&WebDriverJSValue> for JSValue {
fn from(value: &WebDriverJSValue) -> Self {
match value {
WebDriverJSValue::Undefined => Self::Undefined,
WebDriverJSValue::Null => Self::Null,
WebDriverJSValue::Boolean(value) => Self::Boolean(*value),
WebDriverJSValue::Int(value) => Self::Number(*value as f64),
WebDriverJSValue::Number(value) => Self::Number(*value),
WebDriverJSValue::String(value) => Self::String(value.clone()),
WebDriverJSValue::Element(web_element) => Self::Element(web_element.0.clone()),
WebDriverJSValue::Frame(web_frame) => Self::Frame(web_frame.0.clone()),
WebDriverJSValue::Window(web_window) => Self::Window(web_window.0.clone()),
WebDriverJSValue::ArrayLike(vector) => {
Self::Array(vector.iter().map(Into::into).collect())
},
WebDriverJSValue::Object(map) => Self::Object(
map.iter()
.map(|(key, value)| (key.clone(), value.into()))
.collect(),
),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum JavaScriptEvaluationError {
/// An internal Servo error prevented the JavaSript evaluation from completing properly.
/// This indicates a bug in Servo.
InternalError,
/// The `WebView` on which this evaluation request was triggered is not ready. This might
/// happen if the `WebView`'s `Document` is changing due to ongoing load events, for instance.
WebViewNotReady,
/// The script executed successfully, but Servo could not serialize the JavaScript return
/// value into a [`JSValue`].
SerializationError,
}