Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

winit: initial minibrowser #29976

Merged
merged 44 commits into from Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
557183c
winit: add minibrowser feature that depends on egui{,-winit}
delan Jul 6, 2023
7bb3db5
winit: carve out some space at the top of headed windows
delan Jul 6, 2023
8947018
winit: minimal toolbar and egui/winit integration (but no painting)
delan Jul 6, 2023
f9460af
winit: try to paint with egui_glow (doesn’t work yet)
delan Jul 6, 2023
4e01fde
winit: add comment about toolbar size
delan Jul 6, 2023
7103b4b
Add framebuffer object, set it as glow's target
atbrakhi Jul 11, 2023
3be6a17
compositing: clear only the viewport, not the whole framebuffer
delan Jul 11, 2023
9eb92a2
plumb the actual size of the egui toolbar to webrender
atbrakhi Jul 11, 2023
034a831
fix formatting
atbrakhi Jul 11, 2023
e495886
winit: fix crash when fbo is zero
delan Jul 12, 2023
e9f8c3c
winit: don’t bother binding the framebuffer object
delan Jul 12, 2023
a8e1e5b
winit: remove unsafe and get toolbar_height
atbrakhi Jul 16, 2023
aac08c7
winit: location field should reflect the current top-level url
atbrakhi Jul 17, 2023
8da7cf8
merge from master
delan Jul 24, 2023
37ea3f0
[NFC] winit: move Minibrowser out of App::run
delan Jul 26, 2023
d717962
winit: clean up toolbar height code
delan Jul 26, 2023
dda07d9
winit: make App own the Minibrowser if any
delan Jul 26, 2023
6717cf0
winit: make the go button work
delan Jul 26, 2023
8cd288e
winit:make the location field reflect the current top-level url
atbrakhi Jul 27, 2023
9793cad
merge from master
delan Jul 31, 2023
e8c4dea
winit: allow enabling minibrowser from command line
atbrakhi Jul 31, 2023
0d9f487
winit: tell compositor to repaint WR and flush when we repaint
delan Aug 3, 2023
b4df0ac
winit: fix bug where location field edits would get overridden
delan Aug 3, 2023
8dfdf57
winit: borrow the minibrowser once in App::handle_events
delan Aug 3, 2023
bb5cbf8
winit: address todo about viewport origin coordinates
delan Aug 3, 2023
0ab250c
winit: fix some minor problems with comments and errors
delan Aug 4, 2023
bbc6587
winit: update location field once per HistoryChanged event
delan Aug 4, 2023
728a41f
winit: rename Window::set_toolbar_size to set_toolbar_height
delan Aug 4, 2023
3e56ccb
winit: take toolbar height into account in hit testing
delan Aug 4, 2023
ffc971d
winit: pass egui only relevant CursorMoved events
delan Aug 8, 2023
62ec187
winit: scratch that, coalesce minibrowser updates instead
delan Aug 8, 2023
d1ea041
ensure both minibrowser and WR are repainted on every frame
delan Aug 9, 2023
f7a13a7
compositing: only skip framebuffer clear in external present mode
delan Aug 9, 2023
8b8a201
winit: destroy egui glow Painter when shutting down
delan Aug 10, 2023
2a31b39
winit: clean up and fix license lint
delan Aug 10, 2023
dac5b14
fix duplicate versions lint by downgrading bytemuck_derive
delan Aug 10, 2023
1a4ce1f
fix duplicate versions lint by disabling egui-winit/links
delan Aug 10, 2023
573820c
squelch duplicate versions lint by excluding clipboard-win
delan Aug 10, 2023
95dabae
winit: fix compile warnings
delan Aug 10, 2023
bd114b5
winit: make gleam an optional dependency under /minibrowser
delan Aug 10, 2023
00d6187
winit: remove cargo feature, since it’s not really optional
delan Aug 14, 2023
06e0d29
winit: extract Minibrowser and related code to separate module
delan Aug 14, 2023
ebdb007
winit: remove unnecessary trailing comma
delan Aug 14, 2023
b01b10c
winit: simplify the ServoUrl serialisation optimisation
delan Aug 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
448 changes: 395 additions & 53 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions components/compositing/compositor.rs
Expand Up @@ -1733,11 +1733,6 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
let gl = &self.webrender_gl;
self.assert_gl_framebuffer_complete();

// Make framebuffer fully transparent.
gl.clear_color(0.0, 0.0, 0.0, 0.0);
gl.clear(gleam::gl::COLOR_BUFFER_BIT);
self.assert_gl_framebuffer_complete();

