diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2019-04-29 10:45:29 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-29 10:45:29 -0400 |
commit | a9f7b132303f7c02648043df5ebdc25726bd3f88 (patch) | |
tree | eee887c8856ce5a5973e2b1650c6a9bccb7bebe6 | |
parent | 799490a02e9bea575bf34c39f045ef0883539f05 (diff) | |
parent | 21ed7653f491d9e378aa7a334b40e7327f4bfc90 (diff) | |
download | servo-a9f7b132303f7c02648043df5ebdc25726bd3f88.tar.gz servo-a9f7b132303f7c02648043df5ebdc25726bd3f88.zip |
Auto merge of #23233 - paulrouget:glutin-port-refactoring, r=jdm
Glutin port refactoring
Glutin port refactoring in preparation for the compositor and libservo refactoring.
In theory, the only behavior change is for headless mode. The headless event loop now uses winit's event loop (but still headless).
Notes:
- headless and glutin window implementations are now separated
- I split the methods of the embedder in 2: window specific and general methods. In the future, we still want the app to run even without a window or with multiple windows
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23233)
<!-- Reviewable:end -->
-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: |