Skip to content

Commit 8ab1484

Browse files
committed
feat: implement browser scrollbar overlay and clipboard support via upstream integration
1 parent c9698c6 commit 8ab1484

7 files changed

Lines changed: 2495 additions & 5 deletions

File tree

run.log

Lines changed: 2321 additions & 0 deletions
Large diffs are not rendered by default.

src/app/input.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,17 @@ impl BrazenApp {
235235
let config = &self.config.shortcuts;
236236

237237
if matches_shortcut(&config.select_all, &key, &modifiers) {
238+
tracing::info!(target: "brazen::input", "SHORTCUT: select all detected");
238239
self.engine.select_all();
239240
self.shell_state.record_event("shortcut: select all");
240241
handled_shortcut = true;
241242
} else if matches_shortcut(&config.copy, &key, &modifiers) {
243+
tracing::info!(target: "brazen::input", "SHORTCUT: copy detected");
242244
self.engine.copy();
243245
self.shell_state.record_event("shortcut: copy");
244246
handled_shortcut = true;
245247
} else if matches_shortcut(&config.paste, &key, &modifiers) {
248+
tracing::info!(target: "brazen::input", "SHORTCUT: paste detected");
246249
self.engine.paste();
247250
self.shell_state.record_event("shortcut: paste");
248251
handled_shortcut = true;

src/app/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ impl BrazenApp {
564564
self.load_status_started_at = Some(Instant::now());
565565
}
566566
if let Some(started_at) = self.load_status_started_at
567-
&& started_at.elapsed() >= Duration::from_secs(10)
567+
&& started_at.elapsed() >= Duration::from_secs(30)
568568
&& !self.load_status_warned
569569
{
570570
self.load_status_warned = true;

src/app/ui_components.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ fn beautify_url(url: &str) -> String {
3838

3939
impl super::BrazenApp {
4040
pub fn render_browser_view(&mut self, ctx: &eframe::egui::Context) {
41-
eframe::egui::CentralPanel::default().show(ctx, |ui| {
41+
eframe::egui::CentralPanel::default()
42+
.frame(eframe::egui::Frame::central_panel(&ctx.style()).inner_margin(8.0))
43+
.show(ctx, |ui| {
4244
let available = ui.available_size();
4345
let available = eframe::egui::vec2(available.x.floor(), available.y.floor());
4446
let (rect, _response_opt) = if let Some(texture) = &self.render_texture {
@@ -80,6 +82,11 @@ impl super::BrazenApp {
8082

8183
self.render_viewport_rect = Some(rect);
8284

85+
// Draw Scrollbar Overlay
86+
if let Some(scroll_info) = self.engine.scroll_info() {
87+
self.render_scrollbar(ui, rect, &scroll_info);
88+
}
89+
8390
// Scaffold Mode Overlay
8491
if self.shell_state.backend_name == "scaffold" {
8592
let painter = ui.painter().with_clip_rect(rect);
@@ -603,4 +610,34 @@ impl super::BrazenApp {
603610
self.shell_state.pending_context_menu = None;
604611
}
605612
}
613+
614+
fn render_scrollbar(&self, ui: &mut eframe::egui::Ui, rect: eframe::egui::Rect, info: &crate::engine::ScrollInfo) {
615+
if info.content_size.1 <= info.viewport_size.1 || info.viewport_size.1 <= 0.0 {
616+
return;
617+
}
618+
619+
let bar_width = 6.0;
620+
let bar_bg_color = eframe::egui::Color32::from_black_alpha(30);
621+
let bar_handle_color = eframe::egui::Color32::from_white_alpha(100);
622+
623+
let max_scroll = (info.content_size.1 - info.viewport_size.1).max(1.0);
624+
let scroll_ratio = (info.scroll_pos.1 / max_scroll).clamp(0.0, 1.0);
625+
let handle_height_ratio = (info.viewport_size.1 / info.content_size.1).clamp(0.1, 1.0);
626+
let handle_height = rect.height() * handle_height_ratio;
627+
let handle_top = (rect.height() - handle_height) * scroll_ratio;
628+
629+
let bar_rect = eframe::egui::Rect::from_min_size(
630+
rect.right_top() - eframe::egui::vec2(bar_width + 4.0, 0.0),
631+
eframe::egui::vec2(bar_width, rect.height()),
632+
);
633+
634+
let handle_rect = eframe::egui::Rect::from_min_size(
635+
bar_rect.left_top() + eframe::egui::vec2(0.0, handle_top),
636+
eframe::egui::vec2(bar_width, handle_height),
637+
);
638+
639+
let painter = ui.painter().with_clip_rect(rect);
640+
painter.rect_filled(bar_rect, 3.0, bar_bg_color);
641+
painter.rect_filled(handle_rect, 3.0, bar_handle_color);
642+
}
606643
}

src/engine.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ use crate::servo_embedder::{ServoEmbedder, ServoEmbedderConfig};
99

1010
pub type EngineInstanceId = u64;
1111

12+
#[derive(Debug, Clone, PartialEq)]
13+
pub struct ScrollInfo {
14+
pub scroll_pos: (f32, f32),
15+
pub content_size: (f32, f32),
16+
pub viewport_size: (f32, f32),
17+
}
18+
1219
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1320
pub enum FocusState {
1421
Focused,
@@ -380,6 +387,9 @@ pub trait BrowserEngine {
380387
fn take_screenshot(&mut self) -> Result<EngineFrame, String>;
381388
fn health(&self) -> RenderHealth;
382389
fn select_all(&mut self);
390+
fn copy(&mut self);
391+
fn paste(&mut self);
392+
fn scroll_info(&self) -> Option<ScrollInfo>;
383393
}
384394

385395

@@ -642,6 +652,12 @@ impl BrowserEngine for ScaffoldEngine {
642652
}
643653

644654
fn select_all(&mut self) {}
655+
fn copy(&mut self) {}
656+
fn paste(&mut self) {}
657+
658+
fn scroll_info(&self) -> Option<ScrollInfo> {
659+
None
660+
}
645661
}
646662
use std::sync::{Arc, RwLock};
647663
use crate::session::SessionSnapshot;
@@ -1138,4 +1154,14 @@ impl BrowserEngine for ServoEngine {
11381154
fn select_all(&mut self) {
11391155
self.embedder.select_all();
11401156
}
1157+
fn copy(&mut self) {
1158+
self.embedder.copy();
1159+
}
1160+
fn paste(&mut self) {
1161+
self.embedder.paste();
1162+
}
1163+
1164+
fn scroll_info(&self) -> Option<ScrollInfo> {
1165+
self.embedder.scroll_info()
1166+
}
11411167
}

src/servo_embedder.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,32 @@ impl ServoEmbedder {
489489
}
490490
}
491491

492+
pub fn copy(&mut self) {
493+
#[cfg(feature = "servo-upstream")]
494+
if let Some(upstream) = &self.upstream {
495+
upstream.copy();
496+
}
497+
}
498+
499+
pub fn paste(&mut self) {
500+
#[cfg(feature = "servo-upstream")]
501+
if let Some(upstream) = &self.upstream {
502+
upstream.paste();
503+
}
504+
}
505+
506+
pub fn scroll_info(&self) -> Option<crate::engine::ScrollInfo> {
507+
#[cfg(feature = "servo-upstream")]
508+
if let Some(snapshot) = &self.last_snapshot {
509+
return Some(crate::engine::ScrollInfo {
510+
scroll_pos: snapshot.scroll_pos,
511+
content_size: snapshot.content_size,
512+
viewport_size: snapshot.viewport_size,
513+
});
514+
}
515+
None
516+
}
517+
492518
pub fn handle_input(&mut self, event: &InputEvent) {
493519
if self.verbose_logging {
494520
tracing::trace!(target: "brazen::servo", ?event, "input forwarded");
@@ -566,6 +592,7 @@ impl ServoEmbedder {
566592
self.last_pointer.0,
567593
self.last_pointer.1,
568594
);
595+
upstream.update_scroll_info();
569596
}
570597
#[cfg(not(feature = "servo-upstream"))]
571598
let _ = (delta_x, delta_y);
@@ -760,6 +787,7 @@ impl ServoEmbedder {
760787
pixel_format: self.config.pixel_format,
761788
alpha_mode: self.config.alpha_mode,
762789
color_space: self.config.color_space,
790+
scale_factor: surface.metadata.scale_factor_basis_points as f32 / 100.0,
763791
enable_pixel_probe: self.config.enable_pixel_probe,
764792
resources_dir: self.config.resources_dir.clone(),
765793
certificate_path: self.config.certificate_path.clone(),

src/servo_upstream.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub struct UpstreamSnapshot {
4343
pub history_index: usize,
4444
pub animating: bool,
4545
pub last_error: Option<String>,
46+
pub scroll_pos: (f32, f32),
47+
pub content_size: (f32, f32),
48+
pub viewport_size: (f32, f32),
4649
}
4750

4851
#[derive(Debug, Clone)]
@@ -61,6 +64,7 @@ pub struct ServoUpstreamConfig {
6164
pub pixel_format: PixelFormat,
6265
pub alpha_mode: AlphaMode,
6366
pub color_space: ColorSpace,
67+
pub scale_factor: f32,
6468
pub enable_pixel_probe: bool,
6569
pub resources_dir: Option<PathBuf>,
6670
pub certificate_path: Option<PathBuf>,
@@ -88,6 +92,9 @@ impl Default for UpstreamSnapshot {
8892
history_index: 0,
8993
animating: false,
9094
last_error: None,
95+
scroll_pos: (0.0, 0.0),
96+
content_size: (0.0, 0.0),
97+
viewport_size: (0.0, 0.0),
9198
}
9299
}
93100
}
@@ -319,6 +326,7 @@ pub struct ServoUpstreamRuntime {
319326
resource_source: ResourceDirSource,
320327
pending_clipboard_request: Arc<std::sync::Mutex<Option<StringRequest>>>,
321328
event_sender: std::sync::mpsc::Sender<EngineEvent>,
329+
scale_factor: f32,
322330
}
323331

324332
impl std::fmt::Debug for ServoUpstreamRuntime {
@@ -437,6 +445,7 @@ impl ServoUpstreamRuntime {
437445
pending_clipboard_request.clone(),
438446
));
439447
let webview = WebViewBuilder::new(&servo, rendering_context.clone())
448+
.hidpi_scale_factor(libservo::Scale::new(config.scale_factor))
440449
.delegate(delegate)
441450
.clipboard_delegate(clipboard_delegate)
442451
.build();
@@ -480,6 +489,7 @@ impl ServoUpstreamRuntime {
480489
resource_source: source,
481490
pending_clipboard_request,
482491
event_sender,
492+
scale_factor: config.scale_factor,
483493
})
484494
}
485495

@@ -511,6 +521,7 @@ impl ServoUpstreamRuntime {
511521
}
512522

513523
pub fn select_all(&self) {
524+
tracing::info!(target: "brazen::servo", "UPSTREAM: select_all executed");
514525
let script = "document.execCommand('selectAll', false, null);".to_string();
515526
let webview_id = self.webview.id();
516527
self.servo.javascript_evaluator_mut().evaluate(
@@ -520,6 +531,67 @@ impl ServoUpstreamRuntime {
520531
);
521532
}
522533

534+
pub fn copy(&self) {
535+
tracing::info!(target: "brazen::servo", "UPSTREAM: copy executed");
536+
let script = "document.execCommand('copy', false, null);".to_string();
537+
let webview_id = self.webview.id();
538+
self.servo.javascript_evaluator_mut().evaluate(
539+
webview_id,
540+
script,
541+
Box::new(|_| {}),
542+
);
543+
}
544+
545+
pub fn paste(&self) {
546+
tracing::info!(target: "brazen::servo", "UPSTREAM: paste executed");
547+
let script = "document.execCommand('paste', false, null);".to_string();
548+
let webview_id = self.webview.id();
549+
self.servo.javascript_evaluator_mut().evaluate(
550+
webview_id,
551+
script,
552+
Box::new(|_| {}),
553+
);
554+
}
555+
556+
pub fn update_scroll_info(&self) {
557+
let script = "JSON.stringify({
558+
scrollX: window.scrollX,
559+
scrollY: window.scrollY,
560+
contentWidth: document.documentElement.scrollWidth,
561+
contentHeight: document.documentElement.scrollHeight,
562+
viewportWidth: window.innerWidth,
563+
viewportHeight: window.innerHeight
564+
})".to_string();
565+
566+
let snapshot = self.snapshot.clone();
567+
let webview_id = self.webview.id();
568+
self.servo.javascript_evaluator_mut().evaluate(
569+
webview_id,
570+
script,
571+
Box::new(move |res| {
572+
if let Ok(val) = res {
573+
if let libservo::JSValue::String(s) = val {
574+
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&s) {
575+
let mut snap = snapshot.borrow_mut();
576+
snap.scroll_pos = (
577+
json["scrollX"].as_f64().unwrap_or(0.0) as f32,
578+
json["scrollY"].as_f64().unwrap_or(0.0) as f32
579+
);
580+
snap.content_size = (
581+
json["contentWidth"].as_f64().unwrap_or(0.0) as f32,
582+
json["contentHeight"].as_f64().unwrap_or(0.0) as f32
583+
);
584+
snap.viewport_size = (
585+
json["viewportWidth"].as_f64().unwrap_or(0.0) as f32,
586+
json["viewportHeight"].as_f64().unwrap_or(0.0) as f32
587+
);
588+
}
589+
}
590+
}
591+
}),
592+
);
593+
}
594+
523595
pub fn navigate(&self, url: &str) -> Result<(), String> {
524596
let url = Url::parse(url).map_err(|error| format!("invalid url: {error}"))?;
525597
self.webview.load(url);
@@ -620,8 +692,9 @@ impl ServoUpstreamRuntime {
620692
}
621693

622694
pub fn handle_mouse_move(&self, x: f32, y: f32) {
695+
let scale = self.scale_factor;
623696
self.handle_input(InputEvent::MouseMove(MouseMoveEvent::new(
624-
WebViewPoint::Device(DevicePoint::new(x, y)),
697+
WebViewPoint::Device(DevicePoint::new(x * scale, y * scale)),
625698
)));
626699
}
627700

@@ -631,10 +704,11 @@ impl ServoUpstreamRuntime {
631704
} else {
632705
MouseButtonAction::Up
633706
};
707+
let scale = self.scale_factor;
634708
self.handle_input(InputEvent::MouseButton(MouseButtonEvent::new(
635709
action,
636710
MouseButton::from(button),
637-
WebViewPoint::Device(DevicePoint::new(x, y)),
711+
WebViewPoint::Device(DevicePoint::new(x * scale, y * scale)),
638712
)));
639713
}
640714

@@ -651,9 +725,10 @@ impl ServoUpstreamRuntime {
651725
z: 0.0,
652726
mode: WheelMode::DeltaPixel,
653727
};
728+
let scale = self.scale_factor;
654729
self.handle_input(InputEvent::Wheel(WheelEvent::new(
655730
delta,
656-
WebViewPoint::Device(DevicePoint::new(x, y)),
731+
WebViewPoint::Device(DevicePoint::new(x * scale, y * scale)),
657732
)));
658733
}
659734

0 commit comments

Comments
 (0)