aboutsummaryrefslogtreecommitdiffstats
path: root/components/servo
diff options
context:
space:
mode:
Diffstat (limited to 'components/servo')
-rw-r--r--components/servo/examples/winit_minimal.rs153
-rw-r--r--components/servo/lib.rs421
-rw-r--r--components/servo/proxies.rs6
-rw-r--r--components/servo/servo_delegate.rs47
-rw-r--r--components/servo/webview.rs277
-rw-r--r--components/servo/webview_delegate.rs262
6 files changed, 997 insertions, 169 deletions
diff --git a/components/servo/examples/winit_minimal.rs b/components/servo/examples/winit_minimal.rs
index 8a7ea1a78d5..d7a2a43584f 100644
--- a/components/servo/examples/winit_minimal.rs
+++ b/components/servo/examples/winit_minimal.rs
@@ -2,19 +2,19 @@
* 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::cell::Cell;
+use std::cell::{Cell, RefCell};
use std::error::Error;
use std::rc::Rc;
-use compositing::windowing::{AnimationState, EmbedderEvent, EmbedderMethods, WindowMethods};
-use embedder_traits::EmbedderMsg;
+use compositing::windowing::{AnimationState, EmbedderMethods, WindowMethods};
use euclid::{Point2D, Scale, Size2D};
-use servo::{Servo, WebView};
+use servo::{Servo, TouchEventType, WebView};
use servo_geometry::DeviceIndependentPixel;
use surfman::{Connection, SurfaceType};
use tracing::warn;
use url::Url;
-use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel};
+use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel, LayoutVector2D};
+use webrender_api::ScrollLocation;
use webrender_traits::SurfmanRenderingContext;
use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize};
@@ -34,20 +34,48 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut app = App::new(&event_loop);
event_loop.run_app(&mut app)?;
- if let App::Running { servo, .. } = app {
- servo.deinit();
+ if let App::Running(state) = app {
+ if let Some(state) = Rc::into_inner(state) {
+ state.servo.deinit();
+ }
}
Ok(())
}
+struct AppState {
+ window_delegate: Rc<WindowDelegate>,
+ servo: Servo,
+ webviews: RefCell<Vec<WebView>>,
+}
+
+impl ::servo::WebViewDelegate for AppState {
+ fn notify_ready_to_show(&self, webview: WebView) {
+ let rect = self
+ .window_delegate
+ .get_coordinates()
+ .get_viewport()
+ .to_f32();
+ webview.focus();
+ webview.move_resize(rect);
+ webview.raise_to_top(true);
+ }
+
+ fn notify_new_frame_ready(&self, _: WebView) {
+ self.servo.present();
+ }
+
+ fn request_open_auxiliary_webview(&self, parent_webview: WebView) -> Option<WebView> {
+ let webview = self.servo.new_auxiliary_webview();
+ webview.set_delegate(parent_webview.delegate());
+ self.webviews.borrow_mut().push(webview.clone());
+ Some(webview)
+ }
+}
+
enum App {
Initial(Waker),
- Running {
- window_delegate: Rc<WindowDelegate>,
- servo: Servo,
- webviews: Vec<WebView>,
- },
+ Running(Rc<AppState>),
}
impl App {
@@ -103,15 +131,20 @@ impl ApplicationHandler<WakerEvent> for App {
compositing::CompositeTarget::ContextFbo,
);
servo.setup_logging();
- let webviews = vec![servo.new_webview(
- Url::parse("https://demo.servo.org/experiments/twgl-tunnel/")
- .expect("Guaranteed by argument"),
- )];
- *self = Self::Running {
+
+ let app_state = Rc::new(AppState {
window_delegate,
servo,
- webviews,
- };
+ webviews: Default::default(),
+ });
+
+ // Make a new WebView and assign the `AppState` as the delegate.
+ let url = Url::parse("https://servo.org").expect("Guaranteed by argument");
+ let webview = app_state.servo.new_webview(url);
+ webview.set_delegate(app_state.clone());
+ app_state.webviews.borrow_mut().push(webview);
+
+ *self = Self::Running(app_state);
}
}
@@ -121,69 +154,41 @@ impl ApplicationHandler<WakerEvent> for App {
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
- if let Self::Running {
- window_delegate,
- servo,
- webviews,
- } = self
- {
- for message in servo.get_events().collect::<Vec<_>>() {
- match message {
- // FIXME: rust-analyzer autocompletes this as top_level_browsing_context_id
- EmbedderMsg::WebViewOpened(webview_id) => {
- // TODO: We currently assume `webview` refers to the same webview as `_webview_id`
- let rect = window_delegate.get_coordinates().get_viewport().to_f32();
- if let Some(webview) =
- webviews.iter().find(|webview| webview.id() == webview_id)
- {
- webview.focus();
- webview.move_resize(rect);
- webview.raise_to_top(true);
- }
- },
- EmbedderMsg::AllowOpeningWebView(_, webview_id_sender) => {
- let webview = servo.new_auxiliary_webview();
- let _ = webview_id_sender.send(Some(webview.id()));
- webviews.push(webview);
- },
- EmbedderMsg::AllowNavigationRequest(_, pipeline_id, _) => {
- servo.handle_events([EmbedderEvent::AllowNavigationResponse(
- pipeline_id,
- true,
- )]);
- },
- _ => {},
- }
- }
- // FIXME: still needed for the compositor to actually run
- servo.handle_events([]);
+ if let Self::Running(state) = self {
+ state.servo.spin_event_loop();
}
+
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
},
WindowEvent::RedrawRequested => {
- if let Self::Running {
- window_delegate,
- servo,
- ..
- } = self
- {
- servo.present();
- window_delegate.window.request_redraw();
+ if let Self::Running(state) = self {
+ state.webviews.borrow().last().unwrap().composite();
+ state.servo.present();
}
},
- WindowEvent::MouseInput { .. } => {
- // When the window is clicked, close the last webview by dropping its handle,
- // then show the next most recently opened webview.
- //
- // TODO: Test closing webviews a better way, so that we can use mouse input to test
- // input handling.
- if let Self::Running { webviews, .. } = self {
- let _ = webviews.pop();
- match webviews.last() {
- Some(last) => last.show(true),
- None => event_loop.exit(),
+ WindowEvent::MouseWheel { .. } => {
+ if let Self::Running(state) = self {
+ if let Some(webview) = state.webviews.borrow().last() {
+ webview.notify_scroll_event(
+ ScrollLocation::Delta(LayoutVector2D::new(0., -20.)),
+ DeviceIntPoint::new(10, 10),
+ TouchEventType::Down,
+ );
+ }
+ }
+ },
+ WindowEvent::KeyboardInput { event, .. } => {
+ // When pressing 'q' close the latest WebView, then show the next most recently
+ // opened view or quit when none are left.
+ if event.logical_key.to_text() == Some("q") {
+ if let Self::Running(state) = self {
+ let _ = state.webviews.borrow_mut().pop();
+ match state.webviews.borrow().last() {
+ Some(last) => last.show(true),
+ None => event_loop.exit(),
+ }
}
}
},
diff --git a/components/servo/lib.rs b/components/servo/lib.rs
index b21445fda84..7e961f35f20 100644
--- a/components/servo/lib.rs
+++ b/components/servo/lib.rs
@@ -18,19 +18,21 @@
//! `WindowMethods` trait.
mod proxies;
+mod servo_delegate;
mod webview;
+mod webview_delegate;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::max;
+use std::collections::HashMap;
use std::path::PathBuf;
-use std::rc::Rc;
+use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use std::thread;
-use std::vec::Drain;
pub use base::id::TopLevelBrowsingContextId;
-use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId};
+use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId, WebViewId};
use bluetooth::BluetoothThreadFactory;
use bluetooth_traits::BluetoothRequest;
use canvas::canvas_paint_thread::CanvasPaintThread;
@@ -86,8 +88,10 @@ use script_traits::{ScriptToConstellationChan, WindowSizeData};
use servo_config::opts::Opts;
use servo_config::prefs::Preferences;
use servo_config::{opts, pref, prefs};
+use servo_delegate::DefaultServoDelegate;
use servo_media::player::context::GlContext;
use servo_media::ServoMedia;
+use servo_url::ServoUrl;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use surfman::platform::generic::multi::connection::NativeConnection as LinuxNativeConnection;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
@@ -106,6 +110,7 @@ use webrender_traits::{
CrossProcessCompositorApi, WebrenderExternalImageHandlers, WebrenderExternalImageRegistry,
WebrenderImageHandlerType,
};
+use webview::WebViewInner;
#[cfg(feature = "webxr")]
pub use webxr;
pub use {
@@ -117,7 +122,9 @@ pub use {
};
use crate::proxies::ConstellationProxy;
+pub use crate::servo_delegate::{ServoDelegate, ServoError};
pub use crate::webview::WebView;
+pub use crate::webview_delegate::{AllowOrDenyRequest, NavigationRequest, WebViewDelegate};
#[cfg(feature = "webdriver")]
fn webdriver(port: u16, constellation: Sender<ConstellationMsg>) {
@@ -187,10 +194,16 @@ mod media_platform {
/// loop to pump messages between the embedding application and
/// various browser components.
pub struct Servo {
+ delegate: Rc<dyn ServoDelegate>,
compositor: Rc<RefCell<IOCompositor>>,
constellation_proxy: ConstellationProxy,
embedder_receiver: Receiver<EmbedderMsg>,
- messages_for_embedder: Vec<EmbedderMsg>,
+ messages_for_embedder: RefCell<Vec<EmbedderMsg>>,
+ /// A map [`WebView`]s that are managed by this [`Servo`] instance. These are stored
+ /// as `Weak` references so that the embedding application can control their lifetime.
+ /// When accessed, `Servo` will be reponsible for cleaning up the invalid `Weak`
+ /// references.
+ webviews: RefCell<HashMap<WebViewId, Weak<RefCell<WebViewInner>>>>,
/// For single-process Servo instances, this field controls the initialization
/// and deinitialization of the JS Engine. Multiprocess Servo instances have their
/// own instance that exists in the content process instead.
@@ -527,14 +540,24 @@ impl Servo {
);
Servo {
+ delegate: Rc::new(DefaultServoDelegate),
compositor: Rc::new(RefCell::new(compositor)),
constellation_proxy: ConstellationProxy::new(constellation_chan),
embedder_receiver,
- messages_for_embedder: Vec::new(),
+ messages_for_embedder: Default::default(),
+ webviews: Default::default(),
_js_engine_setup: js_engine_setup,
}
}
+ pub fn delegate(&self) -> Rc<dyn ServoDelegate> {
+ self.delegate.clone()
+ }
+
+ pub fn set_delegate(&mut self, delegate: Rc<dyn ServoDelegate>) {
+ self.delegate = delegate;
+ }
+
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
fn get_native_media_display_and_gl_context(
rendering_context: &Rc<dyn RenderingContext>,
@@ -678,8 +701,8 @@ impl Servo {
}
},
- EmbedderEvent::LoadUrl(top_level_browsing_context_id, url) => {
- let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url);
+ EmbedderEvent::LoadUrl(webview_id, url) => {
+ let msg = ConstellationMsg::LoadUrl(webview_id, url);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!("Sending load url to constellation failed ({:?}).", e);
}
@@ -742,6 +765,7 @@ impl Servo {
warn!("Sending navigation to constellation failed ({:?}).", e);
}
self.messages_for_embedder
+ .borrow_mut()
.push(EmbedderMsg::Status(webview_id, None));
},
@@ -776,15 +800,15 @@ impl Servo {
self.compositor.borrow_mut().maybe_start_shutting_down();
},
- EmbedderEvent::ExitFullScreen(top_level_browsing_context_id) => {
- let msg = ConstellationMsg::ExitFullScreen(top_level_browsing_context_id);
+ EmbedderEvent::ExitFullScreen(webview_id) => {
+ let msg = ConstellationMsg::ExitFullScreen(webview_id);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!("Sending exit fullscreen to constellation failed ({:?}).", e);
}
},
- EmbedderEvent::Reload(top_level_browsing_context_id) => {
- let msg = ConstellationMsg::Reload(top_level_browsing_context_id);
+ EmbedderEvent::Reload(webview_id) => {
+ let msg = ConstellationMsg::Reload(webview_id);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!("Sending reload to constellation failed ({:?}).", e);
}
@@ -807,8 +831,8 @@ impl Servo {
self.compositor.borrow_mut().capture_webrender();
},
- EmbedderEvent::NewWebView(url, top_level_browsing_context_id) => {
- let msg = ConstellationMsg::NewWebView(url, top_level_browsing_context_id);
+ EmbedderEvent::NewWebView(url, webview_id) => {
+ let msg = ConstellationMsg::NewWebView(url, webview_id);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!(
"Sending NewBrowser message to constellation failed ({:?}).",
@@ -817,8 +841,8 @@ impl Servo {
}
},
- EmbedderEvent::FocusWebView(top_level_browsing_context_id) => {
- let msg = ConstellationMsg::FocusWebView(top_level_browsing_context_id);
+ EmbedderEvent::FocusWebView(webview_id) => {
+ let msg = ConstellationMsg::FocusWebView(webview_id);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!(
"Sending FocusBrowser message to constellation failed ({:?}).",
@@ -827,8 +851,8 @@ impl Servo {
}
},
- EmbedderEvent::CloseWebView(top_level_browsing_context_id) => {
- let msg = ConstellationMsg::CloseWebView(top_level_browsing_context_id);
+ EmbedderEvent::CloseWebView(webview_id) => {
+ let msg = ConstellationMsg::CloseWebView(webview_id);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!(
"Sending CloseBrowser message to constellation failed ({:?}).",
@@ -871,8 +895,8 @@ impl Servo {
self.send_to_constellation(ConstellationMsg::BlurWebView);
},
- EmbedderEvent::SendError(top_level_browsing_context_id, e) => {
- let msg = ConstellationMsg::SendError(top_level_browsing_context_id, e);
+ EmbedderEvent::SendError(webview_id, e) => {
+ let msg = ConstellationMsg::SendError(webview_id, e);
if let Err(e) = self.constellation_proxy.try_send(msg) {
warn!(
"Sending SendError message to constellation failed ({:?}).",
@@ -923,7 +947,7 @@ impl Servo {
}
}
- fn receive_messages(&mut self) {
+ fn receive_messages(&self) {
while let Ok(message) = self.embedder_receiver.try_recv() {
match (message, self.compositor.borrow().shutdown_state) {
(_, ShutdownState::FinishedShuttingDown) => {
@@ -936,18 +960,19 @@ impl Servo {
(EmbedderMsg::Keyboard(webview_id, key_event), ShutdownState::NotShuttingDown) => {
self.messages_for_embedder
+ .borrow_mut()
.push(EmbedderMsg::Keyboard(webview_id, key_event));
},
(message, ShutdownState::NotShuttingDown) => {
- self.messages_for_embedder.push(message);
+ self.messages_for_embedder.borrow_mut().push(message);
},
}
}
}
- pub fn get_events(&mut self) -> Drain<'_, EmbedderMsg> {
- self.messages_for_embedder.drain(..)
+ pub fn get_events(&self) -> Vec<EmbedderMsg> {
+ std::mem::take(&mut *self.messages_for_embedder.borrow_mut())
}
pub fn handle_events(&mut self, events: impl IntoIterator<Item = EmbedderEvent>) {
@@ -961,8 +986,38 @@ impl Servo {
if self.compositor.borrow().shutdown_state != ShutdownState::FinishedShuttingDown {
self.compositor.borrow_mut().perform_updates();
} else {
- self.messages_for_embedder.push(EmbedderMsg::Shutdown);
+ self.messages_for_embedder
+ .borrow_mut()
+ .push(EmbedderMsg::Shutdown);
+ }
+ }
+
+ /// Spin the Servo event loop, which:
+ ///
+ /// - Performs updates in the compositor, such as queued pinch zoom events
+ /// - Runs delebgate methods on all `WebView`s and `Servo` itself
+ /// - Maybe update the rendered compositor output, but *without* swapping buffers.
+ ///
+ /// The return value of this method indicates whether or not Servo, false indicates that Servo
+ /// has finished shutting down and you should not spin the event loop any longer.
+ pub fn spin_event_loop(&self) -> bool {
+ if self.compositor.borrow_mut().receive_messages() {
+ self.receive_messages();
+ }
+
+ self.call_delegate_methods();
+ if self.constellation_proxy.disconnected() {
+ self.delegate()
+ .notify_error(self, ServoError::LostConnectionWithBackend);
}
+
+ self.compositor.borrow_mut().perform_updates();
+
+ if self.compositor.borrow().shutdown_state == ShutdownState::FinishedShuttingDown {
+ return false;
+ }
+
+ true
}
pub fn pinch_zoom_level(&self) -> f32 {
@@ -996,11 +1051,11 @@ impl Servo {
}
}
- pub fn deinit(self) {
+ pub fn deinit(&self) {
self.compositor.borrow_mut().deinit();
}
- pub fn present(&mut self) {
+ pub fn present(&self) {
self.compositor.borrow_mut().present();
}
@@ -1011,12 +1066,320 @@ impl Servo {
}
pub fn new_webview(&self, url: url::Url) -> WebView {
- WebView::new(&self.constellation_proxy, self.compositor.clone(), url)
+ let webview = WebView::new(&self.constellation_proxy, self.compositor.clone());
+ self.webviews
+ .borrow_mut()
+ .insert(webview.id(), webview.weak_handle());
+ self.constellation_proxy
+ .send(ConstellationMsg::NewWebView(url.into(), webview.id()));
+ webview
}
- /// FIXME: Remove this once we have a webview delegate.
pub fn new_auxiliary_webview(&self) -> WebView {
- WebView::new_auxiliary(&self.constellation_proxy, self.compositor.clone())
+ let webview = WebView::new(&self.constellation_proxy, self.compositor.clone());
+ self.webviews
+ .borrow_mut()
+ .insert(webview.id(), webview.weak_handle());
+ webview
+ }
+
+ fn get_webview_handle(&self, id: WebViewId) -> Option<WebView> {
+ self.webviews
+ .borrow()
+ .get(&id)
+ .and_then(WebView::from_weak_handle)
+ }
+
+ fn call_delegate_methods(&self) {
+ let events = self.get_events();
+ for event in events {
+ match event {
+ EmbedderMsg::Status(webview_id, status_text) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.set_status_text(status_text);
+ }
+ },
+ EmbedderMsg::ChangePageTitle(webview_id, title) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.set_page_title(title);
+ }
+ },
+ EmbedderMsg::MoveTo(webview_id, position) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().request_move_to(webview, position);
+ }
+ },
+ EmbedderMsg::ResizeTo(webview_id, size) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().request_resize_to(webview, size);
+ }
+ },
+ EmbedderMsg::Prompt(webview_id, prompt_definition, prompt_origin) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .show_prompt(webview, prompt_definition, prompt_origin);
+ }
+ },
+ EmbedderMsg::ShowContextMenu(webview_id, ipc_sender, title, items) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .show_context_menu(webview, ipc_sender, title, items);
+ }
+ },
+ EmbedderMsg::AllowNavigationRequest(webview_id, pipeline_id, servo_url) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ let request = NavigationRequest {
+ url: servo_url.into_url(),
+ pipeline_id,
+ constellation_proxy: self.constellation_proxy.clone(),
+ response_sent: false,
+ };
+ webview.delegate().request_navigation(webview, request);
+ }
+ },
+ EmbedderMsg::AllowOpeningWebView(webview_id, response_sender) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ let new_webview =
+ webview.delegate().request_open_auxiliary_webview(webview);
+ let _ = response_sender.send(new_webview.map(|webview| webview.id()));
+ }
+ },
+ EmbedderMsg::WebViewOpened(webview_id) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().notify_ready_to_show(webview);
+ }
+ },
+ EmbedderMsg::WebViewClosed(_) => {},
+ EmbedderMsg::WebViewFocused(webview_id) => {
+ for id in self.webviews.borrow().keys() {
+ if let Some(webview) = self.get_webview_handle(*id) {
+ let focused = webview.id() == webview_id;
+ webview.set_focused(focused);
+ }
+ }
+ },
+ EmbedderMsg::WebViewBlurred => {
+ for id in self.webviews.borrow().keys() {
+ if let Some(webview) = self.get_webview_handle(*id) {
+ webview.set_focused(false);
+ }
+ }
+ },
+ EmbedderMsg::AllowUnload(webview_id, response_sender) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ let request = AllowOrDenyRequest {
+ response_sender,
+ response_sent: false,
+ default_response: true,
+ };
+ webview.delegate().request_unload(webview, request);
+ }
+ },
+ EmbedderMsg::Keyboard(webview_id, keyboard_event) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .notify_keyboard_event(webview, keyboard_event);
+ }
+ },
+ EmbedderMsg::ClearClipboardContents(webview_id) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().clear_clipboard_contents(webview);
+ }
+ },
+ EmbedderMsg::GetClipboardContents(webview_id, ipc_sender) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .get_clipboard_contents(webview, ipc_sender);
+ }
+ },
+ EmbedderMsg::SetClipboardContents(webview_id, string) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().set_clipboard_contents(webview, string);
+ }
+ },
+ EmbedderMsg::SetCursor(webview_id, cursor) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.set_cursor(cursor);
+ }
+ },
+ EmbedderMsg::NewFavicon(webview_id, url) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.set_favicon_url(url.into_url());
+ }
+ },
+ EmbedderMsg::NotifyLoadStatusChanged(webview_id, load_status) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.set_load_status(load_status);
+ }
+ },
+ EmbedderMsg::HistoryChanged(webview_id, urls, current_index) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ let urls: Vec<_> = urls.into_iter().map(ServoUrl::into_url).collect();
+ let current_url = urls[current_index].clone();
+
+ webview.delegate().notify_history_changed(
+ webview.clone(),
+ urls,
+ current_index,
+ );
+ webview.set_url(current_url);
+ }
+ },
+ EmbedderMsg::SetFullscreenState(webview_id, fullscreen) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .request_fullscreen_state_change(webview, fullscreen);
+ }
+ },
+ EmbedderMsg::WebResourceRequested(
+ webview_id,
+ web_resource_request,
+ response_sender,
+ ) => {
+ let webview =
+ webview_id.and_then(|webview_id| self.get_webview_handle(webview_id));
+ if let Some(webview) = webview.clone() {
+ webview.delegate().intercept_web_resource_load(
+ webview,
+ &web_resource_request,
+ response_sender.clone(),
+ );
+ }
+
+ self.delegate().intercept_web_resource_load(
+ webview,
+ &web_resource_request,
+ response_sender,
+ );
+ },
+ EmbedderMsg::Panic(webview_id, reason, backtrace) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .notify_crashed(webview, reason, backtrace);
+ }
+ },
+ EmbedderMsg::GetSelectedBluetoothDevice(webview_id, items, response_sender) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().show_bluetooth_device_dialog(
+ webview,
+ items,
+ response_sender,
+ );
+ }
+ },
+ EmbedderMsg::SelectFiles(
+ webview_id,
+ filter_patterns,
+ allow_select_multiple,
+ response_sender,
+ ) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().show_file_selection_dialog(
+ webview,
+ filter_patterns,
+ allow_select_multiple,
+ response_sender,
+ );
+ }
+ },
+ EmbedderMsg::PromptPermission(webview_id, permission_prompt, result_sender) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().request_permission(
+ webview,
+ permission_prompt,
+ result_sender,
+ );
+ }
+ },
+ EmbedderMsg::ShowIME(webview_id, input_method_type, text, multiline, position) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().show_ime(
+ webview,
+ input_method_type,
+ text,
+ multiline,
+ position,
+ );
+ }
+ },
+ EmbedderMsg::HideIME(webview_id) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().hide_ime(webview);
+ }
+ },
+ EmbedderMsg::ReportProfile(_items) => {},
+ EmbedderMsg::MediaSessionEvent(webview_id, media_session_event) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview
+ .delegate()
+ .notify_media_session_event(webview, media_session_event);
+ }
+ },
+ EmbedderMsg::OnDevtoolsStarted(port, token) => match port {
+ Ok(port) => self
+ .delegate()
+ .notify_devtools_server_started(self, port, token),
+ Err(()) => self
+ .delegate()
+ .notify_error(self, ServoError::DevtoolsFailedToStart),
+ },
+ EmbedderMsg::RequestDevtoolsConnection(response_sender) => {
+ self.delegate().request_devtools_connection(
+ self,
+ AllowOrDenyRequest {
+ response_sender,
+ response_sent: false,
+ default_response: false,
+ },
+ );
+ },
+ EmbedderMsg::ReadyToPresent(webview_ids) => {
+ for webview_id in webview_ids {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().notify_new_frame_ready(webview);
+ }
+ }
+ },
+ EmbedderMsg::EventDelivered(webview_id, event) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().notify_event_delivered(webview, event);
+ }
+ },
+ EmbedderMsg::PlayGamepadHapticEffect(
+ webview_id,
+ gamepad_index,
+ gamepad_haptic_effect_type,
+ ipc_sender,
+ ) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().play_gamepad_haptic_effect(
+ webview,
+ gamepad_index,
+ gamepad_haptic_effect_type,
+ ipc_sender,
+ );
+ }
+ },
+ EmbedderMsg::StopGamepadHapticEffect(webview_id, gamepad_index, ipc_sender) => {
+ if let Some(webview) = self.get_webview_handle(webview_id) {
+ webview.delegate().stop_gamepad_haptic_effect(
+ webview,
+ gamepad_index,
+ ipc_sender,
+ );
+ }
+ },
+ EmbedderMsg::Shutdown => {
+ // This message isn't necessary in the new API -- it's the return value of `spin_event_loop`.
+ },
+ }
+ }
}
}
diff --git a/components/servo/proxies.rs b/components/servo/proxies.rs
index 526eeea21f3..248e1e66f66 100644
--- a/components/servo/proxies.rs
+++ b/components/servo/proxies.rs
@@ -23,6 +23,10 @@ impl ConstellationProxy {
}
}
+ pub fn disconnected(&self) -> bool {
+ self.disconnected.load(Ordering::SeqCst)
+ }
+
pub fn send(&self, msg: ConstellationMsg) {
if self.try_send(msg).is_err() {
warn!("Lost connection to Constellation. Will report to embedder.")
@@ -30,7 +34,7 @@ impl ConstellationProxy {
}
pub fn try_send(&self, msg: ConstellationMsg) -> Result<(), SendError<ConstellationMsg>> {
- if self.disconnected.load(Ordering::SeqCst) {
+ if self.disconnected() {
return Err(SendError(msg));
}
if let Err(error) = self.sender.send(msg) {
diff --git a/components/servo/servo_delegate.rs b/components/servo/servo_delegate.rs
new file mode 100644
index 00000000000..379be8a8513
--- /dev/null
+++ b/components/servo/servo_delegate.rs
@@ -0,0 +1,47 @@
+/* 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 embedder_traits::{WebResourceRequest, WebResourceResponseMsg};
+use ipc_channel::ipc::IpcSender;
+
+use crate::webview_delegate::AllowOrDenyRequest;
+use crate::{Servo, WebView};
+
+#[derive(Clone, Copy, Debug, Hash, PartialEq)]
+pub enum ServoError {
+ /// The channel to the off-the-main-thread web engine has been lost. No further
+ /// attempts to communicate will happen. This is an unrecoverable error in Servo.
+ LostConnectionWithBackend,
+ /// The devtools server, used to expose pages to remote web inspectors has failed
+ /// to start.
+ DevtoolsFailedToStart,
+}
+
+pub trait ServoDelegate {
+ /// Notification that Servo has received a major error.
+ fn notify_error(&self, _servo: &Servo, _error: ServoError) {}
+ /// Report that the DevTools server has started on the given `port`. The `token` that
+ /// be used to bypass the permission prompt from the DevTools client.
+ fn notify_devtools_server_started(&self, _servo: &Servo, _port: u16, _token: String) {}
+ /// Request a DevTools connection from a DevTools client. Typically an embedder application
+ /// will show a permissions prompt when this happens to confirm a connection is allowed.
+ fn request_devtools_connection(&self, _servo: &Servo, _request: AllowOrDenyRequest) {}
+ /// Potentially intercept a resource request. If not handled, the request will not be intercepted.
+ ///
+ /// Note: If this request is associated with a `WebView`, the `WebViewDelegate` will
+ /// receive this notification first and have a chance to intercept the request.
+ ///
+ /// TODO: This API needs to be reworked to match the new model of how responses are sent.
+ fn intercept_web_resource_load(
+ &self,
+ _webview: Option<WebView>,
+ _request: &WebResourceRequest,
+ response_sender: IpcSender<WebResourceResponseMsg>,
+ ) {
+ let _ = response_sender.send(WebResourceResponseMsg::None);
+ }
+}
+
+pub(crate) struct DefaultServoDelegate;
+impl ServoDelegate for DefaultServoDelegate {}
diff --git a/components/servo/webview.rs b/components/servo/webview.rs
index a47ccaf04ff..b2a286fad7a 100644
--- a/components/servo/webview.rs
+++ b/components/servo/webview.rs
@@ -2,9 +2,10 @@
* 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::cell::RefCell;
+use std::cell::{Ref, RefCell, RefMut};
use std::ffi::c_void;
-use std::rc::Rc;
+use std::hash::Hash;
+use std::rc::{Rc, Weak};
use std::time::Duration;
use base::id::WebViewId;
@@ -12,24 +13,47 @@ use compositing::windowing::{MouseWindowEvent, WebRenderDebugOption};
use compositing::IOCompositor;
use compositing_traits::ConstellationMsg;
use embedder_traits::{
- ClipboardEventType, GamepadEvent, MediaSessionActionType, Theme, TouchEventType, TouchId,
- TraversalDirection, WheelDelta,
+ ClipboardEventType, Cursor, GamepadEvent, LoadStatus, MediaSessionActionType, Theme,
+ TouchEventType, TouchId, TraversalDirection, WheelDelta,
};
use keyboard_types::{CompositionEvent, KeyboardEvent};
use url::Url;
use webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePoint, DeviceRect};
use webrender_api::ScrollLocation;
+use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate};
use crate::ConstellationProxy;
#[derive(Clone)]
-pub struct WebView(Rc<WebViewInner>);
+pub struct WebView(Rc<RefCell<WebViewInner>>);
-struct WebViewInner {
+impl PartialEq for WebView {
+ fn eq(&self, other: &Self) -> bool {
+ self.inner().id == other.inner().id
+ }
+}
+
+impl Hash for WebView {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.inner().id.hash(state);
+ }
+}
+
+pub(crate) struct WebViewInner {
// TODO: ensure that WebView instances interact with the correct Servo instance
pub(crate) id: WebViewId,
pub(crate) constellation_proxy: ConstellationProxy,
pub(crate) compositor: Rc<RefCell<IOCompositor>>,
+ pub(crate) delegate: Rc<dyn WebViewDelegate>,
+
+ rect: DeviceRect,
+ load_status: LoadStatus,
+ url: Option<Url>,
+ status_text: Option<String>,
+ page_title: Option<String>,
+ favicon_url: Option<Url>,
+ focused: bool,
+ cursor: Cursor,
}
impl Drop for WebViewInner {
@@ -49,61 +73,175 @@ impl WebView {
pub(crate) fn new(
constellation_proxy: &ConstellationProxy,
compositor: Rc<RefCell<IOCompositor>>,
- url: Url,
) -> Self {
- let webview_id = WebViewId::new();
- constellation_proxy.send(ConstellationMsg::NewWebView(url.into(), webview_id));
-
- Self(Rc::new(WebViewInner {
- id: webview_id,
+ Self(Rc::new(RefCell::new(WebViewInner {
+ id: WebViewId::new(),
constellation_proxy: constellation_proxy.clone(),
compositor,
- }))
+ delegate: Rc::new(DefaultWebViewDelegate),
+ rect: DeviceRect::zero(),
+ load_status: LoadStatus::Complete,
+ url: None,
+ status_text: None,
+ page_title: None,
+ favicon_url: None,
+ focused: false,
+ cursor: Cursor::Pointer,
+ })))
}
- /// FIXME: Remove this once we have a webview delegate.
- pub(crate) fn new_auxiliary(
- constellation_proxy: &ConstellationProxy,
- compositor: Rc<RefCell<IOCompositor>>,
- ) -> Self {
- let webview_id = WebViewId::new();
+ fn inner(&self) -> Ref<'_, WebViewInner> {
+ self.0.borrow()
+ }
+
+ fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
+ self.0.borrow_mut()
+ }
+
+ pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
+ inner.upgrade().map(WebView)
+ }
+
+ pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
+ Rc::downgrade(&self.0)
+ }
- Self(
- WebViewInner {
- id: webview_id,
- constellation_proxy: constellation_proxy.clone(),
- compositor,
- }
- .into(),
- )
+ pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
+ self.inner().delegate.clone()
+ }
+
+ pub fn set_delegate(&self, delegate: Rc<dyn WebViewDelegate>) {
+ self.inner_mut().delegate = delegate;
}
- /// FIXME: Remove this once we have a webview delegate.
pub fn id(&self) -> WebViewId {
- self.0.id
+ self.inner().id
+ }
+
+ pub fn load_status(&self) -> LoadStatus {
+ self.inner().load_status
+ }
+
+ pub(crate) fn set_load_status(self, new_value: LoadStatus) {
+ if self.inner().load_status == new_value {
+ return;
+ }
+ self.inner_mut().load_status = new_value;
+ self.delegate().notify_load_status_changed(self, new_value);
+ }
+
+ pub fn url(&self) -> Option<Url> {
+ self.inner().url.clone()
+ }
+
+ pub(crate) fn set_url(self, new_value: Url) {
+ if self
+ .inner()
+ .url
+ .as_ref()
+ .is_some_and(|url| url == &new_value)
+ {
+ return;
+ }
+ self.inner_mut().url = Some(new_value.clone());
+ self.delegate().notify_url_changed(self, new_value);
+ }
+
+ pub fn status_text(&self) -> Option<String> {
+ self.inner().status_text.clone()
+ }
+
+ pub(crate) fn set_status_text(self, new_value: Option<String>) {
+ if self.inner().status_text == new_value {
+ return;
+ }
+ self.inner_mut().status_text = new_value.clone();
+ self.delegate().notify_status_text_changed(self, new_value);
+ }
+
+ pub fn page_title(&self) -> Option<String> {
+ self.inner().page_title.clone()
+ }
+
+ pub(crate) fn set_page_title(self, new_value: Option<String>) {
+ if self.inner().page_title == new_value {
+ return;
+ }
+ self.inner_mut().page_title = new_value.clone();
+ self.delegate().notify_page_title_changed(self, new_value);
+ }
+
+ pub fn favicon_url(&self) -> Option<Url> {
+ self.inner().favicon_url.clone()
+ }
+
+ pub(crate) fn set_favicon_url(self, new_value: Url) {
+ if self
+ .inner()
+ .favicon_url
+ .as_ref()
+ .is_some_and(|url| url == &new_value)
+ {
+ return;
+ }
+ self.inner_mut().favicon_url = Some(new_value.clone());
+ self.delegate().notify_favicon_url_changed(self, new_value);
+ }
+
+ pub fn focused(&self) -> bool {
+ self.inner().focused
+ }
+
+ pub(crate) fn set_focused(self, new_value: bool) {
+ if self.inner().focused == new_value {
+ return;
+ }
+ self.inner_mut().focused = new_value;
+ self.delegate().notify_focus_changed(self, new_value);
+ }
+
+ pub fn cursor(&self) -> Cursor {
+ self.inner().cursor
+ }
+
+ pub(crate) fn set_cursor(self, new_value: Cursor) {
+ if self.inner().cursor == new_value {
+ return;
+ }
+ self.inner_mut().cursor = new_value;
+ self.delegate().notify_cursor_changed(self, new_value);
}
pub fn focus(&self) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::FocusWebView(self.id()));
}
pub fn blur(&self) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::BlurWebView);
}
+ pub fn rect(&self) -> DeviceRect {
+ self.inner().rect
+ }
+
pub fn move_resize(&self, rect: DeviceRect) {
- self.0
+ if self.inner().rect == rect {
+ return;
+ }
+
+ self.inner_mut().rect = rect;
+ self.inner()
.compositor
.borrow_mut()
.move_resize_webview(self.id(), rect);
}
pub fn show(&self, hide_others: bool) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.show_webview(self.id(), hide_others)
@@ -111,7 +249,7 @@ impl WebView {
}
pub fn hide(&self) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.hide_webview(self.id())
@@ -119,7 +257,7 @@ impl WebView {
}
pub fn raise_to_top(&self, hide_others: bool) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.raise_webview_to_top(self.id(), hide_others)
@@ -127,25 +265,25 @@ impl WebView {
}
pub fn notify_theme_change(&self, theme: Theme) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::ThemeChange(theme))
}
pub fn load(&self, url: Url) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::LoadUrl(self.id(), url.into()))
}
pub fn reload(&self) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::Reload(self.id()))
}
pub fn go_back(&self, amount: usize) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::TraverseHistory(
self.id(),
@@ -154,7 +292,7 @@ impl WebView {
}
pub fn go_forward(&self, amount: usize) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::TraverseHistory(
self.id(),
@@ -163,28 +301,31 @@ impl WebView {
}
pub fn notify_pointer_button_event(&self, event: MouseWindowEvent) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_mouse_window_event_class(event);
}
pub fn notify_pointer_move_event(&self, event: DevicePoint) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_mouse_window_move_event_class(event);
}
pub fn notify_touch_event(&self, event_type: TouchEventType, id: TouchId, point: DevicePoint) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_touch_event(event_type, id, point);
}
pub fn notify_wheel_event(&self, delta: WheelDelta, point: DevicePoint) {
- self.0.compositor.borrow_mut().on_wheel_event(delta, point);
+ self.inner()
+ .compositor
+ .borrow_mut()
+ .on_wheel_event(delta, point);
}
pub fn notify_scroll_event(
@@ -193,123 +334,129 @@ impl WebView {
point: DeviceIntPoint,
touch_event_type: TouchEventType,
) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_scroll_event(location, point, touch_event_type);
}
pub fn notify_keyboard_event(&self, event: KeyboardEvent) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::Keyboard(self.id(), event))
}
pub fn notify_ime_event(&self, event: CompositionEvent) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::IMECompositionEvent(event))
}
pub fn notify_ime_dismissed_event(&self) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::IMEDismissed);
}
pub fn notify_gamepad_event(&self, event: GamepadEvent) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::Gamepad(event));
}
pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::MediaSessionAction(event));
}
pub fn notify_clipboard_event(&self, event: ClipboardEventType) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::Clipboard(event));
}
pub fn notify_vsync(&self) {
- self.0.compositor.borrow_mut().on_vsync();
+ self.inner().compositor.borrow_mut().on_vsync();
}
pub fn notify_rendering_context_resized(&self) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_rendering_context_resized();
}
pub fn set_zoom(&self, new_zoom: f32) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_zoom_window_event(new_zoom);
}
pub fn reset_zoom(&self) {
- self.0.compositor.borrow_mut().on_zoom_reset_window_event();
+ self.inner()
+ .compositor
+ .borrow_mut()
+ .on_zoom_reset_window_event();
}
pub fn set_pinch_zoom(&self, new_pinch_zoom: f32) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.on_pinch_zoom_window_event(new_pinch_zoom);
}
pub fn exit_fullscreen(&self) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::ExitFullScreen(self.id()));
}
pub fn set_throttled(&self, throttled: bool) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::SetWebViewThrottled(self.id(), throttled));
}
pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.toggle_webrender_debug(debugging);
}
pub fn capture_webrender(&self) {
- self.0.compositor.borrow_mut().capture_webrender();
+ self.inner().compositor.borrow_mut().capture_webrender();
}
pub fn invalidate_native_surface(&self) {
- self.0.compositor.borrow_mut().invalidate_native_surface();
+ self.inner()
+ .compositor
+ .borrow_mut()
+ .invalidate_native_surface();
}
pub fn composite(&self) {
- self.0.compositor.borrow_mut().composite();
+ self.inner().compositor.borrow_mut().composite();
}
pub fn replace_native_surface(&self, native_widget: *mut c_void, size: DeviceIntSize) {
- self.0
+ self.inner()
.compositor
.borrow_mut()
.replace_native_surface(native_widget, size);
}
pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::ToggleProfiler(rate, max_duration));
}
pub fn send_error(&self, message: String) {
- self.0
+ self.inner()
.constellation_proxy
.send(ConstellationMsg::SendError(Some(self.id()), message));
}
diff --git a/components/servo/webview_delegate.rs b/components/servo/webview_delegate.rs
new file mode 100644
index 00000000000..5648294b2c6
--- /dev/null
+++ b/components/servo/webview_delegate.rs
@@ -0,0 +1,262 @@
+/* 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::path::PathBuf;
+
+use base::id::PipelineId;
+use compositing_traits::ConstellationMsg;
+use embedder_traits::{
+ CompositorEventVariant, ContextMenuResult, Cursor, FilterPattern, GamepadHapticEffectType,
+ InputMethodType, LoadStatus, MediaSessionEvent, PermissionPrompt, PermissionRequest,
+ PromptDefinition, PromptOrigin, WebResourceRequest, WebResourceResponseMsg,
+};
+use ipc_channel::ipc::IpcSender;
+use keyboard_types::KeyboardEvent;
+use url::Url;
+use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+
+use crate::{ConstellationProxy, WebView};
+
+/// A request to navigate a [`WebView`] or one of its inner frames. This can be handled
+/// asynchronously. If not handled, the request will automatically be allowed.
+pub struct NavigationRequest {
+ pub url: Url,
+ pub(crate) pipeline_id: PipelineId,
+ pub(crate) constellation_proxy: ConstellationProxy,
+ pub(crate) response_sent: bool,
+}
+
+impl NavigationRequest {
+ pub fn allow(mut self) {
+ self.constellation_proxy
+ .send(ConstellationMsg::AllowNavigationResponse(
+ self.pipeline_id,
+ true,
+ ));
+ self.response_sent = true;
+ }
+
+ pub fn deny(mut self) {
+ self.constellation_proxy
+ .send(ConstellationMsg::AllowNavigationResponse(
+ self.pipeline_id,
+ false,
+ ));
+ self.response_sent = true;
+ }
+}
+
+impl Drop for NavigationRequest {
+ fn drop(&mut self) {
+ if !self.response_sent {
+ self.constellation_proxy
+ .send(ConstellationMsg::AllowNavigationResponse(
+ self.pipeline_id,
+ true,
+ ));
+ }
+ }
+}
+
+pub struct AllowOrDenyRequest {
+ pub(crate) response_sender: IpcSender<bool>,
+ pub(crate) response_sent: bool,
+ pub(crate) default_response: bool,
+}
+
+impl AllowOrDenyRequest {
+ pub fn allow(mut self) {
+ let _ = self.response_sender.send(true);
+ self.response_sent = true;
+ }
+
+ pub fn deny(mut self) {
+ let _ = self.response_sender.send(false);
+ self.response_sent = true;
+ }
+}
+
+impl Drop for AllowOrDenyRequest {
+ fn drop(&mut self) {
+ if !self.response_sent {
+ let _ = self.response_sender.send(self.default_response);
+ }
+ }
+}
+
+pub trait WebViewDelegate {
+ /// The URL of the currently loaded page in this [`WebView`] has changed. The new
+ /// URL can accessed via [`WebView::url`].
+ fn notify_url_changed(&self, _webview: WebView, _url: Url) {}
+ /// The page title of the currently loaded page in this [`WebView`] has changed. The new
+ /// title can accessed via [`WebView::page_title`].
+ fn notify_page_title_changed(&self, _webview: WebView, _title: Option<String>) {}
+ /// The status text of the currently loaded page in this [`WebView`] has changed. The new
+ /// status text can accessed via [`WebView::status_text`].
+ fn notify_status_text_changed(&self, _webview: WebView, _status: Option<String>) {}
+ /// This [`WebView`] has either become focused or lost focus. Whether or not the
+ /// [`WebView`] is focused can be accessed via [`WebView::focused`].
+ fn notify_focus_changed(&self, _webview: WebView, _focused: bool) {}
+ /// The `LoadStatus` of the currently loading or loaded page in this [`WebView`] has changed. The new
+ /// status can accessed via [`WebView::load_status`].
+ fn notify_load_status_changed(&self, _webview: WebView, _status: LoadStatus) {}
+ /// The [`Cursor`] of the currently loaded page in this [`WebView`] has changed. The new
+ /// cursor can accessed via [`WebView::cursor`].
+ fn notify_cursor_changed(&self, _webview: WebView, _: Cursor) {}
+ /// The favicon [`Url`] of the currently loaded page in this [`WebView`] has changed. The new
+ /// favicon [`Url`] can accessed via [`WebView::favicon_url`].
+ fn notify_favicon_url_changed(&self, _webview: WebView, _: Url) {}
+
+ /// A [`WebView`] was created and is now ready to show in the user interface.
+ fn notify_ready_to_show(&self, _webview: WebView) {}
+ /// Notify the embedder that it needs to present a new frame.
+ fn notify_new_frame_ready(&self, _webview: WebView) {}
+ /// The given event was delivered to a pipeline in the given webview.
+ fn notify_event_delivered(&self, _webview: WebView, _event: CompositorEventVariant) {}
+ /// The history state has changed.
+ // changed pattern; maybe wasteful if embedder doesn’t care?
+ fn notify_history_changed(&self, _webview: WebView, _: Vec<Url>, _: usize) {}
+
+ /// A keyboard event has been sent to Servo, but remains unprocessed. This allows the
+ /// embedding application to handle key events while first letting the [`WebView`]
+ /// have an opportunity to handle it first. Apart from builtin keybindings, page
+ /// content may expose custom keybindings as well.
+ fn notify_keyboard_event(&self, _webview: WebView, _: KeyboardEvent) {}
+ /// A pipeline in the webview panicked. First string is the reason, second one is the backtrace.
+ fn notify_crashed(&self, _webview: WebView, _reason: String, _backtrace: Option<String>) {}
+ /// Notifies the embedder about media session events
+ /// (i.e. when there is metadata for the active media session, playback state changes...).
+ fn notify_media_session_event(&self, _webview: WebView, _event: MediaSessionEvent) {}
+
+ /// Whether or not to allow a [`WebView`] to load a URL in its main frame or one of its
+ /// nested `<iframe>`s. [`NavigationRequest`]s are accepted by default.
+ fn request_navigation(&self, _webview: WebView, _navigation_request: NavigationRequest) {}
+ /// Whether or not to allow a [`WebView`] to unload a `Document` in its main frame or one
+ /// of its nested `<iframe>`s. By default, unloads are allowed.
+ fn request_unload(&self, _webview: WebView, _unload_request: AllowOrDenyRequest) {}
+ /// Move the window to a point
+ fn request_move_to(&self, _webview: WebView, _: DeviceIntPoint) {}
+ /// Resize the window to size
+ fn request_resize_to(&self, _webview: WebView, _: DeviceIntSize) {}
+ /// Whether or not to allow script to open a new `WebView`. If not handled by the
+ /// embedder, these requests are automatically denied.
+ fn request_open_auxiliary_webview(&self, _parent_webview: WebView) -> Option<WebView> {
+ None
+ }
+ /// Page content has requested that this [`WebView`] be closed. It's the embedder's
+ /// responsibility to either ignore this request or to remove the [`WebView`] from the
+ /// interface.
+ fn request_close(&self, _webview: WebView) {}
+ /// Open interface to request permission specified by prompt.
+ fn request_permission(
+ &self,
+ _webview: WebView,
+ _: PermissionPrompt,
+ result_sender: IpcSender<PermissionRequest>,
+ ) {
+ let _ = result_sender.send(PermissionRequest::Denied);
+ }
+
+ /// Show dialog to user
+ /// TODO: This API needs to be reworked to match the new model of how responses are sent.
+ fn show_prompt(&self, _webview: WebView, prompt: PromptDefinition, _: PromptOrigin) {
+ let _ = match prompt {
+ PromptDefinition::Alert(_, response_sender) => response_sender.send(()),
+ PromptDefinition::OkCancel(_, response_sender) => {
+ response_sender.send(embedder_traits::PromptResult::Dismissed)
+ },
+ PromptDefinition::Input(_, _, response_sender) => response_sender.send(None),
+ PromptDefinition::Credentials(response_sender) => {
+ response_sender.send(Default::default())
+ },
+ };
+ }
+ /// Show a context menu to the user
+ fn show_context_menu(
+ &self,
+ _webview: WebView,
+ result_sender: IpcSender<ContextMenuResult>,
+ _: Option<String>,
+ _: Vec<String>,
+ ) {
+ let _ = result_sender.send(ContextMenuResult::Ignored);
+ }
+
+ /// Inform embedder to clear the clipboard
+ fn clear_clipboard_contents(&self, _webview: WebView) {}
+ /// Gets system clipboard contents
+ fn get_clipboard_contents(&self, _webview: WebView, _: IpcSender<String>) {}
+ /// Sets system clipboard contents
+ fn set_clipboard_contents(&self, _webview: WebView, _: String) {}
+
+ /// Enter or exit fullscreen
+ fn request_fullscreen_state_change(&self, _webview: WebView, _: bool) {}
+ /// Open dialog to select bluetooth device.
+ /// TODO: This API needs to be reworked to match the new model of how responses are sent.
+ fn show_bluetooth_device_dialog(
+ &self,
+ _webview: WebView,
+ _: Vec<String>,
+ response_sender: IpcSender<Option<String>>,
+ ) {
+ let _ = response_sender.send(None);
+ }
+
+ /// Open file dialog to select files. Set boolean flag to true allows to select multiple files.
+ fn show_file_selection_dialog(
+ &self,
+ _webview: WebView,
+ _filter_pattern: Vec<FilterPattern>,
+ _allow_select_mutiple: bool,
+ response_sender: IpcSender<Option<Vec<PathBuf>>>,
+ ) {
+ let _ = response_sender.send(None);
+ }
+
+ /// Request to present an IME to the user when an editable element is focused.
+ /// If `type` is [`InputMethodType::Text`], then the `text` parameter specifies
+ /// the pre-existing text content and the zero-based index into the string
+ /// of the insertion point.
+ fn show_ime(
+ &self,
+ _webview: WebView,
+ _type: InputMethodType,
+ _text: Option<(String, i32)>,
+ _multiline: bool,
+ _position: DeviceIntRect,
+ ) {
+ }
+
+ /// Request to hide the IME when the editable element is blurred.
+ fn hide_ime(&self, _webview: WebView) {}
+
+ /// Request to play a haptic effect on a connected gamepad.
+ fn play_gamepad_haptic_effect(
+ &self,
+ _webview: WebView,
+ _: usize,
+ _: GamepadHapticEffectType,
+ _: IpcSender<bool>,
+ ) {
+ }
+ /// Request to stop a haptic effect on a connected gamepad.
+ fn stop_gamepad_haptic_effect(&self, _webview: WebView, _: usize, _: IpcSender<bool>) {}
+
+ /// Potentially intercept a resource request. If not handled, the request will not be intercepted.
+ ///
+ /// Note: The `ServoDelegate` will also receive this notification and have a chance to intercept
+ /// the request.
+ ///
+ /// TODO: This API needs to be reworked to match the new model of how responses are sent.
+ fn intercept_web_resource_load(
+ &self,
+ _webview: WebView,
+ _request: &WebResourceRequest,
+ _response_sender: IpcSender<WebResourceResponseMsg>,
+ ) {
+ }
+}
+
+pub(crate) struct DefaultWebViewDelegate;
+impl WebViewDelegate for DefaultWebViewDelegate {}