mirror of
https://github.com/servo/servo.git
synced 2025-08-04 21:20:23 +01:00
Toolbar size can be changed if resized, such as entering fullscreen. Hit-test had wrong offsets after fullscreen/resize as `WindowEvent::CursorMoved` set wrong coordinates for `webview_relative_mouse_point` due to outdated toolbar height. Testing: #38297 now works properly. Fixes: #38297 --------- Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
824 lines
33 KiB
Rust
824 lines
33 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* 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/. */
|
||
|
||
//! Application entry point, runs the event loop.
|
||
|
||
use std::cell::Cell;
|
||
use std::collections::HashMap;
|
||
use std::path::Path;
|
||
use std::rc::Rc;
|
||
use std::time::Instant;
|
||
use std::{env, fs};
|
||
|
||
use ::servo::ServoBuilder;
|
||
use constellation_traits::EmbedderToConstellationMessage;
|
||
use crossbeam_channel::unbounded;
|
||
use euclid::{Point2D, Vector2D};
|
||
use ipc_channel::ipc;
|
||
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
||
use log::{info, trace, warn};
|
||
use net::protocols::ProtocolRegistry;
|
||
use servo::config::opts::Opts;
|
||
use servo::config::prefs::Preferences;
|
||
use servo::servo_url::ServoUrl;
|
||
use servo::user_content_manager::{UserContentManager, UserScript};
|
||
use servo::webrender_api::ScrollLocation;
|
||
use servo::{
|
||
EventLoopWaker, ImeEvent, InputEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent,
|
||
WebDriverCommandMsg, WebDriverScriptCommand, WebDriverUserPromptAction, WheelDelta, WheelEvent,
|
||
WheelMode,
|
||
};
|
||
use url::Url;
|
||
use winit::application::ApplicationHandler;
|
||
use winit::event::WindowEvent;
|
||
use winit::event_loop::{ActiveEventLoop, ControlFlow};
|
||
use winit::window::WindowId;
|
||
|
||
use super::app_state::AppState;
|
||
use super::events_loop::{AppEvent, EventLoopProxy, EventsLoop};
|
||
use super::minibrowser::{Minibrowser, MinibrowserEvent};
|
||
use super::{headed_window, headless_window};
|
||
use crate::desktop::app_state::RunningAppState;
|
||
use crate::desktop::protocols;
|
||
use crate::desktop::tracing::trace_winit_event;
|
||
use crate::desktop::webxr::XrDiscoveryWebXrRegistry;
|
||
use crate::desktop::window_trait::WindowPortsMethods;
|
||
use crate::parser::{get_default_url, location_bar_input_to_url};
|
||
use crate::prefs::ServoShellPreferences;
|
||
|
||
pub struct App {
|
||
opts: Opts,
|
||
preferences: Preferences,
|
||
servoshell_preferences: ServoShellPreferences,
|
||
suspended: Cell<bool>,
|
||
minibrowser: Option<Minibrowser>,
|
||
waker: Box<dyn EventLoopWaker>,
|
||
proxy: Option<EventLoopProxy>,
|
||
initial_url: ServoUrl,
|
||
t_start: Instant,
|
||
t: Instant,
|
||
state: AppState,
|
||
|
||
// This is the last field of the struct to ensure that windows are dropped *after* all other
|
||
// references to the relevant rendering contexts have been destroyed.
|
||
// (https://github.com/servo/servo/issues/36711)
|
||
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
|
||
}
|
||
|
||
/// Action to be taken by the caller of [`App::handle_events`].
|
||
pub(crate) enum PumpResult {
|
||
/// The caller should shut down Servo and its related context.
|
||
Shutdown,
|
||
Continue {
|
||
need_update: bool,
|
||
need_window_redraw: bool,
|
||
},
|
||
}
|
||
|
||
impl App {
|
||
pub fn new(
|
||
opts: Opts,
|
||
preferences: Preferences,
|
||
servo_shell_preferences: ServoShellPreferences,
|
||
events_loop: &EventsLoop,
|
||
) -> Self {
|
||
let initial_url = get_default_url(
|
||
servo_shell_preferences.url.as_deref(),
|
||
env::current_dir().unwrap(),
|
||
|path| fs::metadata(path).is_ok(),
|
||
&servo_shell_preferences,
|
||
);
|
||
|
||
let t = Instant::now();
|
||
App {
|
||
opts,
|
||
preferences,
|
||
servoshell_preferences: servo_shell_preferences,
|
||
suspended: Cell::new(false),
|
||
windows: HashMap::new(),
|
||
minibrowser: None,
|
||
waker: events_loop.create_event_loop_waker(),
|
||
proxy: events_loop.event_loop_proxy(),
|
||
initial_url: initial_url.clone(),
|
||
t_start: t,
|
||
t,
|
||
state: AppState::Initializing,
|
||
}
|
||
}
|
||
|
||
/// Initialize Application once event loop start running.
|
||
pub fn init(&mut self, event_loop: Option<&ActiveEventLoop>) {
|
||
let headless = self.servoshell_preferences.headless;
|
||
|
||
assert_eq!(headless, event_loop.is_none());
|
||
let window = match event_loop {
|
||
Some(event_loop) => {
|
||
let proxy = self.proxy.take().expect("Must have a proxy available");
|
||
let window = headed_window::Window::new(&self.servoshell_preferences, event_loop);
|
||
self.minibrowser = Some(Minibrowser::new(
|
||
&window,
|
||
event_loop,
|
||
proxy,
|
||
self.initial_url.clone(),
|
||
));
|
||
Rc::new(window)
|
||
},
|
||
None => headless_window::Window::new(&self.servoshell_preferences),
|
||
};
|
||
|
||
self.windows.insert(window.id(), window);
|
||
|
||
self.suspended.set(false);
|
||
let (_, window) = self.windows.iter().next().unwrap();
|
||
|
||
let mut user_content_manager = UserContentManager::new();
|
||
for script in load_userscripts(self.servoshell_preferences.userscripts_directory.as_deref())
|
||
.expect("Loading userscripts failed")
|
||
{
|
||
user_content_manager.add_script(script);
|
||
}
|
||
|
||
let mut protocol_registry = ProtocolRegistry::default();
|
||
let _ = protocol_registry.register(
|
||
"urlinfo",
|
||
protocols::urlinfo::UrlInfoProtocolHander::default(),
|
||
);
|
||
let _ =
|
||
protocol_registry.register("servo", protocols::servo::ServoProtocolHandler::default());
|
||
let _ = protocol_registry.register(
|
||
"resource",
|
||
protocols::resource::ResourceProtocolHandler::default(),
|
||
);
|
||
|
||
let servo_builder = ServoBuilder::new(window.rendering_context())
|
||
.opts(self.opts.clone())
|
||
.preferences(self.preferences.clone())
|
||
.user_content_manager(user_content_manager)
|
||
.protocol_registry(protocol_registry)
|
||
.event_loop_waker(self.waker.clone());
|
||
|
||
#[cfg(feature = "webxr")]
|
||
let servo_builder = servo_builder.webxr_registry(XrDiscoveryWebXrRegistry::new_boxed(
|
||
window.clone(),
|
||
event_loop,
|
||
&self.preferences,
|
||
));
|
||
|
||
let servo = servo_builder.build();
|
||
servo.setup_logging();
|
||
|
||
// Initialize WebDriver server here before `servo` is moved.
|
||
let webdriver_receiver = self.servoshell_preferences.webdriver_port.map(|port| {
|
||
let (embedder_sender, embedder_receiver) = unbounded();
|
||
let (webdriver_response_sender, webdriver_response_receiver) = ipc::channel().unwrap();
|
||
|
||
// Set the WebDriver response sender to constellation.
|
||
// TODO: consider using Servo API to notify embedder about input events completions
|
||
servo
|
||
.constellation_sender()
|
||
.send(EmbedderToConstellationMessage::SetWebDriverResponseSender(
|
||
webdriver_response_sender,
|
||
))
|
||
.unwrap_or_else(|_| {
|
||
warn!("Failed to set WebDriver response sender in constellation");
|
||
});
|
||
|
||
webdriver_server::start_server(
|
||
port,
|
||
embedder_sender,
|
||
self.waker.clone(),
|
||
webdriver_response_receiver,
|
||
);
|
||
|
||
embedder_receiver
|
||
});
|
||
|
||
let running_state = Rc::new(RunningAppState::new(
|
||
servo,
|
||
window.clone(),
|
||
self.servoshell_preferences.clone(),
|
||
webdriver_receiver,
|
||
));
|
||
running_state.create_and_focus_toplevel_webview(self.initial_url.clone().into_url());
|
||
if let Some(ref mut minibrowser) = self.minibrowser {
|
||
minibrowser.update(window.as_ref(), &running_state, "init");
|
||
}
|
||
|
||
self.state = AppState::Running(running_state);
|
||
}
|
||
|
||
pub(crate) fn animating(&self) -> bool {
|
||
match self.state {
|
||
AppState::Initializing => false,
|
||
AppState::Running(ref running_app_state) => running_app_state.servo().animating(),
|
||
AppState::ShuttingDown => false,
|
||
}
|
||
}
|
||
|
||
/// Handle events with winit contexts
|
||
pub fn handle_events_with_winit(
|
||
&mut self,
|
||
event_loop: &ActiveEventLoop,
|
||
window: Rc<dyn WindowPortsMethods>,
|
||
) {
|
||
let AppState::Running(state) = &self.state else {
|
||
return;
|
||
};
|
||
|
||
match state.pump_event_loop() {
|
||
PumpResult::Shutdown => {
|
||
state.shutdown();
|
||
self.state = AppState::ShuttingDown;
|
||
},
|
||
PumpResult::Continue {
|
||
need_update: update,
|
||
need_window_redraw,
|
||
} => {
|
||
let updated = match (update, &mut self.minibrowser) {
|
||
(true, Some(minibrowser)) => minibrowser.update_webview_data(state),
|
||
_ => false,
|
||
};
|
||
|
||
// If in headed mode, request a winit redraw event, so we can paint the minibrowser.
|
||
if updated || need_window_redraw {
|
||
if let Some(window) = window.winit_window() {
|
||
window.request_redraw();
|
||
}
|
||
}
|
||
},
|
||
}
|
||
|
||
if matches!(self.state, AppState::ShuttingDown) {
|
||
event_loop.exit();
|
||
}
|
||
}
|
||
|
||
/// Handle all servo events with headless mode. Return true if the application should
|
||
/// continue.
|
||
pub fn handle_events_with_headless(&mut self) -> bool {
|
||
let now = Instant::now();
|
||
let event = winit::event::Event::UserEvent(AppEvent::Waker);
|
||
trace_winit_event!(
|
||
event,
|
||
"@{:?} (+{:?}) {event:?}",
|
||
now - self.t_start,
|
||
now - self.t
|
||
);
|
||
self.t = now;
|
||
|
||
// We should always be in the running state.
|
||
let AppState::Running(state) = &self.state else {
|
||
return false;
|
||
};
|
||
|
||
match state.pump_event_loop() {
|
||
PumpResult::Shutdown => {
|
||
state.shutdown();
|
||
self.state = AppState::ShuttingDown;
|
||
},
|
||
PumpResult::Continue { .. } => state.repaint_servo_if_necessary(),
|
||
}
|
||
|
||
!matches!(self.state, AppState::ShuttingDown)
|
||
}
|
||
|
||
/// Takes any events generated during `egui` updates and performs their actions.
|
||
fn handle_servoshell_ui_events(&mut self) {
|
||
let Some(minibrowser) = self.minibrowser.as_ref() else {
|
||
return;
|
||
};
|
||
// We should always be in the running state.
|
||
let AppState::Running(state) = &self.state 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.servoshell_preferences.searchpage,
|
||
) else {
|
||
warn!("failed to parse location");
|
||
break;
|
||
};
|
||
if let Some(focused_webview) = state.focused_webview() {
|
||
focused_webview.load(url.into_url());
|
||
}
|
||
},
|
||
MinibrowserEvent::Back => {
|
||
if let Some(focused_webview) = state.focused_webview() {
|
||
focused_webview.go_back(1);
|
||
}
|
||
},
|
||
MinibrowserEvent::Forward => {
|
||
if let Some(focused_webview) = state.focused_webview() {
|
||
focused_webview.go_forward(1);
|
||
}
|
||
},
|
||
MinibrowserEvent::Reload => {
|
||
minibrowser.update_location_dirty(false);
|
||
if let Some(focused_webview) = state.focused_webview() {
|
||
focused_webview.reload();
|
||
}
|
||
},
|
||
MinibrowserEvent::NewWebView => {
|
||
minibrowser.update_location_dirty(false);
|
||
state.create_and_focus_toplevel_webview(Url::parse("servo:newtab").unwrap());
|
||
},
|
||
MinibrowserEvent::CloseWebView(id) => {
|
||
minibrowser.update_location_dirty(false);
|
||
state.close_webview(id);
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn handle_webdriver_messages(&self) {
|
||
let AppState::Running(running_state) = &self.state else {
|
||
return;
|
||
};
|
||
|
||
let Some(webdriver_receiver) = running_state.webdriver_receiver() else {
|
||
return;
|
||
};
|
||
|
||
while let Ok(msg) = webdriver_receiver.try_recv() {
|
||
match msg {
|
||
WebDriverCommandMsg::SetWebDriverResponseSender(..) => {
|
||
running_state.servo().execute_webdriver_command(msg);
|
||
},
|
||
WebDriverCommandMsg::IsWebViewOpen(webview_id, sender) => {
|
||
let context = running_state.webview_by_id(webview_id);
|
||
|
||
if let Err(error) = sender.send(context.is_some()) {
|
||
warn!("Failed to send response of IsWebViewOpein: {error}");
|
||
}
|
||
},
|
||
WebDriverCommandMsg::IsBrowsingContextOpen(..) => {
|
||
running_state.servo().execute_webdriver_command(msg);
|
||
},
|
||
WebDriverCommandMsg::NewWebView(response_sender, load_status_sender) => {
|
||
let new_webview =
|
||
running_state.create_toplevel_webview(Url::parse("about:blank").unwrap());
|
||
|
||
if let Err(error) = response_sender.send(new_webview.id()) {
|
||
warn!("Failed to send response of NewWebview: {error}");
|
||
}
|
||
if let Some(load_status_sender) = load_status_sender {
|
||
running_state.set_load_status_sender(new_webview.id(), load_status_sender);
|
||
}
|
||
},
|
||
WebDriverCommandMsg::CloseWebView(webview_id) => {
|
||
running_state.close_webview(webview_id);
|
||
},
|
||
WebDriverCommandMsg::FocusWebView(webview_id, response_sender) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
let focus_id = webview.focus();
|
||
running_state.set_pending_focus(focus_id, response_sender);
|
||
}
|
||
},
|
||
WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
|
||
let window = self
|
||
.windows
|
||
.values()
|
||
.next()
|
||
.expect("Should have at least one window in servoshell");
|
||
|
||
if let Err(error) = response_sender.send(window.window_rect()) {
|
||
warn!("Failed to send response of GetWindowSize: {error}");
|
||
}
|
||
},
|
||
WebDriverCommandMsg::MaximizeWebView(webview_id, response_sender) => {
|
||
let window = self
|
||
.windows
|
||
.values()
|
||
.next()
|
||
.expect("Should have at least one window in servoshell");
|
||
window.maximize(
|
||
&running_state
|
||
.webview_by_id(webview_id)
|
||
.expect("Webview must exists as we just verified"),
|
||
);
|
||
|
||
if let Err(error) = response_sender.send(window.window_rect()) {
|
||
warn!("Failed to send response of GetWindowSize: {error}");
|
||
}
|
||
},
|
||
WebDriverCommandMsg::SetWindowRect(webview_id, requested_rect, size_sender) => {
|
||
let Some(webview) = running_state.webview_by_id(webview_id) else {
|
||
continue;
|
||
};
|
||
|
||
let window = self
|
||
.windows
|
||
.values()
|
||
.next()
|
||
.expect("Should have at least one window in servoshell");
|
||
let scale = window.hidpi_scale_factor();
|
||
|
||
let requested_physical_rect =
|
||
(requested_rect.to_f32() * scale).round().to_i32();
|
||
|
||
// Step 17. Set Width/Height.
|
||
window.request_resize(&webview, requested_physical_rect.size());
|
||
|
||
// Step 18. Set position of the window.
|
||
window.set_position(requested_physical_rect.min);
|
||
|
||
if let Err(error) = size_sender.send(window.window_rect()) {
|
||
warn!("Failed to send window size: {error}");
|
||
}
|
||
},
|
||
WebDriverCommandMsg::GetViewportSize(_webview_id, response_sender) => {
|
||
let window = self
|
||
.windows
|
||
.values()
|
||
.next()
|
||
.expect("Should have at least one window in servoshell");
|
||
|
||
let size = window.rendering_context().size2d();
|
||
|
||
if let Err(error) = response_sender.send(size) {
|
||
warn!("Failed to send response of GetViewportSize: {error}");
|
||
}
|
||
},
|
||
// This is only received when start new session.
|
||
WebDriverCommandMsg::GetFocusedWebView(sender) => {
|
||
let focused_webview = running_state.focused_webview();
|
||
|
||
if let Err(error) = sender.send(focused_webview.map(|w| w.id())) {
|
||
warn!("Failed to send response of GetFocusedWebView: {error}");
|
||
};
|
||
},
|
||
WebDriverCommandMsg::LoadUrl(webview_id, url, load_status_sender) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
running_state.set_load_status_sender(webview_id, load_status_sender);
|
||
webview.load(url.into_url());
|
||
}
|
||
},
|
||
WebDriverCommandMsg::Refresh(webview_id, load_status_sender) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
running_state.set_load_status_sender(webview_id, load_status_sender);
|
||
webview.reload();
|
||
}
|
||
},
|
||
WebDriverCommandMsg::GoBack(webview_id, load_status_sender) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
let traversal_id = webview.go_back(1);
|
||
running_state.set_pending_traversal(traversal_id, load_status_sender);
|
||
}
|
||
},
|
||
WebDriverCommandMsg::GoForward(webview_id, load_status_sender) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
let traversal_id = webview.go_forward(1);
|
||
running_state.set_pending_traversal(traversal_id, load_status_sender);
|
||
}
|
||
},
|
||
// Key events don't need hit test so can be forwarded to constellation for now
|
||
WebDriverCommandMsg::SendKeys(webview_id, webdriver_input_events) => {
|
||
let Some(webview) = running_state.webview_by_id(webview_id) else {
|
||
continue;
|
||
};
|
||
|
||
for event in webdriver_input_events {
|
||
match event {
|
||
WebDriverInputEvent::Keyboard(event) => {
|
||
webview.notify_input_event(InputEvent::Keyboard(
|
||
KeyboardEvent::new(event),
|
||
));
|
||
},
|
||
WebDriverInputEvent::Composition(event) => {
|
||
webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
|
||
event,
|
||
)));
|
||
},
|
||
}
|
||
}
|
||
},
|
||
WebDriverCommandMsg::KeyboardAction(webview_id, key_event, msg_id) => {
|
||
// TODO: We should do processing like in `headed_window:handle_keyboard_input`.
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
webview.notify_input_event(
|
||
InputEvent::Keyboard(KeyboardEvent::new(key_event))
|
||
.with_webdriver_message_id(msg_id),
|
||
);
|
||
}
|
||
},
|
||
WebDriverCommandMsg::MouseButtonAction(
|
||
webview_id,
|
||
mouse_event_type,
|
||
mouse_button,
|
||
x,
|
||
y,
|
||
webdriver_message_id,
|
||
) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
webview.notify_input_event(
|
||
InputEvent::MouseButton(MouseButtonEvent::new(
|
||
mouse_event_type,
|
||
mouse_button,
|
||
Point2D::new(x, y),
|
||
))
|
||
.with_webdriver_message_id(webdriver_message_id),
|
||
);
|
||
}
|
||
},
|
||
WebDriverCommandMsg::MouseMoveAction(webview_id, x, y, webdriver_message_id) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
webview.notify_input_event(
|
||
InputEvent::MouseMove(MouseMoveEvent::new(Point2D::new(x, y)))
|
||
.with_webdriver_message_id(webdriver_message_id),
|
||
);
|
||
}
|
||
},
|
||
WebDriverCommandMsg::WheelScrollAction(
|
||
webview_id,
|
||
x,
|
||
y,
|
||
dx,
|
||
dy,
|
||
webdriver_message_id,
|
||
) => {
|
||
if let Some(webview) = running_state.webview_by_id(webview_id) {
|
||
let delta = WheelDelta {
|
||
x: -dx,
|
||
y: -dy,
|
||
z: 0.0,
|
||
mode: WheelMode::DeltaPixel,
|
||
};
|
||
|
||
let point = Point2D::new(x, y);
|
||
let scroll_location =
|
||
ScrollLocation::Delta(Vector2D::new(dx as f32, dy as f32));
|
||
|
||
webview.notify_input_event(
|
||
InputEvent::Wheel(WheelEvent::new(delta, point.to_f32()))
|
||
.with_webdriver_message_id(webdriver_message_id),
|
||
);
|
||
|
||
webview.notify_scroll_event(scroll_location, point.to_i32());
|
||
}
|
||
},
|
||
WebDriverCommandMsg::ScriptCommand(_, ref webdriver_script_command) => {
|
||
self.handle_webdriver_script_command(webdriver_script_command, running_state);
|
||
running_state.servo().execute_webdriver_command(msg);
|
||
},
|
||
WebDriverCommandMsg::CurrentUserPrompt(webview_id, response_sender) => {
|
||
let current_dialog =
|
||
running_state.get_current_active_dialog_webdriver_type(webview_id);
|
||
if let Err(error) = response_sender.send(current_dialog) {
|
||
warn!("Failed to send response of CurrentUserPrompt: {error}");
|
||
};
|
||
},
|
||
WebDriverCommandMsg::HandleUserPrompt(webview_id, action, response_sender) => {
|
||
let response = if running_state.webview_has_active_dialog(webview_id) {
|
||
let alert_text = running_state.alert_text_of_newest_dialog(webview_id);
|
||
|
||
match action {
|
||
WebDriverUserPromptAction::Accept => {
|
||
running_state.accept_active_dialogs(webview_id)
|
||
},
|
||
WebDriverUserPromptAction::Dismiss => {
|
||
running_state.dismiss_active_dialogs(webview_id)
|
||
},
|
||
WebDriverUserPromptAction::Ignore => {},
|
||
};
|
||
|
||
// Return success for AcceptAlert and DismissAlert commands.
|
||
Ok(alert_text)
|
||
} else {
|
||
// Return error for AcceptAlert and DismissAlert commands
|
||
// if there is no active dialog.
|
||
Err(())
|
||
};
|
||
|
||
if let Err(error) = response_sender.send(response) {
|
||
warn!("Failed to send response of HandleUserPrompt: {error}");
|
||
};
|
||
},
|
||
WebDriverCommandMsg::GetAlertText(webview_id, response_sender) => {
|
||
let response = match running_state.alert_text_of_newest_dialog(webview_id) {
|
||
Some(text) => Ok(text),
|
||
None => Err(()),
|
||
};
|
||
|
||
if let Err(error) = response_sender.send(response) {
|
||
warn!("Failed to send response of GetAlertText: {error}");
|
||
};
|
||
},
|
||
WebDriverCommandMsg::SendAlertText(webview_id, text) => {
|
||
running_state.set_alert_text_of_newest_dialog(webview_id, text);
|
||
},
|
||
WebDriverCommandMsg::TakeScreenshot(..) => {
|
||
running_state.servo().execute_webdriver_command(msg);
|
||
},
|
||
};
|
||
}
|
||
}
|
||
|
||
fn handle_webdriver_script_command(
|
||
&self,
|
||
msg: &WebDriverScriptCommand,
|
||
running_state: &RunningAppState,
|
||
) {
|
||
match msg {
|
||
WebDriverScriptCommand::ExecuteScript(_webview_id, response_sender) |
|
||
WebDriverScriptCommand::ExecuteAsyncScript(_webview_id, response_sender) => {
|
||
// Give embedder a chance to interrupt the script command.
|
||
// Webdriver only handles 1 script command at a time, so we can
|
||
// safely set a new interrupt sender and remove the previous one here.
|
||
running_state.set_script_command_interrupt_sender(Some(response_sender.clone()));
|
||
},
|
||
WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
|
||
running_state.set_load_status_sender(*webview_id, load_status_sender.clone());
|
||
},
|
||
WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
|
||
running_state.remove_load_status_sender(*webview_id);
|
||
},
|
||
_ => {
|
||
running_state.set_script_command_interrupt_sender(None);
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ApplicationHandler<AppEvent> for App {
|
||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||
self.init(Some(event_loop));
|
||
}
|
||
|
||
fn window_event(
|
||
&mut self,
|
||
event_loop: &ActiveEventLoop,
|
||
window_id: WindowId,
|
||
event: WindowEvent,
|
||
) {
|
||
let now = Instant::now();
|
||
trace_winit_event!(
|
||
event,
|
||
"@{:?} (+{:?}) {event:?}",
|
||
now - self.t_start,
|
||
now - self.t
|
||
);
|
||
self.t = now;
|
||
|
||
let AppState::Running(state) = &self.state else {
|
||
return;
|
||
};
|
||
|
||
let Some(window) = self.windows.get(&window_id) else {
|
||
return;
|
||
};
|
||
|
||
let window = window.clone();
|
||
if event == WindowEvent::RedrawRequested {
|
||
// We need to redraw the window for some reason.
|
||
trace!("RedrawRequested");
|
||
|
||
// WARNING: do not defer painting or presenting to some later tick of the event
|
||
// loop or servoshell may become unresponsive! (servo#30312)
|
||
if let Some(ref mut minibrowser) = self.minibrowser {
|
||
minibrowser.update(window.as_ref(), state, "RedrawRequested");
|
||
minibrowser.paint(window.winit_window().unwrap());
|
||
}
|
||
}
|
||
|
||
// Handle the event
|
||
let mut consumed = false;
|
||
if let Some(ref mut minibrowser) = self.minibrowser {
|
||
match event {
|
||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||
// Intercept any ScaleFactorChanged events away from EguiGlow::on_window_event, so
|
||
// we can use our own logic for calculating the scale factor and set egui’s
|
||
// scale factor to that value manually.
|
||
let desired_scale_factor = window.hidpi_scale_factor().get();
|
||
let effective_egui_zoom_factor = desired_scale_factor / scale_factor as f32;
|
||
|
||
info!(
|
||
"window scale factor changed to {}, setting egui zoom factor to {}",
|
||
scale_factor, effective_egui_zoom_factor
|
||
);
|
||
|
||
minibrowser
|
||
.context
|
||
.egui_ctx
|
||
.set_zoom_factor(effective_egui_zoom_factor);
|
||
|
||
state.hidpi_scale_factor_changed();
|
||
|
||
// Request a winit redraw event, so we can recomposite, update and paint
|
||
// the minibrowser, and present the new frame.
|
||
window.winit_window().unwrap().request_redraw();
|
||
},
|
||
ref event => {
|
||
let response =
|
||
minibrowser.on_window_event(window.winit_window().unwrap(), state, event);
|
||
// Update minibrowser if there's resize event to sync up with window.
|
||
if let WindowEvent::Resized(_) = event {
|
||
minibrowser.update(
|
||
window.as_ref(),
|
||
state,
|
||
"Sync WebView size with Window Resize event",
|
||
);
|
||
}
|
||
if response.repaint && *event != WindowEvent::RedrawRequested {
|
||
// Request a winit redraw event, so we can recomposite, update and paint
|
||
// the minibrowser, and present the new frame.
|
||
window.winit_window().unwrap().request_redraw();
|
||
}
|
||
|
||
// TODO how do we handle the tab key? (see doc for consumed)
|
||
// Note that servo doesn’t yet support tabbing through links and inputs
|
||
consumed = response.consumed;
|
||
},
|
||
}
|
||
}
|
||
if !consumed {
|
||
window.handle_winit_event(state.clone(), event);
|
||
}
|
||
|
||
// Block until the window gets an event
|
||
if !self.animating() || self.suspended.get() {
|
||
event_loop.set_control_flow(ControlFlow::Wait);
|
||
} else {
|
||
event_loop.set_control_flow(ControlFlow::Poll);
|
||
}
|
||
|
||
// Consume and handle any events from the servoshell UI.
|
||
self.handle_servoshell_ui_events();
|
||
|
||
self.handle_events_with_winit(event_loop, window);
|
||
}
|
||
|
||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
|
||
if let AppEvent::Accessibility(ref event) = event {
|
||
let Some(ref mut minibrowser) = self.minibrowser else {
|
||
return;
|
||
};
|
||
if !minibrowser.handle_accesskit_event(&event.window_event) {
|
||
return;
|
||
}
|
||
if let Some(window) = self.windows.get(&event.window_id) {
|
||
window.winit_window().unwrap().request_redraw();
|
||
}
|
||
return;
|
||
}
|
||
|
||
let now = Instant::now();
|
||
let event = winit::event::Event::UserEvent(event);
|
||
trace_winit_event!(
|
||
event,
|
||
"@{:?} (+{:?}) {event:?}",
|
||
now - self.t_start,
|
||
now - self.t
|
||
);
|
||
self.t = now;
|
||
|
||
if !matches!(self.state, AppState::Running(..)) {
|
||
return;
|
||
};
|
||
let Some(window) = self.windows.values().next() else {
|
||
return;
|
||
};
|
||
let window = window.clone();
|
||
|
||
// Block until the window gets an event
|
||
if !self.animating() || self.suspended.get() {
|
||
event_loop.set_control_flow(ControlFlow::Wait);
|
||
} else {
|
||
event_loop.set_control_flow(ControlFlow::Poll);
|
||
}
|
||
|
||
// Consume and handle any events from the Minibrowser.
|
||
self.handle_servoshell_ui_events();
|
||
|
||
// Consume and handle any events from the WebDriver.
|
||
self.handle_webdriver_messages();
|
||
|
||
self.handle_events_with_winit(event_loop, window);
|
||
}
|
||
|
||
fn suspended(&mut self, _: &ActiveEventLoop) {
|
||
self.suspended.set(true);
|
||
}
|
||
}
|
||
|
||
fn load_userscripts(userscripts_directory: Option<&Path>) -> std::io::Result<Vec<UserScript>> {
|
||
let mut userscripts = Vec::new();
|
||
if let Some(userscripts_directory) = &userscripts_directory {
|
||
let mut files = std::fs::read_dir(userscripts_directory)?
|
||
.map(|e| e.map(|entry| entry.path()))
|
||
.collect::<Result<Vec<_>, _>>()?;
|
||
files.sort_unstable();
|
||
for file in files {
|
||
userscripts.push(UserScript {
|
||
script: std::fs::read_to_string(&file)?,
|
||
source_file: Some(file),
|
||
});
|
||
}
|
||
}
|
||
Ok(userscripts)
|
||
}
|