From 1cdba8843d7bc10834d2ce1b2904eedd99e7ac00 Mon Sep 17 00:00:00 2001 From: Paul Rouget Date: Thu, 22 Feb 2018 10:22:53 +0100 Subject: [PATCH] split window code and browser code in two different files --- Cargo.lock | 2 - components/compositing/compositor_thread.rs | 3 + components/servo/lib.rs | 15 +- ports/servo/Cargo.toml | 2 - ports/servo/browser.rs | 319 ++++++++++++ ports/servo/glutin_app/keyutils.rs | 2 +- ports/servo/glutin_app/mod.rs | 2 +- ports/servo/glutin_app/window.rs | 523 +++++--------------- ports/servo/main.rs | 27 +- 9 files changed, 478 insertions(+), 417 deletions(-) create mode 100644 ports/servo/browser.rs diff --git a/Cargo.lock b/Cargo.lock index 3b9407c176d0..3bd64a6ef467 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2698,14 +2698,12 @@ dependencies = [ "libservo 0.0.1", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "msg 0.0.1", - "net_traits 0.0.1", "osmesa-src 17.3.1-devel (git+https://github.com/servo/osmesa-src)", "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "script_traits 0.0.1", "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "servo_config 0.0.1", "servo_geometry 0.0.1", - "servo_url 0.0.1", "sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "style_traits 0.0.1", "tinyfiledialogs 3.3.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/compositing/compositor_thread.rs b/components/compositing/compositor_thread.rs index a8d37e6459c9..3f43c2ba2bb1 100644 --- a/components/compositing/compositor_thread.rs +++ b/components/compositing/compositor_thread.rs @@ -141,6 +141,8 @@ pub enum EmbedderMsg { LoadComplete(TopLevelBrowsingContextId), /// A pipeline panicked. First string is the reason, second one is the backtrace. Panic(TopLevelBrowsingContextId, String, Option), + /// Servo has shut down + Shutdown, } /// Messages from the painting thread and the constellation thread to the compositor thread. @@ -239,6 +241,7 @@ impl Debug for EmbedderMsg { EmbedderMsg::LoadStart(..) => write!(f, "LoadStart"), EmbedderMsg::LoadComplete(..) => write!(f, "LoadComplete"), EmbedderMsg::Panic(..) => write!(f, "Panic"), + EmbedderMsg::Shutdown => write!(f, "Shutdown"), } } } diff --git a/components/servo/lib.rs b/components/servo/lib.rs index a54d5dd52dde..0baf5be6f6f3 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -107,7 +107,7 @@ use webvr::{WebVRThread, WebVRCompositorHandler}; pub use gleam::gl; pub use servo_config as config; pub use servo_url as url; -pub use msg::constellation_msg::TopLevelBrowsingContextId as BrowserId; +pub use msg::constellation_msg::{KeyState, TopLevelBrowsingContextId as BrowserId}; /// The in-process interface to Servo. /// @@ -372,6 +372,14 @@ impl Servo where Window: WindowMethods + 'static { (_, ShutdownState::ShuttingDown) => {}, + (EmbedderMsg::KeyEvent(top_level_browsing_context, ch, key, state, modified), + ShutdownState::NotShuttingDown) => { + if state == KeyState::Pressed { + let msg = EmbedderMsg::KeyEvent(top_level_browsing_context, ch, key, state, modified); + self.embedder_events.push(msg); + } + }, + (msg, ShutdownState::NotShuttingDown) => { self.embedder_events.push(msg); }, @@ -383,7 +391,7 @@ impl Servo where Window: WindowMethods + 'static { ::std::mem::replace(&mut self.embedder_events, Vec::new()) } - pub fn handle_events(&mut self, events: Vec) -> bool { + pub fn handle_events(&mut self, events: Vec) { if self.compositor.receive_messages() { self.receive_messages(); } @@ -392,8 +400,9 @@ impl Servo where Window: WindowMethods + 'static { } if self.compositor.shutdown_state != ShutdownState::FinishedShuttingDown { self.compositor.perform_updates(); + } else { + self.embedder_events.push(EmbedderMsg::Shutdown); } - self.compositor.shutdown_state != ShutdownState::FinishedShuttingDown } pub fn repaint_synchronously(&mut self) { diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml index e156fabe37b2..4f42def7c539 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -43,11 +43,9 @@ glutin = "0.13" libservo = {path = "../../components/servo"} log = "0.3.5" msg = {path = "../../components/msg"} -net_traits = {path = "../../components/net_traits"} script_traits = {path = "../../components/script_traits"} servo_geometry = {path = "../../components/geometry"} servo_config = {path = "../../components/config"} -servo_url = {path = "../../components/url"} style_traits = {path = "../../components/style_traits"} tinyfiledialogs = "3.0" webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} diff --git a/ports/servo/browser.rs b/ports/servo/browser.rs new file mode 100644 index 000000000000..25e6952c0c01 --- /dev/null +++ b/ports/servo/browser.rs @@ -0,0 +1,319 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use compositing::compositor_thread::EmbedderMsg; +use compositing::windowing::{WebRenderDebugOption, WindowEvent}; +use euclid::{TypedPoint2D, TypedVector2D}; +use glutin_app::keyutils::{CMD_OR_CONTROL, CMD_OR_ALT}; +use glutin_app::window::{Window, LINE_HEIGHT}; +use msg::constellation_msg::{Key, TopLevelBrowsingContextId as BrowserId}; +use msg::constellation_msg::{KeyModifiers, KeyState, TraversalDirection}; +use script_traits::TouchEventType; +use servo::net_traits::pub_domains::is_reg_domain; +use servo::servo_url::ServoUrl; +use servo_config::prefs::PREFS; +use std::mem; +use std::rc::Rc; +use tinyfiledialogs; +use webrender_api::ScrollLocation; + +pub struct Browser { + current_url: Option, + /// id of the top level browsing context. It is unique as tabs + /// are not supported yet. None until created. + browser_id: Option, + + title: Option, + status: Option, + favicon: Option, + loading_state: Option, + window: Rc, + event_queue: Vec, + shutdown_requested: bool, +} + +enum LoadingState { + Connecting, + Loading, + Loaded, +} + +impl Browser { + pub fn new(window: Rc) -> Browser { + Browser { + title: None, + current_url: None, + browser_id: None, + status: None, + favicon: None, + loading_state: None, + window: window, + event_queue: Vec::new(), + shutdown_requested: false, + } + } + + pub fn get_events(&mut self) -> Vec { + mem::replace(&mut self.event_queue, Vec::new()) + } + + pub fn set_browser_id(&mut self, browser_id: BrowserId) { + self.browser_id = Some(browser_id); + } + + pub fn handle_window_events(&mut self, events: Vec) { + for event in events { + match event { + WindowEvent::KeyEvent(ch, key, state, mods) => { + self.handle_key_from_window(ch, key, state, mods); + }, + event => { + self.event_queue.push(event); + } + } + } + } + + pub fn shutdown_requested(&self) -> bool { + self.shutdown_requested + } + + /// Handle key events before sending them to Servo. + fn handle_key_from_window(&mut self, ch: Option, key: Key, state: KeyState, mods: KeyModifiers) { + match (mods, ch, key) { + (CMD_OR_CONTROL, Some('r'), _) => { + if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { + if let Some(id) = self.browser_id { + self.event_queue.push(WindowEvent::Reload(id)); + } + } + } + (CMD_OR_CONTROL, Some('l'), _) => { + if let Some(id) = self.browser_id { + let url: String = if let Some(ref current_url) = self.current_url { + current_url.to_string() + } else { + String::from("") + }; + let title = "URL or search query"; + if let Some(input) = tinyfiledialogs::input_box(title, title, &url) { + if let Some(url) = sanitize_url(&input) { + self.event_queue.push(WindowEvent::LoadUrl(id, url)); + } + } + } + } + (CMD_OR_CONTROL, Some('q'), _) => { + if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { + self.event_queue.push(WindowEvent::Quit); + } + } + (_, Some('3'), _) => if mods ^ KeyModifiers::CONTROL == KeyModifiers::SHIFT { + self.event_queue.push(WindowEvent::CaptureWebRender); + } + (KeyModifiers::CONTROL, None, Key::F10) => { + let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::RenderTargetDebug); + self.event_queue.push(event); + } + (KeyModifiers::CONTROL, None, Key::F11) => { + let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::TextureCacheDebug); + self.event_queue.push(event); + } + (KeyModifiers::CONTROL, None, Key::F12) => { + let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::Profiler); + self.event_queue.push(event); + } + (CMD_OR_ALT, None, Key::Right) | (KeyModifiers::NONE, None, Key::NavigateForward) => { + if let Some(id) = self.browser_id { + let event = WindowEvent::Navigation(id, TraversalDirection::Forward(1)); + self.event_queue.push(event); + } + } + (CMD_OR_ALT, None, Key::Left) | (KeyModifiers::NONE, None, Key::NavigateBackward) => { + if let Some(id) = self.browser_id { + let event = WindowEvent::Navigation(id, TraversalDirection::Back(1)); + self.event_queue.push(event); + } + } + (KeyModifiers::NONE, None, Key::Escape) => { + if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { + self.event_queue.push(WindowEvent::Quit); + } + } + _ => { + let event = self.platform_handle_key(key, mods); + self.event_queue.push(event.unwrap_or(WindowEvent::KeyEvent(ch, key, state, mods))); + } + } + + } + + #[cfg(not(target_os = "win"))] + fn platform_handle_key(&self, key: Key, mods: KeyModifiers) -> Option { + match (mods, key, self.browser_id) { + (CMD_OR_CONTROL, Key::LeftBracket, Some(id)) => { + Some(WindowEvent::Navigation(id, TraversalDirection::Back(1))) + } + (CMD_OR_CONTROL, Key::RightBracket, Some(id)) => { + Some(WindowEvent::Navigation(id, TraversalDirection::Forward(1))) + } + _ => None + } + } + + #[cfg(target_os = "win")] + fn platform_handle_key(&self, key: Key, mods: KeyModifiers) -> Option { + None + } + + /// Handle key events after they have been handled by Servo. + fn handle_key_from_servo(&mut self, _: Option, ch: Option, + key: Key, _: KeyState, mods: KeyModifiers) { + match (mods, ch, key) { + (_, Some('+'), _) => { + if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL { + self.event_queue.push(WindowEvent::Zoom(1.1)); + } else if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL | KeyModifiers::ALT { + self.event_queue.push(WindowEvent::PinchZoom(1.1)); + } + } + (CMD_OR_CONTROL, Some('-'), _) => { + self.event_queue.push(WindowEvent::Zoom(1.0 / 1.1)); + } + (_, Some('-'), _) if mods == CMD_OR_CONTROL | KeyModifiers::ALT => { + self.event_queue.push(WindowEvent::PinchZoom(1.0 / 1.1)); + } + (CMD_OR_CONTROL, Some('0'), _) => { + self.event_queue.push(WindowEvent::ResetZoom); + } + + (KeyModifiers::NONE, None, Key::PageDown) => { + let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0, + -self.window.page_height() + 2.0 * LINE_HEIGHT)); + self.scroll_window_from_key(scroll_location, TouchEventType::Move); + } + (KeyModifiers::NONE, None, Key::PageUp) => { + let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0, + self.window.page_height() - 2.0 * LINE_HEIGHT)); + self.scroll_window_from_key(scroll_location, TouchEventType::Move); + } + + (KeyModifiers::NONE, None, Key::Home) => { + self.scroll_window_from_key(ScrollLocation::Start, TouchEventType::Move); + } + + (KeyModifiers::NONE, None, Key::End) => { + self.scroll_window_from_key(ScrollLocation::End, TouchEventType::Move); + } + + (KeyModifiers::NONE, None, Key::Up) => { + self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(0.0, 3.0 * LINE_HEIGHT)), + TouchEventType::Move); + } + (KeyModifiers::NONE, None, Key::Down) => { + self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(0.0, -3.0 * LINE_HEIGHT)), + TouchEventType::Move); + } + (KeyModifiers::NONE, None, Key::Left) => { + self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(LINE_HEIGHT, 0.0)), + TouchEventType::Move); + } + (KeyModifiers::NONE, None, Key::Right) => { + self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(-LINE_HEIGHT, 0.0)), + TouchEventType::Move); + } + + _ => { + } + } + } + + fn scroll_window_from_key(&mut self, scroll_location: ScrollLocation, phase: TouchEventType) { + let event = WindowEvent::Scroll(scroll_location, TypedPoint2D::zero(), phase); + self.event_queue.push(event); + } + + pub fn handle_servo_events(&mut self, events: Vec) { + for event in events { + match event { + EmbedderMsg::Status(_browser_id, status) => { + self.status = status; + }, + EmbedderMsg::ChangePageTitle(_browser_id, title) => { + self.title = title; + + let fallback_title: String = if let Some(ref current_url) = self.current_url { + current_url.to_string() + } else { + String::from("Untitled") + }; + let title = match self.title { + Some(ref title) if title.len() > 0 => &**title, + _ => &fallback_title, + }; + let title = format!("{} - Servo", title); + self.window.set_title(&title); + } + EmbedderMsg::MoveTo(_browser_id, point) => { + self.window.set_position(point); + } + EmbedderMsg::ResizeTo(_browser_id, size) => { + self.window.set_inner_size(size); + } + EmbedderMsg::AllowNavigation(_browser_id, _url, response_chan) => { + if let Err(e) = response_chan.send(true) { + warn!("Failed to send allow_navigation() response: {}", e); + }; + } + EmbedderMsg::KeyEvent(browser_id, ch, key, state, modified) => { + self.handle_key_from_servo(browser_id, ch, key, state, modified); + } + EmbedderMsg::SetCursor(cursor) => { + self.window.set_cursor(cursor); + } + EmbedderMsg::NewFavicon(_browser_id, url) => { + self.favicon = Some(url); + } + EmbedderMsg::HeadParsed(_browser_id, ) => { + self.loading_state = Some(LoadingState::Loading); + } + EmbedderMsg::HistoryChanged(_browser_id, entries, current) => { + self.current_url = Some(entries[current].url.clone()); + } + EmbedderMsg::SetFullscreenState(_browser_id, state) => { + self.window.set_fullscreen(state); + } + EmbedderMsg::LoadStart(_browser_id) => { + self.loading_state = Some(LoadingState::Connecting); + } + EmbedderMsg::LoadComplete(_browser_id) => { + self.loading_state = Some(LoadingState::Loaded); + } + EmbedderMsg::Shutdown => { + self.shutdown_requested = true; + }, + EmbedderMsg::Panic(_browser_id, _reason, _backtrace) => { + } + } + } + } + +} + +fn sanitize_url(request: &str) -> Option { + let request = request.trim(); + ServoUrl::parse(&request).ok() + .or_else(|| { + if request.contains('/') || is_reg_domain(request) { + ServoUrl::parse(&format!("http://{}", request)).ok() + } else { + None + } + }).or_else(|| { + PREFS.get("shell.searchpage").as_string().and_then(|s: &str| { + let url = s.replace("%s", request); + ServoUrl::parse(&url).ok() + }) + }) +} diff --git a/ports/servo/glutin_app/keyutils.rs b/ports/servo/glutin_app/keyutils.rs index 228491a4e5a6..bce62893cde7 100644 --- a/ports/servo/glutin_app/keyutils.rs +++ b/ports/servo/glutin_app/keyutils.rs @@ -332,7 +332,7 @@ pub fn is_printable(key_code: VirtualKeyCode) -> bool { /// Detect if given char is default ignorable in unicode /// http://www.unicode.org/L2/L2002/02368-default-ignorable.pdf -pub fn is_identifier_ignorable(&self, ch: &char) -> bool { +pub fn is_identifier_ignorable(ch: &char) -> bool { match *ch { '\u{0000}'...'\u{0008}' | '\u{000E}'...'\u{001F}' | '\u{007F}'...'\u{0084}' | '\u{0086}'...'\u{009F}' | diff --git a/ports/servo/glutin_app/mod.rs b/ports/servo/glutin_app/mod.rs index 9f1107228bc1..a7e8ac8f5e8c 100644 --- a/ports/servo/glutin_app/mod.rs +++ b/ports/servo/glutin_app/mod.rs @@ -4,7 +4,7 @@ //! A simple application that uses glutin to open a window for Servo to display in. -mod keyutils; +pub mod keyutils; pub mod window; use servo_config::opts; diff --git a/ports/servo/glutin_app/window.rs b/ports/servo/glutin_app/window.rs index 82020e26bbe4..7fb77d27273b 100644 --- a/ports/servo/glutin_app/window.rs +++ b/ports/servo/glutin_app/window.rs @@ -4,26 +4,20 @@ //! A windowing implementation using glutin. -use compositing::compositor_thread::{EmbedderMsg, EventLoopWaker}; +use compositing::compositor_thread::EventLoopWaker; use compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent}; -use compositing::windowing::{EmbedderCoordinates, WebRenderDebugOption, WindowMethods}; +use compositing::windowing::{EmbedderCoordinates, WindowMethods}; use euclid::{Length, TypedPoint2D, TypedVector2D, TypedScale, TypedSize2D}; #[cfg(target_os = "windows")] use gdi32; use gleam::gl; -use glutin; -use glutin::{Api, GlContext, GlRequest}; -use msg::constellation_msg::{self, Key, TopLevelBrowsingContextId as BrowserId}; -use msg::constellation_msg::{KeyModifiers, KeyState, TraversalDirection}; -use net_traits::pub_domains::is_reg_domain; +use glutin::{self, Api, GlContext, GlRequest}; +use msg::constellation_msg::{Key, KeyState}; #[cfg(any(target_os = "linux", target_os = "macos"))] use osmesa_sys; -use script_traits::{LoadData, TouchEventType}; -use servo::ipc_channel::ipc::IpcSender; +use script_traits::TouchEventType; use servo_config::opts; -use servo_config::prefs::PREFS; use servo_geometry::DeviceIndependentPixel; -use servo_url::ServoUrl; use std::cell::{Cell, RefCell}; #[cfg(any(target_os = "linux", target_os = "macos"))] use std::ffi::CString; @@ -36,8 +30,7 @@ use std::thread; use std::time; use style_traits::DevicePixel; use style_traits::cursor::CursorKind; -use super::keyutils::{self, CMD_OR_ALT, CMD_OR_CONTROL, GlutinKeyModifiers}; -use tinyfiledialogs; +use super::keyutils::{self, GlutinKeyModifiers}; #[cfg(target_os = "windows")] use user32; use webrender_api::{DeviceIntPoint, DeviceUintRect, DeviceUintSize, ScrollLocation}; @@ -50,7 +43,7 @@ use winit::os::macos::{ActivationPolicy, WindowBuilderExt}; // This should vary by zoom level and maybe actual text size (focused or under cursor) -const LINE_HEIGHT: f32 = 38.0; +pub const LINE_HEIGHT: f32 = 38.0; const MULTISAMPLES: u16 = 16; @@ -154,25 +147,14 @@ pub struct Window { kind: WindowKind, screen_size: TypedSize2D, inner_size: Cell>, - mouse_down_button: Cell>, mouse_down_point: Cell>, event_queue: RefCell>, - - /// id of the top level browsing context. It is unique as tabs - /// are not supported yet. None until created. - browser_id: Cell>, - mouse_pos: Cell>, key_modifiers: Cell, - current_url: RefCell>, - - last_pressed_key: Cell>, - + last_pressed_key: Cell>, animation_state: Cell, - fullscreen: Cell, - gl: Rc, suspended: Cell, } @@ -191,10 +173,6 @@ fn window_creation_scale_factor() -> TypedScale) -> Rc { let win_size: DeviceUintSize = (window_size.to_f32() * window_creation_scale_factor()).to_u32(); @@ -291,11 +269,8 @@ impl Window { mouse_down_button: Cell::new(None), mouse_down_point: Cell::new(TypedPoint2D::new(0, 0)), - browser_id: Cell::new(None), - mouse_pos: Cell::new(TypedPoint2D::new(0, 0)), key_modifiers: Cell::new(GlutinKeyModifiers::empty()), - current_url: RefCell::new(None), last_pressed_key: Cell::new(None), gl: gl.clone(), @@ -311,6 +286,107 @@ impl Window { Rc::new(window) } + pub fn get_events(&self) -> Vec { + mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()) + } + + pub fn page_height(&self) -> f32 { + let dpr = self.hidpi_factor(); + match self.kind { + WindowKind::Window(ref window, _) => { + let (_, height) = window.get_inner_size().expect("Failed to get window inner size."); + height as f32 * dpr.get() + }, + WindowKind::Headless(ref context) => { + context.height as f32 * dpr.get() + } + } + } + + pub fn set_title(&self, title: &str) { + if let WindowKind::Window(ref window, _) = self.kind { + window.set_title(title); + } + } + + pub fn set_inner_size(&self, size: DeviceUintSize) { + if let WindowKind::Window(ref window, _) = self.kind { + let size = size.to_f32() / self.hidpi_factor(); + window.set_inner_size(size.width as u32, size.height as u32) + } + } + + pub fn set_position(&self, point: DeviceIntPoint) { + if let WindowKind::Window(ref window, _) = self.kind { + let point = point.to_f32() / self.hidpi_factor(); + window.set_position(point.x as i32, point.y as i32) + } + } + + pub fn set_fullscreen(&self, state: bool) { + match self.kind { + WindowKind::Window(ref window, ..) => { + if self.fullscreen.get() != state { + window.set_fullscreen(None); + } + }, + WindowKind::Headless(..) => {} + } + self.fullscreen.set(state); + } + + fn is_animating(&self) -> bool { + self.animation_state.get() == AnimationState::Animating && !self.suspended.get() + } + + pub fn run(&self, mut servo_callback: T) where T: FnMut() -> bool { + match self.kind { + WindowKind::Window(_, ref events_loop) => { + let mut stop = false; + loop { + if self.is_animating() { + // We block on compositing (servo_callback ends up calling swap_buffers) + events_loop.borrow_mut().poll_events(|e| { + self.glutin_event_to_servo_event(e); + }); + stop = servo_callback(); + } else { + // We block on glutin's event loop (window events) + events_loop.borrow_mut().run_forever(|e| { + self.glutin_event_to_servo_event(e); + if !self.event_queue.borrow().is_empty() { + if !self.suspended.get() { + stop = servo_callback(); + } + } + if stop || self.is_animating() { + winit::ControlFlow::Break + } else { + winit::ControlFlow::Continue + } + }); + } + if stop { + break; + } + } + } + WindowKind::Headless(..) => { + loop { + // Sleep the main thread to avoid using 100% CPU + // This can be done better, see comments in #18777 + if self.event_queue.borrow().is_empty() { + thread::sleep(time::Duration::from_millis(5)); + } + let stop = servo_callback(); + if stop { + break; + } + } + } + } + } + #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] fn gl_version() -> GlRequest { return GlRequest::Specific(Api::OpenGl, (3, 2)); @@ -379,7 +455,7 @@ impl Window { } } - fn handle_window_event(&self, event: winit::Event) { + fn glutin_event_to_servo_event(&self, event: winit::Event) { match event { Event::WindowEvent { event: winit::WindowEvent::ReceivedCharacter(ch), @@ -416,13 +492,22 @@ impl Window { event: winit::WindowEvent::MouseWheel { delta, phase, .. }, .. } => { - let (dx, dy) = match delta { + let (mut dx, mut dy) = match delta { MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT), MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy), }; + // Scroll events snap to the major axis of movement, with vertical + // preferred over horizontal. + if dy.abs() >= dx.abs() { + dx = 0.0; + } else { + dy = 0.0; + } + let scroll_location = ScrollLocation::Delta(TypedVector2D::new(dx, dy)); let phase = glutin_phase_to_touch_event_type(phase); - self.scroll_window(scroll_location, phase); + let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase); + self.event_queue.borrow_mut().push(event); }, Event::WindowEvent { event: winit::WindowEvent::Touch(touch), @@ -481,22 +566,6 @@ impl Window { self.key_modifiers.set(modifiers); } - /// Helper function to send a scroll event. - fn scroll_window(&self, mut scroll_location: ScrollLocation, phase: TouchEventType) { - // Scroll events snap to the major axis of movement, with vertical - // preferred over horizontal. - if let ScrollLocation::Delta(ref mut delta) = scroll_location { - if delta.y.abs() >= delta.x.abs() { - delta.x = 0.0; - } else { - delta.y = 0.0; - } - } - - let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase); - self.event_queue.borrow_mut().push(event); - } - /// Helper function to handle a click fn handle_mouse(&self, button: winit::MouseButton, action: winit::ElementState, @@ -532,115 +601,6 @@ impl Window { self.event_queue.borrow_mut().push(WindowEvent::MouseWindowEventClass(event)); } - pub fn get_events(&self) -> Vec { - mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()) - } - - pub fn handle_servo_events(&self, events: Vec) { - for event in events { - match event { - EmbedderMsg::Status(top_level_browsing_context, message) => self.status(top_level_browsing_context, message), - EmbedderMsg::ChangePageTitle(top_level_browsing_context, title) => self.set_page_title(top_level_browsing_context, title), - EmbedderMsg::MoveTo(top_level_browsing_context, point) => self.set_position(top_level_browsing_context, point), - EmbedderMsg::ResizeTo(top_level_browsing_context, size) => self.set_inner_size(top_level_browsing_context, size), - EmbedderMsg::AllowNavigation(top_level_browsing_context, url, response_chan) => self.allow_navigation(top_level_browsing_context, url, response_chan), - EmbedderMsg::KeyEvent(top_level_browsing_context, ch, key, state, modified) => self.handle_key(top_level_browsing_context, ch, key, state, modified), - EmbedderMsg::SetCursor(cursor) => self.set_cursor(cursor), - EmbedderMsg::NewFavicon(top_level_browsing_context, url) => self.set_favicon(top_level_browsing_context, url), - EmbedderMsg::HeadParsed(top_level_browsing_context, ) => self.head_parsed(top_level_browsing_context, ), - EmbedderMsg::HistoryChanged(top_level_browsing_context, entries, current) => self.history_changed(top_level_browsing_context, entries, current), - EmbedderMsg::SetFullscreenState(top_level_browsing_context, state) => self.set_fullscreen_state(top_level_browsing_context, state), - EmbedderMsg::LoadStart(top_level_browsing_context) => self.load_start(top_level_browsing_context), - EmbedderMsg::LoadComplete(top_level_browsing_context) => self.load_end(top_level_browsing_context), - EmbedderMsg::Panic(top_level_browsing_context, reason, backtrace) => self.handle_panic(top_level_browsing_context, reason, backtrace), - } - } - } - - fn is_animating(&self) -> bool { - self.animation_state.get() == AnimationState::Animating && !self.suspended.get() - } - - pub fn run(&self, mut servo_callback: T) where T: FnMut() -> bool { - match self.kind { - WindowKind::Window(_, ref events_loop) => { - let mut stop = false; - loop { - if self.is_animating() { - // We block on compositing (servo_callback ends up calling swap_buffers) - events_loop.borrow_mut().poll_events(|e| { - self.handle_window_event(e); - }); - stop = servo_callback(); - } else { - // We block on glutin's event loop (window events) - events_loop.borrow_mut().run_forever(|e| { - self.handle_window_event(e); - if !self.event_queue.borrow().is_empty() { - if !self.suspended.get() { - stop = servo_callback(); - } - } - if stop || self.is_animating() { - winit::ControlFlow::Break - } else { - winit::ControlFlow::Continue - } - }); - } - if stop { - break; - } - } - } - WindowKind::Headless(..) => { - loop { - // Sleep the main thread to avoid using 100% CPU - // This can be done better, see comments in #18777 - if self.event_queue.borrow().is_empty() { - thread::sleep(time::Duration::from_millis(5)); - } - let stop = servo_callback(); - if stop { - break; - } - } - } - } - } - - #[cfg(not(target_os = "win"))] - fn platform_handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers, browser_id: BrowserId) { - match (mods, key) { - (CMD_OR_CONTROL, Key::LeftBracket) => { - let event = WindowEvent::Navigation(browser_id, TraversalDirection::Back(1)); - self.event_queue.borrow_mut().push(event); - } - (CMD_OR_CONTROL, Key::RightBracket) => { - let event = WindowEvent::Navigation(browser_id, TraversalDirection::Forward(1)); - self.event_queue.borrow_mut().push(event); - } - _ => {} - } - } - - #[cfg(target_os = "win")] - fn platform_handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers, browser_id: BrowserId) { - } - - fn page_height(&self) -> f32 { - let dpr = self.hidpi_factor(); - match self.kind { - WindowKind::Window(ref window, _) => { - let (_, height) = window.get_inner_size().expect("Failed to get window inner size."); - height as f32 * dpr.get() - }, - WindowKind::Headless(ref context) => { - context.height as f32 * dpr.get() - } - } - } - #[cfg(not(target_os = "windows"))] fn hidpi_factor(&self) -> TypedScale { match self.kind { @@ -660,84 +620,8 @@ impl Window { TypedScale::new(ppi as f32 / 96.0) } - fn set_inner_size(&self, _: BrowserId, size: DeviceUintSize) { - match self.kind { - WindowKind::Window(ref window, ..) => { - let size = size.to_f32() / self.hidpi_factor(); - window.set_inner_size(size.width as u32, size.height as u32) - } - WindowKind::Headless(..) => {} - } - } - - fn set_position(&self, _: BrowserId, point: DeviceIntPoint) { - match self.kind { - WindowKind::Window(ref window, ..) => { - let point = point.to_f32() / self.hidpi_factor(); - window.set_position(point.x as i32, point.y as i32) - } - WindowKind::Headless(..) => {} - } - } - - fn set_fullscreen_state(&self, _: BrowserId, state: bool) { - match self.kind { - WindowKind::Window(ref window, ..) => { - if self.fullscreen.get() != state { - window.set_fullscreen(None); - } - }, - WindowKind::Headless(..) => {} - } - self.fullscreen.set(state); - } - - fn set_page_title(&self, _: BrowserId, title: Option) { - match self.kind { - WindowKind::Window(ref window, ..) => { - let fallback_title: String = if let Some(ref current_url) = *self.current_url.borrow() { - current_url.to_string() - } else { - String::from("Untitled") - }; - - let title = match title { - Some(ref title) if title.len() > 0 => &**title, - _ => &fallback_title, - }; - let title = format!("{} - Servo", title); - window.set_title(&title); - } - WindowKind::Headless(..) => {} - } - } - - fn status(&self, _: BrowserId, _: Option) { - } - - fn load_start(&self, _: BrowserId) { - } - - fn load_end(&self, _: BrowserId) { - if opts::get().no_native_titlebar { - match self.kind { - WindowKind::Window(ref window, ..) => { - window.show(); - } - WindowKind::Headless(..) => {} - } - } - } - - fn history_changed(&self, _: BrowserId, history: Vec, current: usize) { - *self.current_url.borrow_mut() = Some(history[current].url.clone()); - } - - fn head_parsed(&self, _: BrowserId) { - } - /// Has no effect on Android. - fn set_cursor(&self, cursor: CursorKind) { + pub fn set_cursor(&self, cursor: CursorKind) { match self.kind { WindowKind::Window(ref window, ..) => { use winit::MouseCursor; @@ -785,152 +669,6 @@ impl Window { WindowKind::Headless(..) => {} } } - - fn set_favicon(&self, _: BrowserId, _: ServoUrl) { - } - - /// Helper function to handle keyboard events. - fn handle_key(&self, _: Option, ch: Option, key: Key, state: KeyState, mods: constellation_msg::KeyModifiers) { - if state == KeyState::Pressed { - return; - } - let browser_id = match self.browser_id.get() { - Some(id) => id, - None => { unreachable!("Can't get keys without a browser"); } - }; - match (mods, ch, key) { - (_, Some('+'), _) => { - if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL { - self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.1)); - } else if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL | KeyModifiers::ALT { - self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.1)); - } - } - (CMD_OR_CONTROL, Some('-'), _) => { - self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.0 / 1.1)); - } - (_, Some('-'), _) if mods == CMD_OR_CONTROL | KeyModifiers::ALT => { - self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.0 / 1.1)); - } - (CMD_OR_CONTROL, Some('0'), _) => { - self.event_queue.borrow_mut().push(WindowEvent::ResetZoom); - } - - (KeyModifiers::NONE, None, Key::NavigateForward) => { - let event = WindowEvent::Navigation(browser_id, TraversalDirection::Forward(1)); - self.event_queue.borrow_mut().push(event); - } - (KeyModifiers::NONE, None, Key::NavigateBackward) => { - let event = WindowEvent::Navigation(browser_id, TraversalDirection::Back(1)); - self.event_queue.borrow_mut().push(event); - } - - (KeyModifiers::NONE, None, Key::Escape) => { - if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { - self.event_queue.borrow_mut().push(WindowEvent::Quit); - } - } - - (CMD_OR_ALT, None, Key::Right) => { - let event = WindowEvent::Navigation(browser_id, TraversalDirection::Forward(1)); - self.event_queue.borrow_mut().push(event); - } - (CMD_OR_ALT, None, Key::Left) => { - let event = WindowEvent::Navigation(browser_id, TraversalDirection::Back(1)); - self.event_queue.borrow_mut().push(event); - } - - (KeyModifiers::NONE, None, Key::PageDown) => { - let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0, - -self.page_height() + 2.0 * LINE_HEIGHT)); - self.scroll_window(scroll_location, - TouchEventType::Move); - } - (KeyModifiers::NONE, None, Key::PageUp) => { - let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0, - self.page_height() - 2.0 * LINE_HEIGHT)); - self.scroll_window(scroll_location, - TouchEventType::Move); - } - - (KeyModifiers::NONE, None, Key::Home) => { - self.scroll_window(ScrollLocation::Start, TouchEventType::Move); - } - - (KeyModifiers::NONE, None, Key::End) => { - self.scroll_window(ScrollLocation::End, TouchEventType::Move); - } - - (KeyModifiers::NONE, None, Key::Up) => { - self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(0.0, 3.0 * LINE_HEIGHT)), - TouchEventType::Move); - } - (KeyModifiers::NONE, None, Key::Down) => { - self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(0.0, -3.0 * LINE_HEIGHT)), - TouchEventType::Move); - } - (KeyModifiers::NONE, None, Key::Left) => { - self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(LINE_HEIGHT, 0.0)), TouchEventType::Move); - } - (KeyModifiers::NONE, None, Key::Right) => { - self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(-LINE_HEIGHT, 0.0)), TouchEventType::Move); - } - (CMD_OR_CONTROL, Some('r'), _) => { - if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { - self.event_queue.borrow_mut().push(WindowEvent::Reload(browser_id)); - } - } - (CMD_OR_CONTROL, Some('l'), _) => { - if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { - let url: String = if let Some(ref url) = *self.current_url.borrow() { - url.to_string() - } else { - String::from("") - }; - let title = "URL or search query"; - if let Some(input) = tinyfiledialogs::input_box(title, title, &url) { - if let Some(url) = sanitize_url(&input) { - self.event_queue.borrow_mut().push(WindowEvent::LoadUrl(browser_id, url)); - } - } - } - } - (CMD_OR_CONTROL, Some('q'), _) => { - if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() { - self.event_queue.borrow_mut().push(WindowEvent::Quit); - } - } - (_, Some('3'), _) => if mods ^ KeyModifiers::CONTROL == KeyModifiers::SHIFT { - self.event_queue.borrow_mut().push(WindowEvent::CaptureWebRender); - } - (KeyModifiers::CONTROL, None, Key::F10) => { - let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::RenderTargetDebug); - self.event_queue.borrow_mut().push(event); - } - (KeyModifiers::CONTROL, None, Key::F11) => { - let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::TextureCacheDebug); - self.event_queue.borrow_mut().push(event); - } - (KeyModifiers::CONTROL, None, Key::F12) => { - let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::Profiler); - self.event_queue.borrow_mut().push(event); - } - - _ => { - self.platform_handle_key(key, mods, browser_id); - } - } - } - - fn allow_navigation(&self, _: BrowserId, _: ServoUrl, response_chan: IpcSender) { - if let Err(e) = response_chan.send(true) { - warn!("Failed to send allow_navigation() response: {}", e); - }; - } - - fn handle_panic(&self, _: BrowserId, _reason: String, _backtrace: Option) { - // Nothing to do here yet. The crash has already been reported on the console. - } } impl WindowMethods for Window { @@ -1046,20 +784,3 @@ fn glutin_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType { TouchPhase::Cancelled => TouchEventType::Cancel, } } - -fn sanitize_url(request: &str) -> Option { - let request = request.trim(); - ServoUrl::parse(&request).ok() - .or_else(|| { - if request.contains('/') || is_reg_domain(request) { - ServoUrl::parse(&format!("http://{}", request)).ok() - } else { - None - } - }).or_else(|| { - PREFS.get("shell.searchpage").as_string().and_then(|s: &str| { - let url = s.replace("%s", request); - ServoUrl::parse(&url).ok() - }) - }) -} diff --git a/ports/servo/main.rs b/ports/servo/main.rs index d440bf53bcf8..8fa036a6658f 100644 --- a/ports/servo/main.rs +++ b/ports/servo/main.rs @@ -29,13 +29,11 @@ extern crate glutin; // The window backed by glutin #[macro_use] extern crate log; extern crate msg; -extern crate net_traits; #[cfg(any(target_os = "linux", target_os = "macos"))] extern crate osmesa_sys; extern crate script_traits; extern crate servo; extern crate servo_config; extern crate servo_geometry; -extern crate servo_url; #[cfg(all(feature = "unstable", not(target_os = "android")))] #[macro_use] extern crate sig; @@ -63,6 +61,8 @@ use std::panic; use std::process; use std::thread; +mod browser; + pub mod platform { #[cfg(target_os = "macos")] pub use platform::macos::deinit; @@ -163,6 +163,8 @@ fn main() { let window = glutin_app::create_window(); + let mut browser = browser::Browser::new(window.clone()); + // If the url is not provided, we fallback to the homepage in PREFS, // or a blank page in case the homepage is not set either. let cwd = env::current_dir().unwrap(); @@ -178,23 +180,34 @@ fn main() { let (sender, receiver) = ipc::channel().unwrap(); servo.handle_events(vec![WindowEvent::NewBrowser(target_url, sender)]); let browser_id = receiver.recv().unwrap(); - window.set_browser_id(browser_id); + browser.set_browser_id(browser_id); servo.handle_events(vec![WindowEvent::SelectBrowser(browser_id)]); servo.setup_logging(); window.run(|| { let win_events = window.get_events(); + let servo_events = servo.get_events(); + + // FIXME: this could be handled by Servo. We don't need + // a repaint_synchronously function exposed. let need_resize = win_events.iter().any(|e| match *e { WindowEvent::Resize => true, - _ => false + _ => false, }); - let stop = !servo.handle_events(win_events); + + browser.handle_servo_events(servo_events); + browser.handle_window_events(win_events); + + if browser.shutdown_requested() { + return true; + } + + servo.handle_events(browser.get_events()); if need_resize { servo.repaint_synchronously(); } - window.handle_servo_events(servo.get_events()); - stop + false }); servo.deinit();