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 -->
This commit is contained in:
bors-servo 2019-04-29 10:45:29 -04:00 committed by GitHub
commit a9f7b13230
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1287 additions and 1067 deletions

View file

@ -1,6 +1,6 @@
[workspace] [workspace]
members = [ members = [
"ports/servo", "ports/glutin",
"ports/libsimpleservo/capi/", "ports/libsimpleservo/capi/",
"ports/libsimpleservo/jniapi/", "ports/libsimpleservo/jniapi/",
"ports/libmlservo/", "ports/libmlservo/",

View file

@ -103,7 +103,7 @@ impl FrameTreeId {
enum LayerPixel {} enum LayerPixel {}
/// NB: Never block on the constellation, because sometimes the constellation blocks on us. /// 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. /// The application window.
pub window: Rc<Window>, pub window: Rc<Window>,
@ -258,7 +258,7 @@ enum CompositeTarget {
PngFile, PngFile,
} }
impl<Window: WindowMethods> IOCompositor<Window> { impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
fn new(window: Rc<Window>, state: InitialCompositorState) -> Self { fn new(window: Rc<Window>, state: InitialCompositorState) -> Self {
let composite_target = match opts::get().output_file { let composite_target = match opts::get().output_file {
Some(_) => CompositeTarget::PngFile, Some(_) => CompositeTarget::PngFile,

View file

@ -148,8 +148,6 @@ pub trait WindowMethods {
/// Return the GL function pointer trait. /// Return the GL function pointer trait.
#[cfg(feature = "gl")] #[cfg(feature = "gl")]
fn gl(&self) -> Rc<dyn gl::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. /// Get the coordinates of the native window, the screen and the framebuffer.
fn get_coordinates(&self) -> EmbedderCoordinates; fn get_coordinates(&self) -> EmbedderCoordinates;
/// Set whether the application is currently animating. /// Set whether the application is currently animating.
@ -157,6 +155,11 @@ pub trait WindowMethods {
/// will want to avoid blocking on UI events, and just /// will want to avoid blocking on UI events, and just
/// run the event loop at the vsync interval. /// run the event loop at the vsync interval.
fn set_animation_state(&self, _state: AnimationState); 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. /// Register services with a VRServiceManager.
fn register_vr_services( fn register_vr_services(
&self, &self,

View file

@ -67,7 +67,7 @@ use canvas::webgl_thread::WebGLThreads;
use compositing::compositor_thread::{ use compositing::compositor_thread::{
CompositorProxy, CompositorReceiver, InitialCompositorState, Msg, CompositorProxy, CompositorReceiver, InitialCompositorState, Msg,
}; };
use compositing::windowing::{WindowEvent, WindowMethods}; use compositing::windowing::{EmbedderMethods, WindowEvent, WindowMethods};
use compositing::{CompositingReason, IOCompositor, ShutdownState}; use compositing::{CompositingReason, IOCompositor, ShutdownState};
#[cfg(all( #[cfg(all(
not(target_os = "windows"), not(target_os = "windows"),
@ -148,7 +148,7 @@ type MediaBackend = media_platform::MediaBackend;
/// application Servo is embedded in. Clients then create an event /// application Servo is embedded in. Clients then create an event
/// loop to pump messages between the embedding application and /// loop to pump messages between the embedding application and
/// various browser components. /// various browser components.
pub struct Servo<Window: WindowMethods + 'static> { pub struct Servo<Window: WindowMethods + 'static + ?Sized> {
compositor: IOCompositor<Window>, compositor: IOCompositor<Window>,
constellation_chan: Sender<ConstellationMsg>, constellation_chan: Sender<ConstellationMsg>,
embedder_receiver: EmbedderReceiver, embedder_receiver: EmbedderReceiver,
@ -197,9 +197,9 @@ impl webrender_api::RenderNotifier for RenderNotifier {
impl<Window> Servo<Window> impl<Window> Servo<Window>
where 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. // Global configuration options, parsed from the command line.
let opts = opts::get(); let opts = opts::get();
@ -218,9 +218,9 @@ where
// messages to client may need to pump a platform-specific event loop // messages to client may need to pump a platform-specific event loop
// to deliver the message. // to deliver the message.
let (compositor_proxy, compositor_receiver) = 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) = 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( let time_profiler_chan = profile_time::Profiler::create(
&opts.time_profiling, &opts.time_profiling,
opts.time_profiler_trace_path.clone(), opts.time_profiler_trace_path.clone(),
@ -288,7 +288,7 @@ where
let webvr_services = if pref!(dom.webvr.enabled) { let webvr_services = if pref!(dom.webvr.enabled) {
let mut services = VRServiceManager::new(); let mut services = VRServiceManager::new();
services.register_defaults(); services.register_defaults();
window.register_vr_services(&mut services, &mut webvr_heartbeats); embedder.register_vr_services(&mut services, &mut webvr_heartbeats);
Some(services) Some(services)
} else { } else {
None None

View file

@ -17,8 +17,9 @@ cd "$(git rev-parse --show-toplevel)"
PATHS=( PATHS=(
"components/compositing/compositor.rs" "components/compositing/compositor.rs"
"components/constellation/" "components/constellation/"
"ports/servo/glutin_app/mod.rs" "ports/glutin/headed_window.rs"
"ports/servo/glutin_app/window.rs" "ports/glutin/headless_window.rs"
"ports/glutin/embedder.rs"
) )
# Make sure the paths exist # Make sure the paths exist

195
ports/glutin/app.rs Normal file
View file

@ -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))
}

View file

@ -2,13 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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::keyutils::{CMD_OR_ALT, CMD_OR_CONTROL};
use crate::glutin_app::window::{Window, LINE_HEIGHT}; use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT};
use euclid::{TypedPoint2D, TypedVector2D}; use euclid::{TypedPoint2D, TypedVector2D};
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher}; use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent}; use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent};
use servo::embedder_traits::{EmbedderMsg, FilterPattern}; 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::msg::constellation_msg::TraversalDirection;
use servo::net_traits::pub_domains::is_reg_domain; use servo::net_traits::pub_domains::is_reg_domain;
use servo::script_traits::TouchEventType; use servo::script_traits::TouchEventType;
@ -25,7 +25,7 @@ use std::thread;
use std::time::Duration; use std::time::Duration;
use tinyfiledialogs::{self, MessageBoxIcon}; use tinyfiledialogs::{self, MessageBoxIcon};
pub struct Browser { pub struct Browser<Window: WindowPortsMethods + ?Sized> {
current_url: Option<ServoUrl>, current_url: Option<ServoUrl>,
/// id of the top level browsing context. It is unique as tabs /// id of the top level browsing context. It is unique as tabs
/// are not supported yet. None until created. /// are not supported yet. None until created.
@ -52,8 +52,11 @@ enum LoadingState {
Loaded, Loaded,
} }
impl Browser { impl<Window> Browser<Window>
pub fn new(window: Rc<Window>) -> Browser { where
Window: WindowPortsMethods + ?Sized,
{
pub fn new(window: Rc<Window>) -> Browser<Window> {
Browser { Browser {
title: None, title: None,
current_url: None, current_url: None,
@ -126,7 +129,9 @@ impl Browser {
.and_then(|s| s.parse().ok()) .and_then(|s| s.parse().ok())
.unwrap_or(10); .unwrap_or(10);
self.event_queue.push(WindowEvent::ToggleSamplingProfiler( 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, || { .shortcut(Modifiers::CONTROL, Key::F9, || {
self.event_queue.push(WindowEvent::CaptureWebRender) self.event_queue.push(WindowEvent::CaptureWebRender)
@ -403,14 +408,12 @@ impl Browser {
debug!("HideIME received"); debug!("HideIME received");
}, },
EmbedderMsg::ReportProfile(bytes) => { EmbedderMsg::ReportProfile(bytes) => {
let filename = env::var("PROFILE_OUTPUT") let filename = env::var("PROFILE_OUTPUT").unwrap_or("samples.json".to_string());
.unwrap_or("samples.json".to_string()); let result = File::create(&filename).and_then(|mut f| f.write_all(&bytes));
let result = File::create(&filename)
.and_then(|mut f| f.write_all(&bytes));
if let Err(e) = result { if let Err(e) = result {
error!("Failed to store profile: {}", e); error!("Failed to store profile: {}", e);
} }
} },
} }
} }
} }

71
ports/glutin/embedder.rs Normal file
View file

@ -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
}
}
}

View file

@ -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) }
}

View file

@ -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")
}

View file

@ -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
}
}

View file

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 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. // Some shortcuts use Cmd on Mac and Control on other systems.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View file

@ -17,14 +17,8 @@
#![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![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"))] #[cfg(not(target_os = "android"))]
#[macro_use] include!("main2.rs");
extern crate log;
#[cfg(not(target_os = "android"))]
include!("non_android_main.rs");
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub fn main() { pub fn main() {

View file

@ -2,24 +2,29 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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")))] #[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))]
#[macro_use] extern crate sig; #[macro_use]
extern crate sig;
// The window backed by glutin
mod glutin_app;
mod resources;
mod app;
mod browser; 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 backtrace::Backtrace;
use servo::{Servo, BrowserId}; use servo::config::opts::{self, ArgumentParsingResult};
use servo::compositing::windowing::WindowEvent;
use servo::config::opts::{self, ArgumentParsingResult, parse_url_or_filename};
use servo::config::servo_version; use servo::config::servo_version;
use servo::servo_config::pref;
use servo::servo_url::ServoUrl;
use std::env; use std::env;
use std::panic; use std::panic;
use std::process; use std::process;
@ -36,7 +41,10 @@ pub mod platform {
pub fn deinit() {} 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() {} fn install_crash_handler() {}
#[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))] #[cfg(all(feature = "unstable", any(target_os = "macos", target_os = "linux")))]
@ -122,112 +130,7 @@ pub fn main() {
process::exit(0); process::exit(0);
} }
let window = glutin_app::create_window(); App::run();
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();
platform::deinit() 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!()
}

View file

@ -14,18 +14,17 @@ pub fn deinit() {
ptr::read_volatile(&INFO_PLIST[0]); ptr::read_volatile(&INFO_PLIST[0]);
} }
let thread_count = unsafe { let thread_count = unsafe { macos_count_running_threads() };
macos_count_running_threads()
};
if thread_count != 1 { 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 { if opts::get().clean_shutdown {
println!("Waiting until all threads have shutdown"); println!("Waiting until all threads have shutdown");
loop { loop {
let thread_count = unsafe { let thread_count = unsafe { macos_count_running_threads() };
macos_count_running_threads()
};
if thread_count == 1 { if thread_count == 1 {
break; break;
} }
@ -43,6 +42,6 @@ pub fn deinit() {
pub static INFO_PLIST: [u8; 619] = *include_bytes!("Info.plist"); pub static INFO_PLIST: [u8; 619] = *include_bytes!("Info.plist");
#[link(name = "count_threads")] #[link(name = "count_threads")]
extern { extern "C" {
fn macos_count_running_threads() -> i32; fn macos_count_running_threads() -> i32;
} }

View file

@ -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!()
}

View file

@ -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) {}
}

View file

@ -10,7 +10,8 @@ pub mod gl_glue;
pub use servo::script_traits::MouseButton; pub use servo::script_traits::MouseButton;
use servo::compositing::windowing::{ 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::resources::{self, Resource, ResourceReaderMethods};
use servo::embedder_traits::EmbedderMsg; use servo::embedder_traits::EmbedderMsg;
@ -112,9 +113,9 @@ pub trait HostTrait {
} }
pub struct ServoGlue { pub struct ServoGlue {
servo: Servo<ServoCallbacks>, servo: Servo<ServoWindowCallbacks>,
batch_mode: bool, batch_mode: bool,
callbacks: Rc<ServoCallbacks>, callbacks: Rc<ServoWindowCallbacks>,
/// id of the top level browsing context. It is unique as tabs /// id of the top level browsing context. It is unique as tabs
/// are not supported yet. None until created. /// are not supported yet. None until created.
browser_id: Option<BrowserId>, browser_id: Option<BrowserId>,
@ -169,22 +170,25 @@ pub fn init(
gl.clear(gl::COLOR_BUFFER_BIT); gl.clear(gl::COLOR_BUFFER_BIT);
gl.finish(); gl.finish();
let callbacks = Rc::new(ServoCallbacks { let window_callbacks = Rc::new(ServoWindowCallbacks {
gl: gl.clone(), gl: gl.clone(),
host_callbacks: callbacks, host_callbacks: callbacks,
coordinates: RefCell::new(init_opts.coordinates), coordinates: RefCell::new(init_opts.coordinates),
density: init_opts.density, density: init_opts.density,
});
let embedder_callbacks = Box::new(ServoEmbedderCallbacks {
vr_pointer: init_opts.vr_pointer, vr_pointer: init_opts.vr_pointer,
waker, waker,
}); });
let servo = Servo::new(callbacks.clone()); let servo = Servo::new(embedder_callbacks, window_callbacks.clone());
SERVO.with(|s| { SERVO.with(|s| {
let mut servo_glue = ServoGlue { let mut servo_glue = ServoGlue {
servo, servo,
batch_mode: false, batch_mode: false,
callbacks, callbacks: window_callbacks,
browser_id: None, browser_id: None,
browsers: vec![], browsers: vec![],
events: vec![], events: vec![],
@ -546,16 +550,37 @@ impl ServoGlue {
} }
} }
struct ServoCallbacks { struct ServoEmbedderCallbacks {
waker: Box<dyn EventLoopWaker>, waker: Box<dyn EventLoopWaker>,
vr_pointer: Option<*mut c_void>,
}
struct ServoWindowCallbacks {
gl: Rc<dyn gl::Gl>, gl: Rc<dyn gl::Gl>,
host_callbacks: Box<dyn HostTrait>, host_callbacks: Box<dyn HostTrait>,
coordinates: RefCell<Coordinates>, coordinates: RefCell<Coordinates>,
density: f32, 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 { fn prepare_for_composite(&self) -> bool {
debug!("WindowMethods::prepare_for_composite"); debug!("WindowMethods::prepare_for_composite");
self.host_callbacks.make_current(); self.host_callbacks.make_current();
@ -567,11 +592,6 @@ impl WindowMethods for ServoCallbacks {
self.host_callbacks.flush(); 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> { fn gl(&self) -> Rc<dyn gl::Gl> {
debug!("WindowMethods::gl"); debug!("WindowMethods::gl");
self.gl.clone() self.gl.clone()
@ -594,16 +614,6 @@ impl WindowMethods for ServoCallbacks {
hidpi_factor: TypedScale::new(self.density), 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; struct ResourceReaderInstance;

View file

@ -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)
}

View file

@ -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")
}

View file

@ -761,4 +761,4 @@ class MachCommands(CommandBase):
opts += ["-v"] opts += ["-v"]
opts += params opts += params
return check_call(["cargo", "clean"] + opts, 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)

View file

@ -723,20 +723,20 @@ install them, let us know by filing a bug!")
return env return env
def ports_servo_crate(self): def ports_glutin_crate(self):
return path.join(self.context.topdir, "ports", "servo") return path.join(self.context.topdir, "ports", "glutin")
def add_manifest_path(self, args, android=False, libsimpleservo=False): def add_manifest_path(self, args, android=False, libsimpleservo=False):
if "--manifest-path" not in args: if "--manifest-path" not in args:
if libsimpleservo or android: if libsimpleservo or android:
manifest = self.ports_libsimpleservo_manifest(android) manifest = self.ports_libsimpleservo_manifest(android)
else: else:
manifest = self.ports_servo_manifest() manifest = self.ports_glutin_manifest()
args.append("--manifest-path") args.append("--manifest-path")
args.append(manifest) args.append(manifest)
def ports_servo_manifest(self): def ports_glutin_manifest(self):
return path.join(self.context.topdir, "ports", "servo", "Cargo.toml") return path.join(self.context.topdir, "ports", "glutin", "Cargo.toml")
def ports_libsimpleservo_manifest(self, android=False): def ports_libsimpleservo_manifest(self, android=False):
if android: if android:

View file

@ -265,7 +265,7 @@ class PostBuildCommands(CommandBase):
copy2(full_name, destination) copy2(full_name, destination)
returncode = self.call_rustup_run( 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()) env=self.build_env())
if returncode: if returncode:
return returncode return returncode

View file

@ -279,7 +279,7 @@ class MachCommands(CommandBase):
features = self.servo_features() features = self.servo_features()
if len(packages) > 0 or len(in_crate_packages) > 0: 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: for crate in packages:
args += ["-p", "%s_tests" % crate] args += ["-p", "%s_tests" % crate]
for crate in in_crate_packages: for crate in in_crate_packages: