diff options
Diffstat (limited to 'components/servo')
-rw-r--r-- | components/servo/javascript_evaluator.rs | 65 | ||||
-rw-r--r-- | components/servo/lib.rs | 16 | ||||
-rw-r--r-- | components/servo/tests/webview.rs | 82 | ||||
-rw-r--r-- | components/servo/webview.rs | 23 |
4 files changed, 180 insertions, 6 deletions
diff --git a/components/servo/javascript_evaluator.rs b/components/servo/javascript_evaluator.rs new file mode 100644 index 00000000000..41cb5539b05 --- /dev/null +++ b/components/servo/javascript_evaluator.rs @@ -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) + } +} diff --git a/components/servo/lib.rs b/components/servo/lib.rs index b8210450cd8..d2c65429ba9 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -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 diff --git a/components/servo/tests/webview.rs b/components/servo/tests/webview.rs index 89fbe2025a3..41900015b94 100644 --- a/components/servo/tests/webview.rs +++ b/components/servo/tests/webview.rs @@ -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 diff --git a/components/servo/webview.rs b/components/servo/webview.rs index 95eb6dfd154..10786ad8b69 100644 --- a/components/servo/webview.rs +++ b/components/servo/webview.rs @@ -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 |