Something went wrong. Try again.
a servo-backed webview component for gpui
Something went wrong. Try again.
6.3 kB · 194 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195use std::{ cell::{Cell, RefCell}, collections::VecDeque,};
use servo::{ ConsoleLogLevel, ContextMenu, CreateNewWebViewRequest, Cursor, EmbedderControl, EmbedderControlId, InputEventId, InputEventResult, LoadStatus, MediaSessionEvent, MediaSessionPlaybackState, SimpleDialog, TraversalId, WebResourceLoad, WebView, WebViewDelegate,};use tracing::warn;use url::Url;
/// Events surfaced by servo's delegate callbacks, drained by the engine pump.pub(crate) enum EngineEvent { UrlChanged(Url), MainFrameRequest(Url), TitleChanged(Option<String>), LoadStatusChanged(LoadStatus), HistoryChanged(Option<Url>, Option<Url>), TraversalComplete, Crashed(String), Closed, CreateNew, ShowContextMenu(ContextMenu), ShowSimpleDialog(SimpleDialog), HideEmbedderControl(EmbedderControlId), ConsoleMessage(String), MediaPlaybackChanged(bool),}
const ENGINE_EVENT_CAPACITY: usize = 1000;
/// A [`WebViewDelegate`] that funnels everything into an event queue plus a/// pair of dirty flags, so the engine only does work when servo has something/// to report.pub(crate) struct EngineSink { frame_ready: Cell<bool>, needs_update: Cell<bool>, events: RefCell<VecDeque<EngineEvent>>,}
impl Default for EngineSink { fn default() -> Self { Self { frame_ready: Cell::new(false), needs_update: Cell::new(false), events: RefCell::new(VecDeque::with_capacity(ENGINE_EVENT_CAPACITY)), } }}
impl EngineSink { pub(crate) fn take_frame_ready(&self) -> bool { self.frame_ready.replace(false) }
pub(crate) fn take_needs_update(&self) -> bool { self.needs_update.replace(false) }
pub(crate) fn push_event(&self, event: EngineEvent) { self.needs_update.set(true); let mut events = self.events.borrow_mut(); if events.len() == ENGINE_EVENT_CAPACITY { warn!("engine event buffer full, dropping oldest event"); events.pop_front(); } events.push_back(event); }
pub(crate) fn take_events(&self) -> Vec<EngineEvent> { self.events.borrow_mut().drain(..).collect() }}
impl WebViewDelegate for EngineSink { fn notify_new_frame_ready(&self, _webview: WebView) { self.frame_ready.set(true); }
fn load_web_resource(&self, _webview: WebView, load: WebResourceLoad) { let request = load.request(); if request.is_for_main_frame { self.push_event(EngineEvent::MainFrameRequest(request.url.clone())); } }
fn notify_url_changed(&self, _webview: WebView, url: Url) { self.push_event(EngineEvent::UrlChanged(url)); }
fn notify_page_title_changed(&self, _webview: WebView, title: Option<String>) { self.push_event(EngineEvent::TitleChanged(title)); }
fn notify_favicon_changed(&self, _webview: WebView) { self.needs_update.set(true); }
fn notify_load_status_changed(&self, _webview: WebView, status: LoadStatus) { self.push_event(EngineEvent::LoadStatusChanged(status)); }
fn notify_traversal_complete(&self, _webview: WebView, _traversal_id: TraversalId) { self.push_event(EngineEvent::TraversalComplete); }
fn notify_crashed(&self, _webview: WebView, reason: String, _backtrace: Option<String>) { warn!(%reason, "webview crashed"); self.push_event(EngineEvent::Crashed(reason)); }
fn notify_cursor_changed(&self, _webview: WebView, _cursor: Cursor) { self.needs_update.set(true); }
fn notify_status_text_changed(&self, _webview: WebView, _status: Option<String>) { self.needs_update.set(true); }
fn show_console_message(&self, _webview: WebView, _level: ConsoleLogLevel, message: String) { self.push_event(EngineEvent::ConsoleMessage(message)); }
fn notify_history_changed(&self, _webview: WebView, entries: Vec<Url>, current: usize) { let back = current .checked_sub(1) .and_then(|index| entries.get(index).cloned()); let forward = entries.get(current + 1).cloned(); self.push_event(EngineEvent::HistoryChanged(back, forward)); }
fn notify_closed(&self, _webview: WebView) { self.push_event(EngineEvent::Closed); }
fn request_create_new(&self, _parent: WebView, _request: CreateNewWebViewRequest) { // dropping the request answers servo with "no new webview"; the engine // surfaces a `NewWindowRequested` event so a host can act on it. self.push_event(EngineEvent::CreateNew); }
fn show_embedder_control(&self, _webview: WebView, control: EmbedderControl) { match control { EmbedderControl::ContextMenu(menu) => { self.push_event(EngineEvent::ShowContextMenu(menu)) } EmbedderControl::SimpleDialog(dialog) => { self.push_event(EngineEvent::ShowSimpleDialog(dialog)) } EmbedderControl::SelectElement(_) => { warn!("unhandled embedder control: select element") } EmbedderControl::ColorPicker(_) => warn!("unhandled embedder control: color picker"), EmbedderControl::FilePicker(_) => warn!("unhandled embedder control: file picker"), EmbedderControl::InputMethod(_) => warn!("unhandled embedder control: input method"), } }
fn notify_input_event_handled( &self, _webview: WebView, _event_id: InputEventId, _result: InputEventResult, ) { // input verdicts are only meaningful when the embedder offers shortcuts // to the page first; the base component does not, so nothing to do. }
fn hide_embedder_control(&self, _webview: WebView, control_id: EmbedderControlId) { self.push_event(EngineEvent::HideEmbedderControl(control_id)); }
fn notify_media_session_event(&self, _webview: WebView, event: MediaSessionEvent) { if let MediaSessionEvent::PlaybackStateChange(state) = event { let playing = matches!(state, MediaSessionPlaybackState::Playing); self.push_event(EngineEvent::MediaPlaybackChanged(playing)); } }}