Refactoring of the Glutin port in preparation of the compositor refactoring.

This commit is contained in:
Paul Rouget 2019-04-17 10:18:14 +02:00
parent d58ea974ba
commit 21ed7653f4
30 changed files with 1287 additions and 1067 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

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
* 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);
}
}
},
}
}
}

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
* 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")]

View file

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

View file

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

View file

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

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;
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;

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 += 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)

View file

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

View file

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

View file

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