aboutsummaryrefslogtreecommitdiffstats
path: root/components/servo/examples
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2025-02-05 18:08:40 +0800
committerGitHub <noreply@github.com>2025-02-05 10:08:40 +0000
commit175f28866dc254a98c4a911eb38ed9b200fdc6d1 (patch)
treee5f89c66cd473e9ed76c66a057e4bc3cc5451fb0 /components/servo/examples
parent789736590b0dd0806bffeb279f9a9bda9ede0dfc (diff)
downloadservo-175f28866dc254a98c4a911eb38ed9b200fdc6d1.tar.gz
servo-175f28866dc254a98c4a911eb38ed9b200fdc6d1.zip
libservo: Add WebViewDelegate and ServoDelegate and port `winit_minimal` (#35196)
This change adds the second major part of the new API: delegates which have methods called by the Servo loop. When a delegate is set on a `WebView` or on `Servo` itself, the event loop will call into appropriate delegate methods. Applications can implement the delegate on their own structs to add special behavior per-`WebView` or for all `WebView`s. In addition, each delegate has a default implementation, which automatically exposes "reasonable" behavior such as by-default allowing navigation. There's a lot more work to do here, such as refining the delegate methods so that they all have nice interfaces, particulary with regard to delegate methods that need an asynchronous response. This will be handed gradually as we keep working on the API. Signed-off-by: Delan Azabani <dazabani@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Diffstat (limited to 'components/servo/examples')
-rw-r--r--components/servo/examples/winit_minimal.rs153
1 files changed, 79 insertions, 74 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(),
+ }
}
}
},