mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
559 lines
18 KiB
Rust
559 lines
18 KiB
Rust
/* 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/. */
|
|
|
|
use egl::egl::EGLContext;
|
|
use egl::egl::EGLDisplay;
|
|
use egl::egl::EGLSurface;
|
|
use egl::egl::MakeCurrent;
|
|
use egl::egl::SwapBuffers;
|
|
use egl::eglext::eglGetProcAddress;
|
|
use keyboard_types::Key;
|
|
use keyboard_types::KeyState;
|
|
use keyboard_types::KeyboardEvent;
|
|
use log::info;
|
|
use log::warn;
|
|
use servo::compositing::windowing::AnimationState;
|
|
use servo::compositing::windowing::EmbedderCoordinates;
|
|
use servo::compositing::windowing::MouseWindowEvent;
|
|
use servo::compositing::windowing::WindowEvent;
|
|
use servo::compositing::windowing::WindowMethods;
|
|
use servo::embedder_traits::resources::Resource;
|
|
use servo::embedder_traits::resources::ResourceReaderMethods;
|
|
use servo::embedder_traits::EmbedderMsg;
|
|
use servo::embedder_traits::EventLoopWaker;
|
|
use servo::euclid::TypedPoint2D;
|
|
use servo::euclid::TypedRect;
|
|
use servo::euclid::TypedScale;
|
|
use servo::euclid::TypedSize2D;
|
|
use servo::gl;
|
|
use servo::gl::Gl;
|
|
use servo::gl::GlesFns;
|
|
use servo::msg::constellation_msg::TraversalDirection;
|
|
use servo::script_traits::MouseButton;
|
|
use servo::script_traits::TouchEventType;
|
|
use servo::servo_url::ServoUrl;
|
|
use servo::webrender_api::DevicePixel;
|
|
use servo::webrender_api::DevicePoint;
|
|
use servo::webrender_api::LayoutPixel;
|
|
use servo::webrender_api::ScrollLocation;
|
|
use servo::BrowserId;
|
|
use servo::Servo;
|
|
use smallvec::SmallVec;
|
|
use std::ffi::CStr;
|
|
use std::ffi::CString;
|
|
use std::io::Write;
|
|
use std::os::raw::c_char;
|
|
use std::os::raw::c_void;
|
|
use std::path::PathBuf;
|
|
use std::rc::Rc;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use std::time::Instant;
|
|
|
|
#[repr(u32)]
|
|
pub enum MLLogLevel {
|
|
Fatal = 0,
|
|
Error = 1,
|
|
Warning = 2,
|
|
Info = 3,
|
|
Debug = 4,
|
|
Verbose = 5,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[allow(non_camel_case_types)]
|
|
pub enum MLKeyType {
|
|
kNone,
|
|
kCharacter,
|
|
kBackspace,
|
|
kShift,
|
|
kSpeechToText,
|
|
kPageEmoji,
|
|
kPageLowerLetters,
|
|
kPageNumericSymbols,
|
|
kCancel,
|
|
kSubmit,
|
|
kPrevious,
|
|
kNext,
|
|
kClear,
|
|
kClose,
|
|
kEnter,
|
|
kCustom1,
|
|
kCustom2,
|
|
kCustom3,
|
|
kCustom4,
|
|
kCustom5,
|
|
}
|
|
|
|
#[repr(transparent)]
|
|
pub struct MLLogger(extern "C" fn(MLLogLevel, *const c_char));
|
|
|
|
#[repr(transparent)]
|
|
pub struct MLHistoryUpdate(extern "C" fn(MLApp, bool, *const c_char, bool));
|
|
|
|
#[repr(transparent)]
|
|
pub struct MLKeyboard(extern "C" fn(MLApp, bool));
|
|
|
|
#[repr(transparent)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct MLApp(*mut c_void);
|
|
|
|
const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info;
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn init_servo(
|
|
ctxt: EGLContext,
|
|
surf: EGLSurface,
|
|
disp: EGLDisplay,
|
|
app: MLApp,
|
|
logger: MLLogger,
|
|
history_update: MLHistoryUpdate,
|
|
keyboard: MLKeyboard,
|
|
url: *const c_char,
|
|
width: u32,
|
|
height: u32,
|
|
hidpi: f32,
|
|
) -> *mut ServoInstance {
|
|
// Servo initialization goes here!
|
|
servo::embedder_traits::resources::set(Box::new(ResourceReaderInstance::new()));
|
|
let _ = log::set_boxed_logger(Box::new(logger));
|
|
log::set_max_level(LOG_LEVEL);
|
|
let gl = GlesFns::load_with(|symbol| {
|
|
let cstr = CString::new(symbol).expect("Failed to convert GL symbol to a char*");
|
|
eglGetProcAddress(cstr.as_ptr() as _) as _
|
|
});
|
|
|
|
info!("OpenGL version {}", gl.get_string(gl::VERSION));
|
|
let window = Rc::new(WindowInstance {
|
|
ctxt: ctxt,
|
|
surf: surf,
|
|
disp: disp,
|
|
gl: gl,
|
|
width: width,
|
|
height: height,
|
|
hidpi: hidpi,
|
|
});
|
|
|
|
info!("Starting servo");
|
|
let mut servo = Servo::new(window);
|
|
let browser_id = BrowserId::new();
|
|
|
|
let blank_url = ServoUrl::parse("about:blank").expect("Failed to parse about:blank!");
|
|
let url = CStr::from_ptr(url).to_str().unwrap_or("about:blank");
|
|
let url = ServoUrl::parse(url).unwrap_or(blank_url);
|
|
servo.handle_events(vec![WindowEvent::NewBrowser(url, browser_id)]);
|
|
|
|
let result = Box::new(ServoInstance {
|
|
app: app,
|
|
browser_id: browser_id,
|
|
history_update: history_update,
|
|
keyboard: keyboard,
|
|
scroll_state: ScrollState::TriggerUp,
|
|
scroll_scale: TypedScale::new(SCROLL_SCALE / hidpi),
|
|
servo: servo,
|
|
});
|
|
Box::into_raw(result)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn heartbeat_servo(servo: *mut ServoInstance) {
|
|
// Servo heartbeat goes here!
|
|
if let Some(servo) = servo.as_mut() {
|
|
servo.servo.handle_events(vec![]);
|
|
for ((_browser_id, event)) in servo.servo.get_events() {
|
|
match event {
|
|
// Respond to any messages with a response channel
|
|
// to avoid deadlocking the constellation
|
|
EmbedderMsg::AllowNavigation(_url, sender) => {
|
|
let _ = sender.send(true);
|
|
},
|
|
EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
|
|
let _ = sender.send(None);
|
|
},
|
|
EmbedderMsg::AllowUnload(sender) => {
|
|
let _ = sender.send(true);
|
|
},
|
|
EmbedderMsg::Alert(_, sender) => {
|
|
let _ = sender.send(());
|
|
},
|
|
EmbedderMsg::AllowOpeningBrowser(sender) => {
|
|
let _ = sender.send(false);
|
|
},
|
|
// Update the history UI
|
|
EmbedderMsg::HistoryChanged(urls, index) => {
|
|
if let Some(url) = urls.get(index) {
|
|
if let Ok(cstr) = CString::new(url.as_str()) {
|
|
let can_go_back = index > 0;
|
|
let can_go_fwd = (index + 1) < urls.len();
|
|
(servo.history_update.0)(
|
|
servo.app,
|
|
can_go_back,
|
|
cstr.as_ptr(),
|
|
can_go_fwd,
|
|
);
|
|
}
|
|
}
|
|
},
|
|
EmbedderMsg::ShowIME(..) => (servo.keyboard.0)(servo.app, true),
|
|
EmbedderMsg::HideIME => (servo.keyboard.0)(servo.app, false),
|
|
// Ignore most messages for now
|
|
EmbedderMsg::ChangePageTitle(..) |
|
|
EmbedderMsg::BrowserCreated(..) |
|
|
EmbedderMsg::LoadStart |
|
|
EmbedderMsg::LoadComplete |
|
|
EmbedderMsg::CloseBrowser |
|
|
EmbedderMsg::Status(..) |
|
|
EmbedderMsg::SelectFiles(..) |
|
|
EmbedderMsg::MoveTo(..) |
|
|
EmbedderMsg::ResizeTo(..) |
|
|
EmbedderMsg::Keyboard(..) |
|
|
EmbedderMsg::SetCursor(..) |
|
|
EmbedderMsg::NewFavicon(..) |
|
|
EmbedderMsg::HeadParsed |
|
|
EmbedderMsg::SetFullscreenState(..) |
|
|
EmbedderMsg::Shutdown |
|
|
EmbedderMsg::Panic(..) => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn keyboard_servo(
|
|
servo: *mut ServoInstance,
|
|
key_code: char,
|
|
key_type: MLKeyType,
|
|
) {
|
|
if let Some(servo) = servo.as_mut() {
|
|
let key = match key_type {
|
|
MLKeyType::kCharacter => Key::Character([key_code].iter().collect()),
|
|
MLKeyType::kBackspace => Key::Backspace,
|
|
MLKeyType::kEnter => Key::Enter,
|
|
_ => return,
|
|
};
|
|
|
|
let key_down = KeyboardEvent {
|
|
state: KeyState::Down,
|
|
key: key,
|
|
..KeyboardEvent::default()
|
|
};
|
|
|
|
let key_up = KeyboardEvent {
|
|
state: KeyState::Up,
|
|
..key_down.clone()
|
|
};
|
|
|
|
// TODO: can the ML1 generate separate press and release events?
|
|
servo.servo.handle_events(vec![
|
|
WindowEvent::Keyboard(key_down),
|
|
WindowEvent::Keyboard(key_up),
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Some magic numbers.
|
|
|
|
// How far does the cursor have to move for it to count as a drag rather than a click?
|
|
// (In device pixels squared, to avoid taking a sqrt when calculating move distance.)
|
|
const DRAG_CUTOFF_SQUARED: f32 = 900.0;
|
|
|
|
// How much should we scale scrolling by?
|
|
const SCROLL_SCALE: f32 = 3.0;
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn move_servo(servo: *mut ServoInstance, x: f32, y: f32) {
|
|
// Servo's cursor was moved
|
|
if let Some(servo) = servo.as_mut() {
|
|
let point = DevicePoint::new(x, y);
|
|
let (new_state, window_events) = match servo.scroll_state {
|
|
ScrollState::TriggerUp => (
|
|
ScrollState::TriggerUp,
|
|
vec![WindowEvent::MouseWindowMoveEventClass(point)],
|
|
),
|
|
ScrollState::TriggerDown(start)
|
|
if (start - point).square_length() < DRAG_CUTOFF_SQUARED =>
|
|
{
|
|
return
|
|
},
|
|
ScrollState::TriggerDown(start) => (
|
|
ScrollState::TriggerDragging(start, point),
|
|
vec![
|
|
WindowEvent::MouseWindowMoveEventClass(point),
|
|
WindowEvent::Scroll(
|
|
ScrollLocation::Delta((point - start) * servo.scroll_scale),
|
|
start.to_i32(),
|
|
TouchEventType::Down,
|
|
),
|
|
],
|
|
),
|
|
ScrollState::TriggerDragging(start, prev) => (
|
|
ScrollState::TriggerDragging(start, point),
|
|
vec![
|
|
WindowEvent::MouseWindowMoveEventClass(point),
|
|
WindowEvent::Scroll(
|
|
ScrollLocation::Delta((point - prev) * servo.scroll_scale),
|
|
start.to_i32(),
|
|
TouchEventType::Move,
|
|
),
|
|
],
|
|
),
|
|
};
|
|
servo.scroll_state = new_state;
|
|
servo.servo.handle_events(window_events);
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn trigger_servo(servo: *mut ServoInstance, x: f32, y: f32, down: bool) {
|
|
// Servo was triggered
|
|
if let Some(servo) = servo.as_mut() {
|
|
let point = DevicePoint::new(x, y);
|
|
let (new_state, window_events) = match servo.scroll_state {
|
|
ScrollState::TriggerUp if down => (
|
|
ScrollState::TriggerDown(point),
|
|
vec![WindowEvent::MouseWindowEventClass(
|
|
MouseWindowEvent::MouseDown(MouseButton::Left, point),
|
|
)],
|
|
),
|
|
ScrollState::TriggerDown(start) if !down => (
|
|
ScrollState::TriggerUp,
|
|
vec![
|
|
WindowEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(
|
|
MouseButton::Left,
|
|
start,
|
|
)),
|
|
WindowEvent::MouseWindowEventClass(MouseWindowEvent::Click(
|
|
MouseButton::Left,
|
|
start,
|
|
)),
|
|
WindowEvent::MouseWindowMoveEventClass(point),
|
|
],
|
|
),
|
|
ScrollState::TriggerDragging(start, prev) if !down => (
|
|
ScrollState::TriggerUp,
|
|
vec![
|
|
WindowEvent::Scroll(
|
|
ScrollLocation::Delta((point - prev) * servo.scroll_scale),
|
|
start.to_i32(),
|
|
TouchEventType::Up,
|
|
),
|
|
WindowEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(
|
|
MouseButton::Left,
|
|
point,
|
|
)),
|
|
WindowEvent::MouseWindowMoveEventClass(point),
|
|
],
|
|
),
|
|
_ => return,
|
|
};
|
|
servo.scroll_state = new_state;
|
|
servo.servo.handle_events(window_events);
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn traverse_servo(servo: *mut ServoInstance, delta: i32) {
|
|
// Traverse the session history
|
|
if let Some(servo) = servo.as_mut() {
|
|
let window_event = if delta == 0 {
|
|
WindowEvent::Reload(servo.browser_id)
|
|
} else if delta < 0 {
|
|
WindowEvent::Navigation(servo.browser_id, TraversalDirection::Back(-delta as usize))
|
|
} else {
|
|
WindowEvent::Navigation(
|
|
servo.browser_id,
|
|
TraversalDirection::Forward(delta as usize),
|
|
)
|
|
};
|
|
servo.servo.handle_events(vec![window_event]);
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn navigate_servo(servo: *mut ServoInstance, text: *const c_char) {
|
|
if let Some(servo) = servo.as_mut() {
|
|
let text = CStr::from_ptr(text)
|
|
.to_str()
|
|
.expect("Failed to convert text to UTF-8");
|
|
let url = ServoUrl::parse(text).unwrap_or_else(|_| {
|
|
let mut search = ServoUrl::parse("https://duckduckgo.com")
|
|
.expect("Failed to parse search URL")
|
|
.into_url();
|
|
search.query_pairs_mut().append_pair("q", text);
|
|
ServoUrl::from_url(search)
|
|
});
|
|
let window_event = WindowEvent::LoadUrl(servo.browser_id, url);
|
|
servo.servo.handle_events(vec![window_event]);
|
|
}
|
|
}
|
|
|
|
// Some magic numbers for shutdown
|
|
const SHUTDOWN_DURATION: Duration = Duration::from_secs(10);
|
|
const SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(100);
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn discard_servo(servo: *mut ServoInstance) {
|
|
if let Some(servo) = servo.as_mut() {
|
|
let mut servo = Box::from_raw(servo);
|
|
let finish = Instant::now() + SHUTDOWN_DURATION;
|
|
servo.servo.handle_events(vec![WindowEvent::Quit]);
|
|
'outer: loop {
|
|
for (_, msg) in servo.servo.get_events() {
|
|
if let EmbedderMsg::Shutdown = msg {
|
|
break 'outer;
|
|
}
|
|
}
|
|
if Instant::now() > finish {
|
|
warn!("Incomplete shutdown.");
|
|
break 'outer;
|
|
}
|
|
thread::sleep(SHUTDOWN_POLL_INTERVAL);
|
|
servo.servo.handle_events(vec![]);
|
|
}
|
|
servo.servo.deinit();
|
|
}
|
|
}
|
|
|
|
pub struct ServoInstance {
|
|
app: MLApp,
|
|
browser_id: BrowserId,
|
|
history_update: MLHistoryUpdate,
|
|
keyboard: MLKeyboard,
|
|
servo: Servo<WindowInstance>,
|
|
scroll_state: ScrollState,
|
|
scroll_scale: TypedScale<f32, DevicePixel, LayoutPixel>,
|
|
}
|
|
|
|
struct WindowInstance {
|
|
ctxt: EGLContext,
|
|
surf: EGLSurface,
|
|
disp: EGLDisplay,
|
|
gl: Rc<Gl>,
|
|
width: u32,
|
|
height: u32,
|
|
hidpi: f32,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum ScrollState {
|
|
TriggerUp,
|
|
TriggerDown(DevicePoint),
|
|
TriggerDragging(DevicePoint, DevicePoint),
|
|
}
|
|
|
|
impl WindowMethods for WindowInstance {
|
|
fn present(&self) {
|
|
SwapBuffers(self.disp, self.surf);
|
|
}
|
|
|
|
fn prepare_for_composite(&self) -> bool {
|
|
MakeCurrent(self.disp, self.surf, self.surf, self.ctxt);
|
|
true
|
|
}
|
|
|
|
fn gl(&self) -> Rc<Gl> {
|
|
self.gl.clone()
|
|
}
|
|
|
|
fn create_event_loop_waker(&self) -> Box<EventLoopWaker> {
|
|
Box::new(EventLoopWakerInstance::new())
|
|
}
|
|
|
|
fn get_coordinates(&self) -> EmbedderCoordinates {
|
|
EmbedderCoordinates {
|
|
hidpi_factor: TypedScale::new(self.hidpi),
|
|
screen: TypedSize2D::new(self.width as i32, self.height as i32),
|
|
screen_avail: TypedSize2D::new(self.width as i32, self.height as i32),
|
|
window: (
|
|
TypedSize2D::new(self.width as i32, self.height as i32),
|
|
TypedPoint2D::new(0, 0),
|
|
),
|
|
framebuffer: TypedSize2D::new(self.width as i32, self.height as i32),
|
|
viewport: TypedRect::new(
|
|
TypedPoint2D::new(0, 0),
|
|
TypedSize2D::new(self.width as i32, self.height as i32),
|
|
),
|
|
}
|
|
}
|
|
|
|
fn set_animation_state(&self, _state: AnimationState) {}
|
|
}
|
|
|
|
struct EventLoopWakerInstance;
|
|
|
|
impl EventLoopWakerInstance {
|
|
fn new() -> EventLoopWakerInstance {
|
|
EventLoopWakerInstance
|
|
}
|
|
}
|
|
|
|
impl EventLoopWaker for EventLoopWakerInstance {
|
|
fn clone(&self) -> Box<EventLoopWaker + Send> {
|
|
Box::new(EventLoopWakerInstance)
|
|
}
|
|
|
|
fn wake(&self) {}
|
|
}
|
|
|
|
struct ResourceReaderInstance;
|
|
|
|
impl ResourceReaderInstance {
|
|
fn new() -> ResourceReaderInstance {
|
|
ResourceReaderInstance
|
|
}
|
|
}
|
|
|
|
impl ResourceReaderMethods for ResourceReaderInstance {
|
|
fn read(&self, res: Resource) -> Vec<u8> {
|
|
Vec::from(match res {
|
|
Resource::Preferences => &include_bytes!("../../../resources/prefs.json")[..],
|
|
Resource::HstsPreloadList => {
|
|
&include_bytes!("../../../resources/hsts_preload.json")[..]
|
|
},
|
|
Resource::SSLCertificates => &include_bytes!("../../../resources/certs")[..],
|
|
Resource::BadCertHTML => &include_bytes!("../../../resources/badcert.html")[..],
|
|
Resource::NetErrorHTML => &include_bytes!("../../../resources/neterror.html")[..],
|
|
Resource::UserAgentCSS => &include_bytes!("../../../resources/user-agent.css")[..],
|
|
Resource::ServoCSS => &include_bytes!("../../../resources/servo.css")[..],
|
|
Resource::PresentationalHintsCSS => {
|
|
&include_bytes!("../../../resources/presentational-hints.css")[..]
|
|
},
|
|
Resource::QuirksModeCSS => &include_bytes!("../../../resources/quirks-mode.css")[..],
|
|
Resource::RippyPNG => &include_bytes!("../../../resources/rippy.png")[..],
|
|
Resource::DomainList => &include_bytes!("../../../resources/public_domains.txt")[..],
|
|
Resource::BluetoothBlocklist => {
|
|
&include_bytes!("../../../resources/gatt_blocklist.txt")[..]
|
|
},
|
|
})
|
|
}
|
|
|
|
fn sandbox_access_files(&self) -> Vec<PathBuf> {
|
|
vec![]
|
|
}
|
|
|
|
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
|
|
vec![]
|
|
}
|
|
}
|
|
|
|
impl log::Log for MLLogger {
|
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
metadata.level() <= LOG_LEVEL
|
|
}
|
|
|
|
fn log(&self, record: &log::Record) {
|
|
let lvl = match record.level() {
|
|
log::Level::Error => MLLogLevel::Error,
|
|
log::Level::Warn => MLLogLevel::Warning,
|
|
log::Level::Info => MLLogLevel::Info,
|
|
log::Level::Debug => MLLogLevel::Debug,
|
|
log::Level::Trace => MLLogLevel::Verbose,
|
|
};
|
|
let mut msg = SmallVec::<[u8; 128]>::new();
|
|
write!(msg, "{}\0", record.args());
|
|
(self.0)(lvl, &msg[0] as *const _ as *const _);
|
|
}
|
|
|
|
fn flush(&self) {}
|
|
}
|