diff options
author | Delan Azabani <dazabani@igalia.com> | 2025-01-25 16:17:50 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-25 08:17:50 +0000 |
commit | 2ce7709b8bd3568405be52f48db060cf9aec5af6 (patch) | |
tree | a73b0608cd9f1e126a01c9b7a5f91526c1636e3e /components/servo | |
parent | d5d7b0d34f12aec96be76b825851532ac0d33ecf (diff) | |
download | servo-2ce7709b8bd3568405be52f48db060cf9aec5af6.tar.gz servo-2ce7709b8bd3568405be52f48db060cf9aec5af6.zip |
libservo: Add an initial WebView data structure to the API (#35119)
This patch introduces a new handle-based webview API to libservo, with
two main design goals:
1. The lifetime of the handles controls the lifetime of the webview,
giving the embedder full control over exactly when webviews are
created and destroyed. This is consistent with how WebKitGTK’s
WebView works; the engine can only create webviews via a create
request, and can only destroy them via a close request.
2. All methods are infallible; if the constellation dies, the embedder
finds out when calling Servo::handle_events.
For the moment, the embedder is only responsible for creating the
WebView id, and not the internal TopLevelBrowsingContext data
structures. This is so that the ScriptThread is able to get a handle on
the new WebView's WindowProxy in the case that it's an auxiliary
browsing context. In the future, the embedder should also be responsible
for creating the TopLevelBrowsingContext and the ScriptThread should
have mechanism to associate the two views so that WebView creation is
always executed through the same code path in the embedding layer. For
now, it's enough that the embedder can get a handle to the new WebView
when it's creation is requested.
Once we replace EmbedderMsg with a webview delegate trait, we will pass
WebView handles to the embedder, rather than webview ids. We’ll also add
detailed docs, once the design settles.
Signed-off-by: Delan Azabani <dazabani@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components/servo')
-rw-r--r-- | components/servo/Cargo.toml | 1 | ||||
-rw-r--r-- | components/servo/examples/winit_minimal.rs | 75 | ||||
-rw-r--r-- | components/servo/lib.rs | 147 | ||||
-rw-r--r-- | components/servo/proxies.rs | 47 | ||||
-rw-r--r-- | components/servo/webview.rs | 117 |
5 files changed, 303 insertions, 84 deletions
diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 2afbe3a0b63..9db597a8324 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -89,6 +89,7 @@ servo_url = { path = "../url" } style = { workspace = true } style_traits = { workspace = true } tracing = { workspace = true, optional = true } +url = { workspace = true } webdriver_server = { path = "../webdriver_server", optional = true } webgpu = { path = "../webgpu" } webrender = { workspace = true } diff --git a/components/servo/examples/winit_minimal.rs b/components/servo/examples/winit_minimal.rs index da4742ca880..62114194ba9 100644 --- a/components/servo/examples/winit_minimal.rs +++ b/components/servo/examples/winit_minimal.rs @@ -4,19 +4,17 @@ use std::cell::Cell; use std::error::Error; -use std::mem::replace; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use base::id::WebViewId; use compositing::windowing::{AnimationState, EmbedderEvent, EmbedderMethods, WindowMethods}; use embedder_traits::EmbedderMsg; use euclid::{Point2D, Scale, Size2D}; -use servo::Servo; +use servo::{Servo, WebView}; use servo_geometry::DeviceIndependentPixel; -use servo_url::ServoUrl; use surfman::{Connection, SurfaceType}; use tracing::warn; +use url::Url; use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel}; use webrender_traits::SurfmanRenderingContext; use winit::application::ApplicationHandler; @@ -37,6 +35,10 @@ 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(); + } + Ok(()) } @@ -45,8 +47,8 @@ enum App { Running { window_delegate: Rc<WindowDelegate>, servo: Servo, + webviews: Vec<WebView>, }, - Exiting, } impl App { @@ -90,7 +92,7 @@ impl ApplicationHandler<WakerEvent> for App { .make_gl_context_current() .expect("Failed to make context current"); let window_delegate = Rc::new(WindowDelegate::new(window)); - let mut servo = Servo::new( + let servo = Servo::new( Default::default(), Default::default(), Rc::new(rendering_context), @@ -102,14 +104,14 @@ impl ApplicationHandler<WakerEvent> for App { compositing::CompositeTarget::Window, ); servo.setup_logging(); - servo.handle_events([EmbedderEvent::NewWebView( - ServoUrl::parse("https://demo.servo.org/experiments/twgl-tunnel/") + let webviews = vec![servo.new_webview( + Url::parse("https://demo.servo.org/experiments/twgl-tunnel/") .expect("Guaranteed by argument"), - WebViewId::new(), - )]); + )]; *self = Self::Running { window_delegate, servo, + webviews, }; } } @@ -123,46 +125,69 @@ impl ApplicationHandler<WakerEvent> for App { if let Self::Running { window_delegate, servo, + webviews, } = self { - let mut events_for_servo = vec![]; - for (_webview_id, message) in servo.get_events() { + for (_webview_id, 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(); - events_for_servo.extend([ - EmbedderEvent::FocusWebView(webview_id), - EmbedderEvent::MoveResizeWebView(webview_id, rect), - EmbedderEvent::RaiseWebViewToTop(webview_id, true), - ]); + 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, + )]); }, _ => {}, } } - servo.handle_events(events_for_servo); + // FIXME: still needed for the compositor to actually run + servo.handle_events([]); } match event { WindowEvent::CloseRequested => { - if matches!(self, Self::Running { .. }) { - let Self::Running { servo, .. } = replace(self, Self::Exiting) else { - unreachable!() - }; - // TODO: ask Servo to shut down and wait for EmbedderMsg::Shutdown? - servo.deinit(); - } event_loop.exit(); }, WindowEvent::RedrawRequested => { if let Self::Running { window_delegate, servo, + .. } = self { servo.present(); window_delegate.window.request_redraw(); } }, + 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(), + } + } + }, _ => (), } } diff --git a/components/servo/lib.rs b/components/servo/lib.rs index a516f228019..c96926d9af0 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -17,7 +17,11 @@ //! `Servo` is fed events from a generic type that implements the //! `WindowMethods` trait. -use std::borrow::{BorrowMut, Cow}; +mod proxies; +mod webview; + +use std::borrow::Cow; +use std::cell::RefCell; use std::cmp::max; use std::path::PathBuf; use std::rc::Rc; @@ -103,12 +107,15 @@ use webrender_traits::{ }; pub use { background_hang_monitor, base, bluetooth, bluetooth_traits, canvas, canvas_traits, compositing, - constellation, devtools, devtools_traits, embedder_traits, euclid, fonts, ipc_channel, - keyboard_types, layout_thread_2020, media, net, net_traits, profile, profile_traits, script, + devtools, devtools_traits, embedder_traits, euclid, fonts, ipc_channel, keyboard_types, + layout_thread_2020, media, net, net_traits, profile, profile_traits, script, script_layout_interface, script_traits, servo_config as config, servo_config, servo_geometry, - servo_url as url, servo_url, style, style_traits, webrender_api, webrender_traits, + servo_url, style, style_traits, webrender_api, webrender_traits, }; +use crate::proxies::ConstellationProxy; +pub use crate::webview::WebView; + #[cfg(feature = "webdriver")] fn webdriver(port: u16, constellation: Sender<ConstellationMsg>) { webdriver_server::start_server(port, constellation); @@ -177,8 +184,8 @@ mod media_platform { /// loop to pump messages between the embedding application and /// various browser components. pub struct Servo { - compositor: IOCompositor, - constellation_chan: Sender<ConstellationMsg>, + compositor: Rc<RefCell<IOCompositor>>, + constellation_proxy: ConstellationProxy, embedder_receiver: EmbedderReceiver, messages_for_embedder: Vec<(Option<TopLevelBrowsingContextId>, EmbedderMsg)>, profiler_enabled: bool, @@ -440,7 +447,7 @@ impl Servo { ); let (player_context, glplayer_threads) = Self::create_media_window_gl_context( - external_image_handlers.borrow_mut(), + &mut external_image_handlers, external_images.clone(), &rendering_context, ); @@ -518,8 +525,8 @@ impl Servo { ); Servo { - compositor, - constellation_chan, + compositor: Rc::new(RefCell::new(compositor)), + constellation_proxy: ConstellationProxy::new(constellation_chan), embedder_receiver, messages_for_embedder: Vec::new(), profiler_enabled: false, @@ -640,15 +647,15 @@ impl Servo { EmbedderEvent::Idle => {}, EmbedderEvent::Refresh => { - self.compositor.composite(); + self.compositor.borrow_mut().composite(); }, EmbedderEvent::WindowResize => { - return self.compositor.on_resize_window_event(); + return self.compositor.borrow_mut().on_resize_window_event(); }, EmbedderEvent::ThemeChange(theme) => { let msg = ConstellationMsg::ThemeChange(theme); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending platform theme change to constellation failed ({:?}).", e @@ -656,16 +663,17 @@ impl Servo { } }, EmbedderEvent::InvalidateNativeSurface => { - self.compositor.invalidate_native_surface(); + self.compositor.borrow_mut().invalidate_native_surface(); }, EmbedderEvent::ReplaceNativeSurface(native_widget, coords) => { self.compositor + .borrow_mut() .replace_native_surface(native_widget, coords); - self.compositor.composite(); + self.compositor.borrow_mut().composite(); }, EmbedderEvent::AllowNavigationResponse(pipeline_id, allowed) => { let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allowed); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending allow navigation to constellation failed ({:?}).", e @@ -675,57 +683,66 @@ impl Servo { EmbedderEvent::LoadUrl(top_level_browsing_context_id, url) => { let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending load url to constellation failed ({:?}).", e); } }, EmbedderEvent::ClearCache => { let msg = ConstellationMsg::ClearCache; - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending clear cache to constellation failed ({:?}).", e); } }, EmbedderEvent::MouseWindowEventClass(mouse_window_event) => { self.compositor + .borrow_mut() .on_mouse_window_event_class(mouse_window_event); }, EmbedderEvent::MouseWindowMoveEventClass(cursor) => { - self.compositor.on_mouse_window_move_event_class(cursor); + self.compositor + .borrow_mut() + .on_mouse_window_move_event_class(cursor); }, EmbedderEvent::Touch(event_type, identifier, location) => { self.compositor + .borrow_mut() .on_touch_event(event_type, identifier, location); }, EmbedderEvent::Wheel(delta, location) => { - self.compositor.on_wheel_event(delta, location); + self.compositor.borrow_mut().on_wheel_event(delta, location); }, EmbedderEvent::Scroll(scroll_location, cursor, phase) => { self.compositor + .borrow_mut() .on_scroll_event(scroll_location, cursor, phase); }, EmbedderEvent::Zoom(magnification) => { - self.compositor.on_zoom_window_event(magnification); + self.compositor + .borrow_mut() + .on_zoom_window_event(magnification); }, EmbedderEvent::ResetZoom => { - self.compositor.on_zoom_reset_window_event(); + self.compositor.borrow_mut().on_zoom_reset_window_event(); }, EmbedderEvent::PinchZoom(zoom) => { - self.compositor.on_pinch_zoom_window_event(zoom); + self.compositor + .borrow_mut() + .on_pinch_zoom_window_event(zoom); }, EmbedderEvent::Navigation(top_level_browsing_context_id, direction) => { let msg = ConstellationMsg::TraverseHistory(top_level_browsing_context_id, direction); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending navigation to constellation failed ({:?}).", e); } self.messages_for_embedder.push(( @@ -736,14 +753,14 @@ impl Servo { EmbedderEvent::Keyboard(key_event) => { let msg = ConstellationMsg::Keyboard(key_event); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending keyboard event to constellation failed ({:?}).", e); } }, EmbedderEvent::IMEComposition(ime_event) => { let msg = ConstellationMsg::IMECompositionEvent(ime_event); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending composition event to constellation failed ({:?}).", e @@ -753,7 +770,7 @@ impl Servo { EmbedderEvent::IMEDismissed => { let msg = ConstellationMsg::IMEDismissed; - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending IMEDismissed event to constellation failed ({:?}).", e @@ -762,19 +779,19 @@ impl Servo { }, EmbedderEvent::Quit => { - self.compositor.maybe_start_shutting_down(); + self.compositor.borrow_mut().maybe_start_shutting_down(); }, EmbedderEvent::ExitFullScreen(top_level_browsing_context_id) => { let msg = ConstellationMsg::ExitFullScreen(top_level_browsing_context_id); - if let Err(e) = self.constellation_chan.send(msg) { + 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); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending reload to constellation failed ({:?}).", e); } }, @@ -786,22 +803,22 @@ impl Servo { } else { ConstellationMsg::DisableProfiler }; - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending profiler toggle to constellation failed ({:?}).", e); } }, EmbedderEvent::ToggleWebRenderDebug(option) => { - self.compositor.toggle_webrender_debug(option); + self.compositor.borrow_mut().toggle_webrender_debug(option); }, EmbedderEvent::CaptureWebRender => { - self.compositor.capture_webrender(); + self.compositor.borrow_mut().capture_webrender(); }, EmbedderEvent::NewWebView(url, top_level_browsing_context_id) => { let msg = ConstellationMsg::NewWebView(url, top_level_browsing_context_id); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending NewBrowser message to constellation failed ({:?}).", e @@ -811,7 +828,7 @@ impl Servo { EmbedderEvent::FocusWebView(top_level_browsing_context_id) => { let msg = ConstellationMsg::FocusWebView(top_level_browsing_context_id); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending FocusBrowser message to constellation failed ({:?}).", e @@ -821,7 +838,7 @@ impl Servo { EmbedderEvent::CloseWebView(top_level_browsing_context_id) => { let msg = ConstellationMsg::CloseWebView(top_level_browsing_context_id); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending CloseBrowser message to constellation failed ({:?}).", e @@ -830,23 +847,30 @@ impl Servo { }, EmbedderEvent::MoveResizeWebView(webview_id, rect) => { - self.compositor.move_resize_webview(webview_id, rect); + self.compositor + .borrow_mut() + .move_resize_webview(webview_id, rect); }, EmbedderEvent::ShowWebView(webview_id, hide_others) => { - if let Err(UnknownWebView(webview_id)) = - self.compositor.show_webview(webview_id, hide_others) + if let Err(UnknownWebView(webview_id)) = self + .compositor + .borrow_mut() + .show_webview(webview_id, hide_others) { warn!("{webview_id}: ShowWebView on unknown webview id"); } }, EmbedderEvent::HideWebView(webview_id) => { - if let Err(UnknownWebView(webview_id)) = self.compositor.hide_webview(webview_id) { + if let Err(UnknownWebView(webview_id)) = + self.compositor.borrow_mut().hide_webview(webview_id) + { warn!("{webview_id}: HideWebView on unknown webview id"); } }, EmbedderEvent::RaiseWebViewToTop(webview_id, hide_others) => { if let Err(UnknownWebView(webview_id)) = self .compositor + .borrow_mut() .raise_webview_to_top(webview_id, hide_others) { warn!("{webview_id}: RaiseWebViewToTop on unknown webview id"); @@ -858,7 +882,7 @@ impl Servo { EmbedderEvent::SendError(top_level_browsing_context_id, e) => { let msg = ConstellationMsg::SendError(top_level_browsing_context_id, e); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending SendError message to constellation failed ({:?}).", e @@ -868,7 +892,7 @@ impl Servo { EmbedderEvent::MediaSessionAction(a) => { let msg = ConstellationMsg::MediaSessionAction(a); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending MediaSessionAction message to constellation failed ({:?}).", e @@ -878,7 +902,7 @@ impl Servo { EmbedderEvent::SetWebViewThrottled(webview_id, throttled) => { let msg = ConstellationMsg::SetWebViewThrottled(webview_id, throttled); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending SetWebViewThrottled to constellation failed ({:?}).", e @@ -888,12 +912,12 @@ impl Servo { EmbedderEvent::Gamepad(gamepad_event) => { let msg = ConstellationMsg::Gamepad(gamepad_event); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending Gamepad event to constellation failed ({:?}).", e); } }, EmbedderEvent::Vsync => { - self.compositor.on_vsync(); + self.compositor.borrow_mut().on_vsync(); }, EmbedderEvent::ClipboardAction(clipboard_event) => { self.send_to_constellation(ConstellationMsg::Clipboard(clipboard_event)); @@ -904,7 +928,7 @@ impl Servo { fn send_to_constellation(&self, msg: ConstellationMsg) { let variant_name = msg.variant_name(); - if let Err(e) = self.constellation_chan.send(msg) { + if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending {variant_name} to constellation failed: {e:?}"); } } @@ -913,7 +937,7 @@ impl Servo { while let Some((top_level_browsing_context, msg)) = self.embedder_receiver.try_recv_embedder_msg() { - match (msg, self.compositor.shutdown_state) { + match (msg, self.compositor.borrow().shutdown_state) { (_, ShutdownState::FinishedShuttingDown) => { error!( "embedder shouldn't be handling messages after compositor has shut down" @@ -940,7 +964,7 @@ impl Servo { } pub fn handle_events(&mut self, events: impl IntoIterator<Item = EmbedderEvent>) -> bool { - if self.compositor.receive_messages() { + if self.compositor.borrow_mut().receive_messages() { self.receive_messages(); } let mut need_resize = false; @@ -948,8 +972,8 @@ impl Servo { trace!("servo <- embedder EmbedderEvent {:?}", event); need_resize |= self.handle_window_event(event); } - if self.compositor.shutdown_state != ShutdownState::FinishedShuttingDown { - self.compositor.perform_updates(); + if self.compositor.borrow().shutdown_state != ShutdownState::FinishedShuttingDown { + self.compositor.borrow_mut().perform_updates(); } else { self.messages_for_embedder .push((None, EmbedderMsg::Shutdown)); @@ -958,15 +982,15 @@ impl Servo { } pub fn repaint_synchronously(&mut self) { - self.compositor.repaint_synchronously() + self.compositor.borrow_mut().repaint_synchronously() } pub fn pinch_zoom_level(&self) -> f32 { - self.compositor.pinch_zoom_level().get() + self.compositor.borrow_mut().pinch_zoom_level().get() } pub fn setup_logging(&self) { - let constellation_chan = self.constellation_chan.clone(); + let constellation_chan = self.constellation_proxy.sender(); let env = env_logger::Env::default(); let env_logger = EnvLoggerBuilder::from_env(env).build(); let con_logger = FromCompositorLogger::new(constellation_chan); @@ -978,22 +1002,27 @@ impl Servo { log::set_max_level(filter); } - pub fn window(&self) -> &Rc<dyn WindowMethods> { - &self.compositor.window - } - pub fn deinit(self) { - self.compositor.deinit(); + self.compositor.borrow_mut().deinit(); } pub fn present(&mut self) { - self.compositor.present(); + self.compositor.borrow_mut().present(); } /// Return the OpenGL framebuffer name of the most-recently-completed frame when compositing to /// [`CompositeTarget::Fbo`], or None otherwise. pub fn offscreen_framebuffer_id(&self) -> Option<u32> { - self.compositor.offscreen_framebuffer_id() + self.compositor.borrow().offscreen_framebuffer_id() + } + + pub fn new_webview(&self, url: url::Url) -> WebView { + WebView::new(&self.constellation_proxy, self.compositor.clone(), url) + } + + /// 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()) } } diff --git a/components/servo/proxies.rs b/components/servo/proxies.rs new file mode 100644 index 00000000000..526eeea21f3 --- /dev/null +++ b/components/servo/proxies.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 std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use compositing_traits::ConstellationMsg; +use crossbeam_channel::{SendError, Sender}; +use log::warn; + +#[derive(Clone)] +pub(crate) struct ConstellationProxy { + sender: Sender<ConstellationMsg>, + disconnected: Arc<AtomicBool>, +} + +impl ConstellationProxy { + pub fn new(sender: Sender<ConstellationMsg>) -> Self { + Self { + sender, + disconnected: Arc::default(), + } + } + + pub fn send(&self, msg: ConstellationMsg) { + if self.try_send(msg).is_err() { + warn!("Lost connection to Constellation. Will report to embedder.") + } + } + + pub fn try_send(&self, msg: ConstellationMsg) -> Result<(), SendError<ConstellationMsg>> { + if self.disconnected.load(Ordering::SeqCst) { + return Err(SendError(msg)); + } + if let Err(error) = self.sender.send(msg) { + self.disconnected.store(true, Ordering::SeqCst); + return Err(error); + } + + Ok(()) + } + + pub fn sender(&self) -> Sender<ConstellationMsg> { + self.sender.clone() + } +} diff --git a/components/servo/webview.rs b/components/servo/webview.rs new file mode 100644 index 00000000000..03dd954f688 --- /dev/null +++ b/components/servo/webview.rs @@ -0,0 +1,117 @@ +/* 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::cell::RefCell; +use std::rc::Rc; + +use base::id::WebViewId; +use compositing::IOCompositor; +use compositing_traits::ConstellationMsg; +use webrender_api::units::DeviceRect; + +use crate::ConstellationProxy; + +pub struct WebView(Rc<WebViewInner>); + +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>>, +} + +impl Drop for WebViewInner { + fn drop(&mut self) { + self.constellation_proxy + .send(ConstellationMsg::CloseWebView(self.id)); + } +} + +/// Handle for a webview. +/// +/// - The webview exists for exactly as long as there are WebView handles +/// (FIXME: this is not true yet; webviews can still close of their own volition) +/// - All methods are infallible; if the constellation dies, the embedder finds out when calling +/// [Servo::handle_events](crate::Servo::handle_events) +impl WebView { + pub(crate) fn new( + constellation_proxy: &ConstellationProxy, + compositor: Rc<RefCell<IOCompositor>>, + url: url::Url, + ) -> Self { + let webview_id = WebViewId::new(); + constellation_proxy.send(ConstellationMsg::NewWebView(url.into(), webview_id)); + + Self(Rc::new(WebViewInner { + id: webview_id, + constellation_proxy: constellation_proxy.clone(), + compositor, + })) + } + + /// 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(); + + Self( + WebViewInner { + id: webview_id, + constellation_proxy: constellation_proxy.clone(), + compositor, + } + .into(), + ) + } + + /// FIXME: Remove this once we have a webview delegate. + pub fn id(&self) -> WebViewId { + self.0.id + } + + pub fn focus(&self) { + self.0 + .constellation_proxy + .send(ConstellationMsg::FocusWebView(self.id())); + } + + pub fn blur(&self) { + self.0 + .constellation_proxy + .send(ConstellationMsg::BlurWebView); + } + + pub fn move_resize(&self, rect: DeviceRect) { + self.0 + .compositor + .borrow_mut() + .move_resize_webview(self.id(), rect); + } + + pub fn show(&self, hide_others: bool) { + self.0 + .compositor + .borrow_mut() + .show_webview(self.id(), hide_others) + .expect("BUG: invalid WebView instance"); + } + + pub fn hide(&self) { + self.0 + .compositor + .borrow_mut() + .hide_webview(self.id()) + .expect("BUG: invalid WebView instance"); + } + + pub fn raise_to_top(&self, hide_others: bool) { + self.0 + .compositor + .borrow_mut() + .raise_webview_to_top(self.id(), hide_others) + .expect("BUG: invalid WebView instance"); + } +} |