// Make the viewport white.
let viewport = self.embedder_coordinates.get_flipped_viewport();
gl.scissor(
Expand Down
8 changes: 7 additions & 1 deletion ports/winit/Cargo.toml
Expand Up @@ -30,12 +30,13 @@ ProductName = "Servo"

[features]
debugmozjs = ["libservo/debugmozjs"]
default = ["webdriver", "max_log_level"]
default = ["webdriver", "max_log_level", "minibrowser"]
delan marked this conversation as resolved.
Show resolved Hide resolved
jitspew = ["libservo/jitspew"]
js_backtrace = ["libservo/js_backtrace"]
max_log_level = ["log/release_max_level_info"]
media-dummy = ["libservo/media-dummy"]
media-gstreamer = ["libservo/media-gstreamer"]
minibrowser = ["dep:egui", "dep:egui-winit", "dep:egui_glow", "dep:glow"]
native-bluetooth = ["libservo/native-bluetooth"]
no-wgl = ["libservo/no-wgl"]
profilemozjs = ["libservo/profilemozjs"]
Expand All @@ -48,8 +49,13 @@ xr-profile = ["libservo/xr-profile"]
[target.'cfg(not(target_os = "android"))'.dependencies]
backtrace = { workspace = true }
clipboard = "0.5"
egui = { version = "0.22.0", optional = true }
egui_glow = { version = "0.22.0", optional = true, features = ["winit"] }
egui-winit = { version = "0.22.0", optional = true }
euclid = { workspace = true }
getopts = { workspace = true }
gleam = "0.12"
glow = { version = "0.12.2", optional = true }
keyboard-types = { workspace = true }
lazy_static = { workspace = true }
libc = { workspace = true }
Expand Down
154 changes: 150 additions & 4 deletions ports/winit/app.rs
Expand Up @@ -9,18 +9,23 @@ use crate::embedder::EmbedderCallbacks;
use crate::events_loop::{EventsLoop, WakerEvent};
use crate::window_trait::WindowPortsMethods;
use crate::{headed_window, headless_window};
use egui::TopBottomPanel;
use egui_winit::EventResponse;
use gleam::gl;
use winit::window::WindowId;
use winit::event_loop::EventLoopWindowTarget;
use servo::compositing::windowing::EmbedderEvent;
use servo::config::opts::{self, parse_url_or_filename};
use servo::servo_config::pref;
use servo::servo_url::ServoUrl;
use servo::Servo;
use std::cell::{Cell, RefCell};
use std::cell::{Cell, RefCell, RefMut};
use std::collections::HashMap;
use std::env;

use std::rc::Rc;
use std::sync::Arc;
use surfman::GLApi;
use webxr::glwindow::GlWindowDiscovery;

pub struct App {
Expand All @@ -29,6 +34,55 @@ pub struct App {
event_queue: RefCell<Vec<EmbedderEvent>>,
suspended: Cell<bool>,
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
minibrowser: Option<RefCell<Minibrowser>>,
}

struct Minibrowser {
delan marked this conversation as resolved.
Show resolved Hide resolved
context: egui_glow::EguiGlow,
event_queue: RefCell<Vec<MinibrowserEvent>>,
toolbar_height: Cell<f32>,
location: RefCell<String>,

/// Whether the location has been edited by the user without clicking Go.
location_dirty: Cell<bool>,
}

enum MinibrowserEvent {
/// Go button clicked.
Go,

/// Minibrowser repainted and needs glFlush.
Repaint,
}

impl Minibrowser {
fn update(&mut self, window: &winit::window::Window) {
let Self { context, event_queue, location, location_dirty, toolbar_height } = self;
let _duration = context.run(window, |ctx| {
TopBottomPanel::top("toolbar").show(ctx, |ui| {
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
if ui.button("go").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Go);
location_dirty.set(false);
}
if ui.add_sized(
ui.available_size(),
egui::TextEdit::singleline(&mut *location.borrow_mut()),
).changed() {
location_dirty.set(true);
}
},
);
});

toolbar_height.set(ctx.used_rect().height());
});
context.paint(window);
event_queue.borrow_mut().push(MinibrowserEvent::Repaint);
}
}

