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

@ -129,9 +129,10 @@ use embedder_traits::resources::{self, Resource};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy,
FocusSequenceNumber, ImeEvent, InputEvent, MediaSessionActionType, MediaSessionEvent,
MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, Theme,
ViewportDetails, WebDriverCommandMsg, WebDriverLoadStatus,
FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError,
JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState,
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
WebDriverLoadStatus,
};
use euclid::Size2D;
use euclid::default::Size2D as UntypedSize2D;
@ -1477,6 +1478,52 @@ where
EmbedderToConstellationMessage::PaintMetric(pipeline_id, paint_metric_event) => {
self.handle_paint_metric(pipeline_id, paint_metric_event);
},
EmbedderToConstellationMessage::EvaluateJavaScript(
webview_id,
evaluation_id,
script,
) => {
self.handle_evaluate_javascript(webview_id, evaluation_id, script);
},
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_evaluate_javascript(
&mut self,
webview_id: WebViewId,
evaluation_id: JavaScriptEvaluationId,
script: String,
) {
let browsing_context_id = BrowsingContextId::from(webview_id);
let Some(pipeline) = self
.browsing_contexts
.get(&browsing_context_id)
.and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
else {
self.handle_finish_javascript_evaluation(
evaluation_id,
Err(JavaScriptEvaluationError::InternalError),
);
return;
};
if pipeline
.event_loop
.send(ScriptThreadMessage::EvaluateJavaScript(
pipeline.id,
evaluation_id,
script,
))
.is_err()
{
self.handle_finish_javascript_evaluation(
evaluation_id,
Err(JavaScriptEvaluationError::InternalError),
);
}
}
@ -1817,6 +1864,9 @@ where
self.mem_profiler_chan
.send(mem::ProfilerMsg::Report(sender));
},
ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => {
self.handle_finish_javascript_evaluation(evaluation_id, result)
},
}
}
@ -3178,6 +3228,22 @@ where
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_finish_javascript_evaluation(
&mut self,
evaluation_id: JavaScriptEvaluationId,
result: Result<JSValue, JavaScriptEvaluationError>,
) {
self.embedder_proxy
.send(EmbedderMsg::FinishJavaScriptEvaluation(
evaluation_id,
result,
));
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
@ -4691,6 +4757,7 @@ where
NavigationHistoryBehavior::Replace,
);
},
// TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command
WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
Some(browsing_context) => browsing_context.pipeline_id,

View file

@ -77,6 +77,7 @@ mod from_compositor {
Self::SetWebViewThrottled(_, _) => target!("SetWebViewThrottled"),
Self::SetScrollStates(..) => target!("SetScrollStates"),
Self::PaintMetric(..) => target!("PaintMetric"),
Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"),
}
}
}
@ -176,6 +177,7 @@ mod from_script {
Self::TitleChanged(..) => target!("TitleChanged"),
Self::IFrameSizes(..) => target!("IFrameSizes"),
Self::ReportMemory(..) => target!("ReportMemory"),
Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"),
}
}
}
@ -238,6 +240,9 @@ mod from_script {
Self::ShutdownComplete => target_variant!("ShutdownComplete"),
Self::ShowNotification(..) => target_variant!("ShowNotification"),
Self::ShowSelectElementMenu(..) => target_variant!("ShowSelectElementMenu"),
Self::FinishJavaScriptEvaluation(..) => {
target_variant!("FinishJavaScriptEvaluation")
},
}
}
}

View file

