This commit is contained in:
Jouni 2024-06-20 03:05:11 +03:00
parent d4e310293d
commit 097635a784
7 changed files with 874 additions and 381 deletions

795
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,11 @@ libservo = { git = "https://github.com/servo/servo" }
surfman = "0.9.3"
winit = "0.30.0"
glow = "0.13.1"
glutin = "0.32.0"
egui = { git = "https://github.com/AndriBaal/egui", branch = "winit-0.30" }
egui_glow = { git = "https://github.com/AndriBaal/egui", branch = "winit-0.30", features = [ "winit" ] }
egui-winit = { git = "https://github.com/AndriBaal/egui", branch = "winit-0.30", default-features = false, features = [ "wayland", "x11" ] }
toml = "0.5.11"
serde-inline-default = "0.2.0"
@ -15,4 +20,4 @@ serde = "1.0.203"
xdg = "2.5.2"
log = "0.4.21"
env_logger = "0.11.3"
env_logger = "0.10.2"

View File

@ -18,11 +18,11 @@ use winit::{
};
use crate::{
embedder::EmbedderCallbacks, window::Window, winit_mouse_to_servo_mouse,
browser::Browser, embedder::EmbedderCallbacks, window::Window, winit_mouse_to_servo_mouse,
winit_position_to_euclid_point, ServoEvent,
};
type Frame = (Rc<Window>, Servo<Window>);
type Frame = (Rc<Window>, Servo<Window>, Browser);
type ButtonPress = (MouseButton, Point2D<f64, DevicePixel>);
pub struct App {
@ -33,6 +33,7 @@ pub struct App {
current_url: Option<ServoUrl>,
mouse_pos: Cell<Point2D<f64, DevicePixel>>,
mouse_down: Cell<Option<ButtonPress>>,
event_queue: Vec<EmbedderEvent>,
}
impl App {
@ -45,8 +46,113 @@ impl App {
current_url: None,
mouse_pos: Cell::new(Point2D::default()),
mouse_down: Cell::new(None),
event_queue: vec![],
}
}
fn handle_events(&mut self) -> bool {
let (window, servo, browser) = match self.frame.as_mut() {
Some((w, s, b)) => (w, s, b),
None => return false,
};
let mut present = false;
{
let mut servo_events = servo.get_events();
loop {
for (webview_id, msg) in servo_events {
trace!("servo event: view: {:?} msg: {:?}", webview_id, msg);
match msg {
EmbedderMsg::ReadyToPresent(_) => window.request_redraw(),
EmbedderMsg::WebViewOpened(new_webview_id) => {
let rect = window.get_coordinates().get_viewport().to_f32();
self.webviews.push(new_webview_id);
self.event_queue.extend_from_slice(&[
EmbedderEvent::FocusWebView(new_webview_id),
EmbedderEvent::MoveResizeWebView(new_webview_id, rect),
EmbedderEvent::RaiseWebViewToTop(new_webview_id, true),
]);
}
EmbedderMsg::WebViewFocused(webview_id) => {
self.focused_webview = Some(webview_id);
self.event_queue
.push(EmbedderEvent::ShowWebView(webview_id, true));
}
EmbedderMsg::LoadStart
| EmbedderMsg::LoadComplete
| EmbedderMsg::HeadParsed => present = true,
EmbedderMsg::HistoryChanged(urls, current) => {
self.current_url = Some(urls[current].clone());
browser.set_location(self.current_url.as_ref().map(|x| x.to_string()));
present = true;
}
EmbedderMsg::ChangePageTitle(title) => window.set_title(
&title
.or(self.current_url.as_ref().map(|x| x.to_string()))
.unwrap_or("Servo".into()),
),
EmbedderMsg::SetCursor(cursor) => window.set_cursor(cursor),
EmbedderMsg::EventDelivered(event) => {
if let (Some(wvid), CompositorEventVariant::MouseButtonEvent) =
(webview_id, event)
{
self.event_queue.extend_from_slice(&[
EmbedderEvent::RaiseWebViewToTop(wvid, true),
EmbedderEvent::FocusWebView(wvid),
]);
}
}
EmbedderMsg::WebViewClosed(webview_id) => {
self.webviews.retain(|&x| x != webview_id);
self.focused_webview = None;
self.event_queue.push(
self.webviews
.last()
.map(|&x| EmbedderEvent::FocusWebView(x))
.unwrap_or(EmbedderEvent::Quit),
);
}
EmbedderMsg::Shutdown => {
return true;
}
EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
if webview_id.is_some() {
self.event_queue
.push(EmbedderEvent::AllowNavigationResponse(
pipeline_id,
true,
));
}
}
_ => {}
}
}
present |= servo.handle_events(self.event_queue.drain(..));
servo_events = servo.get_events();
if servo_events.len() == 0 {
break;
}
}
}
if present {
servo.present();
let w = window.winit_window();
self.event_queue.extend(browser.update(
w,
self.focused_webview,
servo.offscreen_framebuffer_id(),
));
browser.paint(w);
}
false
}
}
impl ApplicationHandler<ServoEvent> for App {
@ -60,17 +166,26 @@ impl ApplicationHandler<ServoEvent> for App {
) {
trace!("window event: {:?}", event);
let (window, servo) = match self.frame.as_mut() {
Some((w, s)) => (w, s),
let (window, servo, browser) = match self.frame.as_mut() {
Some((w, s, b)) => (w, s, b),
None => return,
};
let mut events = vec![];
match event {
WindowEvent::RedrawRequested => {
servo.present();
let w = window.winit_window();
self.event_queue.extend(browser.update(
w,
self.focused_webview,
servo.offscreen_framebuffer_id(),
));
browser.paint(w);
return;
}
WindowEvent::Resized(size) => {
window.resize(size);
events.push(EmbedderEvent::WindowResize);
self.event_queue.push(EmbedderEvent::WindowResize);
}
WindowEvent::CursorMoved {
device_id: _,
@ -78,7 +193,8 @@ impl ApplicationHandler<ServoEvent> for App {
} => {
let position = winit_position_to_euclid_point(position);
self.mouse_pos.set(position);
events.push(EmbedderEvent::MouseWindowMoveEventClass(position.to_f32()));
self.event_queue
.push(EmbedderEvent::MouseWindowMoveEventClass(position.to_f32()));
}
WindowEvent::MouseInput {
device_id: _,
@ -87,12 +203,14 @@ impl ApplicationHandler<ServoEvent> for App {
} => match button {
winit::event::MouseButton::Back => {
if let Some(x) = self.focused_webview {
events.push(EmbedderEvent::Navigation(x, TraversalDirection::Back(1)));
self.event_queue
.push(EmbedderEvent::Navigation(x, TraversalDirection::Back(1)));
}
}
winit::event::MouseButton::Forward => {
if let Some(x) = self.focused_webview {
events.push(EmbedderEvent::Navigation(x, TraversalDirection::Forward(1)));
self.event_queue
.push(EmbedderEvent::Navigation(x, TraversalDirection::Forward(1)));
}
}
_ => {
@ -103,12 +221,12 @@ impl ApplicationHandler<ServoEvent> for App {
match state {
ElementState::Pressed => {
self.mouse_down.set(Some((sbutton, position)));
events.push(EmbedderEvent::MouseWindowEventClass(
self.event_queue.push(EmbedderEvent::MouseWindowEventClass(
MouseWindowEvent::MouseDown(sbutton, pos),
));
}
ElementState::Released => {
events.push(EmbedderEvent::MouseWindowEventClass(
self.event_queue.push(EmbedderEvent::MouseWindowEventClass(
MouseWindowEvent::MouseUp(sbutton, pos),
));
if let Some((dbut, dpos)) = self.mouse_down.get() {
@ -117,9 +235,11 @@ impl ApplicationHandler<ServoEvent> for App {
+ pixel_dist.y * pixel_dist.y)
.sqrt();
if pixel_dist < 10.0 * window.hidpi_factor() {
events.push(EmbedderEvent::MouseWindowEventClass(
MouseWindowEvent::Click(sbutton, pos),
));
self.event_queue.push(
EmbedderEvent::MouseWindowEventClass(
MouseWindowEvent::Click(sbutton, pos),
),
);
}
}
}
@ -127,12 +247,12 @@ impl ApplicationHandler<ServoEvent> for App {
}
}
},
WindowEvent::CloseRequested => events.push(EmbedderEvent::Quit),
WindowEvent::CloseRequested => self.event_queue.push(EmbedderEvent::Quit),
_ => {}
}
if event == WindowEvent::RedrawRequested || servo.handle_events(events) {
servo.present();
if self.handle_events() {
event_loop.exit();
}
}
@ -141,24 +261,25 @@ impl ApplicationHandler<ServoEvent> for App {
if cause == StartCause::Init {
let window = Rc::new(Window::new(event_loop));
let browser = Browser::new(event_loop, &window.rendering_context());
let embedder = Box::new(EmbedderCallbacks::new(self.ev_waker.clone()));
let servo_data = Servo::new(embedder, window.clone(), None, CompositeTarget::Window);
let servo_data = Servo::new(embedder, window.clone(), None, CompositeTarget::Fbo);
let mut servo = servo_data.servo;
servo.handle_events([EmbedderEvent::NewWebView(
ServoUrl::parse("https://gnu.org").unwrap(),
ServoUrl::parse("https://browserleaks.com").unwrap(),
servo_data.browser_id,
)]);
self.frame = Some((window, servo));
self.frame = Some((window, servo, browser));
debug!("init done");
}
let (window, servo) = match self.frame.as_mut() {
Some((w, s)) => (w, s),
let (window, servo, browser) = match self.frame.as_mut() {
Some((w, s, b)) => (w, s, b),
None => return,
};
@ -170,80 +291,8 @@ impl ApplicationHandler<ServoEvent> for App {
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, _event: ServoEvent) {
let (window, servo) = match self.frame.as_mut() {
Some((w, s)) => (w, s),
None => return,
};
let mut events = vec![];
let mut present = false;
for (webview_id, msg) in servo.get_events() {
trace!("servo event: view: {:?} msg: {:?}", webview_id, msg);
match msg {
EmbedderMsg::ReadyToPresent(_) => window.request_redraw(),
EmbedderMsg::WebViewOpened(new_webview_id) => {
let rect = window.get_coordinates().get_viewport().to_f32();
self.webviews.push(new_webview_id);
events.extend_from_slice(&[
EmbedderEvent::FocusWebView(new_webview_id),
EmbedderEvent::MoveResizeWebView(new_webview_id, rect),
EmbedderEvent::RaiseWebViewToTop(new_webview_id, true),
]);
}
EmbedderMsg::WebViewFocused(webview_id) => {
self.focused_webview = Some(webview_id);
events.push(EmbedderEvent::ShowWebView(webview_id, true));
}
EmbedderMsg::LoadStart | EmbedderMsg::LoadComplete | EmbedderMsg::HeadParsed => {
present = true
}
EmbedderMsg::HistoryChanged(urls, current) => {
self.current_url = Some(urls[current].clone());
present = true;
}
EmbedderMsg::ChangePageTitle(title) => window.set_title(
&title
.or(self.current_url.as_ref().map(|x| x.to_string()))
.unwrap_or("Servo".into()),
),
EmbedderMsg::SetCursor(cursor) => window.set_cursor(cursor),
EmbedderMsg::EventDelivered(event) => {
if let (Some(wvid), CompositorEventVariant::MouseButtonEvent) =
(webview_id, event)
{
events.extend_from_slice(&[
EmbedderEvent::RaiseWebViewToTop(wvid, true),
EmbedderEvent::FocusWebView(wvid),
]);
}
}
EmbedderMsg::WebViewClosed(webview_id) => {
self.webviews.retain(|&x| x != webview_id);
self.focused_webview = None;
events.push(
self.webviews
.last()
.map(|&x| EmbedderEvent::FocusWebView(x))
.unwrap_or(EmbedderEvent::Quit),
);
}
EmbedderMsg::Shutdown => {
event_loop.exit();
return;
}
EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
if webview_id.is_some() {
events.push(EmbedderEvent::AllowNavigationResponse(pipeline_id, true));
}
}
_ => {}
}
}
if servo.handle_events(events) || present {
servo.present();
if self.handle_events() {
event_loop.exit();
}
}

198
src/browser.rs Normal file
View File

@ -0,0 +1,198 @@
use egui::{CentralPanel, Frame, PaintCallback, Pos2, TopBottomPanel, Vec2};
use egui_glow::{CallbackFn, EguiGlow};
use glow::NativeFramebuffer;
use servo::{
base::id::TopLevelBrowsingContextId as WebViewId,
compositing::windowing::EmbedderEvent,
euclid::{Box2D, Point2D, Scale, Size2D},
gl,
servo_geometry::DeviceIndependentPixel,
style_traits::DevicePixel,
webrender_api::units::DeviceRect,
webrender_traits::RenderingContext,
};
use std::{num::NonZeroU32, sync::Arc};
use surfman::GLApi;
use winit::event_loop::ActiveEventLoop;
pub struct Browser {
widget_surface_fbo: Option<glow::NativeFramebuffer>,
egui_glow: EguiGlow,
shapes: Vec<egui::epaint::ClippedShape>,
textures_delta: egui::TexturesDelta,
location: Option<String>,
rect: DeviceRect,
}
impl Browser {
pub fn new(event_loop: &ActiveEventLoop, rendering_context: &RenderingContext) -> Self {
let webrender_gl = match rendering_context.connection().gl_api() {
GLApi::GL => unsafe { gl::GlFns::load_with(|s| rendering_context.get_proc_address(s)) },
GLApi::GLES => unsafe {
gl::GlesFns::load_with(|s| rendering_context.get_proc_address(s))
},
};
if webrender_gl.get_error() != gl::NO_ERROR
|| rendering_context.make_gl_context_current().is_err()
{
panic!("gl error");
}
let gl = unsafe {
glow::Context::from_loader_function(|s| rendering_context.get_proc_address(s))
};
let egui_glow = egui_glow::EguiGlow::new(&event_loop, Arc::new(gl), None, None);
let widget_surface_fbo = rendering_context
.context_surface_info()
.ok()
.and_then(|x| x.and_then(|x| NonZeroU32::new(x.framebuffer_object)))
.map(NativeFramebuffer);
Self {
widget_surface_fbo,
egui_glow,
shapes: vec![],
textures_delta: Default::default(),
location: None,
rect: DeviceRect::default(),
}
}
pub fn set_location(&mut self, x: Option<String>) {
self.location = x;
}
pub fn paint(&mut self, window: &winit::window::Window) {
unsafe {
use glow::HasContext as _;
self.egui_glow
.painter
.gl()
.bind_framebuffer(gl::FRAMEBUFFER, self.widget_surface_fbo);
}
let mut textures_delta = self.textures_delta.to_owned();
for (id, image_delta) in textures_delta.set {
self.egui_glow.painter.set_texture(id, &image_delta);
}
let pixels_per_point = self.egui_glow.egui_ctx.pixels_per_point();
let clipped_primitives = self
.egui_glow
.egui_ctx
.tessellate(self.shapes.to_owned(), pixels_per_point);
self.egui_glow.painter.paint_primitives(
window.inner_size().into(),
pixels_per_point,
&clipped_primitives,
);
for id in textures_delta.free.drain(..) {
self.egui_glow.painter.free_texture(id);
}
}
pub fn update(
&mut self,
window: &winit::window::Window,
focused_webview: Option<WebViewId>,
servo_framebuffer_id: Option<gl::GLuint>,
) -> Vec<EmbedderEvent> {
let mut events = vec![];
let raw_input = self.egui_glow.egui_winit.take_egui_input(window);
let widget_fbo = self.widget_surface_fbo;
let label = self.location.to_owned().unwrap_or("...".into());
let full_output = self.egui_glow.egui_ctx.run(raw_input, |ctx| {
TopBottomPanel::bottom("locbar").show(ctx, |ui| {
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
ui.label(label);
},
);
});
let scale =
Scale::<_, DeviceIndependentPixel, DevicePixel>::new(ctx.pixels_per_point());
CentralPanel::default()
.frame(Frame::none())
.show(ctx, |ui| {
let Pos2 { x, y } = ui.cursor().min;
let Vec2 {
x: width,
y: height,
} = ui.available_size();
let rect =
Box2D::from_origin_and_size(Point2D::new(x, y), Size2D::new(width, height))
* scale;
if rect != self.rect {
self.rect = rect;
if let Some(x) = focused_webview {
events.push(EmbedderEvent::MoveResizeWebView(x, rect));
}
}
let min = ui.cursor().min;
let size = ui.available_size();
let rect = egui::Rect::from_min_size(min, size);
ui.allocate_space(size);
let servo_fbo = servo_framebuffer_id
.and_then(NonZeroU32::new)
.map(NativeFramebuffer);
ui.painter().add(PaintCallback {
rect,
callback: Arc::new(CallbackFn::new(move |info, painter| {
let clip = info.viewport_in_pixels();
let x = clip.left_px;
let y = clip.from_bottom_px;
let width = clip.width_px;
let height = clip.height_px;
unsafe {
use glow::HasContext as _;
painter.gl().clear_color(0.0, 0.0, 0.0, 0.0);
painter.gl().scissor(x, y, width, height);
painter.gl().enable(gl::SCISSOR_TEST);
painter.gl().clear(gl::COLOR_BUFFER_BIT);
painter.gl().disable(gl::SCISSOR_TEST);
painter
.gl()
.bind_framebuffer(gl::READ_FRAMEBUFFER, servo_fbo);
painter
.gl()
.bind_framebuffer(gl::DRAW_FRAMEBUFFER, widget_fbo);
painter.gl().blit_framebuffer(
x,
y,
x + width,
y + height,
x,
y,
x + width,
y + height,
gl::COLOR_BUFFER_BIT,
gl::NEAREST,
);
painter.gl().bind_framebuffer(gl::FRAMEBUFFER, widget_fbo);
}
})),
});
});
});
self.egui_glow
.egui_winit
.handle_platform_output(window, full_output.platform_output);
self.shapes = full_output.shapes;
self.textures_delta.append(full_output.textures_delta);
events
}
}

View File

@ -14,8 +14,7 @@ macro_rules! def {
#[serde_inline_default]
#[derive(Debug, Deserialize)]
pub struct Keys {
}
pub struct Keys {}
def!(Keys);
#[serde_inline_default]

View File

@ -2,6 +2,7 @@
extern crate log;
mod app;
mod browser;
mod embedder;
mod event_loop;
mod utils;

View File

@ -27,7 +27,7 @@ pub struct Window {
}
impl Window {
pub fn new(event_loop: &ActiveEventLoop) -> Window {
pub fn new(event_loop: &ActiveEventLoop) -> Self {
let opts = opts::get();
let win_size = opts.initial_window_size.to_untyped().to_i32();
@ -66,7 +66,7 @@ impl Window {
let rendering_context = RenderingContext::create(&connection, &adapter, surface_type)
.expect("Failed to create WR surfman");
Window {
Self {
winit_window,
rendering_context,
animation_state: Cell::new(AnimationState::Idle),
@ -147,6 +147,10 @@ impl Window {
pub fn is_animating(&self) -> bool {
self.animation_state.get() == AnimationState::Animating
}
pub fn winit_window(&self) -> &winit::window::Window {
&self.winit_window
}
}
impl WindowMethods for Window {