diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | components/compositing/compositor.rs | 4 | ||||
-rw-r--r-- | components/compositing/windowing.rs | 7 | ||||
-rw-r--r-- | components/servo/lib.rs | 14 | ||||
-rwxr-xr-x | etc/ci/check_no_panic.sh | 5 | ||||
-rw-r--r-- | ports/glutin/Cargo.toml (renamed from ports/servo/Cargo.toml) | 0 | ||||
-rw-r--r-- | ports/glutin/app.rs | 195 | ||||
-rw-r--r-- | ports/glutin/browser.rs (renamed from ports/servo/browser.rs) | 27 | ||||
-rw-r--r-- | ports/glutin/build.rs (renamed from ports/servo/build.rs) | 0 | ||||
-rw-r--r-- | ports/glutin/embedder.rs | 71 | ||||
-rw-r--r-- | ports/glutin/events_loop.rs | 98 | ||||
-rw-r--r-- | ports/glutin/headed_window.rs | 533 | ||||
-rw-r--r-- | ports/glutin/headless_window.rs | 200 | ||||
-rw-r--r-- | ports/glutin/keyutils.rs (renamed from ports/servo/glutin_app/keyutils.rs) | 2 | ||||
-rw-r--r-- | ports/glutin/main.rs (renamed from ports/servo/main.rs) | 8 | ||||
-rw-r--r-- | ports/glutin/main2.rs (renamed from ports/servo/non_android_main.rs) | 141 | ||||
-rw-r--r-- | ports/glutin/platform/macos/Info.plist (renamed from ports/servo/platform/macos/Info.plist) | 0 | ||||
-rw-r--r-- | ports/glutin/platform/macos/count_threads.c (renamed from ports/servo/platform/macos/count_threads.c) | 0 | ||||
-rw-r--r-- | ports/glutin/platform/macos/mod.rs (renamed from ports/servo/platform/macos/mod.rs) | 15 | ||||
-rw-r--r-- | ports/glutin/platform/windows/servo.exe.manifest (renamed from ports/servo/platform/windows/servo.exe.manifest) | 0 | ||||
-rw-r--r-- | ports/glutin/resources.rs (renamed from ports/servo/resources.rs) | 0 | ||||
-rw-r--r-- | ports/glutin/skia_symbols.rs | 55 | ||||
-rw-r--r-- | ports/glutin/window_trait.rs | 29 | ||||
-rw-r--r-- | ports/libsimpleservo/api/src/lib.rs | 58 | ||||
-rw-r--r-- | ports/servo/glutin_app/mod.rs | 20 | ||||
-rw-r--r-- | ports/servo/glutin_app/window.rs | 854 | ||||
-rw-r--r-- | python/servo/build_commands.py | 2 | ||||
-rw-r--r-- | python/servo/command_base.py | 10 | ||||
-rw-r--r-- | python/servo/post_build_commands.py | 2 | ||||
-rw-r--r-- | python/servo/testing_commands.py | 2 |
30 files changed, 1287 insertions, 1067 deletions
diff --git a/Cargo.toml b/Cargo.toml index 0cfd8e31053..fd6954186b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "ports/servo", + "ports/glutin", "ports/libsimpleservo/capi/", "ports/libsimpleservo/jniapi/", "ports/libmlservo/", diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 6cbf1d35160..2b1322bfc63 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -103,7 +103,7 @@ impl FrameTreeId { enum LayerPixel {} /// NB: Never block on the constellation, because sometimes the constellation blocks on us. -pub struct IOCompositor<Window: WindowMethods> { +pub struct IOCompositor<Window: WindowMethods + ?Sized> { /// The application window. pub window: Rc<Window>, @@ -258,7 +258,7 @@ enum CompositeTarget { PngFile, } -impl<Window: WindowMethods> IOCompositor<Window> { +impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { fn new(window: Rc<Window>, state: InitialCompositorState) -> Self { let composite_target = match opts::get().output_file { Some(_) => CompositeTarget::PngFile, diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 6105531bf85..fa9f8c481fb 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -148,8 +148,6 @@ pub trait WindowMethods { /// Return the GL function pointer trait. #[cfg(feature = "gl")] fn gl(&self) -> Rc<dyn gl::Gl>; - /// Returns a thread-safe object to wake up the window's event loop. - fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker>; /// Get the coordinates of the native window, the screen and the framebuffer. fn get_coordinates(&self) -> EmbedderCoordinates; /// Set whether the application is currently animating. @@ -157,6 +155,11 @@ pub trait WindowMethods { /// will want to avoid blocking on UI events, and just /// run the event loop at the vsync interval. fn set_animation_state(&self, _state: AnimationState); +} + +pub trait EmbedderMethods { + /// Returns a thread-safe object to wake up the window's event loop. + fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker>; /// Register services with a VRServiceManager. fn register_vr_services( &self, diff --git a/components/servo/lib.rs b/components/servo/lib.rs index d919613c9c0..d3d8d8eb1cb 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -67,7 +67,7 @@ use canvas::webgl_thread::WebGLThreads; use compositing::compositor_thread::{ CompositorProxy, CompositorReceiver, InitialCompositorState, Msg, }; -use compositing::windowing::{WindowEvent, WindowMethods}; +use compositing::windowing::{EmbedderMethods, WindowEvent, WindowMethods}; use compositing::{CompositingReason, IOCompositor, ShutdownState}; #[cfg(all( not(target_os = "windows"), @@ -148,7 +148,7 @@ type MediaBackend = media_platform::MediaBackend; /// application Servo is embedded in. Clients then create an event /// loop to pump messages between the embedding application and /// various browser components. -pub struct Servo<Window: WindowMethods + 'static> { +pub struct Servo<Window: WindowMethods + 'static + ?Sized> { compositor: IOCompositor<Window>, constellation_chan: Sender<ConstellationMsg>, embedder_receiver: EmbedderReceiver, @@ -197,9 +197,9 @@ impl webrender_api::RenderNotifier for RenderNotifier { impl<Window> Servo<Window> where - Window: WindowMethods + 'static, + Window: WindowMethods + 'static + ?Sized, { - pub fn new(window: Rc<Window>) -> Servo<Window> { + pub fn new(embedder: Box<EmbedderMethods>, window: Rc<Window>) -> Servo<Window> { // Global configuration options, parsed from the command line. let opts = opts::get(); @@ -218,9 +218,9 @@ where // messages to client may need to pump a platform-specific event loop // to deliver the message. let (compositor_proxy, compositor_receiver) = - create_compositor_channel(window.create_event_loop_waker()); + create_compositor_channel(embedder.create_event_loop_waker()); let (embedder_proxy, embedder_receiver) = - create_embedder_channel(window.create_event_loop_waker()); + create_embedder_channel(embedder.create_event_loop_waker()); let time_profiler_chan = profile_time::Profiler::create( &opts.time_profiling, opts.time_profiler_trace_path.clone(), @@ -288,7 +288,7 @@ where let webvr_services = if pref!(dom.webvr.enabled) { let mut services = VRServiceManager::new(); services.register_defaults(); - window.register_vr_services(&mut services, &mut webvr_heartbeats); + embedder.register_vr_services(&mut services, &mut webvr_heartbeats); Some(services) } else { None diff --git a/etc/ci/check_no_panic.sh b/etc/ci/check_no_panic.sh index 17343b7fbd3..be59f5ddef1 100755 --- a/etc/ci/check_no_panic.sh +++ b/etc/ci/check_no_panic.sh @@ -17,8 +17,9 @@ cd "$(git rev-parse --show-toplevel)" PATHS=( "components/compositing/compositor.rs" "components/constellation/" - "ports/servo/glutin_app/mod.rs" - "ports/servo/glutin_app/window.rs" + "ports/glutin/headed_window.rs" + "ports/glutin/headless_window.rs" + "ports/glutin/embedder.rs" ) # Make sure the paths exist diff --git a/ports/servo/Cargo.toml b/ports/glutin/Cargo.toml index 4edbef5fd85..4edbef5fd85 100644 --- a/ports/servo/Cargo.toml +++ b/ports/glutin/Cargo.toml diff --git a/ports/glutin/app.rs b/ports/glutin/app.rs new file mode 100644 index 00000000000..59d37fcd246 --- /dev/null +++ b/ports/glutin/app.rs @@ -0,0 +1,195 @@ +/* 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 crate::browser::Browser; +use crate::embedder::EmbedderCallbacks; +use crate::window_trait::WindowPortsMethods; +use crate::events_loop::EventsLoop; +use crate::{headed_window, headless_window}; +use servo::compositing::windowing::WindowEvent; +use servo::config::opts::{self, parse_url_or_filename}; +use servo::servo_config::pref; +use servo::servo_url::ServoUrl; +use servo::{BrowserId, Servo}; +use std::cell::{Cell, RefCell}; +use std::env; +use std::mem; +use std::rc::Rc; + +pub struct App { + events_loop: Rc<RefCell<EventsLoop>>, + window: Rc<WindowPortsMethods>, + servo: RefCell<Servo<WindowPortsMethods>>, + browser: RefCell<Browser<WindowPortsMethods>>, + event_queue: RefCell<Vec<WindowEvent>>, + suspended: Cell<bool>, +} + +impl App { + pub fn run() { + let events_loop = EventsLoop::new(opts::get().headless); + + // Implements window methods, used by compositor. + let window = if opts::get().headless { + headless_window::Window::new(opts::get().initial_window_size) + } else { + headed_window::Window::new(opts::get().initial_window_size, events_loop.borrow().as_winit()) + }; + + // Implements embedder methods, used by libservo and constellation. + let embedder = Box::new(EmbedderCallbacks::new( + events_loop.clone(), + window.gl(), + )); + + // Handle browser state. + let browser = Browser::new(window.clone()); + + let mut servo = Servo::new(embedder, window.clone()); + let browser_id = BrowserId::new(); + servo.handle_events(vec![WindowEvent::NewBrowser(get_default_url(), browser_id)]); + servo.setup_logging(); + + let app = App { + event_queue: RefCell::new(vec![]), + events_loop, + window: window, + browser: RefCell::new(browser), + servo: RefCell::new(servo), + suspended: Cell::new(false), + }; + + app.run_loop(); + } + + fn get_events(&self) -> Vec<WindowEvent> { + mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()) + } + + fn has_events(&self) -> bool { + !self.event_queue.borrow().is_empty() || self.window.has_events() + } + + fn winit_event_to_servo_event(&self, event: glutin::Event) { + match event { + // App level events + glutin::Event::Suspended(suspended) => { + self.suspended.set(suspended); + if !suspended { + self.event_queue.borrow_mut().push(WindowEvent::Idle); + } + }, + glutin::Event::Awakened => { + self.event_queue.borrow_mut().push(WindowEvent::Idle); + }, + glutin::Event::DeviceEvent { .. } => {}, + + // Window level events + glutin::Event::WindowEvent { + window_id, event, .. + } => { + if Some(window_id) != self.window.id() { + warn!("Got an event from unknown window"); + } else { + self.window.winit_event_to_servo_event(event); + } + }, + } + } + + fn run_loop(self) { + let mut stop = false; + loop { + let mut events_loop = self.events_loop.borrow_mut(); + if self.window.is_animating() && !self.suspended.get() { + // We block on compositing (self.handle_events() ends up calling swap_buffers) + events_loop.poll_events(|e| { + self.winit_event_to_servo_event(e); + }); + stop = self.handle_events(); + } else { + // We block on winit's event loop (window events) + events_loop.run_forever(|e| { + self.winit_event_to_servo_event(e); + if self.has_events() && !self.suspended.get() { + stop = self.handle_events(); + } + if stop || self.window.is_animating() && !self.suspended.get() { + glutin::ControlFlow::Break + } else { + glutin::ControlFlow::Continue + } + }); + } + if stop { + break; + } + } + + self.servo.into_inner().deinit() + } + + fn handle_events(&self) -> bool { + let mut browser = self.browser.borrow_mut(); + let mut servo = self.servo.borrow_mut(); + + let win_events = self.window.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, + }); + + let mut app_events = self.get_events(); + app_events.extend(win_events); + + browser.handle_window_events(app_events); + + let mut servo_events = servo.get_events(); + loop { + browser.handle_servo_events(servo_events); + servo.handle_events(browser.get_events()); + if browser.shutdown_requested() { + return true; + } + servo_events = servo.get_events(); + if servo_events.is_empty() { + break; + } + } + + if need_resize { + servo.repaint_synchronously(); + } + false + } +} + +fn get_default_url() -> ServoUrl { + // 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(); + let cmdline_url = opts::get().url.clone(); + let pref_url = { + let homepage_url = pref!(shell.homepage); + parse_url_or_filename(&cwd, &homepage_url).ok() + }; + let blank_url = ServoUrl::parse("about:blank").ok(); + + cmdline_url.or(pref_url).or(blank_url).unwrap() +} + +#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] +pub fn gl_version() -> glutin::GlRequest { + glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2)) +} + +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +pub fn gl_version() -> glutin::GlRequest { + glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (3, 0)) +} diff --git a/ports/servo/browser.rs b/ports/glutin/browser.rs index 6e74b462399..e12d283fc4e 100644 --- a/ports/servo/browser.rs +++ b/ports/glutin/browser.rs @@ -2,13 +2,13 @@ * 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 crate::glutin_app::keyutils::{CMD_OR_CONTROL, CMD_OR_ALT}; -use crate::glutin_app::window::{Window, LINE_HEIGHT}; +use crate::keyutils::{CMD_OR_ALT, CMD_OR_CONTROL}; +use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT}; use euclid::{TypedPoint2D, TypedVector2D}; use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher}; use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent}; use servo::embedder_traits::{EmbedderMsg, FilterPattern}; -use servo::msg::constellation_msg::{TopLevelBrowsingContextId as BrowserId}; +use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId; use servo::msg::constellation_msg::TraversalDirection; use servo::net_traits::pub_domains::is_reg_domain; use servo::script_traits::TouchEventType; @@ -25,7 +25,7 @@ use std::thread; use std::time::Duration; use tinyfiledialogs::{self, MessageBoxIcon}; -pub struct Browser { +pub struct Browser<Window: WindowPortsMethods + ?Sized> { current_url: Option<ServoUrl>, /// id of the top level browsing context. It is unique as tabs /// are not supported yet. None until created. @@ -52,8 +52,11 @@ enum LoadingState { Loaded, } -impl Browser { - pub fn new(window: Rc<Window>) -> Browser { +impl<Window> Browser<Window> +where + Window: WindowPortsMethods + ?Sized, +{ + pub fn new(window: Rc<Window>) -> Browser<Window> { Browser { title: None, current_url: None, @@ -126,7 +129,9 @@ impl Browser { .and_then(|s| s.parse().ok()) .unwrap_or(10); self.event_queue.push(WindowEvent::ToggleSamplingProfiler( - Duration::from_millis(rate), Duration::from_secs(duration))); + Duration::from_millis(rate), + Duration::from_secs(duration), + )); }) .shortcut(Modifiers::CONTROL, Key::F9, || { self.event_queue.push(WindowEvent::CaptureWebRender) @@ -403,14 +408,12 @@ impl Browser { debug!("HideIME received"); }, EmbedderMsg::ReportProfile(bytes) => { - let filename = env::var("PROFILE_OUTPUT") - .unwrap_or("samples.json".to_string()); - let result = File::create(&filename) - .and_then(|mut f| f.write_all(&bytes)); + let filename = env::var("PROFILE_OUTPUT").unwrap_or("samples.json".to_string()); + let result = File::create(&filename).and_then(|mut f| f.write_all(&bytes)); if let Err(e) = result { error!("Failed to store profile: {}", e); } - } + }, } } } diff --git a/ports/servo/build.rs b/ports/glutin/build.rs index 9da6e9e53bd..9da6e9e53bd 100644 --- a/ports/servo/build.rs +++ b/ports/glutin/build.rs diff --git a/ports/glutin/embedder.rs b/ports/glutin/embedder.rs new file mode 100644 index 00000000000..c5a04c874e5 --- /dev/null +++ b/ports/glutin/embedder.rs @@ -0,0 +1,71 @@ +/* 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/. */ + +//! Implements the global methods required by Servo (not window/gl/compositor related). + +use crate::app; +use crate::events_loop::EventsLoop; +use gleam::gl; +use glutin; +use glutin::dpi::LogicalSize; +use glutin::{ContextBuilder, GlWindow}; +use rust_webvr::GlWindowVRService; +use servo::compositing::windowing::EmbedderMethods; +use servo::embedder_traits::EventLoopWaker; +use servo::servo_config::{opts, pref}; +use servo::webvr::VRServiceManager; +use servo::webvr_traits::WebVRMainThreadHeartbeat; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct EmbedderCallbacks { + events_loop: Rc<RefCell<EventsLoop>>, + gl: Rc<dyn gl::Gl>, +} + +impl EmbedderCallbacks { + pub fn new(events_loop: Rc<RefCell<EventsLoop>>, gl: Rc<gl::Gl>) -> EmbedderCallbacks { + EmbedderCallbacks { events_loop, gl } + } +} + +impl EmbedderMethods for EmbedderCallbacks { + fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> { + self.events_loop.borrow().create_event_loop_waker() + } + + fn register_vr_services( + &self, + services: &mut VRServiceManager, + heartbeats: &mut Vec<Box<WebVRMainThreadHeartbeat>>, + ) { + if !opts::get().headless { + if pref!(dom.webvr.test) { + warn!("Creating test VR display"); + // This is safe, because register_vr_services is called from the main thread. + let name = String::from("Test VR Display"); + let size = opts::get().initial_window_size.to_f64(); + let size = LogicalSize::new(size.width, size.height); + let window_builder = glutin::WindowBuilder::new() + .with_title(name.clone()) + .with_dimensions(size) + .with_visibility(false) + .with_multitouch(); + let context_builder = ContextBuilder::new() + .with_gl(app::gl_version()) + .with_vsync(false); // Assume the browser vsync is the same as the test VR window vsync + let gl_window = + GlWindow::new(window_builder, context_builder, &*self.events_loop.borrow().as_winit()) + .expect("Failed to create window."); + let gl = self.gl.clone(); + let (service, heartbeat) = GlWindowVRService::new(name, gl_window, gl); + + services.register(Box::new(service)); + heartbeats.push(Box::new(heartbeat)); + } + } else { + // FIXME: support headless mode + } + } +} diff --git a/ports/glutin/events_loop.rs b/ports/glutin/events_loop.rs new file mode 100644 index 00000000000..fbe3cd57017 --- /dev/null +++ b/ports/glutin/events_loop.rs @@ -0,0 +1,98 @@ +/* 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/. */ + +//! An event loop implementation that works in headless mode. + + +use glutin; +use servo::embedder_traits::EventLoopWaker; +use std::sync::Arc; +use std::rc::Rc; +use std::cell::RefCell; +use std::thread; +use std::time; + +pub struct EventsLoop(Option<glutin::EventsLoop>); + +impl EventsLoop { + // Ideally, we could use the winit event loop in both modes, + // but on Linux, the event loop requires a X11 server. + #[cfg(not(target_os = "linux"))] + pub fn new(_headless: bool) -> Rc<RefCell<EventsLoop>> { + Rc::new(RefCell::new(EventsLoop(Some(glutin::EventsLoop::new())))) + } + #[cfg(target_os = "linux")] + pub fn new(headless: bool) -> Rc<RefCell<EventsLoop>> { + let events_loop = if headless { + None + } else { + Some(glutin::EventsLoop::new()) + }; + Rc::new(RefCell::new(EventsLoop(events_loop))) + } +} + +impl EventsLoop { + pub fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> { + if let Some(ref events_loop) = self.0 { + Box::new(HeadedEventLoopWaker::new(&events_loop)) + } else { + Box::new(HeadlessEventLoopWaker) + } + } + pub fn as_winit(&self) -> &glutin::EventsLoop { + &self.0.as_ref().expect("Can't access winit event loop while using the fake headless event loop") + } + pub fn poll_events<F>(&mut self, callback: F) where F: FnMut(glutin::Event) { + if let Some(ref mut events_loop) = self.0 { + events_loop.poll_events(callback); + } else { + self.sleep(); + } + } + pub fn run_forever<F>(&mut self, mut callback: F) where F: FnMut(glutin::Event) -> glutin::ControlFlow { + if let Some(ref mut events_loop) = self.0 { + events_loop.run_forever(callback); + } else { + loop { + self.sleep(); + if callback(glutin::Event::Awakened) == glutin::ControlFlow::Break { + break; + } + } + } + } + fn sleep(&self) { + thread::sleep(time::Duration::from_millis(5)); + } +} + +struct HeadedEventLoopWaker { + proxy: Arc<glutin::EventsLoopProxy>, +} +impl HeadedEventLoopWaker { + fn new(events_loop: &glutin::EventsLoop) -> HeadedEventLoopWaker { + let proxy = Arc::new(events_loop.create_proxy()); + HeadedEventLoopWaker { proxy } + } +} +impl EventLoopWaker for HeadedEventLoopWaker { + fn wake(&self) { + // kick the OS event loop awake. + if let Err(err) = self.proxy.wakeup() { + warn!("Failed to wake up event loop ({}).", err); + } + } + fn clone(&self) -> Box<dyn EventLoopWaker + Send> { + Box::new(HeadedEventLoopWaker { + proxy: self.proxy.clone(), + }) + } +} + +struct HeadlessEventLoopWaker; +impl EventLoopWaker for HeadlessEventLoopWaker { + fn wake(&self) {} + fn clone(&self) -> Box<dyn EventLoopWaker + Send> { Box::new(HeadlessEventLoopWaker) } +} diff --git a/ports/glutin/headed_window.rs b/ports/glutin/headed_window.rs new file mode 100644 index 00000000000..754cccf175b --- /dev/null +++ b/ports/glutin/headed_window.rs @@ -0,0 +1,533 @@ +/* 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/. */ + +//! A glutin window implementation. + +use crate::app; +use crate::keyutils::keyboard_event_from_winit; +use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT}; +use euclid::{TypedPoint2D, TypedScale, TypedSize2D, TypedVector2D}; +use gleam::gl; +use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; +#[cfg(target_os = "macos")] +use glutin::os::macos::{ActivationPolicy, WindowBuilderExt}; +#[cfg(any(target_os = "linux", target_os = "windows"))] +use glutin::Icon; +use glutin::{ContextBuilder, GlContext, GlWindow}; +use glutin::{ElementState, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase}; +#[cfg(any(target_os = "linux", target_os = "windows"))] +use image; +use keyboard_types::{Key, KeyState, KeyboardEvent}; +use servo::compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent}; +use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods}; +use servo::embedder_traits::Cursor; +use servo::script_traits::TouchEventType; +use servo::servo_config::opts; +use servo::servo_geometry::DeviceIndependentPixel; +use servo::style_traits::DevicePixel; +use servo::webrender_api::{ + DeviceIntPoint, DeviceIntRect, DeviceIntSize, FramebufferIntSize, ScrollLocation, +}; +use std::cell::{Cell, RefCell}; +use std::mem; +use std::rc::Rc; +#[cfg(target_os = "windows")] +use winapi; + +const MULTISAMPLES: u16 = 16; + +#[cfg(target_os = "macos")] +fn builder_with_platform_options(mut builder: glutin::WindowBuilder) -> glutin::WindowBuilder { + if opts::get().output_file.is_some() { + // Prevent the window from showing in Dock.app, stealing focus, + // when generating an output file. + builder = builder.with_activation_policy(ActivationPolicy::Prohibited) + } + builder +} + +#[cfg(not(target_os = "macos"))] +fn builder_with_platform_options(builder: glutin::WindowBuilder) -> glutin::WindowBuilder { + builder +} + +pub struct Window { + gl_window: GlWindow, + screen_size: TypedSize2D<u32, DeviceIndependentPixel>, + inner_size: Cell<TypedSize2D<u32, DeviceIndependentPixel>>, + mouse_down_button: Cell<Option<glutin::MouseButton>>, + mouse_down_point: Cell<TypedPoint2D<i32, DevicePixel>>, + primary_monitor: glutin::MonitorId, + event_queue: RefCell<Vec<WindowEvent>>, + mouse_pos: Cell<TypedPoint2D<i32, DevicePixel>>, + last_pressed: Cell<Option<KeyboardEvent>>, + animation_state: Cell<AnimationState>, + fullscreen: Cell<bool>, + gl: Rc<dyn gl::Gl>, +} + +#[cfg(not(target_os = "windows"))] +fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { + TypedScale::new(1.0) +} + +#[cfg(target_os = "windows")] +fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { + let hdc = unsafe { winapi::um::winuser::GetDC(::std::ptr::null_mut()) }; + let ppi = unsafe { winapi::um::wingdi::GetDeviceCaps(hdc, winapi::um::wingdi::LOGPIXELSY) }; + TypedScale::new(ppi as f32 / 96.0) +} + +impl Window { + pub fn new( + win_size: TypedSize2D<u32, DeviceIndependentPixel>, + events_loop: &glutin::EventsLoop, + ) -> Rc<dyn WindowPortsMethods> { + let opts = opts::get(); + + // If there's no chrome, start off with the window invisible. It will be set to visible in + // `load_end()`. This avoids an ugly flash of unstyled content (especially important since + // unstyled content is white and chrome often has a transparent background). See issue + // #9996. + let visible = opts.output_file.is_none() && !opts.no_native_titlebar; + + let win_size: DeviceIntSize = (win_size.to_f32() * window_creation_scale_factor()).to_i32(); + let width = win_size.to_untyped().width; + let height = win_size.to_untyped().height; + + let mut window_builder = glutin::WindowBuilder::new() + .with_title("Servo".to_string()) + .with_decorations(!opts.no_native_titlebar) + .with_transparency(opts.no_native_titlebar) + .with_dimensions(LogicalSize::new(width as f64, height as f64)) + .with_visibility(visible) + .with_multitouch(); + + window_builder = builder_with_platform_options(window_builder); + + let mut context_builder = ContextBuilder::new() + .with_gl(app::gl_version()) + .with_vsync(opts.enable_vsync); + + if opts.use_msaa { + context_builder = context_builder.with_multisampling(MULTISAMPLES) + } + + let glutin_window = GlWindow::new(window_builder, context_builder, &events_loop) + .expect("Failed to create window."); + + #[cfg(any(target_os = "linux", target_os = "windows"))] + { + let icon_bytes = include_bytes!("../../resources/servo64.png"); + glutin_window.set_window_icon(Some(load_icon(icon_bytes))); + } + + unsafe { + glutin_window + .context() + .make_current() + .expect("Couldn't make window current"); + } + + let primary_monitor = events_loop.get_primary_monitor(); + + let PhysicalSize { + width: screen_width, + height: screen_height, + } = primary_monitor.get_dimensions(); + let screen_size = TypedSize2D::new(screen_width as u32, screen_height as u32); + // TODO(ajeffrey): can this fail? + let LogicalSize { width, height } = glutin_window + .get_inner_size() + .expect("Failed to get window inner size."); + let inner_size = TypedSize2D::new(width as u32, height as u32); + + glutin_window.show(); + + let gl = match gl::GlType::default() { + gl::GlType::Gl => unsafe { + gl::GlFns::load_with(|s| glutin_window.get_proc_address(s) as *const _) + }, + gl::GlType::Gles => unsafe { + gl::GlesFns::load_with(|s| glutin_window.get_proc_address(s) as *const _) + }, + }; + + gl.clear_color(0.6, 0.6, 0.6, 1.0); + gl.clear(gl::COLOR_BUFFER_BIT); + gl.finish(); + + let window = Window { + gl_window: glutin_window, + event_queue: RefCell::new(vec![]), + mouse_down_button: Cell::new(None), + mouse_down_point: Cell::new(TypedPoint2D::new(0, 0)), + mouse_pos: Cell::new(TypedPoint2D::new(0, 0)), + last_pressed: Cell::new(None), + gl: gl.clone(), + animation_state: Cell::new(AnimationState::Idle), + fullscreen: Cell::new(false), + inner_size: Cell::new(inner_size), + primary_monitor, + screen_size, + }; + + window.present(); + + Rc::new(window) + } + + fn handle_received_character(&self, mut ch: char) { + info!("winit received character: {:?}", ch); + if ch.is_control() { + if ch as u8 >= 32 { + return; + } + // shift ASCII control characters to lowercase + ch = (ch as u8 + 96) as char; + } + let mut event = if let Some(event) = self.last_pressed.replace(None) { + event + } else if ch.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. + return; + } else { + // For combined characters like the letter e with an acute accent + // no keyboard event is emitted. A dummy event is created in this case. + KeyboardEvent::default() + }; + event.key = Key::Character(ch.to_string()); + self.event_queue + .borrow_mut() + .push(WindowEvent::Keyboard(event)); + } + + fn handle_keyboard_input(&self, input: KeyboardInput) { + let event = keyboard_event_from_winit(input); + if event.state == KeyState::Down && event.key == Key::Unidentified { + // If pressed and probably printable, we expect a ReceivedCharacter event. + self.last_pressed.set(Some(event)); + } else if event.key != Key::Unidentified { + self.last_pressed.set(None); + self.event_queue + .borrow_mut() + .push(WindowEvent::Keyboard(event)); + } + } + + /// Helper function to handle a click + fn handle_mouse( + &self, + button: glutin::MouseButton, + action: glutin::ElementState, + coords: TypedPoint2D<i32, DevicePixel>, + ) { + use servo::script_traits::MouseButton; + + let max_pixel_dist = 10.0 * self.servo_hidpi_factor().get(); + let event = match action { + ElementState::Pressed => { + self.mouse_down_point.set(coords); + self.mouse_down_button.set(Some(button)); + MouseWindowEvent::MouseDown(MouseButton::Left, coords.to_f32()) + }, + ElementState::Released => { + let mouse_up_event = MouseWindowEvent::MouseUp(MouseButton::Left, coords.to_f32()); + match self.mouse_down_button.get() { + None => mouse_up_event, + Some(but) if button == but => { + let pixel_dist = self.mouse_down_point.get() - coords; + let pixel_dist = + ((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(WindowEvent::MouseWindowEventClass(mouse_up_event)); + MouseWindowEvent::Click(MouseButton::Left, coords.to_f32()) + } else { + mouse_up_event + } + }, + Some(_) => mouse_up_event, + } + }, + }; + self.event_queue + .borrow_mut() + .push(WindowEvent::MouseWindowEventClass(event)); + } + + fn device_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { + TypedScale::new(self.gl_window.get_hidpi_factor() as f32) + } + + fn servo_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { + match opts::get().device_pixels_per_px { + Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px), + _ => match opts::get().output_file { + Some(_) => TypedScale::new(1.0), + None => self.device_hidpi_factor(), + }, + } + } +} + +impl WindowPortsMethods for Window { + fn get_events(&self) -> Vec<WindowEvent> { + mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()) + } + + fn has_events(&self) -> bool { + !self.event_queue.borrow().is_empty() + } + + fn page_height(&self) -> f32 { + let dpr = self.servo_hidpi_factor(); + let size = self + .gl_window + .get_inner_size() + .expect("Failed to get window inner size."); + size.height as f32 * dpr.get() + } + + fn set_title(&self, title: &str) { + self.gl_window.set_title(title); + } + + fn set_inner_size(&self, size: DeviceIntSize) { + let size = size.to_f32() / self.device_hidpi_factor(); + self.gl_window + .set_inner_size(LogicalSize::new(size.width.into(), size.height.into())) + } + + fn set_position(&self, point: DeviceIntPoint) { + let point = point.to_f32() / self.device_hidpi_factor(); + self.gl_window + .set_position(LogicalPosition::new(point.x.into(), point.y.into())) + } + + fn set_fullscreen(&self, state: bool) { + if self.fullscreen.get() != state { + self.gl_window + .set_fullscreen(Some(self.primary_monitor.clone())); + } + self.fullscreen.set(state); + } + + fn get_fullscreen(&self) -> bool { + return self.fullscreen.get(); + } + + fn set_cursor(&self, cursor: Cursor) { + use glutin::MouseCursor; + + let winit_cursor = match cursor { + Cursor::Default => MouseCursor::Default, + Cursor::Pointer => MouseCursor::Hand, + Cursor::ContextMenu => MouseCursor::ContextMenu, + Cursor::Help => MouseCursor::Help, + Cursor::Progress => MouseCursor::Progress, + Cursor::Wait => MouseCursor::Wait, + Cursor::Cell => MouseCursor::Cell, + Cursor::Crosshair => MouseCursor::Crosshair, + Cursor::Text => MouseCursor::Text, + Cursor::VerticalText => MouseCursor::VerticalText, + Cursor::Alias => MouseCursor::Alias, + Cursor::Copy => MouseCursor::Copy, + Cursor::Move => MouseCursor::Move, + Cursor::NoDrop => MouseCursor::NoDrop, + Cursor::NotAllowed => MouseCursor::NotAllowed, + Cursor::Grab => MouseCursor::Grab, + Cursor::Grabbing => MouseCursor::Grabbing, + Cursor::EResize => MouseCursor::EResize, + Cursor::NResize => MouseCursor::NResize, + Cursor::NeResize => MouseCursor::NeResize, + Cursor::NwResize => MouseCursor::NwResize, + Cursor::SResize => MouseCursor::SResize, + Cursor::SeResize => MouseCursor::SeResize, + Cursor::SwResize => MouseCursor::SwResize, + Cursor::WResize => MouseCursor::WResize, + Cursor::EwResize => MouseCursor::EwResize, + Cursor::NsResize => MouseCursor::NsResize, + Cursor::NeswResize => MouseCursor::NeswResize, + Cursor::NwseResize => MouseCursor::NwseResize, + Cursor::ColResize => MouseCursor::ColResize, + Cursor::RowResize => MouseCursor::RowResize, + Cursor::AllScroll => MouseCursor::AllScroll, + Cursor::ZoomIn => MouseCursor::ZoomIn, + Cursor::ZoomOut => MouseCursor::ZoomOut, + _ => MouseCursor::Default, + }; + self.gl_window.set_cursor(winit_cursor); + } + + fn is_animating(&self) -> bool { + self.animation_state.get() == AnimationState::Animating + } + + fn id(&self) -> Option<glutin::WindowId> { + Some(self.gl_window.id()) + } + + fn winit_event_to_servo_event(&self, event: glutin::WindowEvent) { + match event { + glutin::WindowEvent::ReceivedCharacter(ch) => self.handle_received_character(ch), + glutin::WindowEvent::KeyboardInput { input, .. } => self.handle_keyboard_input(input), + glutin::WindowEvent::MouseInput { state, button, .. } => { + if button == MouseButton::Left || button == MouseButton::Right { + self.handle_mouse(button, state, self.mouse_pos.get()); + } + }, + glutin::WindowEvent::CursorMoved { position, .. } => { + let pos = position.to_physical(self.device_hidpi_factor().get() as f64); + let (x, y): (i32, i32) = pos.into(); + self.mouse_pos.set(TypedPoint2D::new(x, y)); + self.event_queue + .borrow_mut() + .push(WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new( + x as f32, y as f32, + ))); + }, + glutin::WindowEvent::MouseWheel { delta, phase, .. } => { + let (mut dx, mut dy) = match delta { + MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT), + MouseScrollDelta::PixelDelta(position) => { + let position = + position.to_physical(self.device_hidpi_factor().get() as f64); + (position.x as f32, position.y as f32) + }, + }; + // 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 = winit_phase_to_touch_event_type(phase); + let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase); + self.event_queue.borrow_mut().push(event); + }, + glutin::WindowEvent::Touch(touch) => { + use servo::script_traits::TouchId; + + let phase = winit_phase_to_touch_event_type(touch.phase); + let id = TouchId(touch.id as i32); + let position = touch + .location + .to_physical(self.device_hidpi_factor().get() as f64); + let point = TypedPoint2D::new(position.x as f32, position.y as f32); + self.event_queue + .borrow_mut() + .push(WindowEvent::Touch(phase, id, point)); + }, + glutin::WindowEvent::Refresh => { + self.event_queue.borrow_mut().push(WindowEvent::Refresh); + }, + glutin::WindowEvent::CloseRequested => { + self.event_queue.borrow_mut().push(WindowEvent::Quit); + }, + glutin::WindowEvent::Resized(size) => { + // size is DeviceIndependentPixel. + // gl_window.resize() takes DevicePixel. + let size = size.to_physical(self.device_hidpi_factor().get() as f64); + self.gl_window.resize(size); + // window.set_inner_size() takes DeviceIndependentPixel. + let (width, height) = size.into(); + let new_size = TypedSize2D::new(width, height); + if self.inner_size.get() != new_size { + self.inner_size.set(new_size); + self.event_queue.borrow_mut().push(WindowEvent::Resize); + } + }, + _ => {}, + } + } +} + +impl WindowMethods for Window { + fn gl(&self) -> Rc<dyn gl::Gl> { + self.gl.clone() + } + + fn get_coordinates(&self) -> EmbedderCoordinates { + // TODO(ajeffrey): can this fail? + let dpr = self.device_hidpi_factor(); + let LogicalSize { width, height } = self + .gl_window + .get_outer_size() + .expect("Failed to get window outer size."); + let LogicalPosition { x, y } = self + .gl_window + .get_position() + .unwrap_or(LogicalPosition::new(0., 0.)); + let win_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32(); + let win_origin = (TypedPoint2D::new(x as f32, y as f32) * dpr).to_i32(); + let screen = (self.screen_size.to_f32() * dpr).to_i32(); + + let LogicalSize { width, height } = self + .gl_window + .get_inner_size() + .expect("Failed to get window inner size."); + let inner_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32(); + let viewport = DeviceIntRect::new(TypedPoint2D::zero(), inner_size); + let framebuffer = FramebufferIntSize::from_untyped(&viewport.size.to_untyped()); + + EmbedderCoordinates { + viewport, + framebuffer, + window: (win_size, win_origin), + screen: screen, + // FIXME: Glutin doesn't have API for available size. Fallback to screen size + screen_avail: screen, + hidpi_factor: self.servo_hidpi_factor(), + } + } + + fn present(&self) { + if let Err(err) = self.gl_window.swap_buffers() { + warn!("Failed to swap window buffers ({}).", err); + } + } + + fn set_animation_state(&self, state: AnimationState) { + self.animation_state.set(state); + } + + fn prepare_for_composite(&self) -> bool { + if let Err(err) = unsafe { self.gl_window.context().make_current() } { + warn!("Couldn't make window current: {}", err); + } + true + } +} + +fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType { + match phase { + TouchPhase::Started => TouchEventType::Down, + TouchPhase::Moved => TouchEventType::Move, + TouchPhase::Ended => TouchEventType::Up, + TouchPhase::Cancelled => TouchEventType::Cancel, + } +} + +#[cfg(any(target_os = "linux", target_os = "windows"))] +fn load_icon(icon_bytes: &[u8]) -> Icon { + let (icon_rgba, icon_width, icon_height) = { + use image::{GenericImageView, Pixel}; + let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");; + let (width, height) = image.dimensions(); + let mut rgba = Vec::with_capacity((width * height) as usize * 4); + for (_, _, pixel) in image.pixels() { + rgba.extend_from_slice(&pixel.to_rgba().data); + } + (rgba, width, height) + }; + Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon") +} diff --git a/ports/glutin/headless_window.rs b/ports/glutin/headless_window.rs new file mode 100644 index 00000000000..c14228048b7 --- /dev/null +++ b/ports/glutin/headless_window.rs @@ -0,0 +1,200 @@ +/* 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/. */ + +//! A headless window implementation. + +use crate::window_trait::WindowPortsMethods; +use glutin; +use euclid::{TypedPoint2D, TypedScale, TypedSize2D}; +use gleam::gl; +use servo::compositing::windowing::{AnimationState, WindowEvent}; +use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods}; +use servo::servo_config::opts; +use servo::servo_geometry::DeviceIndependentPixel; +use servo::style_traits::DevicePixel; +use servo::webrender_api::{DeviceIntRect, FramebufferIntSize}; +use std::cell::Cell; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use std::ffi::CString; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use std::mem; +use std::os::raw::c_void; +use std::ptr; +use std::rc::Rc; + +#[cfg(any(target_os = "linux", target_os = "macos"))] +struct HeadlessContext { + width: u32, + height: u32, + _context: osmesa_sys::OSMesaContext, + _buffer: Vec<u32>, +} + +#[cfg(not(any(target_os = "linux", target_os = "macos")))] +struct HeadlessContext { + width: u32, + height: u32, +} + +impl HeadlessContext { + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn new(width: u32, height: u32) -> HeadlessContext { + let mut attribs = Vec::new(); + + attribs.push(osmesa_sys::OSMESA_PROFILE); + attribs.push(osmesa_sys::OSMESA_CORE_PROFILE); + attribs.push(osmesa_sys::OSMESA_CONTEXT_MAJOR_VERSION); + attribs.push(3); + attribs.push(osmesa_sys::OSMESA_CONTEXT_MINOR_VERSION); + attribs.push(3); + attribs.push(0); + + let context = + unsafe { osmesa_sys::OSMesaCreateContextAttribs(attribs.as_ptr(), ptr::null_mut()) }; + + assert!(!context.is_null()); + + let mut buffer = vec![0; (width * height) as usize]; + + unsafe { + let ret = osmesa_sys::OSMesaMakeCurrent( + context, + buffer.as_mut_ptr() as *mut _, + gl::UNSIGNED_BYTE, + width as i32, + height as i32, + ); + assert_ne!(ret, 0); + }; + + HeadlessContext { + width: width, + height: height, + _context: context, + _buffer: buffer, + } + } + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + fn new(width: u32, height: u32) -> HeadlessContext { + HeadlessContext { + width: width, + height: height, + } + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn get_proc_address(s: &str) -> *const c_void { + let c_str = CString::new(s).expect("Unable to create CString"); + unsafe { mem::transmute(osmesa_sys::OSMesaGetProcAddress(c_str.as_ptr())) } + } + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + fn get_proc_address(_: &str) -> *const c_void { + ptr::null() as *const _ + } +} + +pub struct Window { + context: HeadlessContext, + animation_state: Cell<AnimationState>, + fullscreen: Cell<bool>, + gl: Rc<dyn gl::Gl>, +} + +impl Window { + pub fn new(size: TypedSize2D<u32, DeviceIndependentPixel>) -> Rc<dyn WindowPortsMethods> { + let context = HeadlessContext::new(size.width, size.height); + let gl = unsafe { gl::GlFns::load_with(|s| HeadlessContext::get_proc_address(s)) }; + + // Print some information about the headless renderer that + // can be useful in diagnosing CI failures on build machines. + println!("{}", gl.get_string(gl::VENDOR)); + println!("{}", gl.get_string(gl::RENDERER)); + println!("{}", gl.get_string(gl::VERSION)); + + let window = Window { + context, + gl, + animation_state: Cell::new(AnimationState::Idle), + fullscreen: Cell::new(false), + }; + + Rc::new(window) + } + + fn servo_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { + match opts::get().device_pixels_per_px { + Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px), + _ => TypedScale::new(1.0), + } + } +} + +impl WindowPortsMethods for Window { + fn get_events(&self) -> Vec<WindowEvent> { + vec![] + } + + fn has_events(&self) -> bool { + false + } + + fn id(&self) -> Option<glutin::WindowId> { + None + } + + fn page_height(&self) -> f32 { + let dpr = self.servo_hidpi_factor(); + self.context.height as f32 * dpr.get() + } + + fn set_fullscreen(&self, state: bool) { + self.fullscreen.set(state); + } + + fn get_fullscreen(&self) -> bool { + return self.fullscreen.get(); + } + + fn is_animating(&self) -> bool { + self.animation_state.get() == AnimationState::Animating + } + + fn winit_event_to_servo_event(&self, _event: glutin::WindowEvent) { + // Not expecting any winit events. + } +} + +impl WindowMethods for Window { + fn gl(&self) -> Rc<dyn gl::Gl> { + self.gl.clone() + } + + fn get_coordinates(&self) -> EmbedderCoordinates { + let dpr = self.servo_hidpi_factor(); + let size = + (TypedSize2D::new(self.context.width, self.context.height).to_f32() * dpr).to_i32(); + let viewport = DeviceIntRect::new(TypedPoint2D::zero(), size); + let framebuffer = FramebufferIntSize::from_untyped(&size.to_untyped()); + EmbedderCoordinates { + viewport, + framebuffer, + window: (size, TypedPoint2D::zero()), + screen: size, + screen_avail: size, + hidpi_factor: dpr, + } + } + + fn present(&self) {} + + fn set_animation_state(&self, state: AnimationState) { + self.animation_state.set(state); + } + + fn prepare_for_composite(&self) -> bool { + true + } +} diff --git a/ports/servo/glutin_app/keyutils.rs b/ports/glutin/keyutils.rs index add042a2382..7dd7fdf5ca5 100644 --- a/ports/servo/glutin_app/keyutils.rs +++ b/ports/glutin/keyutils.rs @@ -2,8 +2,8 @@ * 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 keyboard_types::{Code, Key, KeyboardEvent, KeyState, Modifiers, Location}; use glutin::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode}; +use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, Modifiers}; // Some shortcuts use Cmd on Mac and Control on other systems. #[cfg(target_os = "macos")] diff --git a/ports/servo/main.rs b/ports/glutin/main.rs index 17e270a0115..23be648bad8 100644 --- a/ports/servo/main.rs +++ b/ports/glutin/main.rs @@ -17,14 +17,8 @@ #![cfg_attr(feature = "unstable", feature(core_intrinsics))] -// Have this here rather than in non_android_main.rs to work around -// https://github.com/rust-lang/rust/issues/53205 #[cfg(not(target_os = "android"))] -#[macro_use] -extern crate log; - -#[cfg(not(target_os = "android"))] -include!("non_android_main.rs"); +include!("main2.rs"); #[cfg(target_os = "android")] pub fn main() { diff --git a/ports/servo/non_android_main.rs b/ports/glutin/main2.rs index 767d27a646c..73fa1caf618 100644 --- a/ports/servo/non_android_main.rs +++ b/ports/glutin/main2.rs @@ -2,24 +2,29 @@ * 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/. */ -#[macro_use] extern crate lazy_static; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; #[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))] -#[macro_use] extern crate sig; - -// The window backed by glutin -mod glutin_app; - -mod resources; +#[macro_use] +extern crate sig; +mod app; mod browser; +mod embedder; +mod events_loop; +mod headed_window; +mod headless_window; +mod keyutils; +mod resources; +mod skia_symbols; +mod window_trait; +use app::App; use backtrace::Backtrace; -use servo::{Servo, BrowserId}; -use servo::compositing::windowing::WindowEvent; -use servo::config::opts::{self, ArgumentParsingResult, parse_url_or_filename}; +use servo::config::opts::{self, ArgumentParsingResult}; use servo::config::servo_version; -use servo::servo_config::pref; -use servo::servo_url::ServoUrl; use std::env; use std::panic; use std::process; @@ -36,7 +41,10 @@ pub mod platform { pub fn deinit() {} } -#[cfg(any(not(feature = "unstable"), not(any(target_os = "macos", target_os = "linux"))))] +#[cfg(any( + not(feature = "unstable"), + not(any(target_os = "macos", target_os = "linux")) +))] fn install_crash_handler() {} #[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))] @@ -122,112 +130,7 @@ pub fn main() { process::exit(0); } - 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(); - let cmdline_url = opts::get().url.clone(); - let pref_url = { - let homepage_url = pref!(shell.homepage); - parse_url_or_filename(&cwd, &homepage_url).ok() - }; - let blank_url = ServoUrl::parse("about:blank").ok(); - - let target_url = cmdline_url.or(pref_url).or(blank_url).unwrap(); - - let mut servo = Servo::new(window.clone()); - let browser_id = BrowserId::new(); - servo.handle_events(vec![WindowEvent::NewBrowser(target_url, browser_id)]); - - servo.setup_logging(); - - window.run(|| { - let win_events = window.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, - }); - - browser.handle_window_events(win_events); - - let mut servo_events = servo.get_events(); - loop { - browser.handle_servo_events(servo_events); - servo.handle_events(browser.get_events()); - if browser.shutdown_requested() { - return true; - } - servo_events = servo.get_events(); - if servo_events.is_empty() { - break; - } - } - - if need_resize { - servo.repaint_synchronously(); - } - false - }); - - servo.deinit(); + App::run(); platform::deinit() } - -// These functions aren't actually called. They are here as a link -// hack because Skia references them. - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glBindVertexArrayOES(_array: usize) { - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glDeleteVertexArraysOES(_n: isize, _arrays: *const ()) { - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glGenVertexArraysOES(_n: isize, _arrays: *const ()) { - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glRenderbufferStorageMultisampleIMG( - _: isize, - _: isize, - _: isize, - _: isize, - _: isize, -) { - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glFramebufferTexture2DMultisampleIMG( - _: isize, - _: isize, - _: isize, - _: isize, - _: isize, - _: isize, -) { - unimplemented!() -} - -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn glDiscardFramebufferEXT(_: isize, _: isize, _: *const ()) { - unimplemented!() -} diff --git a/ports/servo/platform/macos/Info.plist b/ports/glutin/platform/macos/Info.plist index c2855eb82b4..c2855eb82b4 100644 --- a/ports/servo/platform/macos/Info.plist +++ b/ports/glutin/platform/macos/Info.plist diff --git a/ports/servo/platform/macos/count_threads.c b/ports/glutin/platform/macos/count_threads.c index bc3328d278d..bc3328d278d 100644 --- a/ports/servo/platform/macos/count_threads.c +++ b/ports/glutin/platform/macos/count_threads.c diff --git a/ports/servo/platform/macos/mod.rs b/ports/glutin/platform/macos/mod.rs index 808f07d8340..730f4072576 100644 --- a/ports/servo/platform/macos/mod.rs +++ b/ports/glutin/platform/macos/mod.rs @@ -14,18 +14,17 @@ pub fn deinit() { ptr::read_volatile(&INFO_PLIST[0]); } - let thread_count = unsafe { - macos_count_running_threads() - }; + let thread_count = unsafe { macos_count_running_threads() }; if thread_count != 1 { - println!("{} threads are still running after shutdown (bad).", thread_count); + println!( + "{} threads are still running after shutdown (bad).", + thread_count + ); if opts::get().clean_shutdown { println!("Waiting until all threads have shutdown"); loop { - let thread_count = unsafe { - macos_count_running_threads() - }; + let thread_count = unsafe { macos_count_running_threads() }; if thread_count == 1 { break; } @@ -43,6 +42,6 @@ pub fn deinit() { pub static INFO_PLIST: [u8; 619] = *include_bytes!("Info.plist"); #[link(name = "count_threads")] -extern { +extern "C" { fn macos_count_running_threads() -> i32; } diff --git a/ports/servo/platform/windows/servo.exe.manifest b/ports/glutin/platform/windows/servo.exe.manifest index 23b2abe1b72..23b2abe1b72 100644 --- a/ports/servo/platform/windows/servo.exe.manifest +++ b/ports/glutin/platform/windows/servo.exe.manifest diff --git a/ports/servo/resources.rs b/ports/glutin/resources.rs index 5a48037f524..5a48037f524 100644 --- a/ports/servo/resources.rs +++ b/ports/glutin/resources.rs diff --git a/ports/glutin/skia_symbols.rs b/ports/glutin/skia_symbols.rs new file mode 100644 index 00000000000..ceca7697dd3 --- /dev/null +++ b/ports/glutin/skia_symbols.rs @@ -0,0 +1,55 @@ +/* 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/. */ + +//! These functions aren't actually called. They are here as a link +//! hack because Skia references them. + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glBindVertexArrayOES(_array: usize) { + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glDeleteVertexArraysOES(_n: isize, _arrays: *const ()) { + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glGenVertexArraysOES(_n: isize, _arrays: *const ()) { + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glRenderbufferStorageMultisampleIMG( + _: isize, + _: isize, + _: isize, + _: isize, + _: isize, +) { + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glFramebufferTexture2DMultisampleIMG( + _: isize, + _: isize, + _: isize, + _: isize, + _: isize, + _: isize, +) { + unimplemented!() +} + +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn glDiscardFramebufferEXT(_: isize, _: isize, _: *const ()) { + unimplemented!() +} diff --git a/ports/glutin/window_trait.rs b/ports/glutin/window_trait.rs new file mode 100644 index 00000000000..83b208fe993 --- /dev/null +++ b/ports/glutin/window_trait.rs @@ -0,0 +1,29 @@ +/* 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/. */ + +//! Definition of Window. +//! Implemented by headless and headed windows. + +use glutin; +use servo::compositing::windowing::{WindowEvent, WindowMethods}; +use servo::embedder_traits::Cursor; +use servo::webrender_api::{DeviceIntPoint, DeviceIntSize}; + +// 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<WindowEvent>; + fn id(&self) -> Option<glutin::WindowId>; + fn has_events(&self) -> bool; + fn page_height(&self) -> f32; + fn get_fullscreen(&self) -> bool; + fn winit_event_to_servo_event(&self, event: glutin::WindowEvent); + fn is_animating(&self) -> bool; + fn set_title(&self, _title: &str) {} + fn set_inner_size(&self, _size: DeviceIntSize) {} + fn set_position(&self, _point: DeviceIntPoint) {} + fn set_fullscreen(&self, _state: bool) {} + fn set_cursor(&self, _cursor: Cursor) {} +} diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs index 09878dc2542..2df9da4fd76 100644 --- a/ports/libsimpleservo/api/src/lib.rs +++ b/ports/libsimpleservo/api/src/lib.rs @@ -10,7 +10,8 @@ pub mod gl_glue; pub use servo::script_traits::MouseButton; use servo::compositing::windowing::{ - AnimationState, EmbedderCoordinates, MouseWindowEvent, WindowEvent, WindowMethods, + AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowEvent, + WindowMethods, }; use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods}; use servo::embedder_traits::EmbedderMsg; @@ -112,9 +113,9 @@ pub trait HostTrait { } pub struct ServoGlue { - servo: Servo<ServoCallbacks>, + servo: Servo<ServoWindowCallbacks>, batch_mode: bool, - callbacks: Rc<ServoCallbacks>, + callbacks: Rc<ServoWindowCallbacks>, /// id of the top level browsing context. It is unique as tabs /// are not supported yet. None until created. browser_id: Option<BrowserId>, @@ -169,22 +170,25 @@ pub fn init( gl.clear(gl::COLOR_BUFFER_BIT); gl.finish(); - let callbacks = Rc::new(ServoCallbacks { + let window_callbacks = Rc::new(ServoWindowCallbacks { gl: gl.clone(), host_callbacks: callbacks, coordinates: RefCell::new(init_opts.coordinates), density: init_opts.density, + }); + + let embedder_callbacks = Box::new(ServoEmbedderCallbacks { vr_pointer: init_opts.vr_pointer, waker, }); - let servo = Servo::new(callbacks.clone()); + let servo = Servo::new(embedder_callbacks, window_callbacks.clone()); SERVO.with(|s| { let mut servo_glue = ServoGlue { servo, batch_mode: false, - callbacks, + callbacks: window_callbacks, browser_id: None, browsers: vec![], events: vec![], @@ -546,16 +550,37 @@ impl ServoGlue { } } -struct ServoCallbacks { +struct ServoEmbedderCallbacks { waker: Box<dyn EventLoopWaker>, + vr_pointer: Option<*mut c_void>, +} + +struct ServoWindowCallbacks { gl: Rc<dyn gl::Gl>, host_callbacks: Box<dyn HostTrait>, coordinates: RefCell<Coordinates>, density: f32, - vr_pointer: Option<*mut c_void>, } -impl WindowMethods for ServoCallbacks { +impl EmbedderMethods for ServoEmbedderCallbacks { + fn register_vr_services( + &self, + services: &mut VRServiceManager, + _: &mut Vec<Box<VRMainThreadHeartbeat>>, + ) { + debug!("EmbedderMethods::register_vrexternal"); + if let Some(ptr) = self.vr_pointer { + services.register_vrexternal(VRExternalShmemPtr::new(ptr)); + } + } + + fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> { + debug!("EmbedderMethods::create_event_loop_waker"); + self.waker.clone() + } +} + +impl WindowMethods for ServoWindowCallbacks { fn prepare_for_composite(&self) -> bool { debug!("WindowMethods::prepare_for_composite"); self.host_callbacks.make_current(); @@ -567,11 +592,6 @@ impl WindowMethods for ServoCallbacks { self.host_callbacks.flush(); } - fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> { - debug!("WindowMethods::create_event_loop_waker"); - self.waker.clone() - } - fn gl(&self) -> Rc<dyn gl::Gl> { debug!("WindowMethods::gl"); self.gl.clone() @@ -594,16 +614,6 @@ impl WindowMethods for ServoCallbacks { hidpi_factor: TypedScale::new(self.density), } } - - fn register_vr_services( - &self, - services: &mut VRServiceManager, - _: &mut Vec<Box<VRMainThreadHeartbeat>>, - ) { - if let Some(ptr) = self.vr_pointer { - services.register_vrexternal(VRExternalShmemPtr::new(ptr)); - } - } } struct ResourceReaderInstance; diff --git a/ports/servo/glutin_app/mod.rs b/ports/servo/glutin_app/mod.rs deleted file mode 100644 index 444a1b7796d..00000000000 --- a/ports/servo/glutin_app/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -/* 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/. */ - -//! A simple application that uses glutin to open a window for Servo to display in. - -pub mod keyutils; -pub mod window; - -use servo::servo_config::opts; -use std::rc::Rc; - -pub fn create_window() -> Rc<window::Window> { - // Read command-line options. - let opts = opts::get(); - let foreground = opts.output_file.is_none() && !opts.headless; - - // Open a window. - window::Window::new(foreground, opts.initial_window_size) -} diff --git a/ports/servo/glutin_app/window.rs b/ports/servo/glutin_app/window.rs deleted file mode 100644 index 8c4acf75f93..00000000000 --- a/ports/servo/glutin_app/window.rs +++ /dev/null @@ -1,854 +0,0 @@ -/* 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/. */ - -//! A windowing implementation using winit. - -use euclid::{TypedPoint2D, TypedVector2D, TypedScale, TypedSize2D}; -use gleam::gl; -use glutin::{Api, ContextBuilder, GlContext, GlRequest, GlWindow}; -#[cfg(any(target_os = "linux", target_os = "windows"))] -use image; -use keyboard_types::{Key, KeyboardEvent, KeyState}; -use rust_webvr::GlWindowVRService; -use servo::compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent}; -use servo::compositing::windowing::{EmbedderCoordinates, WindowMethods}; -use servo::embedder_traits::{Cursor, EventLoopWaker}; -use servo::script_traits::TouchEventType; -use servo::servo_config::{opts, pref}; -use servo::servo_geometry::DeviceIndependentPixel; -use servo::style_traits::DevicePixel; -use servo::webrender_api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, FramebufferIntSize, ScrollLocation}; -use servo::webvr::VRServiceManager; -use servo::webvr_traits::WebVRMainThreadHeartbeat; -use std::cell::{Cell, RefCell}; -#[cfg(any(target_os = "linux", target_os = "macos"))] -use std::ffi::CString; -use std::mem; -use std::os::raw::c_void; -use std::ptr; -use std::rc::Rc; -use std::sync::Arc; -use std::thread; -use std::time; -use super::keyutils::keyboard_event_from_winit; -#[cfg(target_os = "windows")] -use winapi; -use glutin::{ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, KeyboardInput}; -#[cfg(any(target_os = "linux", target_os = "windows"))] -use glutin::Icon; -use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; -#[cfg(target_os = "macos")] -use glutin::os::macos::{ActivationPolicy, WindowBuilderExt}; - -// This should vary by zoom level and maybe actual text size (focused or under cursor) -pub const LINE_HEIGHT: f32 = 38.0; - -const MULTISAMPLES: u16 = 16; - -#[cfg(target_os = "macos")] -fn builder_with_platform_options(mut builder: glutin::WindowBuilder) -> glutin::WindowBuilder { - if opts::get().headless || opts::get().output_file.is_some() { - // Prevent the window from showing in Dock.app, stealing focus, - // or appearing at all when running in headless mode or generating an - // output file. - builder = builder.with_activation_policy(ActivationPolicy::Prohibited) - } - builder -} - -#[cfg(not(target_os = "macos"))] -fn builder_with_platform_options(builder: glutin::WindowBuilder) -> glutin::WindowBuilder { - builder -} - -#[cfg(any(target_os = "linux", target_os = "macos"))] -struct HeadlessContext { - width: u32, - height: u32, - _context: osmesa_sys::OSMesaContext, - _buffer: Vec<u32>, -} - -#[cfg(not(any(target_os = "linux", target_os = "macos")))] -struct HeadlessContext { - width: u32, - height: u32, -} - -impl HeadlessContext { - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn new(width: u32, height: u32) -> HeadlessContext { - let mut attribs = Vec::new(); - - attribs.push(osmesa_sys::OSMESA_PROFILE); - attribs.push(osmesa_sys::OSMESA_CORE_PROFILE); - attribs.push(osmesa_sys::OSMESA_CONTEXT_MAJOR_VERSION); - attribs.push(3); - attribs.push(osmesa_sys::OSMESA_CONTEXT_MINOR_VERSION); - attribs.push(3); - attribs.push(0); - - let context = - unsafe { osmesa_sys::OSMesaCreateContextAttribs(attribs.as_ptr(), ptr::null_mut()) }; - - assert!(!context.is_null()); - - let mut buffer = vec![0; (width * height) as usize]; - - unsafe { - let ret = osmesa_sys::OSMesaMakeCurrent( - context, - buffer.as_mut_ptr() as *mut _, - gl::UNSIGNED_BYTE, - width as i32, - height as i32, - ); - assert_ne!(ret, 0); - }; - - HeadlessContext { - width: width, - height: height, - _context: context, - _buffer: buffer, - } - } - - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - fn new(width: u32, height: u32) -> HeadlessContext { - HeadlessContext { - width: width, - height: height, - } - } - - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn get_proc_address(s: &str) -> *const c_void { - let c_str = CString::new(s).expect("Unable to create CString"); - unsafe { mem::transmute(osmesa_sys::OSMesaGetProcAddress(c_str.as_ptr())) } - } - - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - fn get_proc_address(_: &str) -> *const c_void { - ptr::null() as *const _ - } -} - -enum WindowKind { - Window(GlWindow, RefCell<glutin::EventsLoop>), - Headless(HeadlessContext), -} - -/// The type of a window. -pub struct Window { - kind: WindowKind, - screen_size: TypedSize2D<u32, DeviceIndependentPixel>, - inner_size: Cell<TypedSize2D<u32, DeviceIndependentPixel>>, - mouse_down_button: Cell<Option<glutin::MouseButton>>, - mouse_down_point: Cell<TypedPoint2D<i32, DevicePixel>>, - event_queue: RefCell<Vec<WindowEvent>>, - mouse_pos: Cell<TypedPoint2D<i32, DevicePixel>>, - last_pressed: Cell<Option<KeyboardEvent>>, - animation_state: Cell<AnimationState>, - fullscreen: Cell<bool>, - gl: Rc<dyn gl::Gl>, - suspended: Cell<bool>, -} - -#[cfg(not(target_os = "windows"))] -fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { - TypedScale::new(1.0) -} - -#[cfg(target_os = "windows")] -fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { - let hdc = unsafe { winapi::um::winuser::GetDC(::std::ptr::null_mut()) }; - let ppi = unsafe { winapi::um::wingdi::GetDeviceCaps(hdc, winapi::um::wingdi::LOGPIXELSY) }; - TypedScale::new(ppi as f32 / 96.0) -} - -impl Window { - pub fn new( - is_foreground: bool, - window_size: TypedSize2D<u32, DeviceIndependentPixel>, - ) -> Rc<Window> { - let win_size: DeviceIntSize = - (window_size.to_f32() * window_creation_scale_factor()).to_i32(); - let width = win_size.to_untyped().width; - let height = win_size.to_untyped().height; - - // If there's no chrome, start off with the window invisible. It will be set to visible in - // `load_end()`. This avoids an ugly flash of unstyled content (especially important since - // unstyled content is white and chrome often has a transparent background). See issue - // #9996. - let visible = is_foreground && !opts::get().no_native_titlebar; - - let screen_size; - let inner_size; - let window_kind = if opts::get().headless { - screen_size = TypedSize2D::new(width as u32, height as u32); - inner_size = TypedSize2D::new(width as u32, height as u32); - WindowKind::Headless(HeadlessContext::new(width as u32, height as u32)) - } else { - let events_loop = glutin::EventsLoop::new(); - let mut window_builder = glutin::WindowBuilder::new() - .with_title("Servo".to_string()) - .with_decorations(!opts::get().no_native_titlebar) - .with_transparency(opts::get().no_native_titlebar) - .with_dimensions(LogicalSize::new(width as f64, height as f64)) - .with_visibility(visible) - .with_multitouch(); - - window_builder = builder_with_platform_options(window_builder); - - let mut context_builder = ContextBuilder::new() - .with_gl(Window::gl_version()) - .with_vsync(opts::get().enable_vsync); - - if opts::get().use_msaa { - context_builder = context_builder.with_multisampling(MULTISAMPLES) - } - - let glutin_window = GlWindow::new(window_builder, context_builder, &events_loop) - .expect("Failed to create window."); - - #[cfg(any(target_os = "linux", target_os = "windows"))] - { - let icon_bytes = include_bytes!("../../../resources/servo64.png"); - glutin_window.set_window_icon(Some(load_icon(icon_bytes))); - } - - unsafe { - glutin_window - .context() - .make_current() - .expect("Couldn't make window current"); - } - - let PhysicalSize { - width: screen_width, - height: screen_height, - } = events_loop.get_primary_monitor().get_dimensions(); - screen_size = TypedSize2D::new(screen_width as u32, screen_height as u32); - // TODO(ajeffrey): can this fail? - let LogicalSize { width, height } = glutin_window - .get_inner_size() - .expect("Failed to get window inner size."); - inner_size = TypedSize2D::new(width as u32, height as u32); - - glutin_window.show(); - - WindowKind::Window(glutin_window, RefCell::new(events_loop)) - }; - - let gl = match window_kind { - WindowKind::Window(ref window, ..) => match gl::GlType::default() { - gl::GlType::Gl => unsafe { - gl::GlFns::load_with(|s| window.get_proc_address(s) as *const _) - }, - gl::GlType::Gles => unsafe { - gl::GlesFns::load_with(|s| window.get_proc_address(s) as *const _) - }, - }, - WindowKind::Headless(..) => unsafe { - gl::GlFns::load_with(|s| HeadlessContext::get_proc_address(s)) - }, - }; - - if opts::get().headless { - // Print some information about the headless renderer that - // can be useful in diagnosing CI failures on build machines. - println!("{}", gl.get_string(gl::VENDOR)); - println!("{}", gl.get_string(gl::RENDERER)); - println!("{}", gl.get_string(gl::VERSION)); - } - - gl.clear_color(0.6, 0.6, 0.6, 1.0); - gl.clear(gl::COLOR_BUFFER_BIT); - gl.finish(); - - let window = Window { - kind: window_kind, - event_queue: RefCell::new(vec![]), - mouse_down_button: Cell::new(None), - mouse_down_point: Cell::new(TypedPoint2D::new(0, 0)), - mouse_pos: Cell::new(TypedPoint2D::new(0, 0)), - last_pressed: Cell::new(None), - gl: gl.clone(), - animation_state: Cell::new(AnimationState::Idle), - fullscreen: Cell::new(false), - inner_size: Cell::new(inner_size), - screen_size, - suspended: Cell::new(false), - }; - - window.present(); - - Rc::new(window) - } - - pub fn get_events(&self) -> Vec<WindowEvent> { - mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new()) - } - - pub fn page_height(&self) -> f32 { - let dpr = self.servo_hidpi_factor(); - match self.kind { - WindowKind::Window(ref window, _) => { - let size = window - .get_inner_size() - .expect("Failed to get window inner size."); - 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: DeviceIntSize) { - if let WindowKind::Window(ref window, _) = self.kind { - let size = size.to_f32() / self.device_hidpi_factor(); - window.set_inner_size(LogicalSize::new(size.width.into(), size.height.into())) - } - } - - pub fn set_position(&self, point: DeviceIntPoint) { - if let WindowKind::Window(ref window, _) = self.kind { - let point = point.to_f32() / self.device_hidpi_factor(); - window.set_position(LogicalPosition::new(point.x.into(), point.y.into())) - } - } - - pub fn set_fullscreen(&self, state: bool) { - match self.kind { - WindowKind::Window(ref window, ..) => { - if self.fullscreen.get() != state { - window.set_fullscreen(Some(window.get_primary_monitor())); - } - }, - WindowKind::Headless(..) => {}, - } - self.fullscreen.set(state); - } - - pub fn get_fullscreen(&self) -> bool { - return self.fullscreen.get(); - } - - fn is_animating(&self) -> bool { - self.animation_state.get() == AnimationState::Animating && !self.suspended.get() - } - - pub fn run<T>(&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.winit_event_to_servo_event(e); - }); - stop = servo_callback(); - } else { - // We block on winit's event loop (window events) - events_loop.borrow_mut().run_forever(|e| { - self.winit_event_to_servo_event(e); - if !self.event_queue.borrow().is_empty() { - if !self.suspended.get() { - stop = servo_callback(); - } - } - if stop || self.is_animating() { - glutin::ControlFlow::Break - } else { - glutin::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)); - } - - #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] - fn gl_version() -> GlRequest { - GlRequest::Specific(Api::OpenGlEs, (3, 0)) - } - - fn handle_received_character(&self, mut ch: char) { - info!("winit received character: {:?}", ch); - if ch.is_control() { - if ch as u8 >= 32 { - return; - } - // shift ASCII control characters to lowercase - ch = (ch as u8 + 96) as char; - } - let mut event = if let Some(event) = self.last_pressed.replace(None) { - event - } else if ch.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. - return - } else { - // For combined characters like the letter e with an acute accent - // no keyboard event is emitted. A dummy event is created in this case. - KeyboardEvent::default() - }; - event.key = Key::Character(ch.to_string()); - self.event_queue - .borrow_mut() - .push(WindowEvent::Keyboard(event)); - } - - fn handle_keyboard_input( - &self, - input: KeyboardInput, - ) { - let event = keyboard_event_from_winit(input); - if event.state == KeyState::Down && event.key == Key::Unidentified { - // If pressed and probably printable, we expect a ReceivedCharacter event. - self.last_pressed.set(Some(event)); - } else if event.key != Key::Unidentified { - self.last_pressed.set(None); - self.event_queue - .borrow_mut() - .push(WindowEvent::Keyboard(event)); - } - } - - fn winit_event_to_servo_event(&self, event: glutin::Event) { - if let WindowKind::Window(ref window, _) = self.kind { - if let Event::WindowEvent { window_id, .. } = event { - if window.id() != window_id { - return; - } - } - } - match event { - Event::WindowEvent { - event: glutin::WindowEvent::ReceivedCharacter(ch), - .. - } => self.handle_received_character(ch), - Event::WindowEvent { - event: - glutin::WindowEvent::KeyboardInput { - input, - .. - }, - .. - } => self.handle_keyboard_input(input), - Event::WindowEvent { - event: glutin::WindowEvent::MouseInput { state, button, .. }, - .. - } => { - if button == MouseButton::Left || button == MouseButton::Right { - self.handle_mouse(button, state, self.mouse_pos.get()); - } - }, - Event::WindowEvent { - event: glutin::WindowEvent::CursorMoved { position, .. }, - .. - } => { - let pos = position.to_physical(self.device_hidpi_factor().get() as f64); - let (x, y): (i32, i32) = pos.into(); - self.mouse_pos.set(TypedPoint2D::new(x, y)); - self.event_queue - .borrow_mut() - .push(WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new( - x as f32, y as f32, - ))); - }, - Event::WindowEvent { - event: glutin::WindowEvent::MouseWheel { delta, phase, .. }, - .. - } => { - let (mut dx, mut dy) = match delta { - MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT), - MouseScrollDelta::PixelDelta(position) => { - let position = - position.to_physical(self.device_hidpi_factor().get() as f64); - (position.x as f32, position.y as f32) - }, - }; - // 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 = winit_phase_to_touch_event_type(phase); - let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase); - self.event_queue.borrow_mut().push(event); - }, - Event::WindowEvent { - event: glutin::WindowEvent::Touch(touch), - .. - } => { - use servo::script_traits::TouchId; - - let phase = winit_phase_to_touch_event_type(touch.phase); - let id = TouchId(touch.id as i32); - let position = touch - .location - .to_physical(self.device_hidpi_factor().get() as f64); - let point = TypedPoint2D::new(position.x as f32, position.y as f32); - self.event_queue - .borrow_mut() - .push(WindowEvent::Touch(phase, id, point)); - }, - Event::WindowEvent { - event: glutin::WindowEvent::Refresh, - .. - } => self.event_queue.borrow_mut().push(WindowEvent::Refresh), - Event::WindowEvent { - event: glutin::WindowEvent::CloseRequested, - .. - } => { - self.event_queue.borrow_mut().push(WindowEvent::Quit); - }, - Event::WindowEvent { - event: glutin::WindowEvent::Resized(size), - .. - } => { - // size is DeviceIndependentPixel. - // window.resize() takes DevicePixel. - if let WindowKind::Window(ref window, _) = self.kind { - let size = size.to_physical(self.device_hidpi_factor().get() as f64); - window.resize(size); - } - // window.set_inner_size() takes DeviceIndependentPixel. - let (width, height) = size.into(); - let new_size = TypedSize2D::new(width, height); - if self.inner_size.get() != new_size { - self.inner_size.set(new_size); - self.event_queue.borrow_mut().push(WindowEvent::Resize); - } - }, - Event::Suspended(suspended) => { - self.suspended.set(suspended); - if !suspended { - self.event_queue.borrow_mut().push(WindowEvent::Idle); - } - }, - Event::Awakened => { - self.event_queue.borrow_mut().push(WindowEvent::Idle); - }, - _ => {}, - } - } - - /// Helper function to handle a click - fn handle_mouse( - &self, - button: glutin::MouseButton, - action: glutin::ElementState, - coords: TypedPoint2D<i32, DevicePixel>, - ) { - use servo::script_traits::MouseButton; - - let max_pixel_dist = 10.0 * self.servo_hidpi_factor().get(); - let event = match action { - ElementState::Pressed => { - self.mouse_down_point.set(coords); - self.mouse_down_button.set(Some(button)); - MouseWindowEvent::MouseDown(MouseButton::Left, coords.to_f32()) - }, - ElementState::Released => { - let mouse_up_event = MouseWindowEvent::MouseUp(MouseButton::Left, coords.to_f32()); - match self.mouse_down_button.get() { - None => mouse_up_event, - Some(but) if button == but => { - let pixel_dist = self.mouse_down_point.get() - coords; - let pixel_dist = - ((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(WindowEvent::MouseWindowEventClass(mouse_up_event)); - MouseWindowEvent::Click(MouseButton::Left, coords.to_f32()) - } else { - mouse_up_event - } - }, - Some(_) => mouse_up_event, - } - }, - }; - self.event_queue - .borrow_mut() - .push(WindowEvent::MouseWindowEventClass(event)); - } - - fn device_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { - match self.kind { - WindowKind::Window(ref window, ..) => TypedScale::new(window.get_hidpi_factor() as f32), - WindowKind::Headless(..) => TypedScale::new(1.0), - } - } - - fn servo_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> { - match opts::get().device_pixels_per_px { - Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px), - _ => match opts::get().output_file { - Some(_) => TypedScale::new(1.0), - None => self.device_hidpi_factor(), - }, - } - } - - pub fn set_cursor(&self, cursor: Cursor) { - match self.kind { - WindowKind::Window(ref window, ..) => { - use glutin::MouseCursor; - - let winit_cursor = match cursor { - Cursor::Default => MouseCursor::Default, - Cursor::Pointer => MouseCursor::Hand, - Cursor::ContextMenu => MouseCursor::ContextMenu, - Cursor::Help => MouseCursor::Help, - Cursor::Progress => MouseCursor::Progress, - Cursor::Wait => MouseCursor::Wait, - Cursor::Cell => MouseCursor::Cell, - Cursor::Crosshair => MouseCursor::Crosshair, - Cursor::Text => MouseCursor::Text, - Cursor::VerticalText => MouseCursor::VerticalText, - Cursor::Alias => MouseCursor::Alias, - Cursor::Copy => MouseCursor::Copy, - Cursor::Move => MouseCursor::Move, - Cursor::NoDrop => MouseCursor::NoDrop, - Cursor::NotAllowed => MouseCursor::NotAllowed, - Cursor::Grab => MouseCursor::Grab, - Cursor::Grabbing => MouseCursor::Grabbing, - Cursor::EResize => MouseCursor::EResize, - Cursor::NResize => MouseCursor::NResize, - Cursor::NeResize => MouseCursor::NeResize, - Cursor::NwResize => MouseCursor::NwResize, - Cursor::SResize => MouseCursor::SResize, - Cursor::SeResize => MouseCursor::SeResize, - Cursor::SwResize => MouseCursor::SwResize, - Cursor::WResize => MouseCursor::WResize, - Cursor::EwResize => MouseCursor::EwResize, - Cursor::NsResize => MouseCursor::NsResize, - Cursor::NeswResize => MouseCursor::NeswResize, - Cursor::NwseResize => MouseCursor::NwseResize, - Cursor::ColResize => MouseCursor::ColResize, - Cursor::RowResize => MouseCursor::RowResize, - Cursor::AllScroll => MouseCursor::AllScroll, - Cursor::ZoomIn => MouseCursor::ZoomIn, - Cursor::ZoomOut => MouseCursor::ZoomOut, - _ => MouseCursor::Default, - }; - window.set_cursor(winit_cursor); - }, - WindowKind::Headless(..) => {}, - } - } -} - -impl WindowMethods for Window { - fn gl(&self) -> Rc<dyn gl::Gl> { - self.gl.clone() - } - - fn get_coordinates(&self) -> EmbedderCoordinates { - match self.kind { - WindowKind::Window(ref window, _) => { - // TODO(ajeffrey): can this fail? - let dpr = self.device_hidpi_factor(); - let LogicalSize { width, height } = window - .get_outer_size() - .expect("Failed to get window outer size."); - let LogicalPosition { x, y } = window - .get_position() - .unwrap_or(LogicalPosition::new(0., 0.)); - let win_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32(); - let win_origin = (TypedPoint2D::new(x as f32, y as f32) * dpr).to_i32(); - let screen = (self.screen_size.to_f32() * dpr).to_i32(); - - let LogicalSize { width, height } = window - .get_inner_size() - .expect("Failed to get window inner size."); - let inner_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_i32(); - let viewport = DeviceIntRect::new(TypedPoint2D::zero(), inner_size); - let framebuffer = FramebufferIntSize::from_untyped(&viewport.size.to_untyped()); - - EmbedderCoordinates { - viewport, - framebuffer, - window: (win_size, win_origin), - screen: screen, - // FIXME: Glutin doesn't have API for available size. Fallback to screen size - screen_avail: screen, - hidpi_factor: self.servo_hidpi_factor(), - } - }, - WindowKind::Headless(ref context) => { - let dpr = self.servo_hidpi_factor(); - let size = - (TypedSize2D::new(context.width, context.height).to_f32() * dpr).to_i32(); - let viewport = DeviceIntRect::new(TypedPoint2D::zero(), size); - let framebuffer = FramebufferIntSize::from_untyped(&size.to_untyped()); - EmbedderCoordinates { - viewport, - framebuffer, - window: (size, TypedPoint2D::zero()), - screen: size, - screen_avail: size, - hidpi_factor: dpr, - } - }, - } - } - - fn present(&self) { - match self.kind { - WindowKind::Window(ref window, ..) => { - if let Err(err) = window.swap_buffers() { - warn!("Failed to swap window buffers ({}).", err); - } - }, - WindowKind::Headless(..) => {}, - } - } - - fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> { - struct GlutinEventLoopWaker { - proxy: Option<Arc<glutin::EventsLoopProxy>>, - } - impl GlutinEventLoopWaker { - fn new(window: &Window) -> GlutinEventLoopWaker { - let proxy = match window.kind { - WindowKind::Window(_, ref events_loop) => { - Some(Arc::new(events_loop.borrow().create_proxy())) - }, - WindowKind::Headless(..) => None, - }; - GlutinEventLoopWaker { proxy } - } - } - impl EventLoopWaker for GlutinEventLoopWaker { - fn wake(&self) { - // kick the OS event loop awake. - if let Some(ref proxy) = self.proxy { - if let Err(err) = proxy.wakeup() { - warn!("Failed to wake up event loop ({}).", err); - } - } - } - fn clone(&self) -> Box<dyn EventLoopWaker + Send> { - Box::new(GlutinEventLoopWaker { - proxy: self.proxy.clone(), - }) - } - } - - Box::new(GlutinEventLoopWaker::new(&self)) - } - - fn set_animation_state(&self, state: AnimationState) { - self.animation_state.set(state); - } - - fn prepare_for_composite(&self) -> bool { - if let WindowKind::Window(ref window, ..) = self.kind { - if let Err(err) = unsafe { window.context().make_current() } { - warn!("Couldn't make window current: {}", err); - } - }; - true - } - - fn register_vr_services( - &self, - services: &mut VRServiceManager, - heartbeats: &mut Vec<Box<WebVRMainThreadHeartbeat>> - ) { - if pref!(dom.webvr.test) { - warn!("Creating test VR display"); - // TODO: support dom.webvr.test in headless environments - if let WindowKind::Window(_, ref events_loop) = self.kind { - // This is safe, because register_vr_services is called from the main thread. - let name = String::from("Test VR Display"); - let size = self.inner_size.get().to_f64(); - let size = LogicalSize::new(size.width, size.height); - let mut window_builder = glutin::WindowBuilder::new() - .with_title(name.clone()) - .with_dimensions(size) - .with_visibility(false) - .with_multitouch(); - window_builder = builder_with_platform_options(window_builder); - let context_builder = ContextBuilder::new() - .with_gl(Window::gl_version()) - .with_vsync(false); // Assume the browser vsync is the same as the test VR window vsync - let gl_window = GlWindow::new(window_builder, context_builder, &*events_loop.borrow()) - .expect("Failed to create window."); - let gl = self.gl.clone(); - let (service, heartbeat) = GlWindowVRService::new(name, gl_window, gl); - - services.register(Box::new(service)); - heartbeats.push(Box::new(heartbeat)); - } - } - } -} - -fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType { - match phase { - TouchPhase::Started => TouchEventType::Down, - TouchPhase::Moved => TouchEventType::Move, - TouchPhase::Ended => TouchEventType::Up, - TouchPhase::Cancelled => TouchEventType::Cancel, - } -} - -#[cfg(any(target_os = "linux", target_os = "windows"))] -fn load_icon(icon_bytes: &[u8]) -> Icon { - let (icon_rgba, icon_width, icon_height) = { - use image::{GenericImageView, Pixel}; - let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");; - let (width, height) = image.dimensions(); - let mut rgba = Vec::with_capacity((width * height) as usize * 4); - for (_, _, pixel) in image.pixels() { - rgba.extend_from_slice(&pixel.to_rgba().data); - } - (rgba, width, height) - }; - Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon") -} diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index d516f1639dc..512fdfe4977 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -761,4 +761,4 @@ class MachCommands(CommandBase): opts += ["-v"] opts += params return check_call(["cargo", "clean"] + opts, - env=self.build_env(), cwd=self.ports_servo_crate(), verbose=verbose) + env=self.build_env(), cwd=self.ports_glutin_crate(), verbose=verbose) diff --git a/python/servo/command_base.py b/python/servo/command_base.py index 2d159df3773..ac9789ca389 100644 --- a/python/servo/command_base.py +++ b/python/servo/command_base.py @@ -723,20 +723,20 @@ install them, let us know by filing a bug!") return env - def ports_servo_crate(self): - return path.join(self.context.topdir, "ports", "servo") + def ports_glutin_crate(self): + return path.join(self.context.topdir, "ports", "glutin") def add_manifest_path(self, args, android=False, libsimpleservo=False): if "--manifest-path" not in args: if libsimpleservo or android: manifest = self.ports_libsimpleservo_manifest(android) else: - manifest = self.ports_servo_manifest() + manifest = self.ports_glutin_manifest() args.append("--manifest-path") args.append(manifest) - def ports_servo_manifest(self): - return path.join(self.context.topdir, "ports", "servo", "Cargo.toml") + def ports_glutin_manifest(self): + return path.join(self.context.topdir, "ports", "glutin", "Cargo.toml") def ports_libsimpleservo_manifest(self, android=False): if android: diff --git a/python/servo/post_build_commands.py b/python/servo/post_build_commands.py index 4050f33f932..4d27475f74b 100644 --- a/python/servo/post_build_commands.py +++ b/python/servo/post_build_commands.py @@ -265,7 +265,7 @@ class PostBuildCommands(CommandBase): copy2(full_name, destination) returncode = self.call_rustup_run( - ["cargo", "doc", "--manifest-path", self.ports_servo_manifest()] + params, + ["cargo", "doc", "--manifest-path", self.ports_glutin_manifest()] + params, env=self.build_env()) if returncode: return returncode diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py index c8bb5e7511c..f4b324d4c62 100644 --- a/python/servo/testing_commands.py +++ b/python/servo/testing_commands.py @@ -279,7 +279,7 @@ class MachCommands(CommandBase): features = self.servo_features() if len(packages) > 0 or len(in_crate_packages) > 0: - args = ["cargo", "bench" if bench else "test", "--manifest-path", self.ports_servo_manifest()] + args = ["cargo", "bench" if bench else "test", "--manifest-path", self.ports_glutin_manifest()] for crate in packages: args += ["-p", "%s_tests" % crate] for crate in in_crate_packages: |