impl App {
Expand Down Expand Up @@ -60,8 +114,42 @@ impl App {
servo: None,
suspended: Cell::new(false),
windows: HashMap::new(),
minibrowser: None,
};

if opts::get().minibrowser && window.winit_window().is_some() {
// Make sure the gl context is made current.
let webrender_surfman = window.webrender_surfman();
let webrender_gl = match webrender_surfman.connection().gl_api() {
GLApi::GL => unsafe { gl::GlFns::load_with(|s| webrender_surfman.get_proc_address(s)) },
GLApi::GLES => unsafe {
gl::GlesFns::load_with(|s| webrender_surfman.get_proc_address(s))
},
};
let gl = unsafe {
glow::Context::from_loader_function(|s| {
webrender_surfman.get_proc_address(s)
})
};
webrender_surfman.make_gl_context_current().unwrap();
debug_assert_eq!(webrender_gl.get_error(), gleam::gl::NO_ERROR,);
delan marked this conversation as resolved.
Show resolved Hide resolved

// Set up egui context for minibrowser ui
// Adapted from https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_glow/examples/pure_glow.rs
app.minibrowser = Some(Minibrowser {
context: egui_glow::EguiGlow::new(events_loop.as_winit(), Arc::new(gl), None),
event_queue: RefCell::new(vec![]),
toolbar_height: 0f32.into(),
location: RefCell::new(ServoUrl::into_string(get_default_url())),
location_dirty: false.into(),
}.into());
}

if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.update(window.winit_window().unwrap());
window.set_toolbar_size(minibrowser.toolbar_height.get());
}

