web browser (Servo)
git clone https://git.lucas.co/cce-browser.git
Open page-created webviews (window.open / target=_blank) as tabs
Implement WebViewDelegate::request_create_new: build the auxiliary
WebView from the request against the shared rendering context (the
delegate holds a Weak to itself to re-delegate the child), queue it in
HostShared, and let the next pump adopt it as a focused tab — dropping
the request would deny the open. Also accept a start URL as a CLI
argument, parsed like URL-bar input.
Live-verified: clicking a target=_blank link on a data: page opened
example.com in a new active tab.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/main.rs | 12 +++++++++---
src/webview.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 12779f9..0172bd2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -374,16 +374,22 @@ impl Application for BrowserApp {
type Message = Message;
fn new(_qh: &QueueHandle<EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
- let url = Url::parse(HOME_URL).expect("home url");
+ // Optional CLI arg: the start URL (same parsing as the URL bar).
+ let url = std::env::args()
+ .nth(1)
+ .and_then(|arg| parse_url_input(&arg))
+ .unwrap_or_else(|| Url::parse(HOME_URL).expect("home url"));
+ let url_input = url.to_string();
+ let cursor = url_input.len();
let host = ServoHost::new(sender, url, (1200, 800));
Self {
host,
win: (1200.0, 800.0),
scale: 1.0,
pointer: (0.0, 0.0),
- url_input: HOME_URL.to_string(),
+ url_input,
url_focused: false,
- cursor: HOME_URL.len(),
+ cursor,
loading: true,
title: None,
}
diff --git a/src/webview.rs b/src/webview.rs
index a101b1d..bd11ac4 100644
--- a/src/webview.rs
+++ b/src/webview.rs
@@ -17,10 +17,11 @@ use std::rc::Rc;
use dpi::PhysicalSize;
use euclid::Scale;
use servo::{
- DeviceIntRect, DevicePoint, EventLoopWaker, InputEvent, Key as DomKey, KeyState,
- KeyboardEvent, LoadStatus, MouseButton as DomMouseButton, MouseButtonAction, MouseButtonEvent,
- MouseMoveEvent, RenderingContext, Servo, ServoBuilder, SoftwareRenderingContext, WebView,
- WebViewBuilder, WebViewDelegate, WebViewId, WheelDelta, WheelEvent, WheelMode,
+ CreateNewWebViewRequest, DeviceIntRect, DevicePoint, EventLoopWaker, InputEvent,
+ Key as DomKey, KeyState, KeyboardEvent, LoadStatus, MouseButton as DomMouseButton,
+ MouseButtonAction, MouseButtonEvent, MouseMoveEvent, RenderingContext, Servo, ServoBuilder,
+ SoftwareRenderingContext, WebView, WebViewBuilder, WebViewDelegate, WebViewId, WheelDelta,
+ WheelEvent, WheelMode,
};
use url::Url;
@@ -40,11 +41,18 @@ struct TabSignals {
struct HostShared {
dirty: Cell<bool>,
per: RefCell<HashMap<WebViewId, TabSignals>>,
+ /// WebViews created by pages (window.open / target=_blank), built in the
+ /// delegate and adopted as tabs by the next `pump`.
+ pending_new: RefCell<Vec<WebView>>,
}
struct Delegate {
shared: Rc<HostShared>,
wake: calloop::channel::Sender<Message>,
+ context: Rc<SoftwareRenderingContext>,
+ /// Handle to this same Rc'd delegate, so page-opened webviews can be
+ /// delegated back here; filled right after construction.
+ self_rc: RefCell<std::rc::Weak<Delegate>>,
}
impl Delegate {
@@ -71,6 +79,19 @@ impl WebViewDelegate for Delegate {
fn notify_load_status_changed(&self, webview: WebView, status: LoadStatus) {
self.with_tab(&webview, |t| t.loading = status != LoadStatus::Complete);
}
+
+ fn request_create_new(&self, _parent_webview: WebView, request: CreateNewWebViewRequest) {
+ let Some(delegate) = self.self_rc.borrow().upgrade() else {
+ return; // dropping the request denies it
+ };
+ let webview = request
+ .builder(self.context.clone())
+ .delegate(delegate)
+ .build();
+ self.shared.pending_new.borrow_mut().push(webview);
+ self.shared.dirty.set(true);
+ let _ = self.wake.send(Message::Spin);
+ }
}
/// Wakes the calloop event loop from Servo's internal threads.
@@ -126,7 +147,13 @@ impl ServoHost {
.build();
let shared = Rc::new(HostShared::default());
- let delegate = Rc::new(Delegate { shared: shared.clone(), wake });
+ let delegate = Rc::new(Delegate {
+ shared: shared.clone(),
+ wake,
+ context: context.clone(),
+ self_rc: RefCell::new(std::rc::Weak::new()),
+ });
+ *delegate.self_rc.borrow_mut() = Rc::downgrade(&delegate);
let mut host = Self {
servo,
@@ -251,6 +278,19 @@ impl ServoHost {
/// tab if it produced a frame. Returns (new frame, any state change).
pub fn pump(&mut self) -> (bool, bool) {
self.servo.spin_event_loop();
+ // Adopt page-opened webviews as tabs; like a browser popup, the
+ // newest one takes focus.
+ let opened: Vec<WebView> = self.shared.pending_new.borrow_mut().drain(..).collect();
+ for webview in opened {
+ self.tabs.push(Tab {
+ webview,
+ title: None,
+ url: None,
+ loading: true,
+ image: None,
+ });
+ self.activate(self.tabs.len() - 1);
+ }
let dirty = self.shared.dirty.take();
let mut active_frame = false;
if dirty {