@ -91,6 +91,7 @@ impl MixedMessage {
#[cfg(feature = "webgpu")]
ScriptThreadMessage::SetWebGPUPort(..) => None,
ScriptThreadMessage::SetScrollStates(id, ..) => Some(*id),
ScriptThreadMessage::EvaluateJavaScript(id, _, _) => Some(*id),
},
MixedMessage::FromScript(inner_msg) => match inner_msg {
MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => {

View file

@ -50,9 +50,9 @@ use devtools_traits::{
};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, MediaSessionActionType,
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails,
WebDriverScriptCommand,
CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent,
JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, MouseButton,
MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverScriptCommand,
};
use euclid::Point2D;
use euclid::default::Rect;
@ -156,6 +156,7 @@ use crate::script_runtime::{
};
use crate::task_queue::TaskQueue;
use crate::task_source::{SendableTaskSource, TaskSourceName};
use crate::webdriver_handlers::jsval_to_webdriver;
use crate::{devtools, webdriver_handlers};
thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = const { Cell::new(None) });
@ -1878,6 +1879,9 @@ impl ScriptThread {
ScriptThreadMessage::SetScrollStates(pipeline_id, scroll_states) => {
self.handle_set_scroll_states(pipeline_id, scroll_states)
},
ScriptThreadMessage::EvaluateJavaScript(pipeline_id, evaluation_id, script) => {
self.handle_evaluate_javascript(pipeline_id, evaluation_id, script, can_gc);
},
}
}
@ -3815,6 +3819,53 @@ impl ScriptThread {
)
}
}
fn handle_evaluate_javascript(
&self,
pipeline_id: PipelineId,
evaluation_id: JavaScriptEvaluationId,
script: String,
can_gc: CanGc,
) {
let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
let _ = self.senders.pipeline_to_constellation_sender.send((
pipeline_id,
ScriptToConstellationMessage::FinishJavaScriptEvaluation(
evaluation_id,
Err(JavaScriptEvaluationError::WebViewNotReady),
),
));
return;
};
let global_scope = window.as_global_scope();
let realm = enter_realm(global_scope);
let context = window.get_cx();
rooted!(in(*context) let mut return_value = UndefinedValue());
global_scope.evaluate_js_on_global_with_result(
&script,
return_value.handle_mut(),
ScriptFetchOptions::default_classic_script(global_scope),
global_scope.api_base_url(),
can_gc,
);
let result = match jsval_to_webdriver(
context,
global_scope,
return_value.handle(),
(&realm).into(),
can_gc,
) {
Ok(ref value) => Ok(value.into()),
Err(_) => Err(JavaScriptEvaluationError::SerializationError),
};
let _ = self.senders.pipeline_to_constellation_sender.send((
pipeline_id,
ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result),
));
}
}
impl Drop for ScriptThread {

View file

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap;
use base::id::WebViewId;
use constellation_traits::EmbedderToConstellationMessage;
use embedder_traits::{JSValue, JavaScriptEvaluationError, JavaScriptEvaluationId};
use crate::ConstellationProxy;
struct PendingEvaluation {
callback: Box<dyn FnOnce(Result<JSValue, JavaScriptEvaluationError>)>,
}
pub(crate) struct JavaScriptEvaluator {
current_id: JavaScriptEvaluationId,
constellation_proxy: ConstellationProxy,
pending_evaluations: HashMap<JavaScriptEvaluationId, PendingEvaluation>,
}
impl JavaScriptEvaluator {
pub(crate) fn new(constellation_proxy: ConstellationProxy) -> Self {
Self {
current_id: JavaScriptEvaluationId(0),
constellation_proxy,
pending_evaluations: Default::default(),
}
}
fn generate_id(&mut self) -> JavaScriptEvaluationId {
let next_id = JavaScriptEvaluationId(self.current_id.0 + 1);
std::mem::replace(&mut self.current_id, next_id)
}
pub(crate) fn evaluate(
&mut self,
webview_id: WebViewId,
script: String,
callback: Box<dyn FnOnce(Result<JSValue, JavaScriptEvaluationError>)>,
) {
let evaluation_id = self.generate_id();
self.constellation_proxy
.send(EmbedderToConstellationMessage::EvaluateJavaScript(
webview_id,
evaluation_id,
script,
));
self.pending_evaluations
.insert(evaluation_id, PendingEvaluation { callback });
}
pub(crate) fn finish_evaluation(
&mut self,
evaluation_id: JavaScriptEvaluationId,
result: Result<JSValue, JavaScriptEvaluationError>,
) {
(self
.pending_evaluations
.remove(&evaluation_id)
.expect("Received request to finish unknown JavaScript evaluation.")
.callback)(result)
}
}

View file

@ -18,6 +18,7 @@
//! `WindowMethods` trait.
mod clipboard_delegate;
mod javascript_evaluator;
mod proxies;
mod responders;
mod servo_delegate;
@ -82,6 +83,7 @@ pub use gleam::gl;
use gleam::gl::RENDERER;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use javascript_evaluator::JavaScriptEvaluator;
pub use keyboard_types::*;
use layout::LayoutFactoryImpl;
use log::{Log, Metadata, Record, debug, warn};
@ -196,6 +198,9 @@ pub struct Servo {
compositor: Rc<RefCell<IOCompositor>>,
constellation_proxy: ConstellationProxy,
embedder_receiver: Receiver<EmbedderMsg>,
/// A struct that tracks ongoing JavaScript evaluations and is responsible for
/// calling the callback when the evaluation is complete.
javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>,
/// Tracks whether we are in the process of shutting down, or have shut down.
/// This is shared with `WebView`s and the `ServoRenderer`.
shutdown_state: Rc<Cell<ShutdownState>>,
@ -487,10 +492,14 @@ impl Servo {
opts.debug.convert_mouse_to_touch,
);
let constellation_proxy = ConstellationProxy::new(constellation_chan);
Self {
delegate: RefCell::new(Rc::new(DefaultServoDelegate)),
compositor: Rc::new(RefCell::new(compositor)),
constellation_proxy: ConstellationProxy::new(constellation_chan),
javascript_evaluator: Rc::new(RefCell::new(JavaScriptEvaluator::new(
constellation_proxy.clone(),
))),
constellation_proxy,
embedder_receiver,
shutdown_state,
webviews: Default::default(),
@ -738,6 +747,11 @@ impl Servo {
webview.delegate().request_unload(webview, request);
}
},
EmbedderMsg::FinishJavaScriptEvaluation(evaluation_id, result) => {
self.javascript_evaluator
.borrow_mut()
.finish_evaluation(evaluation_id, result);
},
EmbedderMsg::Keyboard(webview_id, keyboard_event) => {
if let Some(webview) = self.get_webview_handle(webview_id) {
webview

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

View file

@ -13,8 +13,8 @@ use compositing_traits::WebViewTrait;
use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
use dpi::PhysicalSize;
use embedder_traits::{
Cursor, InputEvent, LoadStatus, MediaSessionActionType, ScreenGeometry, Theme, TouchEventType,
ViewportDetails,
Cursor, InputEvent, JSValue, JavaScriptEvaluationError, LoadStatus, MediaSessionActionType,
ScreenGeometry, Theme, TouchEventType, ViewportDetails,
};
use euclid::{Point2D, Scale, Size2D};
use servo_geometry::DeviceIndependentPixel;
@ -23,6 +23,7 @@ use webrender_api::ScrollLocation;
use webrender_api::units::{DeviceIntPoint, DevicePixel, DeviceRect};
use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
use crate::javascript_evaluator::JavaScriptEvaluator;
use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate};
use crate::{ConstellationProxy, Servo, WebRenderDebugOption};
@ -75,6 +76,7 @@ pub(crate) struct WebViewInner {
pub(crate) compositor: Rc<RefCell<IOCompositor>>,
pub(crate) delegate: Rc<dyn WebViewDelegate>,
pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>,
rect: DeviceRect,
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
@ -117,9 +119,10 @@ impl WebView {
compositor: servo.compositor.clone(),
delegate: builder.delegate,
clipboard_delegate: Rc::new(DefaultClipboardDelegate),
javascript_evaluator: servo.javascript_evaluator.clone(),
rect: DeviceRect::from_origin_and_size(Point2D::origin(), size),
hidpi_scale_factor: builder.hidpi_scale_factor,
load_status: LoadStatus::Complete,
load_status: LoadStatus::Started,
url: None,
status_text: None,
page_title: None,
@ -549,6 +552,20 @@ impl WebView {
pub fn paint(&self) -> bool {
self.inner().compositor.borrow_mut().render()
}
/// Evaluate the specified string of JavaScript code. Once execution is complete or an error
/// occurs, Servo will call `callback`.
pub fn evaluate_javascript<T: ToString>(
&self,
script: T,
callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
) {
self.inner().javascript_evaluator.borrow_mut().evaluate(
self.id(),
script.to_string(),
Box::new(callback),
);
}
}
/// A structure used to expose a view of the [`WebView`] to the Servo

View file

@ -15,8 +15,8 @@ use base::id::{
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
use embedder_traits::{
AnimationState, EmbedderMsg, FocusSequenceNumber, MediaSessionEvent, TouchEventResult,
ViewportDetails,
AnimationState, EmbedderMsg, FocusSequenceNumber, JSValue, JavaScriptEvaluationError,
JavaScriptEvaluationId, MediaSessionEvent, TouchEventResult, ViewportDetails,
};
use euclid::default::Size2D as UntypedSize2D;
use http::{HeaderMap, Method};
@ -644,6 +644,11 @@ pub enum ScriptToConstellationMessage {
IFrameSizes(Vec<IFrameSizeMsg>),
/// Request results from the memory reporter.
ReportMemory(IpcSender<MemoryReportResult>),
/// Return the result of the evaluated JavaScript with the given [`JavaScriptEvaluationId`].
FinishJavaScriptEvaluation(
JavaScriptEvaluationId,
Result<JSValue, JavaScriptEvaluationError>,
),
}
impl fmt::Debug for ScriptToConstellationMessage {

View file

@ -19,8 +19,8 @@ use base::Epoch;
use base::cross_process_instant::CrossProcessInstant;
use base::id::{MessagePortId, PipelineId, WebViewId};
use embedder_traits::{
CompositorHitTestResult, Cursor, InputEvent, MediaSessionActionType, Theme, ViewportDetails,
WebDriverCommandMsg,
CompositorHitTestResult, Cursor, InputEvent, JavaScriptEvaluationId, MediaSessionActionType,
Theme, ViewportDetails, WebDriverCommandMsg,
};
use euclid::Vector2D;
pub use from_script_message::*;
@ -92,6 +92,9 @@ pub enum EmbedderToConstellationMessage {
SetScrollStates(PipelineId, Vec<ScrollState>),
/// Notify the constellation that a particular paint metric event has happened for the given pipeline.
PaintMetric(PipelineId, PaintMetricEvent),
/// Evaluate a JavaScript string in the context of a `WebView`. When execution is complete or an
/// error is encountered, a correpsonding message will be sent to the embedding layer.
EvaluateJavaScript(WebViewId, JavaScriptEvaluationId, String),
}
/// A description of a paint metric that is sent from the Servo renderer to the

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,
}

View file

@ -27,8 +27,8 @@ use crossbeam_channel::{RecvTimeoutError, Sender};
use devtools_traits::ScriptToDevtoolsControlMsg;
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
CompositorHitTestResult, FocusSequenceNumber, InputEvent, MediaSessionActionType, Theme,
ViewportDetails, WebDriverScriptCommand,
CompositorHitTestResult, FocusSequenceNumber, InputEvent, JavaScriptEvaluationId,
MediaSessionActionType, Theme, ViewportDetails, WebDriverScriptCommand,
};
use euclid::{Rect, Scale, Size2D, UnknownUnit};
use ipc_channel::ipc::{IpcReceiver, IpcSender};
@ -245,6 +245,9 @@ pub enum ScriptThreadMessage {
/// The compositor scrolled and is updating the scroll states of the nodes in the given
/// pipeline via the Constellation.
SetScrollStates(PipelineId, Vec<ScrollState>),
/// Evaluate the given JavaScript and return a result via a corresponding message
/// to the Constellation.
EvaluateJavaScript(PipelineId, JavaScriptEvaluationId, String),
}
impl fmt::Debug for ScriptThreadMessage {