let ev_waker = events_loop.create_event_loop_waker();
events_loop.run_forever(move |e, w, control_flow| {
if let winit::event::Event::NewEvents(winit::event::StartCause::Init) = e {
Expand Down Expand Up @@ -114,7 +202,24 @@ impl App {
}

// Handle the event
app.queue_embedder_events_for_winit_event(e);
let response = match e {
winit::event::Event::WindowEvent { ref event, .. } => {
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.context.on_event(&event)
delan marked this conversation as resolved.
Show resolved Hide resolved
} else {
EventResponse { consumed: false, repaint: false }
}
}
_ => EventResponse { consumed: false, repaint: false },
};

// TODO how do we handle the tab key? (see doc for consumed)
if !response.consumed {
app.queue_embedder_events_for_winit_event(e);
}
if response.repaint {
app.minibrowser().unwrap().update(window.winit_window().unwrap());
}

let animating = app.is_animating();

Expand Down Expand Up @@ -183,6 +288,37 @@ impl App {
}
}

/// Takes any outstanding events from the [Minibrowser], converting them to [EmbedderEvent] and
/// routing those to the App event queue.
fn queue_embedder_events_for_minibrowser_events(&self, minibrowser: &mut RefMut<Minibrowser>) {
for event in minibrowser.event_queue.borrow_mut().drain(..) {
match event {
MinibrowserEvent::Go => {
let browser_id = self.browser.borrow().browser_id().unwrap();
let location = minibrowser.location.borrow();
let Ok(url) = ServoUrl::parse(&location) else {
warn!("failed to parse location");
break;
};
self.event_queue.borrow_mut().push(EmbedderEvent::LoadUrl(browser_id, url));
},
MinibrowserEvent::Repaint => {
self.event_queue.borrow_mut().push(EmbedderEvent::Refresh);
},
}
}
}

/// Updates the location field when the [Browser] says it has changed, unless the user has
/// started editing it without clicking Go.
fn update_location_in_toolbar(&self, minibrowser: &mut RefMut<Minibrowser>) {
if !minibrowser.location_dirty.get() {
if let Some(current_url) = self.browser.borrow_mut().take_top_level_url_change() {
minibrowser.location = RefCell::new(ServoUrl::into_string(current_url));
}
}
}

/// Pumps events and messages between the embedder and Servo, where embedder events flow
/// towards Servo and embedder messages flow away from Servo, and also runs the compositor.
///
Expand All @@ -191,15 +327,21 @@ impl App {
/// collect embedder messages from the various Servo components, then take them out of the
/// Servo interface so that the Browser can handle them.
fn handle_events(&mut self) -> bool {
let mut browser = self.browser.borrow_mut();

// FIXME:
// As of now, we support only one browser (self.browser)
// but have multiple windows (dom.webxr.glwindow). We forward
// the events of all the windows combined to that single
// browser instance. Pressing the "a" key on the glwindow
// will send a key event to the servo window.

// Consume and handle any events from the Minibrowser.
if let Some(mut minibrowser) = self.minibrowser() {
self.queue_embedder_events_for_minibrowser_events(&mut minibrowser);
self.update_location_in_toolbar(&mut minibrowser);
}

let mut browser = self.browser.borrow_mut();

// Take any outstanding embedder events from the App and its Windows.
let mut embedder_events = self.get_events();
for (_win_id, window) in &self.windows {
Expand Down Expand Up @@ -236,6 +378,10 @@ impl App {
}
false
}

fn minibrowser(&self) -> Option<RefMut<Minibrowser>> {
self.minibrowser.as_ref().map(|x| x.borrow_mut())
}
}

fn get_default_url() -> ServoUrl {
Expand Down
18 changes: 18 additions & 0 deletions ports/winit/browser.rs
Expand Up @@ -31,6 +31,8 @@ use tinyfiledialogs::{self, MessageBoxIcon, OkCancel, YesNo};

pub struct Browser<Window: WindowPortsMethods + ?Sized> {
current_url: Option<ServoUrl>,
current_url_taken: bool,
delan marked this conversation as resolved.
Show resolved Hide resolved

/// id of the top level browsing context. It is unique as tabs
/// are not supported yet. None until created.
browser_id: Option<BrowserId>,
Expand All @@ -57,6 +59,7 @@ where
Browser {
title: None,
current_url: None,
current_url_taken: false,
browser_id: None,
browsers: Vec::new(),
window,
Expand All @@ -72,6 +75,20 @@ where
}
}

pub fn browser_id(&self) -> Option<BrowserId> {
self.browser_id
}

/// Returns current_url iff there has been a HistoryChanged event since the last call.
pub fn take_top_level_url_change(&mut self) -> Option<ServoUrl> {
if self.current_url_taken {
None
} else {
self.current_url_taken = true;
self.current_url.clone()
}
}

pub fn get_events(&mut self) -> Vec<EmbedderEvent> {
std::mem::take(&mut self.event_queue)
}
Expand Down Expand Up @@ -437,6 +454,7 @@ where
},
EmbedderMsg::HistoryChanged(urls, current) => {
self.current_url = Some(urls[current].clone());
self.current_url_taken = false;
delan marked this conversation as resolved.
Show resolved Hide resolved
},
EmbedderMsg::SetFullscreenState(state) => {
self.window.set_fullscreen(state);
Expand Down
20 changes: 18 additions & 2 deletions ports/winit/headed_window.rs
Expand Up @@ -52,6 +52,7 @@ pub struct Window {
webrender_surfman: WebrenderSurfman,
screen_size: Size2D<u32, DeviceIndependentPixel>,
inner_size: Cell<Size2D<u32, DeviceIndependentPixel>>,
toolbar_size: Cell<f32>,
mouse_down_button: Cell<Option<winit::event::MouseButton>>,
mouse_down_point: Cell<Point2D<i32, DevicePixel>>,
primary_monitor: winit::monitor::MonitorHandle,
Expand Down Expand Up @@ -155,6 +156,7 @@ impl Window {
device_pixels_per_px,
xr_window_poses: RefCell::new(vec![]),
modifiers_state: Cell::new(ModifiersState::empty()),
toolbar_size: Cell::new(0.0),
}
}

Expand Down Expand Up @@ -503,6 +505,14 @@ impl WindowPortsMethods for Window {
self.xr_window_poses.borrow_mut().push(pose.clone());
Box::new(XRWindow { winit_window, pose })
}

fn winit_window(&self) -> Option<&winit::window::Window> {
Some(&self.winit_window)
}

fn set_toolbar_size(&self, height: f32) {
self.toolbar_size.set(height);
}
}

impl WindowMethods for Window {
Expand All @@ -524,8 +534,14 @@ impl WindowMethods for Window {
let PhysicalSize { width, height } = self
.winit_window
.inner_size();
let inner_size = (Size2D::new(width as f32, height as f32) * dpr).to_i32();
let viewport = DeviceIntRect::new(Point2D::zero(), inner_size);

// Subtract the minibrowser toolbar height if any
let toolbar_height = self.toolbar_size.get();
let inner_size = Size2D::new(width as f32, height as f32) * dpr;
let viewport_size = inner_size - Size2D::new(0f32, toolbar_height);
let viewport_origin = DeviceIntPoint::zero(); // bottom left
let viewport = DeviceIntRect::new(viewport_origin, viewport_size.to_i32());

let framebuffer = DeviceIntSize::from_untyped(viewport.size.to_untyped());
EmbedderCoordinates {
viewport,
Expand Down
8 changes: 8 additions & 0 deletions ports/winit/headless_window.rs
Expand Up @@ -108,6 +108,14 @@ impl WindowPortsMethods for Window {
) -> Box<dyn webxr::glwindow::GlWindow> {
unimplemented!()
}

fn winit_window(&self) -> Option<&winit::window::Window> {
None
}

fn set_toolbar_size(&self, _height: f32) {
unimplemented!("headless Window only")
}
}

impl WindowMethods for Window {
Expand Down
2 changes: 2 additions & 0 deletions ports/winit/window_trait.rs
Expand Up @@ -31,4 +31,6 @@ pub trait WindowPortsMethods: WindowMethods {
&self,
events_loop: &winit::event_loop::EventLoopWindowTarget<WakerEvent>
) -> Box<dyn webxr::glwindow::GlWindow>;
fn winit_window(&self) -> Option<&winit::window::Window>;
fn set_toolbar_size(&self, height: f32);
}