mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
libservo: Port desktop servoshell to use the new WebView
API (#35183)
This removes all uses of `EmbedderEvent` in the desktop servoshell to use the new `WebView` API -- filling it out when necessary. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
parent
a1cf0cbf86
commit
9dbc942bee
15 changed files with 826 additions and 798 deletions
|
@ -427,19 +427,19 @@ impl BackgroundHangMonitorWorker {
|
|||
},
|
||||
recv(self.control_port) -> event => {
|
||||
match event {
|
||||
Ok(BackgroundHangMonitorControlMsg::EnableSampler(rate, max_duration)) => {
|
||||
println!("Enabling profiler.");
|
||||
self.sampling_duration = Some(rate);
|
||||
self.sampling_max_duration = Some(max_duration);
|
||||
self.sampling_baseline = Instant::now();
|
||||
Ok(BackgroundHangMonitorControlMsg::ToggleSampler(rate, max_duration)) => {
|
||||
if self.sampling_duration.is_some() {
|
||||
println!("Enabling profiler.");
|
||||
self.finish_sampled_profile();
|
||||
self.sampling_duration = None;
|
||||
} else {
|
||||
println!("Disabling profiler.");
|
||||
self.sampling_duration = Some(rate);
|
||||
self.sampling_max_duration = Some(max_duration);
|
||||
self.sampling_baseline = Instant::now();
|
||||
}
|
||||
None
|
||||
},
|
||||
Ok(BackgroundHangMonitorControlMsg::DisableSampler) => {
|
||||
println!("Disabling profiler.");
|
||||
self.finish_sampled_profile();
|
||||
self.sampling_duration = None;
|
||||
return true;
|
||||
},
|
||||
Ok(BackgroundHangMonitorControlMsg::Exit(sender)) => {
|
||||
for component in self.monitored_components.values_mut() {
|
||||
component.exit_signal.signal_to_exit();
|
||||
|
|
|
@ -1318,7 +1318,7 @@ impl IOCompositor {
|
|||
self.pipeline_details.remove(&pipeline_id);
|
||||
}
|
||||
|
||||
pub fn on_resize_window_event(&mut self) -> bool {
|
||||
pub fn on_rendering_context_resized(&mut self) -> bool {
|
||||
if self.shutdown_state != ShutdownState::NotShuttingDown {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1443,24 +1443,15 @@ where
|
|||
self.forward_event(destination_pipeline_id, event);
|
||||
},
|
||||
FromCompositorMsg::SetCursor(cursor) => self.handle_set_cursor_msg(cursor),
|
||||
FromCompositorMsg::EnableProfiler(rate, max_duration) => {
|
||||
FromCompositorMsg::ToggleProfiler(rate, max_duration) => {
|
||||
for background_monitor_control_sender in &self.background_monitor_control_senders {
|
||||
if let Err(e) = background_monitor_control_sender.send(
|
||||
BackgroundHangMonitorControlMsg::EnableSampler(rate, max_duration),
|
||||
BackgroundHangMonitorControlMsg::ToggleSampler(rate, max_duration),
|
||||
) {
|
||||
warn!("error communicating with sampling profiler: {}", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
FromCompositorMsg::DisableProfiler => {
|
||||
for background_monitor_control_sender in &self.background_monitor_control_senders {
|
||||
if let Err(e) = background_monitor_control_sender
|
||||
.send(BackgroundHangMonitorControlMsg::DisableSampler)
|
||||
{
|
||||
warn!("error communicating with sampling profiler: {}", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
FromCompositorMsg::ExitFullScreen(top_level_browsing_context_id) => {
|
||||
self.handle_exit_fullscreen_msg(top_level_browsing_context_id);
|
||||
},
|
||||
|
|
|
@ -85,8 +85,7 @@ mod from_compositor {
|
|||
Self::BlurWebView => target!("BlurWebView"),
|
||||
Self::ForwardEvent(_, event) => event.log_target(),
|
||||
Self::SetCursor(..) => target!("SetCursor"),
|
||||
Self::EnableProfiler(..) => target!("EnableProfiler"),
|
||||
Self::DisableProfiler => target!("DisableProfiler"),
|
||||
Self::ToggleProfiler(..) => target!("EnableProfiler"),
|
||||
Self::ExitFullScreen(_) => target!("ExitFullScreen"),
|
||||
Self::MediaSessionAction(_) => target!("MediaSessionAction"),
|
||||
Self::SetWebViewThrottled(_, _) => target!("SetWebViewThrottled"),
|
||||
|
|
|
@ -30,7 +30,7 @@ use std::thread;
|
|||
use std::vec::Drain;
|
||||
|
||||
pub use base::id::TopLevelBrowsingContextId;
|
||||
use base::id::{PipelineNamespace, PipelineNamespaceId};
|
||||
use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId};
|
||||
use bluetooth::BluetoothThreadFactory;
|
||||
use bluetooth_traits::BluetoothRequest;
|
||||
use canvas::canvas_paint_thread::CanvasPaintThread;
|
||||
|
@ -189,7 +189,6 @@ pub struct Servo {
|
|||
constellation_proxy: ConstellationProxy,
|
||||
embedder_receiver: EmbedderReceiver,
|
||||
messages_for_embedder: Vec<(Option<TopLevelBrowsingContextId>, EmbedderMsg)>,
|
||||
profiler_enabled: bool,
|
||||
/// For single-process Servo instances, this field controls the initialization
|
||||
/// and deinitialization of the JS Engine. Multiprocess Servo instances have their
|
||||
/// own instance that exists in the content process instead.
|
||||
|
@ -530,7 +529,6 @@ impl Servo {
|
|||
constellation_proxy: ConstellationProxy::new(constellation_chan),
|
||||
embedder_receiver,
|
||||
messages_for_embedder: Vec::new(),
|
||||
profiler_enabled: false,
|
||||
_js_engine_setup: js_engine_setup,
|
||||
}
|
||||
}
|
||||
|
@ -652,7 +650,7 @@ impl Servo {
|
|||
},
|
||||
|
||||
EmbedderEvent::WindowResize => {
|
||||
return self.compositor.borrow_mut().on_resize_window_event();
|
||||
return self.compositor.borrow_mut().on_rendering_context_resized();
|
||||
},
|
||||
EmbedderEvent::ThemeChange(theme) => {
|
||||
let msg = ConstellationMsg::ThemeChange(theme);
|
||||
|
@ -798,13 +796,10 @@ impl Servo {
|
|||
},
|
||||
|
||||
EmbedderEvent::ToggleSamplingProfiler(rate, max_duration) => {
|
||||
self.profiler_enabled = !self.profiler_enabled;
|
||||
let msg = if self.profiler_enabled {
|
||||
ConstellationMsg::EnableProfiler(rate, max_duration)
|
||||
} else {
|
||||
ConstellationMsg::DisableProfiler
|
||||
};
|
||||
if let Err(e) = self.constellation_proxy.try_send(msg) {
|
||||
if let Err(e) = self
|
||||
.constellation_proxy
|
||||
.try_send(ConstellationMsg::ToggleProfiler(rate, max_duration))
|
||||
{
|
||||
warn!("Sending profiler toggle to constellation failed ({:?}).", e);
|
||||
}
|
||||
},
|
||||
|
@ -1003,6 +998,20 @@ impl Servo {
|
|||
log::set_max_level(filter);
|
||||
}
|
||||
|
||||
pub fn start_shutting_down(&self) {
|
||||
self.compositor.borrow_mut().maybe_start_shutting_down();
|
||||
}
|
||||
|
||||
pub fn allow_navigation_response(&self, pipeline_id: PipelineId, allow: bool) {
|
||||
let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allow);
|
||||
if let Err(e) = self.constellation_proxy.try_send(msg) {
|
||||
warn!(
|
||||
"Sending allow navigation to constellation failed ({:?}).",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self) {
|
||||
self.compositor.borrow_mut().deinit();
|
||||
}
|
||||
|
|
|
@ -3,15 +3,26 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::c_void;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use base::id::WebViewId;
|
||||
use compositing::windowing::{MouseWindowEvent, WebRenderDebugOption};
|
||||
use compositing::IOCompositor;
|
||||
use compositing_traits::ConstellationMsg;
|
||||
use webrender_api::units::DeviceRect;
|
||||
use embedder_traits::{
|
||||
ClipboardEventType, GamepadEvent, MediaSessionActionType, Theme, TouchEventType, TouchId,
|
||||
TraversalDirection, WheelDelta,
|
||||
};
|
||||
use keyboard_types::{CompositionEvent, KeyboardEvent};
|
||||
use url::Url;
|
||||
use webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePoint, DeviceRect};
|
||||
use webrender_api::ScrollLocation;
|
||||
|
||||
use crate::ConstellationProxy;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WebView(Rc<WebViewInner>);
|
||||
|
||||
struct WebViewInner {
|
||||
|
@ -38,7 +49,7 @@ impl WebView {
|
|||
pub(crate) fn new(
|
||||
constellation_proxy: &ConstellationProxy,
|
||||
compositor: Rc<RefCell<IOCompositor>>,
|
||||
url: url::Url,
|
||||
url: Url,
|
||||
) -> Self {
|
||||
let webview_id = WebViewId::new();
|
||||
constellation_proxy.send(ConstellationMsg::NewWebView(url.into(), webview_id));
|
||||
|
@ -114,4 +125,188 @@ impl WebView {
|
|||
.raise_webview_to_top(self.id(), hide_others)
|
||||
.expect("BUG: invalid WebView instance");
|
||||
}
|
||||
|
||||
pub fn notify_theme_change(&self, theme: Theme) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::ThemeChange(theme))
|
||||
}
|
||||
|
||||
pub fn load(&self, url: Url) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::LoadUrl(self.id(), url.into()))
|
||||
}
|
||||
|
||||
pub fn reload(&self) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::Reload(self.id()))
|
||||
}
|
||||
|
||||
pub fn go_back(&self, amount: usize) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::TraverseHistory(
|
||||
self.id(),
|
||||
TraversalDirection::Back(amount),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn go_forward(&self, amount: usize) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::TraverseHistory(
|
||||
self.id(),
|
||||
TraversalDirection::Forward(amount),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn notify_pointer_button_event(&self, event: MouseWindowEvent) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_mouse_window_event_class(event);
|
||||
}
|
||||
|
||||
pub fn notify_pointer_move_event(&self, event: DevicePoint) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_mouse_window_move_event_class(event);
|
||||
}
|
||||
|
||||
pub fn notify_touch_event(&self, event_type: TouchEventType, id: TouchId, point: DevicePoint) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_touch_event(event_type, id, point);
|
||||
}
|
||||
|
||||
pub fn notify_wheel_event(&self, delta: WheelDelta, point: DevicePoint) {
|
||||
self.0.compositor.borrow_mut().on_wheel_event(delta, point);
|
||||
}
|
||||
|
||||
pub fn notify_scroll_event(
|
||||
&self,
|
||||
location: ScrollLocation,
|
||||
point: DeviceIntPoint,
|
||||
touch_event_type: TouchEventType,
|
||||
) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_scroll_event(location, point, touch_event_type);
|
||||
}
|
||||
|
||||
pub fn notify_keyboard_event(&self, event: KeyboardEvent) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::Keyboard(event))
|
||||
}
|
||||
|
||||
pub fn notify_ime_event(&self, event: CompositionEvent) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::IMECompositionEvent(event))
|
||||
}
|
||||
|
||||
pub fn notify_ime_dismissed_event(&self) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::IMEDismissed);
|
||||
}
|
||||
|
||||
pub fn notify_gamepad_event(&self, event: GamepadEvent) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::Gamepad(event));
|
||||
}
|
||||
|
||||
pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::MediaSessionAction(event));
|
||||
}
|
||||
|
||||
pub fn notify_clipboard_event(&self, event: ClipboardEventType) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::Clipboard(event));
|
||||
}
|
||||
|
||||
pub fn notify_vsync(&self) {
|
||||
self.0.compositor.borrow_mut().on_vsync();
|
||||
}
|
||||
|
||||
pub fn notify_rendering_context_resized(&self) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_rendering_context_resized();
|
||||
}
|
||||
|
||||
pub fn set_zoom(&self, new_zoom: f32) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_zoom_window_event(new_zoom);
|
||||
}
|
||||
|
||||
pub fn reset_zoom(&self) {
|
||||
self.0.compositor.borrow_mut().on_zoom_reset_window_event();
|
||||
}
|
||||
|
||||
pub fn set_pinch_zoom(&self, new_pinch_zoom: f32) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.on_pinch_zoom_window_event(new_pinch_zoom);
|
||||
}
|
||||
|
||||
pub fn exit_fullscreen(&self) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::ExitFullScreen(self.id()));
|
||||
}
|
||||
|
||||
pub fn set_throttled(&self, throttled: bool) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::SetWebViewThrottled(self.id(), throttled));
|
||||
}
|
||||
|
||||
pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.toggle_webrender_debug(debugging);
|
||||
}
|
||||
|
||||
pub fn capture_webrender(&self) {
|
||||
self.0.compositor.borrow_mut().capture_webrender();
|
||||
}
|
||||
|
||||
pub fn invalidate_native_surface(&self) {
|
||||
self.0.compositor.borrow_mut().invalidate_native_surface();
|
||||
}
|
||||
|
||||
pub fn replace_native_surface(&self, native_widget: *mut c_void, size: DeviceIntSize) {
|
||||
self.0
|
||||
.compositor
|
||||
.borrow_mut()
|
||||
.replace_native_surface(native_widget, size);
|
||||
}
|
||||
|
||||
pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::ToggleProfiler(rate, max_duration));
|
||||
}
|
||||
|
||||
pub fn send_error(&self, message: String) {
|
||||
self.0
|
||||
.constellation_proxy
|
||||
.send(ConstellationMsg::SendError(Some(self.id()), message));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,9 +205,8 @@ pub trait BackgroundHangMonitorExitSignal: Send {
|
|||
/// Messages to control the sampling profiler.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum BackgroundHangMonitorControlMsg {
|
||||
/// Enable the sampler, with a given sampling rate and max total sampling duration.
|
||||
EnableSampler(Duration, Duration),
|
||||
DisableSampler,
|
||||
/// Toggle the sampler, with a given sampling rate and max total sampling duration.
|
||||
ToggleSampler(Duration, Duration),
|
||||
/// Exit, and propagate the signal to monitored components.
|
||||
Exit(IpcSender<()>),
|
||||
}
|
||||
|
|
|
@ -75,9 +75,7 @@ pub enum ConstellationMsg {
|
|||
/// Requesting a change to the onscreen cursor.
|
||||
SetCursor(Cursor),
|
||||
/// Enable the sampling profiler, with a given sampling rate and max total sampling duration.
|
||||
EnableProfiler(Duration, Duration),
|
||||
/// Disable the sampling profiler.
|
||||
DisableProfiler,
|
||||
ToggleProfiler(Duration, Duration),
|
||||
/// Request to exit from fullscreen mode
|
||||
ExitFullScreen(TopLevelBrowsingContextId),
|
||||
/// Media session action.
|
||||
|
@ -129,8 +127,7 @@ impl ConstellationMsg {
|
|||
SendError(..) => "SendError",
|
||||
ForwardEvent(..) => "ForwardEvent",
|
||||
SetCursor(..) => "SetCursor",
|
||||
EnableProfiler(..) => "EnableProfiler",
|
||||
DisableProfiler => "DisableProfiler",
|
||||
ToggleProfiler(..) => "EnableProfiler",
|
||||
ExitFullScreen(..) => "ExitFullScreen",
|
||||
MediaSessionAction(..) => "MediaSessionAction",
|
||||
SetWebViewThrottled(..) => "SetWebViewThrottled",
|
||||
|
|
|
@ -10,10 +10,10 @@ use std::rc::Rc;
|
|||
use std::time::Instant;
|
||||
use std::{env, fs};
|
||||
|
||||
use log::{info, trace};
|
||||
use arboard::Clipboard;
|
||||
use log::{info, trace, warn};
|
||||
use raw_window_handle::HasDisplayHandle;
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::compositing::windowing::{AnimationState, EmbedderEvent, WindowMethods};
|
||||
use servo::compositing::windowing::{AnimationState, WindowMethods};
|
||||
use servo::compositing::CompositeTarget;
|
||||
use servo::config::opts::Opts;
|
||||
use servo::config::prefs::Preferences;
|
||||
|
@ -22,6 +22,7 @@ use servo::servo_url::ServoUrl;
|
|||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use servo::{EventLoopWaker, Servo};
|
||||
use surfman::Connection;
|
||||
use url::Url;
|
||||
use webxr::glwindow::GlWindowDiscovery;
|
||||
#[cfg(target_os = "windows")]
|
||||
use webxr::openxr::{AppInfo, OpenXrDiscovery};
|
||||
|
@ -31,22 +32,22 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow};
|
|||
use winit::window::WindowId;
|
||||
|
||||
use super::events_loop::{EventsLoop, WakerEvent};
|
||||
use super::minibrowser::Minibrowser;
|
||||
use super::minibrowser::{Minibrowser, MinibrowserEvent};
|
||||
use super::webview::WebViewManager;
|
||||
use super::{headed_window, headless_window};
|
||||
use crate::desktop::embedder::{EmbedderCallbacks, XrDiscovery};
|
||||
use crate::desktop::tracing::trace_winit_event;
|
||||
use crate::desktop::window_trait::WindowPortsMethods;
|
||||
use crate::parser::get_default_url;
|
||||
use crate::parser::{get_default_url, location_bar_input_to_url};
|
||||
use crate::prefs::ServoShellPreferences;
|
||||
|
||||
pub struct App {
|
||||
opts: Opts,
|
||||
preferences: Preferences,
|
||||
servo_shell_preferences: ServoShellPreferences,
|
||||
clipboard: Option<Clipboard>,
|
||||
servo: Option<Servo>,
|
||||
webviews: Option<WebViewManager<dyn WindowPortsMethods>>,
|
||||
event_queue: Vec<EmbedderEvent>,
|
||||
webviews: Option<WebViewManager>,
|
||||
suspended: Cell<bool>,
|
||||
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
|
||||
minibrowser: Option<Minibrowser>,
|
||||
|
@ -91,7 +92,7 @@ impl App {
|
|||
opts,
|
||||
preferences,
|
||||
servo_shell_preferences,
|
||||
event_queue: vec![],
|
||||
clipboard: Clipboard::new().ok(),
|
||||
webviews: None,
|
||||
servo: None,
|
||||
suspended: Cell::new(false),
|
||||
|
@ -164,7 +165,7 @@ impl App {
|
|||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
self.webviews.as_mut().unwrap(),
|
||||
None,
|
||||
self.servo.as_mut(),
|
||||
"init",
|
||||
);
|
||||
window.set_toolbar_height(minibrowser.toolbar_height);
|
||||
|
@ -173,7 +174,6 @@ impl App {
|
|||
self.windows.insert(window.id(), window);
|
||||
|
||||
self.suspended.set(false);
|
||||
self.event_queue.push(EmbedderEvent::Idle);
|
||||
let (_, window) = self.windows.iter().next().unwrap();
|
||||
|
||||
let xr_discovery = if pref!(dom_webxr_openxr_enabled) && !self.opts.headless {
|
||||
|
@ -213,7 +213,7 @@ impl App {
|
|||
} else {
|
||||
CompositeTarget::Window
|
||||
};
|
||||
let mut servo = Servo::new(
|
||||
let servo = Servo::new(
|
||||
self.opts.clone(),
|
||||
self.preferences.clone(),
|
||||
Rc::new(rendering_context),
|
||||
|
@ -223,12 +223,11 @@ impl App {
|
|||
composite_target,
|
||||
);
|
||||
|
||||
servo.handle_events(vec![EmbedderEvent::NewWebView(
|
||||
self.initial_url.to_owned(),
|
||||
WebViewId::new(),
|
||||
)]);
|
||||
servo.setup_logging();
|
||||
|
||||
let webview = servo.new_webview(self.initial_url.clone().into_url());
|
||||
self.webviews.as_mut().unwrap().add(webview);
|
||||
|
||||
self.servo = Some(servo);
|
||||
}
|
||||
|
||||
|
@ -236,61 +235,47 @@ impl App {
|
|||
self.windows.iter().any(|(_, window)| window.is_animating())
|
||||
}
|
||||
|
||||
fn get_events(&mut self) -> Vec<EmbedderEvent> {
|
||||
std::mem::take(&mut self.event_queue)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Spins the Servo event loop, and (for now) handles a few other tasks:
|
||||
/// - Notifying Servo about incoming gamepad events
|
||||
/// - Receiving updates from Servo
|
||||
/// - Performing updates in the compositor, such as queued pinch zoom events
|
||||
///
|
||||
/// As the embedder, we push embedder events through our event queues, from the App queue and
|
||||
/// Window queues to the WebViewManager queue, and from the WebViewManager queue to Servo. We
|
||||
/// receive and collect embedder messages from the various Servo components, then take them out
|
||||
/// of the Servo interface so that the WebViewManager can handle them.
|
||||
/// In the future, these tasks may be decoupled.
|
||||
fn handle_events(&mut self) -> PumpResult {
|
||||
let mut embedder_events = self.get_events();
|
||||
let webviews = self.webviews.as_mut().unwrap();
|
||||
|
||||
// Take any outstanding embedder events from the App and its Windows.
|
||||
for window in self.windows.values() {
|
||||
embedder_events.extend(window.get_events());
|
||||
}
|
||||
|
||||
// Catch some keyboard events, and push the rest onto the WebViewManager event queue.
|
||||
webviews.handle_window_events(embedder_events);
|
||||
|
||||
// If the Gamepad API is enabled, handle gamepad events from GilRs.
|
||||
// Checking for focused_webview_id should ensure we'll have a valid browsing context.
|
||||
if pref!(dom_gamepad_enabled) && webviews.focused_webview_id().is_some() {
|
||||
webviews.handle_gamepad_events();
|
||||
}
|
||||
|
||||
// Take any new embedder messages from Servo itself.
|
||||
let mut embedder_messages = self.servo.as_mut().unwrap().get_events();
|
||||
// Take any new embedder messages from Servo.
|
||||
let servo = self.servo.as_mut().expect("Servo should be running.");
|
||||
let mut embedder_messages: Vec<_> = servo.get_events().collect();
|
||||
let mut need_resize = false;
|
||||
let mut need_present = false;
|
||||
let mut need_update = false;
|
||||
loop {
|
||||
// Consume and handle those embedder messages.
|
||||
let servo_event_response = webviews.handle_servo_events(&self.opts, embedder_messages);
|
||||
let servo_event_response = webviews.handle_servo_events(
|
||||
servo,
|
||||
&mut self.clipboard,
|
||||
&self.opts,
|
||||
embedder_messages,
|
||||
);
|
||||
need_present |= servo_event_response.need_present;
|
||||
need_update |= servo_event_response.need_update;
|
||||
|
||||
// Route embedder events from the WebViewManager to the relevant Servo components,
|
||||
// receives and collects embedder messages from various Servo components,
|
||||
// and runs the compositor.
|
||||
need_resize |= self
|
||||
.servo
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.handle_events(webviews.get_events());
|
||||
// Runs the compositor, and receives and collects embedder messages from various Servo components.
|
||||
need_resize |= servo.handle_events(vec![]);
|
||||
if webviews.shutdown_requested() {
|
||||
return PumpResult::Shutdown;
|
||||
}
|
||||
|
||||
// Take any new embedder messages from Servo itself.
|
||||
embedder_messages = self.servo.as_mut().unwrap().get_events();
|
||||
if embedder_messages.len() == 0 {
|
||||
embedder_messages = servo.get_events().collect();
|
||||
if embedder_messages.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -333,7 +318,7 @@ impl App {
|
|||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
webviews,
|
||||
self.servo.as_ref().unwrap().offscreen_framebuffer_id(),
|
||||
self.servo.as_mut(),
|
||||
"update_location_in_toolbar",
|
||||
);
|
||||
}
|
||||
|
@ -351,7 +336,7 @@ impl App {
|
|||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
self.webviews.as_mut().unwrap(),
|
||||
self.servo.as_ref().unwrap().offscreen_framebuffer_id(),
|
||||
self.servo.as_mut(),
|
||||
"PumpResult::Present::Immediate",
|
||||
);
|
||||
minibrowser.paint(window.winit_window().unwrap());
|
||||
|
@ -391,7 +376,6 @@ impl App {
|
|||
if self.servo.is_none() {
|
||||
return false;
|
||||
}
|
||||
self.event_queue.push(EmbedderEvent::Idle);
|
||||
|
||||
let mut exit = false;
|
||||
match self.handle_events() {
|
||||
|
@ -421,6 +405,62 @@ impl App {
|
|||
}
|
||||
exit
|
||||
}
|
||||
|
||||
/// Takes any events generated during `egui` updates and performs their actions.
|
||||
fn handle_servoshell_ui_events(&mut self) {
|
||||
let Some(webviews) = self.webviews.as_mut() else {
|
||||
return;
|
||||
};
|
||||
let Some(minibrowser) = self.minibrowser.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(servo) = self.servo.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in minibrowser.take_events() {
|
||||
match event {
|
||||
MinibrowserEvent::Go(location) => {
|
||||
minibrowser.update_location_dirty(false);
|
||||
let Some(url) = location_bar_input_to_url(
|
||||
&location.clone(),
|
||||
&self.servo_shell_preferences.searchpage,
|
||||
) else {
|
||||
warn!("failed to parse location");
|
||||
break;
|
||||
};
|
||||
if let Some(focused_webview) = webviews.focused_webview() {
|
||||
focused_webview.servo_webview.load(url.into_url());
|
||||
}
|
||||
},
|
||||
MinibrowserEvent::Back => {
|
||||
if let Some(focused_webview) = webviews.focused_webview() {
|
||||
focused_webview.servo_webview.go_back(1);
|
||||
}
|
||||
},
|
||||
MinibrowserEvent::Forward => {
|
||||
if let Some(focused_webview) = webviews.focused_webview() {
|
||||
focused_webview.servo_webview.go_forward(1);
|
||||
}
|
||||
},
|
||||
MinibrowserEvent::Reload => {
|
||||
minibrowser.update_location_dirty(false);
|
||||
if let Some(focused_webview) = webviews.focused_webview() {
|
||||
focused_webview.servo_webview.reload();
|
||||
}
|
||||
},
|
||||
MinibrowserEvent::NewWebView => {
|
||||
minibrowser.update_location_dirty(false);
|
||||
let webview = servo.new_webview(Url::parse("servo:newtab").unwrap());
|
||||
webviews.add(webview);
|
||||
},
|
||||
MinibrowserEvent::CloseWebView(id) => {
|
||||
minibrowser.update_location_dirty(false);
|
||||
webviews.close_webview(servo, id);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler<WakerEvent> for App {
|
||||
|
@ -463,7 +503,7 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
self.webviews.as_mut().unwrap(),
|
||||
self.servo.as_ref().unwrap().offscreen_framebuffer_id(),
|
||||
self.servo.as_mut(),
|
||||
"RedrawRequested",
|
||||
);
|
||||
minibrowser.paint(window.winit_window().unwrap());
|
||||
|
@ -505,7 +545,7 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
self.webviews.as_mut().unwrap(),
|
||||
self.servo.as_ref().unwrap().offscreen_framebuffer_id(),
|
||||
self.servo.as_mut(),
|
||||
"Sync WebView size with Window Resize event",
|
||||
);
|
||||
}
|
||||
|
@ -522,11 +562,9 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
}
|
||||
}
|
||||
if !consumed {
|
||||
if event == winit::event::WindowEvent::RedrawRequested {
|
||||
self.event_queue.push(EmbedderEvent::Idle);
|
||||
if let (Some(servo), Some(webviews)) = (self.servo.as_ref(), self.webviews.as_mut()) {
|
||||
window.handle_winit_event(servo, &mut self.clipboard, webviews, event);
|
||||
}
|
||||
|
||||
window.queue_embedder_events_for_winit_event(event);
|
||||
}
|
||||
|
||||
let animating = self.is_animating();
|
||||
|
@ -538,16 +576,8 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
event_loop.set_control_flow(ControlFlow::Poll);
|
||||
}
|
||||
|
||||
// Consume and handle any events from the Minibrowser.
|
||||
if let Some(ref minibrowser) = self.minibrowser {
|
||||
let webviews = &mut self.webviews.as_mut().unwrap();
|
||||
let app_event_queue = &mut self.event_queue;
|
||||
minibrowser.queue_embedder_events_for_minibrowser_events(
|
||||
webviews,
|
||||
app_event_queue,
|
||||
&self.servo_shell_preferences,
|
||||
);
|
||||
}
|
||||
// Consume and handle any events from the servoshell UI.
|
||||
self.handle_servoshell_ui_events();
|
||||
|
||||
self.handle_events_with_winit(event_loop, window);
|
||||
}
|
||||
|
@ -567,7 +597,6 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
if self.servo.is_none() {
|
||||
return;
|
||||
}
|
||||
self.event_queue.push(EmbedderEvent::Idle);
|
||||
|
||||
let Some(window) = self.windows.values().next() else {
|
||||
return;
|
||||
|
@ -584,15 +613,7 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
}
|
||||
|
||||
// Consume and handle any events from the Minibrowser.
|
||||
if let Some(ref minibrowser) = self.minibrowser {
|
||||
let webviews = &mut self.webviews.as_mut().unwrap();
|
||||
let app_event_queue = &mut self.event_queue;
|
||||
minibrowser.queue_embedder_events_for_minibrowser_events(
|
||||
webviews,
|
||||
app_event_queue,
|
||||
&self.servo_shell_preferences,
|
||||
);
|
||||
}
|
||||
self.handle_servoshell_ui_events();
|
||||
|
||||
self.handle_events_with_winit(event_loop, window);
|
||||
}
|
||||
|
|
|
@ -6,13 +6,17 @@
|
|||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use arboard::Clipboard;
|
||||
use euclid::{Angle, Length, Point2D, Rotation3D, Scale, Size2D, UnknownUnit, Vector2D, Vector3D};
|
||||
use log::{debug, info, trace};
|
||||
use keyboard_types::{Modifiers, ShortcutMatcher};
|
||||
use log::{debug, info};
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use servo::compositing::windowing::{
|
||||
AnimationState, EmbedderCoordinates, EmbedderEvent, MouseWindowEvent, WindowMethods,
|
||||
AnimationState, EmbedderCoordinates, MouseWindowEvent, WebRenderDebugOption, WindowMethods,
|
||||
};
|
||||
use servo::config::opts::Opts;
|
||||
use servo::servo_config::pref;
|
||||
|
@ -21,10 +25,11 @@ use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize,
|
|||
use servo::webrender_api::ScrollLocation;
|
||||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use servo::{
|
||||
Cursor, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton, Theme, TouchEventType,
|
||||
TouchId, WheelDelta, WheelMode,
|
||||
ClipboardEventType, Cursor, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton,
|
||||
Servo, Theme, TouchEventType, TouchId, WebView, WheelDelta, WheelMode,
|
||||
};
|
||||
use surfman::{Context, Device, SurfaceType};
|
||||
use url::Url;
|
||||
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use winit::event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
|
@ -33,8 +38,10 @@ use winit::keyboard::{Key as LogicalKey, ModifiersState, NamedKey};
|
|||
use winit::window::Icon;
|
||||
|
||||
use super::geometry::{winit_position_to_euclid_point, winit_size_to_euclid_size};
|
||||
use super::keyutils::keyboard_event_from_winit;
|
||||
use super::keyutils::{keyboard_event_from_winit, CMD_OR_ALT};
|
||||
use super::webview::{WebView as ServoShellWebView, WebViewManager};
|
||||
use super::window_trait::{WindowPortsMethods, LINE_HEIGHT};
|
||||
use crate::desktop::keyutils::CMD_OR_CONTROL;
|
||||
|
||||
pub struct Window {
|
||||
winit_window: winit::window::Window,
|
||||
|
@ -44,7 +51,6 @@ pub struct Window {
|
|||
mouse_down_button: Cell<Option<MouseButton>>,
|
||||
mouse_down_point: Cell<Point2D<i32, DevicePixel>>,
|
||||
monitor: winit::monitor::MonitorHandle,
|
||||
event_queue: RefCell<Vec<EmbedderEvent>>,
|
||||
mouse_pos: Cell<Point2D<i32, DevicePixel>>,
|
||||
last_pressed: Cell<Option<(KeyboardEvent, Option<LogicalKey>)>>,
|
||||
/// A map of winit's key codes to key values that are interpreted from
|
||||
|
@ -130,7 +136,6 @@ impl Window {
|
|||
debug!("Created window {:?}", winit_window.id());
|
||||
Window {
|
||||
winit_window,
|
||||
event_queue: RefCell::new(vec![]),
|
||||
mouse_down_button: Cell::new(None),
|
||||
mouse_down_point: Cell::new(Point2D::zero()),
|
||||
mouse_pos: Cell::new(Point2D::zero()),
|
||||
|
@ -148,19 +153,19 @@ impl Window {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_received_character(&self, mut ch: char) {
|
||||
info!("winit received character: {:?}", ch);
|
||||
if ch.is_control() {
|
||||
if ch as u8 >= 32 {
|
||||
fn handle_received_character(&self, webview: &WebView, mut character: char) {
|
||||
info!("winit received character: {:?}", character);
|
||||
if character.is_control() {
|
||||
if character as u8 >= 32 {
|
||||
return;
|
||||
}
|
||||
// shift ASCII control characters to lowercase
|
||||
ch = (ch as u8 + 96) as char;
|
||||
character = (character as u8 + 96) as char;
|
||||
}
|
||||
let (mut event, key_code) = if let Some((event, key_code)) = self.last_pressed.replace(None)
|
||||
{
|
||||
(event, key_code)
|
||||
} else if ch.is_ascii() {
|
||||
} else if character.is_ascii() {
|
||||
// Some keys like Backspace emit a control character in winit
|
||||
// but they are already dealt with in handle_keyboard_input
|
||||
// so just ignore the character.
|
||||
|
@ -170,7 +175,7 @@ impl Window {
|
|||
// no keyboard event is emitted. A dummy event is created in this case.
|
||||
(KeyboardEvent::default(), None)
|
||||
};
|
||||
event.key = Key::Character(ch.to_string());
|
||||
event.key = Key::Character(character.to_string());
|
||||
|
||||
if event.state == KeyState::Down {
|
||||
// Ensure that when we receive a keyup event from winit, we are able
|
||||
|
@ -187,50 +192,69 @@ impl Window {
|
|||
for xr_window_pose in &*xr_poses {
|
||||
xr_window_pose.handle_xr_translation(&event);
|
||||
}
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::Keyboard(event));
|
||||
webview.notify_keyboard_event(event);
|
||||
}
|
||||
|
||||
fn handle_keyboard_input(&self, input: KeyEvent) {
|
||||
if let Some(input_text) = &input.text {
|
||||
for ch in input_text.chars() {
|
||||
self.handle_received_character(ch);
|
||||
fn handle_keyboard_input(
|
||||
&self,
|
||||
servo: &Servo,
|
||||
clipboard: &mut Option<Clipboard>,
|
||||
webviews: &mut WebViewManager,
|
||||
winit_event: KeyEvent,
|
||||
) {
|
||||
// First, handle servoshell key bindings that are not overridable by, or visible to, the page.
|
||||
let mut keyboard_event =
|
||||
keyboard_event_from_winit(&winit_event, self.modifiers_state.get());
|
||||
if self.handle_intercepted_key_bindings(servo, clipboard, webviews, &keyboard_event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Then we deliver character and keyboard events to the page in the focused webview.
|
||||
let Some(webview) = webviews
|
||||
.focused_webview()
|
||||
.map(|webview| webview.servo_webview.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(input_text) = &winit_event.text {
|
||||
for character in input_text.chars() {
|
||||
self.handle_received_character(&webview, character);
|
||||
}
|
||||
}
|
||||
|
||||
let mut event = keyboard_event_from_winit(&input, self.modifiers_state.get());
|
||||
trace!("handling {:?}", event);
|
||||
if event.state == KeyState::Down && event.key == Key::Unidentified {
|
||||
if keyboard_event.state == KeyState::Down && keyboard_event.key == Key::Unidentified {
|
||||
// If pressed and probably printable, we expect a ReceivedCharacter event.
|
||||
// Wait for that to be received and don't queue any event right now.
|
||||
self.last_pressed
|
||||
.set(Some((event, Some(input.logical_key))));
|
||||
.set(Some((keyboard_event, Some(winit_event.logical_key))));
|
||||
return;
|
||||
} else if event.state == KeyState::Up && event.key == Key::Unidentified {
|
||||
} else if keyboard_event.state == KeyState::Up && keyboard_event.key == Key::Unidentified {
|
||||
// If release and probably printable, this is following a ReceiverCharacter event.
|
||||
if let Some(key) = self.keys_down.borrow_mut().remove(&input.logical_key) {
|
||||
event.key = key;
|
||||
if let Some(key) = self.keys_down.borrow_mut().remove(&winit_event.logical_key) {
|
||||
keyboard_event.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
if event.key != Key::Unidentified {
|
||||
if keyboard_event.key != Key::Unidentified {
|
||||
self.last_pressed.set(None);
|
||||
let xr_poses = self.xr_window_poses.borrow();
|
||||
for xr_window_pose in &*xr_poses {
|
||||
xr_window_pose.handle_xr_rotation(&input, self.modifiers_state.get());
|
||||
xr_window_pose.handle_xr_rotation(&winit_event, self.modifiers_state.get());
|
||||
}
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::Keyboard(event));
|
||||
webview.notify_keyboard_event(keyboard_event);
|
||||
}
|
||||
|
||||
// servoshell also has key bindings that are visible to, and overridable by, the page.
|
||||
// See the handler for EmbedderMsg::Keyboard in webview.rs for those.
|
||||
}
|
||||
|
||||
/// Helper function to handle a click
|
||||
fn handle_mouse(
|
||||
&self,
|
||||
button: MouseButton,
|
||||
action: ElementState,
|
||||
webview: &WebView,
|
||||
button: winit::event::MouseButton,
|
||||
action: winit::event::ElementState,
|
||||
coords: Point2D<i32, DevicePixel>,
|
||||
) {
|
||||
let max_pixel_dist = 10.0 * self.hidpi_factor().get();
|
||||
|
@ -256,9 +280,7 @@ impl Window {
|
|||
((pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y) as f32)
|
||||
.sqrt();
|
||||
if pixel_dist < max_pixel_dist {
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::MouseWindowEventClass(mouse_up_event));
|
||||
webview.notify_pointer_button_event(mouse_up_event);
|
||||
MouseWindowEvent::Click(mouse_button, coords.to_f32())
|
||||
} else {
|
||||
mouse_up_event
|
||||
|
@ -268,17 +290,138 @@ impl Window {
|
|||
}
|
||||
},
|
||||
};
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::MouseWindowEventClass(event));
|
||||
webview.notify_pointer_button_event(event);
|
||||
}
|
||||
|
||||
/// Handle key events before sending them to Servo.
|
||||
fn handle_intercepted_key_bindings(
|
||||
&self,
|
||||
servo: &Servo,
|
||||
clipboard: &mut Option<Clipboard>,
|
||||
webviews: &mut WebViewManager,
|
||||
key_event: &KeyboardEvent,
|
||||
) -> bool {
|
||||
let Some(focused_webview) = webviews
|
||||
.focused_webview()
|
||||
.map(|webview| webview.servo_webview.clone())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut handled = true;
|
||||
ShortcutMatcher::from_event(key_event.clone())
|
||||
.shortcut(CMD_OR_CONTROL, 'R', || focused_webview.reload())
|
||||
.shortcut(CMD_OR_CONTROL, 'W', || {
|
||||
webviews.close_webview(servo, focused_webview.id());
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'P', || {
|
||||
let rate = env::var("SAMPLING_RATE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(10);
|
||||
let duration = env::var("SAMPLING_DURATION")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(10);
|
||||
focused_webview.toggle_sampling_profiler(
|
||||
Duration::from_millis(rate),
|
||||
Duration::from_secs(duration),
|
||||
);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'X', || {
|
||||
focused_webview.notify_clipboard_event(ClipboardEventType::Cut);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'C', || {
|
||||
focused_webview.notify_clipboard_event(ClipboardEventType::Copy);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'V', || {
|
||||
let text = clipboard
|
||||
.as_mut()
|
||||
.and_then(|clipboard| clipboard.get_text().ok())
|
||||
.unwrap_or_default();
|
||||
focused_webview.notify_clipboard_event(ClipboardEventType::Paste(text));
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F9, || {
|
||||
focused_webview.capture_webrender();
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F10, || {
|
||||
focused_webview.toggle_webrender_debugging(WebRenderDebugOption::RenderTargetDebug);
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F11, || {
|
||||
focused_webview.toggle_webrender_debugging(WebRenderDebugOption::TextureCacheDebug);
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F12, || {
|
||||
focused_webview.toggle_webrender_debugging(WebRenderDebugOption::Profiler);
|
||||
})
|
||||
.shortcut(CMD_OR_ALT, Key::ArrowRight, || {
|
||||
focused_webview.go_forward(1);
|
||||
})
|
||||
.optional_shortcut(
|
||||
cfg!(not(target_os = "windows")),
|
||||
CMD_OR_CONTROL,
|
||||
']',
|
||||
|| {
|
||||
focused_webview.go_forward(1);
|
||||
},
|
||||
)
|
||||
.shortcut(CMD_OR_ALT, Key::ArrowLeft, || {
|
||||
focused_webview.go_back(1);
|
||||
})
|
||||
.optional_shortcut(
|
||||
cfg!(not(target_os = "windows")),
|
||||
CMD_OR_CONTROL,
|
||||
'[',
|
||||
|| {
|
||||
focused_webview.go_back(1);
|
||||
},
|
||||
)
|
||||
.optional_shortcut(
|
||||
self.get_fullscreen(),
|
||||
Modifiers::empty(),
|
||||
Key::Escape,
|
||||
|| focused_webview.exit_fullscreen(),
|
||||
)
|
||||
// Select the first 8 tabs via shortcuts
|
||||
.shortcut(CMD_OR_CONTROL, '1', || webviews.focus_webview_by_index(0))
|
||||
.shortcut(CMD_OR_CONTROL, '2', || webviews.focus_webview_by_index(1))
|
||||
.shortcut(CMD_OR_CONTROL, '3', || webviews.focus_webview_by_index(2))
|
||||
.shortcut(CMD_OR_CONTROL, '4', || webviews.focus_webview_by_index(3))
|
||||
.shortcut(CMD_OR_CONTROL, '5', || webviews.focus_webview_by_index(4))
|
||||
.shortcut(CMD_OR_CONTROL, '6', || webviews.focus_webview_by_index(5))
|
||||
.shortcut(CMD_OR_CONTROL, '7', || webviews.focus_webview_by_index(6))
|
||||
.shortcut(CMD_OR_CONTROL, '8', || webviews.focus_webview_by_index(7))
|
||||
// Cmd/Ctrl 9 is a bit different in that it focuses the last tab instead of the 9th
|
||||
.shortcut(CMD_OR_CONTROL, '9', || {
|
||||
let len = webviews.webviews().len();
|
||||
if len > 0 {
|
||||
webviews.focus_webview_by_index(len - 1)
|
||||
}
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::PageDown, || {
|
||||
if let Some(index) = webviews.get_focused_webview_index() {
|
||||
webviews.focus_webview_by_index((index + 1) % webviews.webviews().len())
|
||||
}
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::PageUp, || {
|
||||
if let Some(index) = webviews.get_focused_webview_index() {
|
||||
let new_index = if index == 0 {
|
||||
webviews.webviews().len() - 1
|
||||
} else {
|
||||
index - 1
|
||||
};
|
||||
webviews.focus_webview_by_index(new_index)
|
||||
}
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'T', || {
|
||||
webviews.add(servo.new_webview(Url::parse("servo:newtab").unwrap()));
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'Q', || servo.start_shutting_down())
|
||||
.otherwise(|| handled = false);
|
||||
handled
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowPortsMethods for Window {
|
||||
fn get_events(&self) -> Vec<EmbedderEvent> {
|
||||
std::mem::take(&mut *self.event_queue.borrow_mut())
|
||||
}
|
||||
|
||||
fn device_hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||
Scale::new(self.winit_window.scale_factor() as f32)
|
||||
}
|
||||
|
@ -299,7 +442,7 @@ impl WindowPortsMethods for Window {
|
|||
self.winit_window.set_title(title);
|
||||
}
|
||||
|
||||
fn request_inner_size(&self, size: DeviceIntSize) -> Option<DeviceIntSize> {
|
||||
fn request_resize(&self, _: &ServoShellWebView, size: DeviceIntSize) -> Option<DeviceIntSize> {
|
||||
let toolbar_height = self.toolbar_height() * self.hidpi_factor();
|
||||
let toolbar_height = toolbar_height.get().ceil() as i32;
|
||||
let total_size = PhysicalSize::new(size.width, size.height + toolbar_height);
|
||||
|
@ -393,25 +536,36 @@ impl WindowPortsMethods for Window {
|
|||
self.winit_window.id()
|
||||
}
|
||||
|
||||
fn queue_embedder_events_for_winit_event(&self, event: winit::event::WindowEvent) {
|
||||
fn handle_winit_event(
|
||||
&self,
|
||||
servo: &Servo,
|
||||
clipboard: &mut Option<Clipboard>,
|
||||
webviews: &mut WebViewManager,
|
||||
event: winit::event::WindowEvent,
|
||||
) {
|
||||
let Some(webview) = webviews
|
||||
.focused_webview()
|
||||
.map(|webview| webview.servo_webview.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
match event {
|
||||
winit::event::WindowEvent::KeyboardInput { event, .. } => {
|
||||
self.handle_keyboard_input(event)
|
||||
self.handle_keyboard_input(servo, clipboard, webviews, event)
|
||||
},
|
||||
winit::event::WindowEvent::ModifiersChanged(modifiers) => {
|
||||
self.modifiers_state.set(modifiers.state())
|
||||
},
|
||||
winit::event::WindowEvent::MouseInput { state, button, .. } => {
|
||||
if button == MouseButton::Left || button == MouseButton::Right {
|
||||
self.handle_mouse(button, state, self.mouse_pos.get());
|
||||
self.handle_mouse(&webview, button, state, self.mouse_pos.get());
|
||||
}
|
||||
},
|
||||
winit::event::WindowEvent::CursorMoved { position, .. } => {
|
||||
let position = winit_position_to_euclid_point(position);
|
||||
self.mouse_pos.set(position.to_i32());
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::MouseWindowMoveEventClass(position.to_f32()));
|
||||
webview.notify_pointer_move_event(position.to_f32());
|
||||
},
|
||||
winit::event::WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||
let (mut dx, mut dy, mode) = match delta {
|
||||
|
@ -434,7 +588,6 @@ impl WindowPortsMethods for Window {
|
|||
};
|
||||
let pos = self.mouse_pos.get();
|
||||
let position = Point2D::new(pos.x as f32, pos.y as f32);
|
||||
let wheel_event = EmbedderEvent::Wheel(wheel_delta, position);
|
||||
|
||||
// Scroll events snap to the major axis of movement, with vertical
|
||||
// preferred over horizontal.
|
||||
|
@ -446,47 +599,35 @@ impl WindowPortsMethods for Window {
|
|||
|
||||
let scroll_location = ScrollLocation::Delta(Vector2D::new(dx as f32, dy as f32));
|
||||
let phase = winit_phase_to_touch_event_type(phase);
|
||||
let scroll_event =
|
||||
EmbedderEvent::Scroll(scroll_location, self.mouse_pos.get(), phase);
|
||||
|
||||
// Send events
|
||||
self.event_queue.borrow_mut().push(wheel_event);
|
||||
self.event_queue.borrow_mut().push(scroll_event);
|
||||
webview.notify_wheel_event(wheel_delta, position);
|
||||
webview.notify_scroll_event(scroll_location, self.mouse_pos.get(), phase);
|
||||
},
|
||||
winit::event::WindowEvent::Touch(touch) => {
|
||||
let phase = winit_phase_to_touch_event_type(touch.phase);
|
||||
let touch_event_type = winit_phase_to_touch_event_type(touch.phase);
|
||||
let id = TouchId(touch.id as i32);
|
||||
let position = touch.location;
|
||||
let point = Point2D::new(position.x as f32, position.y as f32);
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::Touch(phase, id, point));
|
||||
webview.notify_touch_event(touch_event_type, id, point);
|
||||
},
|
||||
winit::event::WindowEvent::PinchGesture { delta, .. } => {
|
||||
let magnification = delta as f32 + 1.0;
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::PinchZoom(magnification));
|
||||
webview.set_pinch_zoom(delta as f32 + 1.0);
|
||||
},
|
||||
winit::event::WindowEvent::CloseRequested => {
|
||||
self.event_queue.borrow_mut().push(EmbedderEvent::Quit);
|
||||
servo.start_shutting_down();
|
||||
},
|
||||
winit::event::WindowEvent::Resized(new_size) => {
|
||||
if self.inner_size.get() != new_size {
|
||||
self.inner_size.set(new_size);
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::WindowResize);
|
||||
webview.notify_rendering_context_resized();
|
||||
}
|
||||
},
|
||||
winit::event::WindowEvent::ThemeChanged(theme) => {
|
||||
let theme = match theme {
|
||||
webview.notify_theme_change(match theme {
|
||||
winit::window::Theme::Light => Theme::Light,
|
||||
winit::window::Theme::Dark => Theme::Dark,
|
||||
};
|
||||
self.event_queue
|
||||
.borrow_mut()
|
||||
.push(EmbedderEvent::ThemeChange(theme));
|
||||
});
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use arboard::Clipboard;
|
||||
use euclid::num::Zero;
|
||||
use euclid::{Box2D, Length, Point2D, Scale, Size2D};
|
||||
use servo::compositing::windowing::{
|
||||
AnimationState, EmbedderCoordinates, EmbedderEvent, WindowMethods,
|
||||
};
|
||||
use servo::compositing::windowing::{AnimationState, EmbedderCoordinates, WindowMethods};
|
||||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::webrender_api::units::{DeviceIntSize, DevicePixel};
|
||||
use servo::Servo;
|
||||
|
||||
use super::webview::{WebView, WebViewManager};
|
||||
use crate::desktop::window_trait::WindowPortsMethods;
|
||||
|
||||
pub struct Window {
|
||||
|
@ -25,7 +25,6 @@ pub struct Window {
|
|||
inner_size: Cell<DeviceIntSize>,
|
||||
screen_size: Size2D<i32, DeviceIndependentPixel>,
|
||||
window_rect: Box2D<i32, DeviceIndependentPixel>,
|
||||
event_queue: RwLock<Vec<EmbedderEvent>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
@ -55,7 +54,6 @@ impl Window {
|
|||
inner_size,
|
||||
screen_size,
|
||||
window_rect,
|
||||
event_queue: RwLock::new(Vec::new()),
|
||||
};
|
||||
|
||||
Rc::new(window)
|
||||
|
@ -63,18 +61,11 @@ impl Window {
|
|||
}
|
||||
|
||||
impl WindowPortsMethods for Window {
|
||||
fn get_events(&self) -> Vec<EmbedderEvent> {
|
||||
match self.event_queue.write() {
|
||||
Ok(ref mut event_queue) => std::mem::take(event_queue),
|
||||
Err(_) => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> winit::window::WindowId {
|
||||
winit::window::WindowId::dummy()
|
||||
}
|
||||
|
||||
fn request_inner_size(&self, size: DeviceIntSize) -> Option<DeviceIntSize> {
|
||||
fn request_resize(&self, webview: &WebView, size: DeviceIntSize) -> Option<DeviceIntSize> {
|
||||
// Surfman doesn't support zero-sized surfaces.
|
||||
let new_size = DeviceIntSize::new(size.width.max(1), size.height.max(1));
|
||||
if self.inner_size.get() == new_size {
|
||||
|
@ -82,9 +73,12 @@ impl WindowPortsMethods for Window {
|
|||
}
|
||||
|
||||
self.inner_size.set(new_size);
|
||||
if let Ok(ref mut queue) = self.event_queue.write() {
|
||||
queue.push(EmbedderEvent::WindowResize);
|
||||
}
|
||||
|
||||
// Because we are managing the rendering surface ourselves, there will be no other
|
||||
// notification (such as from the display manager) that it has changed size, so we
|
||||
// must notify the compositor here.
|
||||
webview.servo_webview.notify_rendering_context_resized();
|
||||
|
||||
Some(new_size)
|
||||
}
|
||||
|
||||
|
@ -116,7 +110,13 @@ impl WindowPortsMethods for Window {
|
|||
self.animation_state.get() == AnimationState::Animating
|
||||
}
|
||||
|
||||
fn queue_embedder_events_for_winit_event(&self, _event: winit::event::WindowEvent) {
|
||||
fn handle_winit_event(
|
||||
&self,
|
||||
_: &Servo,
|
||||
_: &mut Option<Clipboard>,
|
||||
_: &mut WebViewManager,
|
||||
_: winit::event::WindowEvent,
|
||||
) {
|
||||
// Not expecting any winit events.
|
||||
}
|
||||
|
||||
|
|
|
@ -20,22 +20,18 @@ use gleam::gl;
|
|||
use glow::NativeFramebuffer;
|
||||
use log::{trace, warn};
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::compositing::windowing::EmbedderEvent;
|
||||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_api::units::DevicePixel;
|
||||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use servo::{TopLevelBrowsingContextId, TraversalDirection};
|
||||
use servo::Servo;
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
use winit::window::Window;
|
||||
|
||||
use super::egui_glue::EguiGlow;
|
||||
use super::geometry::winit_position_to_euclid_point;
|
||||
use super::webview::{LoadStatus, WebViewManager};
|
||||
use super::window_trait::WindowPortsMethods;
|
||||
use crate::parser::location_bar_input_to_url;
|
||||
use crate::prefs::ServoShellPreferences;
|
||||
use super::webview::{LoadStatus, WebView, WebViewManager};
|
||||
|
||||
pub struct Minibrowser {
|
||||
pub context: EguiGlow,
|
||||
|
@ -60,11 +56,12 @@ pub struct Minibrowser {
|
|||
|
||||
pub enum MinibrowserEvent {
|
||||
/// Go button clicked.
|
||||
Go,
|
||||
Go(String),
|
||||
Back,
|
||||
Forward,
|
||||
Reload,
|
||||
NewWebView,
|
||||
CloseWebView(WebViewId),
|
||||
}
|
||||
|
||||
fn truncate_with_ellipsis(input: &str, max_length: usize) -> String {
|
||||
|
@ -116,6 +113,10 @@ impl Minibrowser {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn take_events(&self) -> Vec<MinibrowserEvent> {
|
||||
self.event_queue.take()
|
||||
}
|
||||
|
||||
/// Preprocess the given [winit::event::WindowEvent], returning unconsumed for mouse events in
|
||||
/// the Servo browser rect. This is needed because the CentralPanel we create for our webview
|
||||
/// would otherwise make egui report events in that area as consumed.
|
||||
|
@ -169,15 +170,15 @@ impl Minibrowser {
|
|||
.min_size(Vec2 { x: 20.0, y: 20.0 })
|
||||
}
|
||||
|
||||
/// Draws a browser tab, checking for clicks and returns an appropriate [EmbedderEvent]
|
||||
/// Draws a browser tab, checking for clicks and queues appropriate `MinibrowserEvent`s.
|
||||
/// Using a custom widget here would've been nice, but it doesn't seem as though egui
|
||||
/// supports that, so we arrange multiple Widgets in a way that they look connected.
|
||||
fn browser_tab(
|
||||
ui: &mut egui::Ui,
|
||||
label: &str,
|
||||
selected: bool,
|
||||
webview_id: TopLevelBrowsingContextId,
|
||||
) -> Option<EmbedderEvent> {
|
||||
webview: &WebView,
|
||||
event_queue: &mut Vec<MinibrowserEvent>,
|
||||
) {
|
||||
let old_item_spacing = ui.spacing().item_spacing;
|
||||
let old_visuals = ui.visuals().clone();
|
||||
let active_bg_color = old_visuals.widgets.active.weak_bg_fill;
|
||||
|
@ -213,6 +214,7 @@ impl Minibrowser {
|
|||
visuals.widgets.hovered.rounding = rounding;
|
||||
visuals.widgets.inactive.rounding = rounding;
|
||||
|
||||
let selected = webview.focused;
|
||||
let tab = ui.add(SelectableLabel::new(
|
||||
selected,
|
||||
truncate_with_ellipsis(label, 20),
|
||||
|
@ -242,11 +244,9 @@ impl Minibrowser {
|
|||
let close_button = ui.add(egui::Button::new("X").fill(fill_color));
|
||||
*ui.visuals_mut() = old_visuals;
|
||||
if close_button.clicked() || close_button.middle_clicked() || tab.middle_clicked() {
|
||||
Some(EmbedderEvent::CloseWebView(webview_id))
|
||||
event_queue.push(MinibrowserEvent::CloseWebView(webview.servo_webview.id()))
|
||||
} else if !selected && tab.clicked() {
|
||||
Some(EmbedderEvent::FocusWebView(webview_id))
|
||||
} else {
|
||||
None
|
||||
webview.servo_webview.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,8 +256,8 @@ impl Minibrowser {
|
|||
pub fn update(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
webviews: &mut WebViewManager<dyn WindowPortsMethods>,
|
||||
servo_framebuffer_id: Option<gl::GLuint>,
|
||||
webviews: &mut WebViewManager,
|
||||
servo: Option<&mut Servo>,
|
||||
reason: &'static str,
|
||||
) {
|
||||
let now = Instant::now();
|
||||
|
@ -277,6 +277,9 @@ impl Minibrowser {
|
|||
..
|
||||
} = self;
|
||||
let widget_fbo = *widget_surface_fbo;
|
||||
let servo_framebuffer_id = servo
|
||||
.as_ref()
|
||||
.and_then(|servo| servo.offscreen_framebuffer_id());
|
||||
let _duration = context.run(window, |ctx| {
|
||||
// TODO: While in fullscreen add some way to mitigate the increased phishing risk
|
||||
// when not displaying the URL bar: https://github.com/servo/servo/issues/32443
|
||||
|
@ -342,8 +345,9 @@ impl Minibrowser {
|
|||
if location_field.lost_focus() &&
|
||||
ui.input(|i| i.clone().key_pressed(Key::Enter))
|
||||
{
|
||||
event_queue.borrow_mut().push(MinibrowserEvent::Go);
|
||||
location_dirty.set(false);
|
||||
event_queue
|
||||
.borrow_mut()
|
||||
.push(MinibrowserEvent::Go(location.borrow().clone()));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -352,26 +356,19 @@ impl Minibrowser {
|
|||
});
|
||||
};
|
||||
|
||||
let mut embedder_events = vec![];
|
||||
|
||||
// A simple Tab header strip
|
||||
TopBottomPanel::top("tabs").show(ctx, |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
ui.available_size(),
|
||||
egui::Layout::left_to_right(egui::Align::Center),
|
||||
|ui| {
|
||||
for (webview_id, webview) in webviews.webviews().into_iter() {
|
||||
for (_, webview) in webviews.webviews().into_iter() {
|
||||
let label = match (&webview.title, &webview.url) {
|
||||
(Some(title), _) if !title.is_empty() => title,
|
||||
(_, Some(url)) => &url.to_string(),
|
||||
_ => "New Tab",
|
||||
};
|
||||
if let Some(event) =
|
||||
Self::browser_tab(ui, label, webview.focused, webview_id)
|
||||
{
|
||||
location_dirty.set(false);
|
||||
embedder_events.push(event);
|
||||
}
|
||||
Self::browser_tab(ui, label, webview, &mut event_queue.borrow_mut());
|
||||
}
|
||||
if ui.add(Minibrowser::toolbar_button("+")).clicked() {
|
||||
event_queue.borrow_mut().push(MinibrowserEvent::NewWebView);
|
||||
|
@ -408,8 +405,7 @@ impl Minibrowser {
|
|||
) * scale;
|
||||
if rect != webview.rect {
|
||||
webview.rect = rect;
|
||||
embedder_events
|
||||
.push(EmbedderEvent::MoveResizeWebView(focused_webview_id, rect));
|
||||
webview.servo_webview.move_resize(rect)
|
||||
}
|
||||
let min = ui.cursor().min;
|
||||
let size = ui.available_size();
|
||||
|
@ -471,10 +467,6 @@ impl Minibrowser {
|
|||
});
|
||||
});
|
||||
|
||||
if !embedder_events.is_empty() {
|
||||
webviews.handle_window_events(embedder_events);
|
||||
}
|
||||
|
||||
*last_update = now;
|
||||
});
|
||||
}
|
||||
|
@ -491,58 +483,9 @@ impl Minibrowser {
|
|||
self.context.paint(window);
|
||||
}
|
||||
|
||||
/// Takes any outstanding events from the [Minibrowser], converting them to [EmbedderEvent] and
|
||||
/// routing those to the App event queue.
|
||||
pub fn queue_embedder_events_for_minibrowser_events(
|
||||
&self,
|
||||
browser: &WebViewManager<dyn WindowPortsMethods>,
|
||||
app_event_queue: &mut Vec<EmbedderEvent>,
|
||||
preferences: &ServoShellPreferences,
|
||||
) {
|
||||
for event in self.event_queue.borrow_mut().drain(..) {
|
||||
let browser_id = browser.focused_webview_id().unwrap();
|
||||
match event {
|
||||
MinibrowserEvent::Go => {
|
||||
let location = self.location.borrow();
|
||||
let Some(url) =
|
||||
location_bar_input_to_url(&location.clone(), &preferences.searchpage)
|
||||
else {
|
||||
warn!("failed to parse location");
|
||||
break;
|
||||
};
|
||||
app_event_queue.push(EmbedderEvent::LoadUrl(browser_id, url));
|
||||
},
|
||||
MinibrowserEvent::Back => {
|
||||
app_event_queue.push(EmbedderEvent::Navigation(
|
||||
browser_id,
|
||||
TraversalDirection::Back(1),
|
||||
));
|
||||
},
|
||||
MinibrowserEvent::Forward => {
|
||||
app_event_queue.push(EmbedderEvent::Navigation(
|
||||
browser_id,
|
||||
TraversalDirection::Forward(1),
|
||||
));
|
||||
},
|
||||
MinibrowserEvent::Reload => {
|
||||
let browser_id = browser.focused_webview_id().unwrap();
|
||||
app_event_queue.push(EmbedderEvent::Reload(browser_id));
|
||||
},
|
||||
MinibrowserEvent::NewWebView => {
|
||||
self.location_dirty.set(false);
|
||||
let url = ServoUrl::parse("servo:newtab").unwrap();
|
||||
app_event_queue.push(EmbedderEvent::NewWebView(url, WebViewId::new()));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the location field from the given [WebViewManager], unless the user has started
|
||||
/// editing it without clicking Go, returning true iff it has changed (needing an egui update).
|
||||
pub fn update_location_in_toolbar(
|
||||
&mut self,
|
||||
browser: &mut WebViewManager<dyn WindowPortsMethods>,
|
||||
) -> bool {
|
||||
pub fn update_location_in_toolbar(&mut self, browser: &mut WebViewManager) -> bool {
|
||||
// User edited without clicking Go?
|
||||
if self.location_dirty.get() {
|
||||
return false;
|
||||
|
@ -557,21 +500,19 @@ impl Minibrowser {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_location_dirty(&self, dirty: bool) {
|
||||
self.location_dirty.set(dirty);
|
||||
}
|
||||
|
||||
/// Updates the spinner from the given [WebViewManager], returning true iff it has changed
|
||||
/// (needing an egui update).
|
||||
pub fn update_spinner_in_toolbar(
|
||||
&mut self,
|
||||
browser: &mut WebViewManager<dyn WindowPortsMethods>,
|
||||
) -> bool {
|
||||
pub fn update_spinner_in_toolbar(&mut self, browser: &mut WebViewManager) -> bool {
|
||||
let need_update = browser.load_status() != self.load_status;
|
||||
self.load_status = browser.load_status();
|
||||
need_update
|
||||
}
|
||||
|
||||
pub fn update_status_text(
|
||||
&mut self,
|
||||
browser: &mut WebViewManager<dyn WindowPortsMethods>,
|
||||
) -> bool {
|
||||
pub fn update_status_text(&mut self, browser: &mut WebViewManager) -> bool {
|
||||
let need_update = browser.status_text() != self.status_text;
|
||||
self.status_text = browser.status_text();
|
||||
need_update
|
||||
|
@ -579,10 +520,7 @@ impl Minibrowser {
|
|||
|
||||
/// Updates all fields taken from the given [WebViewManager], such as the location field.
|
||||
/// Returns true iff the egui needs an update.
|
||||
pub fn update_webview_data(
|
||||
&mut self,
|
||||
browser: &mut WebViewManager<dyn WindowPortsMethods>,
|
||||
) -> bool {
|
||||
pub fn update_webview_data(&mut self, browser: &mut WebViewManager) -> bool {
|
||||
// Note: We must use the "bitwise OR" (|) operator here instead of "logical OR" (||)
|
||||
// because logical OR would short-circuit if any of the functions return true.
|
||||
// We want to ensure that all functions are called. The "bitwise OR" operator
|
||||
|
|
|
@ -36,21 +36,7 @@ macro_rules! trace_embedder_msg {
|
|||
};
|
||||
}
|
||||
|
||||
/// Log an event to servo ([servo::compositing::windowing::EmbedderEvent]) at trace level.
|
||||
/// - To disable tracing: RUST_LOG='servoshell>servo@=off'
|
||||
/// - To enable tracing: RUST_LOG='servoshell>servo@'
|
||||
/// - Recommended filters when tracing is enabled:
|
||||
/// - servoshell>servo@Idle=off
|
||||
/// - servoshell>servo@MouseWindowMoveEventClass=off
|
||||
macro_rules! trace_embedder_event {
|
||||
// This macro only exists to put the docs in the same file as the target prefix,
|
||||
// so the macro definition is always the same.
|
||||
($event:expr, $($rest:tt)+) => {
|
||||
::log::trace!(target: $crate::desktop::tracing::LogTarget::log_target(&$event), $($rest)+)
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use {trace_embedder_event, trace_embedder_msg, trace_winit_event};
|
||||
pub(crate) use {trace_embedder_msg, trace_winit_event};
|
||||
|
||||
/// Get the log target for an event, as a static string.
|
||||
pub(crate) trait LogTarget {
|
||||
|
@ -185,61 +171,3 @@ mod from_servo {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod to_servo {
|
||||
use super::LogTarget;
|
||||
|
||||
macro_rules! target {
|
||||
($($name:literal)+) => {
|
||||
concat!("servoshell>servo@", $($name),+)
|
||||
};
|
||||
}
|
||||
|
||||
impl LogTarget for servo::compositing::windowing::EmbedderEvent {
|
||||
fn log_target(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Idle => target!("Idle"),
|
||||
Self::Refresh => target!("Refresh"),
|
||||
Self::WindowResize => target!("WindowResize"),
|
||||
Self::ThemeChange(..) => target!("ThemeChange"),
|
||||
Self::AllowNavigationResponse(..) => target!("AllowNavigationResponse"),
|
||||
Self::LoadUrl(..) => target!("LoadUrl"),
|
||||
Self::MouseWindowEventClass(..) => target!("MouseWindowEventClass"),
|
||||
Self::MouseWindowMoveEventClass(..) => target!("MouseWindowMoveEventClass"),
|
||||
Self::Touch(..) => target!("Touch"),
|
||||
Self::Wheel(..) => target!("Wheel"),
|
||||
Self::Scroll(..) => target!("Scroll"),
|
||||
Self::Zoom(..) => target!("Zoom"),
|
||||
Self::PinchZoom(..) => target!("PinchZoom"),
|
||||
Self::ResetZoom => target!("ResetZoom"),
|
||||
Self::Navigation(..) => target!("Navigation"),
|
||||
Self::Quit => target!("Quit"),
|
||||
Self::ExitFullScreen(..) => target!("ExitFullScreen"),
|
||||
Self::Keyboard(..) => target!("Keyboard"),
|
||||
Self::Reload(..) => target!("Reload"),
|
||||
Self::NewWebView(..) => target!("NewWebView"),
|
||||
Self::CloseWebView(..) => target!("CloseWebView"),
|
||||
Self::SendError(..) => target!("SendError"),
|
||||
Self::MoveResizeWebView(..) => target!("MoveResizeWebView"),
|
||||
Self::ShowWebView(..) => target!("ShowWebView"),
|
||||
Self::HideWebView(..) => target!("HideWebView"),
|
||||
Self::RaiseWebViewToTop(..) => target!("RaiseWebViewToTop"),
|
||||
Self::FocusWebView(..) => target!("FocusWebView"),
|
||||
Self::BlurWebView => target!("BlurWebView"),
|
||||
Self::ToggleWebRenderDebug(..) => target!("ToggleWebRenderDebug"),
|
||||
Self::CaptureWebRender => target!("CaptureWebRender"),
|
||||
Self::ClearCache => target!("ClearCache"),
|
||||
Self::ToggleSamplingProfiler(..) => target!("ToggleSamplingProfiler"),
|
||||
Self::MediaSessionAction(..) => target!("MediaSessionAction"),
|
||||
Self::SetWebViewThrottled(..) => target!("SetWebViewThrottled"),
|
||||
Self::IMEComposition(..) => target!("IMEComposition"),
|
||||
Self::IMEDismissed => target!("IMEDismissed"),
|
||||
Self::InvalidateNativeSurface => target!("InvalidateNativeSurface"),
|
||||
Self::ReplaceNativeSurface(..) => target!("ReplaceNativeSurface"),
|
||||
Self::Gamepad(..) => target!("Gamepad"),
|
||||
Self::Vsync => target!("Vsync"),
|
||||
Self::ClipboardAction(..) => target!("ClipboardAction"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,42 +2,37 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::vec::Drain;
|
||||
use std::{env, thread};
|
||||
|
||||
use arboard::Clipboard;
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use euclid::Vector2D;
|
||||
use gilrs::ff::{BaseEffect, BaseEffectType, Effect, EffectBuilder, Repeat, Replay, Ticks};
|
||||
use gilrs::{EventType, Gilrs};
|
||||
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use servo::base::id::TopLevelBrowsingContextId as WebViewId;
|
||||
use servo::compositing::windowing::{EmbedderEvent, WebRenderDebugOption};
|
||||
use log::{debug, error, info, warn};
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::config::opts::Opts;
|
||||
use servo::ipc_channel::ipc::IpcSender;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_api::units::DeviceRect;
|
||||
use servo::webrender_api::ScrollLocation;
|
||||
use servo::{
|
||||
ClipboardEventType, CompositorEventVariant, ContextMenuResult, DualRumbleEffectParams,
|
||||
EmbedderMsg, FilterPattern, GamepadEvent, GamepadHapticEffectType, GamepadIndex,
|
||||
GamepadInputBounds, GamepadSupportedHapticEffects, GamepadUpdateType, PermissionPrompt,
|
||||
PermissionRequest, PromptCredentialsInput, PromptDefinition, PromptOrigin, PromptResult,
|
||||
TouchEventType, TraversalDirection,
|
||||
CompositorEventVariant, ContextMenuResult, DualRumbleEffectParams, EmbedderMsg, FilterPattern,
|
||||
GamepadEvent, GamepadHapticEffectType, GamepadIndex, GamepadInputBounds,
|
||||
GamepadSupportedHapticEffects, GamepadUpdateType, PermissionPrompt, PermissionRequest,
|
||||
PromptCredentialsInput, PromptDefinition, PromptOrigin, PromptResult, Servo, TouchEventType,
|
||||
};
|
||||
use tinyfiledialogs::{self, MessageBoxIcon, OkCancel, YesNo};
|
||||
|
||||
use super::keyutils::{CMD_OR_ALT, CMD_OR_CONTROL};
|
||||
use super::keyutils::CMD_OR_CONTROL;
|
||||
use super::window_trait::{WindowPortsMethods, LINE_HEIGHT};
|
||||
use crate::desktop::tracing::{trace_embedder_event, trace_embedder_msg};
|
||||
use crate::desktop::tracing::trace_embedder_msg;
|
||||
|
||||
pub struct WebViewManager<Window: WindowPortsMethods + ?Sized> {
|
||||
pub struct WebViewManager {
|
||||
status_text: Option<String>,
|
||||
|
||||
/// List of top-level browsing contexts.
|
||||
|
@ -52,25 +47,12 @@ pub struct WebViewManager<Window: WindowPortsMethods + ?Sized> {
|
|||
/// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
|
||||
focused_webview_id: Option<WebViewId>,
|
||||
|
||||
/// Pre-creation state for WebViews.
|
||||
/// This is needed because in some situations the WebViewOpened event is sent
|
||||
/// after ChangePageTitle and HistoryChanged
|
||||
webview_preload_data: HashMap<WebViewId, WebViewPreloadData>,
|
||||
|
||||
window: Rc<Window>,
|
||||
event_queue: Vec<EmbedderEvent>,
|
||||
clipboard: Option<Clipboard>,
|
||||
window: Rc<dyn WindowPortsMethods>,
|
||||
gamepad: Option<Gilrs>,
|
||||
haptic_effects: HashMap<usize, HapticEffect>,
|
||||
shutdown_requested: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct WebViewPreloadData {
|
||||
title: Option<String>,
|
||||
url: Option<ServoUrl>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum LoadStatus {
|
||||
HeadParsed,
|
||||
|
@ -79,23 +61,24 @@ pub enum LoadStatus {
|
|||
}
|
||||
|
||||
// The state of each Tab/WebView
|
||||
#[derive(Debug)]
|
||||
pub struct WebView {
|
||||
pub rect: DeviceRect,
|
||||
pub title: Option<String>,
|
||||
pub url: Option<ServoUrl>,
|
||||
pub focused: bool,
|
||||
pub load_status: LoadStatus,
|
||||
pub servo_webview: ::servo::WebView,
|
||||
}
|
||||
|
||||
impl WebView {
|
||||
fn new(rect: DeviceRect, preload_data: WebViewPreloadData) -> Self {
|
||||
fn new(servo_webview: ::servo::WebView) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
title: preload_data.title,
|
||||
url: preload_data.url,
|
||||
rect: DeviceRect::zero(),
|
||||
title: None,
|
||||
url: None,
|
||||
focused: false,
|
||||
load_status: LoadStatus::LoadComplete,
|
||||
servo_webview,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,25 +93,14 @@ pub struct HapticEffect {
|
|||
pub sender: IpcSender<bool>,
|
||||
}
|
||||
|
||||
impl<Window> WebViewManager<Window>
|
||||
where
|
||||
Window: WindowPortsMethods + ?Sized,
|
||||
{
|
||||
pub fn new(window: Rc<Window>) -> WebViewManager<Window> {
|
||||
impl WebViewManager {
|
||||
pub fn new(window: Rc<dyn WindowPortsMethods>) -> WebViewManager {
|
||||
WebViewManager {
|
||||
status_text: None,
|
||||
webviews: HashMap::default(),
|
||||
creation_order: vec![],
|
||||
focused_webview_id: None,
|
||||
webview_preload_data: HashMap::default(),
|
||||
window,
|
||||
clipboard: match Clipboard::new() {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
warn!("Error creating clipboard context ({})", e);
|
||||
None
|
||||
},
|
||||
},
|
||||
gamepad: match Gilrs::new() {
|
||||
Ok(g) => Some(g),
|
||||
Err(e) => {
|
||||
|
@ -137,8 +109,6 @@ where
|
|||
},
|
||||
},
|
||||
haptic_effects: HashMap::default(),
|
||||
|
||||
event_queue: Vec::new(),
|
||||
shutdown_requested: false,
|
||||
}
|
||||
}
|
||||
|
@ -147,18 +117,41 @@ where
|
|||
self.webviews.get_mut(&webview_id)
|
||||
}
|
||||
|
||||
// Returns the existing preload data for the given WebView, or a new one.
|
||||
fn ensure_preload_data_mut(&mut self, webview_id: &WebViewId) -> &mut WebViewPreloadData {
|
||||
if let Entry::Vacant(entry) = self.webview_preload_data.entry(*webview_id) {
|
||||
entry.insert(WebViewPreloadData::default());
|
||||
}
|
||||
self.webview_preload_data.get_mut(webview_id).unwrap()
|
||||
pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> {
|
||||
self.webviews.get(&webview_id)
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, webview: ::servo::WebView) {
|
||||
self.creation_order.push(webview.id());
|
||||
self.webviews.insert(webview.id(), WebView::new(webview));
|
||||
}
|
||||
|
||||
pub fn focused_webview_id(&self) -> Option<WebViewId> {
|
||||
self.focused_webview_id
|
||||
}
|
||||
|
||||
pub fn close_webview(&mut self, servo: &Servo, webview_id: WebViewId) {
|
||||
// This can happen because we can trigger a close with a UI action and then get the
|
||||
// close event from Servo later.
|
||||
if !self.webviews.contains_key(&webview_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.webviews.retain(|&id, _| id != webview_id);
|
||||
self.creation_order.retain(|&id| id != webview_id);
|
||||
self.focused_webview_id = None;
|
||||
match self.last_created_webview() {
|
||||
Some(last_created_webview) => last_created_webview.servo_webview.focus(),
|
||||
None => servo.start_shutting_down(),
|
||||
}
|
||||
}
|
||||
|
||||
fn last_created_webview(&self) -> Option<&WebView> {
|
||||
self.creation_order
|
||||
.last()
|
||||
.and_then(|id| self.webviews.get(id))
|
||||
}
|
||||
|
||||
pub fn current_url_string(&self) -> Option<String> {
|
||||
match self.focused_webview() {
|
||||
Some(webview) => webview.url.as_ref().map(|url| url.to_string()),
|
||||
|
@ -167,10 +160,8 @@ where
|
|||
}
|
||||
|
||||
pub fn focused_webview(&self) -> Option<&WebView> {
|
||||
match self.focused_webview_id {
|
||||
Some(id) => self.webviews.get(&id),
|
||||
None => None,
|
||||
}
|
||||
self.focused_webview_id
|
||||
.and_then(|id| self.webviews.get(&id))
|
||||
}
|
||||
|
||||
pub fn load_status(&self) -> LoadStatus {
|
||||
|
@ -184,10 +175,6 @@ where
|
|||
self.status_text.clone()
|
||||
}
|
||||
|
||||
pub fn get_events(&mut self) -> Vec<EmbedderEvent> {
|
||||
std::mem::take(&mut self.event_queue)
|
||||
}
|
||||
|
||||
// Returns the webviews in the creation order.
|
||||
pub fn webviews(&self) -> Vec<(WebViewId, &WebView)> {
|
||||
let mut res = vec![];
|
||||
|
@ -197,22 +184,15 @@ where
|
|||
res
|
||||
}
|
||||
|
||||
pub fn handle_window_events(&mut self, events: Vec<EmbedderEvent>) {
|
||||
for event in events {
|
||||
trace_embedder_event!(event, "{event:?}");
|
||||
match event {
|
||||
EmbedderEvent::Keyboard(key_event) => {
|
||||
self.handle_key_from_window(key_event);
|
||||
},
|
||||
event => {
|
||||
self.event_queue.push(event);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle updates to connected gamepads from GilRs
|
||||
pub fn handle_gamepad_events(&mut self) {
|
||||
let Some(webview) = self
|
||||
.focused_webview()
|
||||
.map(|webview| webview.servo_webview.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(ref mut gilrs) = self.gamepad {
|
||||
while let Some(event) = gilrs.next_event() {
|
||||
let gamepad = gilrs.gamepad(event.id);
|
||||
|
@ -303,7 +283,7 @@ where
|
|||
}
|
||||
|
||||
if let Some(event) = gamepad_event {
|
||||
self.event_queue.push(EmbedderEvent::Gamepad(event));
|
||||
webview.notify_gamepad_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,242 +391,33 @@ where
|
|||
self.shutdown_requested
|
||||
}
|
||||
|
||||
fn focus_webview_by_index(&self, index: usize) -> Option<EmbedderEvent> {
|
||||
Some(EmbedderEvent::FocusWebView(self.webviews().get(index)?.0))
|
||||
pub(crate) fn focus_webview_by_index(&self, index: usize) {
|
||||
if let Some((_, webview)) = self.webviews().get(index) {
|
||||
webview.servo_webview.focus();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_focused_webview_index(&self) -> Option<usize> {
|
||||
pub(crate) fn get_focused_webview_index(&self) -> Option<usize> {
|
||||
let focused_id = self.focused_webview_id?;
|
||||
self.webviews()
|
||||
.iter()
|
||||
.position(|webview| webview.0 == focused_id)
|
||||
}
|
||||
|
||||
/// Handle key events before sending them to Servo.
|
||||
fn handle_key_from_window(&mut self, key_event: KeyboardEvent) {
|
||||
let embedder_event = ShortcutMatcher::from_event(key_event.clone())
|
||||
.shortcut(CMD_OR_CONTROL, 'R', || {
|
||||
self.focused_webview_id.map(EmbedderEvent::Reload)
|
||||
})
|
||||
// Select the first 8 tabs via shortcuts
|
||||
.shortcut(CMD_OR_CONTROL, '1', || self.focus_webview_by_index(0))
|
||||
.shortcut(CMD_OR_CONTROL, '2', || self.focus_webview_by_index(1))
|
||||
.shortcut(CMD_OR_CONTROL, '3', || self.focus_webview_by_index(2))
|
||||
.shortcut(CMD_OR_CONTROL, '4', || self.focus_webview_by_index(3))
|
||||
.shortcut(CMD_OR_CONTROL, '5', || self.focus_webview_by_index(4))
|
||||
.shortcut(CMD_OR_CONTROL, '6', || self.focus_webview_by_index(5))
|
||||
.shortcut(CMD_OR_CONTROL, '7', || self.focus_webview_by_index(6))
|
||||
.shortcut(CMD_OR_CONTROL, '8', || self.focus_webview_by_index(7))
|
||||
// Cmd/Ctrl 9 is a bit different in that it focuses the last tab instead of the 9th
|
||||
.shortcut(CMD_OR_CONTROL, '9', || {
|
||||
let len = self.webviews().len();
|
||||
if len > 0 {
|
||||
self.focus_webview_by_index(len - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::PageDown, || {
|
||||
let i = self.get_focused_webview_index()?;
|
||||
self.focus_webview_by_index((i + 1) % self.webviews().len())
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::PageUp, || {
|
||||
let index = self.get_focused_webview_index()?;
|
||||
let new_index = if index == 0 {
|
||||
self.webviews().len() - 1
|
||||
} else {
|
||||
index - 1
|
||||
};
|
||||
self.focus_webview_by_index(new_index)
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'W', || {
|
||||
self.focused_webview_id.map(EmbedderEvent::CloseWebView)
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'T', || {
|
||||
let url = ServoUrl::parse("servo:newtab").unwrap();
|
||||
Some(EmbedderEvent::NewWebView(url, WebViewId::new()))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'Q', || Some(EmbedderEvent::Quit))
|
||||
.shortcut(CMD_OR_CONTROL, 'P', || {
|
||||
let rate = env::var("SAMPLING_RATE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(10);
|
||||
let duration = env::var("SAMPLING_DURATION")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(10);
|
||||
Some(EmbedderEvent::ToggleSamplingProfiler(
|
||||
Duration::from_millis(rate),
|
||||
Duration::from_secs(duration),
|
||||
))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'X', || {
|
||||
Some(EmbedderEvent::ClipboardAction(ClipboardEventType::Cut))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'C', || {
|
||||
Some(EmbedderEvent::ClipboardAction(ClipboardEventType::Copy))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'V', || {
|
||||
Some(EmbedderEvent::ClipboardAction(ClipboardEventType::Paste(
|
||||
self.clipboard
|
||||
.as_mut()
|
||||
.and_then(|clipboard| clipboard.get_text().ok())
|
||||
.unwrap_or_else(|| {
|
||||
warn!("Error getting clipboard text. Returning empty string.");
|
||||
String::new()
|
||||
}),
|
||||
)))
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F9, || {
|
||||
Some(EmbedderEvent::CaptureWebRender)
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F10, || {
|
||||
Some(EmbedderEvent::ToggleWebRenderDebug(
|
||||
WebRenderDebugOption::RenderTargetDebug,
|
||||
))
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F11, || {
|
||||
Some(EmbedderEvent::ToggleWebRenderDebug(
|
||||
WebRenderDebugOption::TextureCacheDebug,
|
||||
))
|
||||
})
|
||||
.shortcut(Modifiers::CONTROL, Key::F12, || {
|
||||
Some(EmbedderEvent::ToggleWebRenderDebug(
|
||||
WebRenderDebugOption::Profiler,
|
||||
))
|
||||
})
|
||||
.shortcut(CMD_OR_ALT, Key::ArrowRight, || {
|
||||
self.focused_webview_id
|
||||
.map(|id| EmbedderEvent::Navigation(id, TraversalDirection::Forward(1)))
|
||||
})
|
||||
.optional_shortcut(
|
||||
cfg!(not(target_os = "windows")),
|
||||
CMD_OR_CONTROL,
|
||||
']',
|
||||
|| {
|
||||
self.focused_webview_id()
|
||||
.map(|id| EmbedderEvent::Navigation(id, TraversalDirection::Forward(1)))
|
||||
},
|
||||
)
|
||||
.shortcut(CMD_OR_ALT, Key::ArrowLeft, || {
|
||||
self.focused_webview_id
|
||||
.map(|id| EmbedderEvent::Navigation(id, TraversalDirection::Back(1)))
|
||||
})
|
||||
.optional_shortcut(
|
||||
cfg!(not(target_os = "windows")),
|
||||
CMD_OR_CONTROL,
|
||||
'[',
|
||||
|| {
|
||||
self.focused_webview_id
|
||||
.map(|id| EmbedderEvent::Navigation(id, TraversalDirection::Back(1)))
|
||||
},
|
||||
)
|
||||
.optional_shortcut(
|
||||
self.window.get_fullscreen(),
|
||||
Modifiers::empty(),
|
||||
Key::Escape,
|
||||
|| self.focused_webview_id.map(EmbedderEvent::ExitFullScreen),
|
||||
)
|
||||
.otherwise(|| {
|
||||
self.focused_webview_id
|
||||
.map(|_id| EmbedderEvent::Keyboard(key_event))
|
||||
})
|
||||
.flatten();
|
||||
if let Some(event) = embedder_event {
|
||||
self.event_queue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle key events after they have been handled by Servo.
|
||||
fn handle_key_from_servo(&mut self, webview_id: Option<WebViewId>, event: KeyboardEvent) {
|
||||
ShortcutMatcher::from_event(event)
|
||||
.shortcut(CMD_OR_CONTROL, '=', || {
|
||||
self.event_queue.push(EmbedderEvent::Zoom(1.1))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, '+', || {
|
||||
self.event_queue.push(EmbedderEvent::Zoom(1.1))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, '-', || {
|
||||
self.event_queue.push(EmbedderEvent::Zoom(1.0 / 1.1))
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, '0', || {
|
||||
self.event_queue.push(EmbedderEvent::ResetZoom)
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::PageDown, || {
|
||||
let scroll_location = ScrollLocation::Delta(Vector2D::new(
|
||||
0.0,
|
||||
-self.window.page_height() + 2.0 * LINE_HEIGHT,
|
||||
));
|
||||
self.scroll_window_from_key(scroll_location, TouchEventType::Move, webview_id);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::PageUp, || {
|
||||
let scroll_location = ScrollLocation::Delta(Vector2D::new(
|
||||
0.0,
|
||||
self.window.page_height() - 2.0 * LINE_HEIGHT,
|
||||
));
|
||||
self.scroll_window_from_key(scroll_location, TouchEventType::Move, webview_id);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::Home, || {
|
||||
self.scroll_window_from_key(
|
||||
ScrollLocation::Start,
|
||||
TouchEventType::Move,
|
||||
webview_id,
|
||||
);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::End, || {
|
||||
self.scroll_window_from_key(ScrollLocation::End, TouchEventType::Move, webview_id);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowUp, || {
|
||||
self.scroll_window_from_key(
|
||||
ScrollLocation::Delta(Vector2D::new(0.0, 3.0 * LINE_HEIGHT)),
|
||||
TouchEventType::Move,
|
||||
webview_id,
|
||||
);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowDown, || {
|
||||
self.scroll_window_from_key(
|
||||
ScrollLocation::Delta(Vector2D::new(0.0, -3.0 * LINE_HEIGHT)),
|
||||
TouchEventType::Move,
|
||||
webview_id,
|
||||
);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowLeft, || {
|
||||
self.scroll_window_from_key(
|
||||
ScrollLocation::Delta(Vector2D::new(LINE_HEIGHT, 0.0)),
|
||||
TouchEventType::Move,
|
||||
webview_id,
|
||||
);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowRight, || {
|
||||
self.scroll_window_from_key(
|
||||
ScrollLocation::Delta(Vector2D::new(-LINE_HEIGHT, 0.0)),
|
||||
TouchEventType::Move,
|
||||
webview_id,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn scroll_window_from_key(
|
||||
&mut self,
|
||||
scroll_location: ScrollLocation,
|
||||
phase: TouchEventType,
|
||||
webview_id: Option<WebViewId>,
|
||||
) {
|
||||
// In minibrowser mode the webview is offset by the toolbar
|
||||
let origin = webview_id
|
||||
.and_then(|id| self.webviews.get(&id))
|
||||
.map(|webview| webview.rect.min.ceil().to_i32())
|
||||
.unwrap_or(Point2D::zero());
|
||||
let event = EmbedderEvent::Scroll(scroll_location, origin, phase);
|
||||
self.event_queue.push(event);
|
||||
fn send_error(&self, webview_id: Option<WebViewId>, error: String) {
|
||||
let Some(webview) = webview_id.and_then(|id| self.get(id)) else {
|
||||
return warn!("{error}");
|
||||
};
|
||||
webview.servo_webview.send_error(error);
|
||||
}
|
||||
|
||||
/// Returns true if the caller needs to manually present a new frame.
|
||||
pub fn handle_servo_events(
|
||||
&mut self,
|
||||
servo: &mut Servo,
|
||||
clipboard: &mut Option<Clipboard>,
|
||||
opts: &Opts,
|
||||
events: Drain<'_, (Option<WebViewId>, EmbedderMsg)>,
|
||||
events: Vec<(Option<WebViewId>, EmbedderMsg)>,
|
||||
) -> ServoEventResponse {
|
||||
let mut need_present = self.load_status() != LoadStatus::LoadComplete;
|
||||
let mut need_update = false;
|
||||
|
@ -656,6 +427,7 @@ where
|
|||
} else {
|
||||
trace_embedder_msg!(msg, "{msg:?}");
|
||||
}
|
||||
|
||||
match msg {
|
||||
EmbedderMsg::Status(status) => {
|
||||
self.status_text = status;
|
||||
|
@ -674,31 +446,24 @@ where
|
|||
));
|
||||
}
|
||||
need_update = true;
|
||||
} else {
|
||||
let data = self.ensure_preload_data_mut(&webview_id);
|
||||
data.title = title.clone();
|
||||
}
|
||||
}
|
||||
},
|
||||
EmbedderMsg::MoveTo(point) => {
|
||||
self.window.set_position(point);
|
||||
},
|
||||
EmbedderMsg::ResizeTo(size) => {
|
||||
EmbedderMsg::ResizeTo(inner_size) => {
|
||||
if let Some(webview_id) = webview_id {
|
||||
let new_rect = self.get_mut(webview_id).and_then(|webview| {
|
||||
if webview.rect.size() != size.to_f32() {
|
||||
webview.rect.set_size(size.to_f32());
|
||||
Some(webview.rect)
|
||||
} else {
|
||||
None
|
||||
if let Some(webview) = self.get_mut(webview_id) {
|
||||
if webview.rect.size() != inner_size.to_f32() {
|
||||
webview.rect.set_size(inner_size.to_f32());
|
||||
webview.servo_webview.move_resize(webview.rect);
|
||||
}
|
||||
});
|
||||
if let Some(new_rect) = new_rect {
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::MoveResizeWebView(webview_id, new_rect));
|
||||
};
|
||||
if let Some(webview) = self.get(webview_id) {
|
||||
self.window.request_resize(webview, inner_size);
|
||||
}
|
||||
}
|
||||
self.window.request_inner_size(size);
|
||||
},
|
||||
EmbedderMsg::Prompt(definition, origin) => {
|
||||
let res = if opts.headless {
|
||||
|
@ -785,33 +550,32 @@ where
|
|||
.expect("Thread spawning failed")
|
||||
};
|
||||
if let Err(e) = res {
|
||||
let reason = format!("Failed to send Prompt response: {}", e);
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::SendError(webview_id, reason));
|
||||
self.send_error(webview_id, format!("Failed to send Prompt response: {e}"))
|
||||
}
|
||||
},
|
||||
EmbedderMsg::AllowUnload(sender) => {
|
||||
// Always allow unload for now.
|
||||
if let Err(e) = sender.send(true) {
|
||||
let reason = format!("Failed to send AllowUnload response: {}", e);
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::SendError(webview_id, reason));
|
||||
self.send_error(
|
||||
webview_id,
|
||||
format!("Failed to send AllowUnload response: {e}"),
|
||||
)
|
||||
}
|
||||
},
|
||||
EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
|
||||
if let Some(_webview_id) = webview_id {
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::AllowNavigationResponse(pipeline_id, true));
|
||||
servo.allow_navigation_response(pipeline_id, true);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::AllowOpeningWebView(response_chan) => {
|
||||
// Note: would be a place to handle pop-ups config.
|
||||
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
|
||||
if let Err(e) = response_chan.send(Some(WebViewId::new())) {
|
||||
warn!("Failed to send AllowOpeningWebView response: {}", e);
|
||||
};
|
||||
let webview = servo.new_auxiliary_webview();
|
||||
match response_chan.send(Some(webview.id())) {
|
||||
Ok(()) => self.add(webview),
|
||||
Err(error) => warn!("Failed to send AllowOpeningWebView response: {error}"),
|
||||
}
|
||||
},
|
||||
EmbedderMsg::WebViewOpened(new_webview_id) => {
|
||||
println!("WebView opened: {:?}", new_webview_id);
|
||||
let scale = self.window.hidpi_factor().get();
|
||||
let toolbar = self.window.toolbar_height().get();
|
||||
|
||||
|
@ -820,41 +584,30 @@ where
|
|||
let mut rect = self.window.get_coordinates().get_viewport().to_f32();
|
||||
rect.min.y += toolbar * scale;
|
||||
|
||||
// Make sure to not add duplicates into the creation_order vector.
|
||||
// This can happen as explained in https://github.com/servo/servo/issues/33075
|
||||
let preload_data = self.ensure_preload_data_mut(&new_webview_id).clone();
|
||||
if let Entry::Vacant(entry) = self.webviews.entry(new_webview_id) {
|
||||
entry.insert(WebView::new(rect, preload_data));
|
||||
self.creation_order.push(new_webview_id);
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::FocusWebView(new_webview_id));
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::MoveResizeWebView(new_webview_id, rect));
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::RaiseWebViewToTop(new_webview_id, true));
|
||||
}
|
||||
let webview = self
|
||||
.webviews
|
||||
.get(&new_webview_id)
|
||||
.expect("Unknown webview opened.");
|
||||
webview.servo_webview.focus();
|
||||
webview.servo_webview.move_resize(rect);
|
||||
webview.servo_webview.raise_to_top(true);
|
||||
},
|
||||
EmbedderMsg::WebViewClosed(webview_id) => {
|
||||
self.webviews.retain(|&id, _| id != webview_id);
|
||||
self.creation_order.retain(|&id| id != webview_id);
|
||||
self.focused_webview_id = None;
|
||||
if let Some(&newest_webview_id) = self.creation_order.last() {
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::FocusWebView(newest_webview_id));
|
||||
} else {
|
||||
self.event_queue.push(EmbedderEvent::Quit);
|
||||
}
|
||||
self.close_webview(servo, webview_id);
|
||||
},
|
||||
EmbedderMsg::WebViewFocused(webview_id) => {
|
||||
for (id, webview) in &mut self.webviews {
|
||||
webview.focused = *id == webview_id;
|
||||
if let Some(webview) = self.focused_webview_id.and_then(|id| self.get_mut(id)) {
|
||||
webview.focused = false;
|
||||
}
|
||||
self.focused_webview_id = Some(webview_id);
|
||||
need_update = true;
|
||||
|
||||
// Show the most recently created webview and hide all others.
|
||||
// TODO: Stop doing this once we have full multiple webviews support
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::ShowWebView(webview_id, true));
|
||||
if let Some(webview) = self.get_mut(webview_id) {
|
||||
webview.focused = true;
|
||||
webview.servo_webview.show(true);
|
||||
self.focused_webview_id = Some(webview_id);
|
||||
need_update = true;
|
||||
};
|
||||
},
|
||||
EmbedderMsg::WebViewBlurred => {
|
||||
for webview in self.webviews.values_mut() {
|
||||
|
@ -863,28 +616,24 @@ where
|
|||
self.focused_webview_id = None;
|
||||
},
|
||||
EmbedderMsg::Keyboard(key_event) => {
|
||||
self.handle_key_from_servo(webview_id, key_event);
|
||||
self.handle_overridable_key_bindings(webview_id, key_event);
|
||||
},
|
||||
EmbedderMsg::ClearClipboardContents => {
|
||||
self.clipboard
|
||||
clipboard
|
||||
.as_mut()
|
||||
.and_then(|clipboard| clipboard.clear().ok());
|
||||
},
|
||||
EmbedderMsg::GetClipboardContents(sender) => {
|
||||
let contents = self
|
||||
.clipboard
|
||||
let contents = clipboard
|
||||
.as_mut()
|
||||
.and_then(|clipboard| clipboard.get_text().ok())
|
||||
.unwrap_or_else(|| {
|
||||
warn!("Error getting clipboard text. Returning empty string.");
|
||||
String::new()
|
||||
});
|
||||
.unwrap_or_default();
|
||||
if let Err(e) = sender.send(contents) {
|
||||
warn!("Failed to send clipboard ({})", e);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::SetClipboardContents(text) => {
|
||||
if let Some(ref mut clipboard) = self.clipboard {
|
||||
if let Some(clipboard) = clipboard.as_mut() {
|
||||
if let Err(e) = clipboard.set_text(text) {
|
||||
warn!("Error setting clipboard contents ({})", e);
|
||||
}
|
||||
|
@ -897,42 +646,31 @@ where
|
|||
// FIXME: show favicons in the UI somehow
|
||||
},
|
||||
EmbedderMsg::HeadParsed => {
|
||||
if let Some(webview_id) = webview_id {
|
||||
if let Some(webview) = self.get_mut(webview_id) {
|
||||
webview.load_status = LoadStatus::HeadParsed;
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
if let Some(webview) = webview_id.and_then(|id| self.get_mut(id)) {
|
||||
webview.load_status = LoadStatus::HeadParsed;
|
||||
need_update = true;
|
||||
};
|
||||
},
|
||||
EmbedderMsg::HistoryChanged(urls, current) => {
|
||||
if let Some(webview_id) = webview_id {
|
||||
if let Some(webview) = self.get_mut(webview_id) {
|
||||
webview.url = Some(urls[current].clone());
|
||||
need_update = true;
|
||||
} else {
|
||||
let data = self.ensure_preload_data_mut(&webview_id);
|
||||
data.url = Some(urls[current].clone());
|
||||
}
|
||||
}
|
||||
if let Some(webview) = webview_id.and_then(|id| self.get_mut(id)) {
|
||||
webview.url = Some(urls[current].clone());
|
||||
need_update = true;
|
||||
};
|
||||
},
|
||||
EmbedderMsg::SetFullscreenState(state) => {
|
||||
self.window.set_fullscreen(state);
|
||||
},
|
||||
EmbedderMsg::LoadStart => {
|
||||
if let Some(webview_id) = webview_id {
|
||||
if let Some(webview) = self.get_mut(webview_id) {
|
||||
webview.load_status = LoadStatus::LoadStart;
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
if let Some(webview) = webview_id.and_then(|id| self.get_mut(id)) {
|
||||
webview.load_status = LoadStatus::LoadStart;
|
||||
need_update = true;
|
||||
};
|
||||
},
|
||||
EmbedderMsg::LoadComplete => {
|
||||
if let Some(webview_id) = webview_id {
|
||||
if let Some(webview) = self.get_mut(webview_id) {
|
||||
webview.load_status = LoadStatus::LoadComplete;
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
if let Some(webview) = webview_id.and_then(|id| self.get_mut(id)) {
|
||||
webview.load_status = LoadStatus::LoadComplete;
|
||||
need_update = true;
|
||||
};
|
||||
},
|
||||
EmbedderMsg::WebResourceRequested(_web_resource_request, _response_sender) => {},
|
||||
EmbedderMsg::Shutdown => {
|
||||
|
@ -942,21 +680,23 @@ where
|
|||
EmbedderMsg::GetSelectedBluetoothDevice(devices, sender) => {
|
||||
let selected = platform_get_selected_devices(devices);
|
||||
if let Err(e) = sender.send(selected) {
|
||||
let reason =
|
||||
format!("Failed to send GetSelectedBluetoothDevice response: {}", e);
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::SendError(None, reason));
|
||||
self.send_error(
|
||||
webview_id,
|
||||
format!("Failed to send GetSelectedBluetoothDevice response: {e}"),
|
||||
);
|
||||
};
|
||||
},
|
||||
EmbedderMsg::SelectFiles(patterns, multiple_files, sender) => {
|
||||
let res = match (opts.headless, get_selected_files(patterns, multiple_files)) {
|
||||
let result = match (opts.headless, get_selected_files(patterns, multiple_files))
|
||||
{
|
||||
(true, _) | (false, None) => sender.send(None),
|
||||
(false, Some(files)) => sender.send(Some(files)),
|
||||
};
|
||||
if let Err(e) = res {
|
||||
let reason = format!("Failed to send SelectFiles response: {}", e);
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::SendError(None, reason));
|
||||
if let Err(e) = result {
|
||||
self.send_error(
|
||||
webview_id,
|
||||
format!("Failed to send SelectFiles response: {e}"),
|
||||
);
|
||||
};
|
||||
},
|
||||
EmbedderMsg::PromptPermission(prompt, sender) => {
|
||||
|
@ -993,15 +733,12 @@ where
|
|||
need_present = true;
|
||||
},
|
||||
EmbedderMsg::EventDelivered(event) => {
|
||||
if let (Some(webview_id), CompositorEventVariant::MouseButtonEvent) =
|
||||
(webview_id, event)
|
||||
{
|
||||
trace!("{}: Got a mouse button event", webview_id);
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::RaiseWebViewToTop(webview_id, true));
|
||||
self.event_queue
|
||||
.push(EmbedderEvent::FocusWebView(webview_id));
|
||||
}
|
||||
if let Some(webview) = webview_id.and_then(|id| self.get_mut(id)) {
|
||||
if let CompositorEventVariant::MouseButtonEvent = event {
|
||||
webview.servo_webview.raise_to_top(true);
|
||||
webview.servo_webview.focus();
|
||||
}
|
||||
};
|
||||
},
|
||||
EmbedderMsg::PlayGamepadHapticEffect(index, effect, effect_complete_sender) => {
|
||||
match effect {
|
||||
|
@ -1024,6 +761,69 @@ where
|
|||
need_update,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle servoshell key bindings that may have been prevented by the page in the focused webview.
|
||||
fn handle_overridable_key_bindings(
|
||||
&mut self,
|
||||
webview_id: Option<WebViewId>,
|
||||
event: KeyboardEvent,
|
||||
) {
|
||||
let Some(webview) = webview_id.and_then(|id| self.get(id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let origin = webview.rect.min.ceil().to_i32();
|
||||
let webview = &webview.servo_webview;
|
||||
ShortcutMatcher::from_event(event)
|
||||
.shortcut(CMD_OR_CONTROL, '=', || {
|
||||
webview.set_zoom(1.1);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, '+', || {
|
||||
webview.set_zoom(1.1);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, '-', || {
|
||||
webview.set_zoom(1.0 / 1.1);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, '0', || {
|
||||
webview.reset_zoom();
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::PageDown, || {
|
||||
let scroll_location = ScrollLocation::Delta(Vector2D::new(
|
||||
0.0,
|
||||
-self.window.page_height() + 2.0 * LINE_HEIGHT,
|
||||
));
|
||||
webview.notify_scroll_event(scroll_location, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::PageUp, || {
|
||||
let scroll_location = ScrollLocation::Delta(Vector2D::new(
|
||||
0.0,
|
||||
self.window.page_height() - 2.0 * LINE_HEIGHT,
|
||||
));
|
||||
webview.notify_scroll_event(scroll_location, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::Home, || {
|
||||
webview.notify_scroll_event(ScrollLocation::Start, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::End, || {
|
||||
webview.notify_scroll_event(ScrollLocation::End, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowUp, || {
|
||||
let location = ScrollLocation::Delta(Vector2D::new(0.0, 3.0 * LINE_HEIGHT));
|
||||
webview.notify_scroll_event(location, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowDown, || {
|
||||
let location = ScrollLocation::Delta(Vector2D::new(0.0, -3.0 * LINE_HEIGHT));
|
||||
webview.notify_scroll_event(location, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowLeft, || {
|
||||
let location = ScrollLocation::Delta(Vector2D::new(LINE_HEIGHT, 0.0));
|
||||
webview.notify_scroll_event(location, origin, TouchEventType::Move);
|
||||
})
|
||||
.shortcut(Modifiers::empty(), Key::ArrowRight, || {
|
||||
let location = ScrollLocation::Delta(Vector2D::new(-LINE_HEIGHT, 0.0));
|
||||
webview.notify_scroll_event(location, origin, TouchEventType::Move);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
|
@ -7,17 +7,19 @@
|
|||
|
||||
use std::rc::Rc;
|
||||
|
||||
use arboard::Clipboard;
|
||||
use euclid::{Length, Scale};
|
||||
use servo::compositing::windowing::{EmbedderEvent, WindowMethods};
|
||||
use servo::compositing::windowing::WindowMethods;
|
||||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
|
||||
use servo::Cursor;
|
||||
use servo::{Cursor, Servo};
|
||||
|
||||
use super::webview::{WebView, WebViewManager};
|
||||
|
||||
// This should vary by zoom level and maybe actual text size (focused or under cursor)
|
||||
pub const LINE_HEIGHT: f32 = 38.0;
|
||||
|
||||
pub trait WindowPortsMethods: WindowMethods {
|
||||
fn get_events(&self) -> Vec<EmbedderEvent>;
|
||||
fn id(&self) -> winit::window::WindowId;
|
||||
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||
self.device_pixel_ratio_override()
|
||||
|
@ -29,10 +31,18 @@ pub trait WindowPortsMethods: WindowMethods {
|
|||
) -> Option<Scale<f32, DeviceIndependentPixel, DevicePixel>>;
|
||||
fn page_height(&self) -> f32;
|
||||
fn get_fullscreen(&self) -> bool;
|
||||
fn queue_embedder_events_for_winit_event(&self, event: winit::event::WindowEvent);
|
||||
fn handle_winit_event(
|
||||
&self,
|
||||
servo: &Servo,
|
||||
clipboard: &mut Option<Clipboard>,
|
||||
webviews: &mut WebViewManager,
|
||||
event: winit::event::WindowEvent,
|
||||
);
|
||||
fn is_animating(&self) -> bool;
|
||||
fn set_title(&self, _title: &str) {}
|
||||
fn request_inner_size(&self, size: DeviceIntSize) -> Option<DeviceIntSize>;
|
||||
/// Request a new inner size for the window, not including external decorations.
|
||||
fn request_resize(&self, webview: &WebView, inner_size: DeviceIntSize)
|
||||
-> Option<DeviceIntSize>;
|
||||
fn set_position(&self, _point: DeviceIntPoint) {}
|
||||
fn set_fullscreen(&self, _state: bool) {}
|
||||
fn set_cursor(&self, _cursor: Cursor) {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue