Merge remote-tracking branch 'upstream/master' into feat-cow-infra

`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
This commit is contained in:
yvt 2021-07-10 17:24:27 +09:00
commit 01a7de50ab
222172 changed files with 4491618 additions and 9970552 deletions

View file

@ -0,0 +1,16 @@
[package]
name = "servo_allocator"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
path = "lib.rs"
[target.'cfg(not(windows))'.dependencies]
jemalloc-sys = { version = "0.3.2" }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["heapapi"] }

115
components/allocator/lib.rs Normal file
View file

@ -0,0 +1,115 @@
/* 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/. */
//! Selecting the default global allocator for Servo
#[global_allocator]
static ALLOC: Allocator = Allocator;
pub use crate::platform::*;
#[cfg(not(windows))]
pub use jemalloc_sys;
#[cfg(not(windows))]
mod platform {
use jemalloc_sys as ffi;
use std::alloc::{GlobalAlloc, Layout};
use std::os::raw::{c_int, c_void};
/// Get the size of a heap block.
pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
ffi::malloc_usable_size(ptr as *const _)
}
/// Memory allocation APIs compatible with libc
pub mod libc_compat {
pub use super::ffi::{free, malloc, realloc};
}
pub struct Allocator;
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
#[cfg(all(any(
target_arch = "arm",
target_arch = "mips",
target_arch = "mipsel",
target_arch = "powerpc"
)))]
const MIN_ALIGN: usize = 8;
#[cfg(all(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64",
target_arch = "powerpc64le",
target_arch = "mips64",
target_arch = "s390x",
target_arch = "sparc64"
)))]
const MIN_ALIGN: usize = 16;
fn layout_to_flags(align: usize, size: usize) -> c_int {
// If our alignment is less than the minimum alignment they we may not
// have to pass special flags asking for a higher alignment. If the
// alignment is greater than the size, however, then this hits a sort of odd
// case where we still need to ask for a custom alignment. See #25 for more
// info.
if align <= MIN_ALIGN && align <= size {
0
} else {
// Equivalent to the MALLOCX_ALIGN(a) macro.
align.trailing_zeros() as _
}
}
unsafe impl GlobalAlloc for Allocator {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let flags = layout_to_flags(layout.align(), layout.size());
ffi::mallocx(layout.size(), flags) as *mut u8
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
ffi::calloc(1, layout.size()) as *mut u8
} else {
let flags = layout_to_flags(layout.align(), layout.size()) | ffi::MALLOCX_ZERO;
ffi::mallocx(layout.size(), flags) as *mut u8
}
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let flags = layout_to_flags(layout.align(), layout.size());
ffi::sdallocx(ptr as *mut _, layout.size(), flags)
}
#[inline]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let flags = layout_to_flags(layout.align(), new_size);
ffi::rallocx(ptr as *mut _, new_size, flags) as *mut u8
}
}
}
#[cfg(windows)]
mod platform {
pub use std::alloc::System as Allocator;
use std::os::raw::c_void;
use winapi::um::heapapi::{GetProcessHeap, HeapSize, HeapValidate};
/// Get the size of a heap block.
pub unsafe extern "C" fn usable_size(mut ptr: *const c_void) -> usize {
let heap = GetProcessHeap();
if HeapValidate(heap, 0, ptr) == 0 {
ptr = *(ptr as *const *const c_void).offset(-1);
}
HeapSize(heap, 0, ptr) as usize
}
}

View file

@ -3,15 +3,15 @@ name = "servo_atoms"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
build = "build.rs"
workspace = "../.."
[lib]
path = "lib.rs"
[dependencies]
string_cache = {version = "0.5", features = ["heapsize"]}
string_cache = "0.8"
[build-dependencies]
string_cache_codegen = "0.4"
string_cache_codegen = "0.5"

View file

@ -1,19 +1,31 @@
/* 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 http://mozilla.org/MPL/2.0/. */
extern crate string_cache_codegen;
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::env;
use std::fs::File;
use std::io::{BufReader, BufRead};
use std::io::{BufRead, BufReader};
use std::path::Path;
fn main() {
let static_atoms = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("static_atoms.txt");
let static_atoms =
Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("static_atoms.txt");
let static_atoms = BufReader::new(File::open(&static_atoms).unwrap());
string_cache_codegen::AtomType::new("Atom", "atom!")
let mut atom_type = string_cache_codegen::AtomType::new("Atom", "atom!");
macro_rules! predefined {
($($name: expr,)+) => {
{
$(
atom_type.atom($name);
)+
}
}
}
include!("../style/counter_style/predefined.rs");
atom_type
.atoms(static_atoms.lines().map(Result::unwrap))
.write_to_file(&Path::new(&env::var("OUT_DIR").unwrap()).join("atom.rs"))
.write_to_file(&Path::new(&env::var_os("OUT_DIR").unwrap()).join("atom.rs"))
.unwrap();
}

View file

@ -1,7 +1,5 @@
/* 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 http://mozilla.org/MPL/2.0/. */
extern crate string_cache;
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
include!(concat!(env!("OUT_DIR"), "/atom.rs"));

View file

@ -1,68 +1,154 @@
serif
sans-serif
cursive
fantasy
monospace
left
center
right
hidden
submit
button
reset
radio
checkbox
file
image
password
text
search
url
tel
email
datetime
date
month
week
time
datetime-local
number
dir
DOMContentLoaded
select
input
load
loadstart
loadend
progress
transitionend
error
readystatechange
message
close
storage
activate
webglcontextcreationerror
mouseover
beforeunload
message
click
keydown
keypress
abort
invalid
activate
addtrack
animationcancel
animationend
animationiteration
animationstart
beforeunload
button
canplay
canplaythrough
center
change
open
toggle
statechange
controllerchange
fetch
characteristicvaluechanged
checkbox
click
close
closing
color
complete
compositionend
compositionstart
compositionupdate
controllerchange
cursive
datachannel
date
datetime-local
dir
durationchange
email
emptied
end
ended
error
fantasy
fetch
file
fill
fill-opacity
formdata
fullscreenchange
fullscreenerror
gattserverdisconnected
hashchange
hidden
icecandidate
iceconnectionstatechange
icegatheringstatechange
image
input
inputsourceschange
invalid
keydown
keypress
kind
left
ltr
load
loadeddata
loadedmetadata
loadend
loadstart
message
message
messageerror
monospace
month
mousedown
mousemove
mouseover
mouseup
negotiationneeded
none
number
onchange
open
pagehide
pageshow
password
pause
play
playing
popstate
postershown
print
progress
radio
range
ratechange
readystatechange
referrer
reftest-wait
rejectionhandled
removetrack
reset
resize
resourcetimingbufferfull
right
rtl
sans-serif
safe-area-inset-top
safe-area-inset-bottom
safe-area-inset-left
safe-area-inset-right
scan
screen
scroll-position
search
seeked
seeking
select
selectend
selectionchange
selectstart
serif
sessionavailable
signalingstatechange
squeeze
squeezeend
squeezestart
srclang
statechange
stroke
stroke-opacity
storage
submit
suspend
tel
text
time
timeupdate
toggle
track
transitioncancel
transitionend
transitionrun
transitionstart
uncapturederror
unhandledrejection
unload
url
visibilitychange
volumechange
waiting
webglcontextcreationerror
webkitAnimationEnd
webkitAnimationIteration
webkitAnimationStart
webkitTransitionEnd
webkitTransitionRun
week
width

View file

@ -0,0 +1,32 @@
[package]
name = "background_hang_monitor"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
publish = false
edition = "2018"
[lib]
name = "background_hang_monitor"
path = "lib.rs"
test = false
doctest = false
[dependencies]
backtrace = "0.3"
crossbeam-channel = "0.4"
ipc-channel = "0.14"
libc = "0.2"
log = "0.4"
msg = { path = "../msg" }
serde_json = "1.0"
[dev-dependencies]
lazy_static = "1.0"
[target.'cfg(target_os = "macos")'.dependencies]
mach = "0.3"
[target.'cfg(all(target_os = "linux", not(any(target_arch = "arm", target_arch = "aarch64"))))'.dependencies]
nix = "0.14"
unwind-sys = "0.1.1"

View file

@ -0,0 +1,593 @@
/* 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 crate::sampler::{NativeStack, Sampler};
use crossbeam_channel::{after, never, unbounded, Receiver, Sender};
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use msg::constellation_msg::MonitoredComponentId;
use msg::constellation_msg::{
BackgroundHangMonitor, BackgroundHangMonitorClone, BackgroundHangMonitorExitSignal,
BackgroundHangMonitorRegister,
};
use msg::constellation_msg::{
BackgroundHangMonitorControlMsg, HangAlert, HangAnnotation, HangMonitorAlert,
};
use std::cell::Cell;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Weak};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Clone)]
pub struct HangMonitorRegister {
sender: Weak<Sender<(MonitoredComponentId, MonitoredComponentMsg)>>,
tether: Sender<Never>,
monitoring_enabled: bool,
}
impl HangMonitorRegister {
/// Start a new hang monitor worker, and return a handle to register components for monitoring.
pub fn init(
constellation_chan: IpcSender<HangMonitorAlert>,
control_port: IpcReceiver<BackgroundHangMonitorControlMsg>,
monitoring_enabled: bool,
) -> Box<dyn BackgroundHangMonitorRegister> {
// Create a channel to pass messages of type `MonitoredComponentMsg`.
// See the discussion in `<HangMonitorRegister as
// BackgroundHangMonitorRegister>::register_component` for why we wrap
// the sender with `Arc` and why `HangMonitorRegister` only maintains
// a weak reference to it.
let (sender, port) = unbounded();
let sender = Arc::new(sender);
let sender_weak = Arc::downgrade(&sender);
// Create a "tether" channel, whose sole purpose is to keep the worker
// thread alive. The worker thread will terminates when all copies of
// `tether` are dropped.
let (tether, tether_port) = unbounded();
let _ = thread::Builder::new()
.spawn(move || {
let mut monitor = BackgroundHangMonitorWorker::new(
constellation_chan,
control_port,
(sender, port),
tether_port,
monitoring_enabled,
);
while monitor.run() {
// Monitoring until all senders have been dropped...
}
})
.expect("Couldn't start BHM worker.");
Box::new(HangMonitorRegister {
sender: sender_weak,
tether,
monitoring_enabled,
})
}
}
impl BackgroundHangMonitorRegister for HangMonitorRegister {
/// Register a component for monitoring.
/// Returns a dedicated wrapper around a sender
/// to be used for communication with the hang monitor worker.
fn register_component(
&self,
component_id: MonitoredComponentId,
transient_hang_timeout: Duration,
permanent_hang_timeout: Duration,
exit_signal: Option<Box<dyn BackgroundHangMonitorExitSignal>>,
) -> Box<dyn BackgroundHangMonitor> {
let bhm_chan = BackgroundHangMonitorChan::new(
self.sender.clone(),
self.tether.clone(),
component_id,
self.monitoring_enabled,
);
#[cfg(all(
target_os = "windows",
any(target_arch = "x86_64", target_arch = "x86")
))]
let sampler = crate::sampler_windows::WindowsSampler::new();
#[cfg(target_os = "macos")]
let sampler = crate::sampler_mac::MacOsSampler::new();
#[cfg(all(
target_os = "linux",
not(any(target_arch = "arm", target_arch = "aarch64"))
))]
let sampler = crate::sampler_linux::LinuxSampler::new();
#[cfg(any(target_os = "android", target_arch = "arm", target_arch = "aarch64"))]
let sampler = crate::sampler::DummySampler::new();
// When a component is registered, and there's an exit request that
// reached BHM, we want an exit signal to be delivered to the
// component's exit signal handler eventually. However, there's a race
// condition between the reception of `BackgroundHangMonitorControlMsg::
// Exit` and `MonitoredComponentMsg::Register` that needs to handled
// carefully. When the worker receives an `Exit` message, it stops
// processing messages, and any further `Register` messages sent to the
// worker thread are ignored. If the submissions of `Exit` and
// `Register` messages are far apart enough, the channel is closed by
// the time the client attempts to send a `Register` message, and
// therefore the client can figure out by `Sender::send`'s return value
// that it must deliver an exit signal. However, if these message
// submissions are close enough, the `Register` message is still sent,
// but the worker thread might exit before it sees the message, leaving
// the message unprocessed and the exit signal unsent.
//
// To fix this, we wrap the exit signal handler in an RAII wrapper of
// type `SignalToExitOnDrop` to automatically send a signal when it's
// dropped. This way, we can make sure the exit signal is sent even if
// the message couldn't reach the worker thread and be processed.
//
// However, as it turns out, `crossbeam-channel`'s channels don't drop
// remaining messages until all associated senders *and* receivers are
// dropped. This means the exit signal won't be delivered as long as
// there's at least one `HangMonitorRegister` or
// `BackgroundHangMonitorChan` maintaining a copy of the sender. To work
// around this and guarantee a rapid delivery of the exit signal, the
// sender is wrapped in `Arc`, and only the worker thread maintains a
// strong reference, thus ensuring both the sender and receiver are
// dropped as soon as the worker thread exits.
let exit_signal = SignalToExitOnDrop(exit_signal);
// If the tether is dropped after this call, the worker thread might
// exit before processing the `Register` message because there's no
// implicit ordering guarantee between two channels. If this happens,
// an exit signal will be sent despite we haven't received a
// corresponding exit request. To enforce the correct ordering and
// prevent a false exit signal from being sent, we include a copy of
// `self.tether` in the `Register` message.
let tether = self.tether.clone();
bhm_chan.send(MonitoredComponentMsg::Register(
sampler,
thread::current().name().map(str::to_owned),
transient_hang_timeout,
permanent_hang_timeout,
exit_signal,
tether,
));
Box::new(bhm_chan)
}
}
impl BackgroundHangMonitorClone for HangMonitorRegister {
fn clone_box(&self) -> Box<dyn BackgroundHangMonitorRegister> {
Box::new(self.clone())
}
}
/// Messages sent from monitored components to the monitor.
enum MonitoredComponentMsg {
/// Register component for monitoring,
Register(
Box<dyn Sampler>,
Option<String>,
Duration,
Duration,
SignalToExitOnDrop,
Sender<Never>,
),
/// Unregister component for monitoring.
Unregister,
/// Notify start of new activity for a given component,
NotifyActivity(HangAnnotation),
/// Notify start of waiting for a new task to come-in for processing.
NotifyWait,
}
/// Stable equivalent to the `!` type
enum Never {}
/// A wrapper around a sender to the monitor,
/// which will send the Id of the monitored component along with each message,
/// and keep track of whether the monitor is still listening on the other end.
struct BackgroundHangMonitorChan {
sender: Weak<Sender<(MonitoredComponentId, MonitoredComponentMsg)>>,
_tether: Sender<Never>,
component_id: MonitoredComponentId,
disconnected: Cell<bool>,
monitoring_enabled: bool,
}
impl BackgroundHangMonitorChan {
fn new(
sender: Weak<Sender<(MonitoredComponentId, MonitoredComponentMsg)>>,
tether: Sender<Never>,
component_id: MonitoredComponentId,
monitoring_enabled: bool,
) -> Self {
BackgroundHangMonitorChan {
sender,
_tether: tether,
component_id: component_id,
disconnected: Default::default(),
monitoring_enabled,
}
}
fn send(&self, msg: MonitoredComponentMsg) {
if self.disconnected.get() {
return;
}
// The worker thread owns both the receiver *and* the only strong
// reference to the sender. An `upgrade` failure means the latter is
// gone, and a `send` failure means the former is gone. They are dropped
// simultaneously, but we might observe an intermediate state.
if self
.sender
.upgrade()
.and_then(|sender| sender.send((self.component_id.clone(), msg)).ok())
.is_none()
{
warn!("BackgroundHangMonitor has gone away");
self.disconnected.set(true);
}
}
}
impl BackgroundHangMonitor for BackgroundHangMonitorChan {
fn notify_activity(&self, annotation: HangAnnotation) {
if self.monitoring_enabled {
let msg = MonitoredComponentMsg::NotifyActivity(annotation);
self.send(msg);
}
}
fn notify_wait(&self) {
if self.monitoring_enabled {
let msg = MonitoredComponentMsg::NotifyWait;
self.send(msg);
}
}
fn unregister(&self) {
let msg = MonitoredComponentMsg::Unregister;
self.send(msg);
}
}
/// Wraps [`BackgroundHangMonitorExitSignal`] and calls `signal_to_exit` when
/// dropped.
struct SignalToExitOnDrop(Option<Box<dyn BackgroundHangMonitorExitSignal>>);
impl SignalToExitOnDrop {
/// Call `BackgroundHangMonitorExitSignal::signal_to_exit` now.
fn signal_to_exit(&mut self) {
if let Some(signal) = self.0.take() {
signal.signal_to_exit();
}
}
/// Disassociate `BackgroundHangMonitorExitSignal` from itself, preventing
/// `BackgroundHangMonitorExitSignal::signal_to_exit` from being called in
/// the future.
fn release(&mut self) {
self.0 = None;
}
}
impl Drop for SignalToExitOnDrop {
#[inline]
fn drop(&mut self) {
self.signal_to_exit();
}
}
struct MonitoredComponent {
sampler: Box<dyn Sampler>,
last_activity: Instant,
last_annotation: Option<HangAnnotation>,
transient_hang_timeout: Duration,
permanent_hang_timeout: Duration,
sent_transient_alert: bool,
sent_permanent_alert: bool,
is_waiting: bool,
exit_signal: SignalToExitOnDrop,
}
struct Sample(MonitoredComponentId, Instant, NativeStack);
struct BackgroundHangMonitorWorker {
component_names: HashMap<MonitoredComponentId, String>,
monitored_components: HashMap<MonitoredComponentId, MonitoredComponent>,
constellation_chan: IpcSender<HangMonitorAlert>,
port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>,
_port_sender: Arc<Sender<(MonitoredComponentId, MonitoredComponentMsg)>>,
tether_port: Receiver<Never>,
control_port: Receiver<BackgroundHangMonitorControlMsg>,
sampling_duration: Option<Duration>,
sampling_max_duration: Option<Duration>,
last_sample: Instant,
creation: Instant,
sampling_baseline: Instant,
samples: VecDeque<Sample>,
monitoring_enabled: bool,
}
impl BackgroundHangMonitorWorker {
fn new(
constellation_chan: IpcSender<HangMonitorAlert>,
control_port: IpcReceiver<BackgroundHangMonitorControlMsg>,
(port_sender, port): (
Arc<Sender<(MonitoredComponentId, MonitoredComponentMsg)>>,
Receiver<(MonitoredComponentId, MonitoredComponentMsg)>,
),
tether_port: Receiver<Never>,
monitoring_enabled: bool,
) -> Self {
let control_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(control_port);
Self {
component_names: Default::default(),
monitored_components: Default::default(),
constellation_chan,
port,
_port_sender: port_sender,
tether_port,
control_port,
sampling_duration: None,
sampling_max_duration: None,
last_sample: Instant::now(),
sampling_baseline: Instant::now(),
creation: Instant::now(),
samples: Default::default(),
monitoring_enabled,
}
}
fn finish_sampled_profile(&mut self) {
let mut bytes = vec![];
bytes.extend(
format!(
"{{ \"rate\": {}, \"start\": {}, \"data\": [\n",
self.sampling_duration.unwrap().as_millis(),
(self.sampling_baseline - self.creation).as_millis(),
)
.as_bytes(),
);
let mut first = true;
let to_resolve = self.samples.len();
for (i, Sample(id, instant, stack)) in self.samples.drain(..).enumerate() {
println!("Resolving {}/{}", i + 1, to_resolve);
let profile = stack.to_hangprofile();
let name = match self.component_names.get(&id) {
Some(ref s) => format!("\"{}\"", s),
None => format!("null"),
};
let json = format!(
"{}{{ \"name\": {}, \"namespace\": {}, \"index\": {}, \"type\": \"{:?}\", \
\"time\": {}, \"frames\": {} }}",
if !first { ",\n" } else { "" },
name,
id.0.namespace_id.0,
id.0.index.0.get(),
id.1,
(instant - self.sampling_baseline).as_millis(),
serde_json::to_string(&profile.backtrace).unwrap(),
);
bytes.extend(json.as_bytes());
first = false;
}
bytes.extend(b"\n] }");
let _ = self
.constellation_chan
.send(HangMonitorAlert::Profile(bytes));
}
fn run(&mut self) -> bool {
let tick = if let Some(duration) = self.sampling_duration {
let duration = duration
.checked_sub(Instant::now() - self.last_sample)
.unwrap_or_else(|| Duration::from_millis(0));
after(duration)
} else {
if self.monitoring_enabled {
after(Duration::from_millis(100))
} else {
never()
}
};
let received = select! {
recv(self.port) -> event => {
// Since we own the `Arc<Sender<_>>`, the channel never
// gets disconnected.
Some(event.unwrap())
},
recv(self.tether_port) -> event => {
// This arm can only reached by a tether disconnection
match event {
Ok(x) => match x {}
Err(_) => {}
}
// All associated `HangMonitorRegister` and
// `BackgroundHangMonitorChan` have been dropped. Suppress
// `signal_to_exit` and exit the BHM.
for component in self.monitored_components.values_mut() {
component.exit_signal.release();
}
return false;
},
recv(self.control_port) -> event => {
match event {
Ok(BackgroundHangMonitorControlMsg::EnableSampler(rate, max_duration)) => {
println!("Enabling profiler.");
self.sampling_duration = Some(rate);
self.sampling_max_duration = Some(max_duration);
self.sampling_baseline = Instant::now();
None
},
Ok(BackgroundHangMonitorControlMsg::DisableSampler) => {
println!("Disabling profiler.");
self.finish_sampled_profile();
self.sampling_duration = None;
return true;
},
Ok(BackgroundHangMonitorControlMsg::Exit(sender)) => {
for component in self.monitored_components.values_mut() {
component.exit_signal.signal_to_exit();
}
// Confirm exit with to the constellation.
let _ = sender.send(());
// Also exit the BHM.
return false;
},
Err(_) => return false,
}
}
recv(tick) -> _ => None,
};
if let Some(msg) = received {
self.handle_msg(msg);
while let Ok(another_msg) = self.port.try_recv() {
// Handle any other incoming messages,
// before performing a hang checkpoint.
self.handle_msg(another_msg);
}
}
if let Some(duration) = self.sampling_duration {
let now = Instant::now();
if now - self.last_sample > duration {
self.sample();
self.last_sample = now;
}
} else {
self.perform_a_hang_monitor_checkpoint();
}
true
}
fn handle_msg(&mut self, msg: (MonitoredComponentId, MonitoredComponentMsg)) {
match msg {
(
component_id,
MonitoredComponentMsg::Register(
sampler,
name,
transient_hang_timeout,
permanent_hang_timeout,
exit_signal,
_tether,
),
) => {
let component = MonitoredComponent {
sampler,
last_activity: Instant::now(),
last_annotation: None,
transient_hang_timeout,
permanent_hang_timeout,
sent_transient_alert: false,
sent_permanent_alert: false,
is_waiting: true,
exit_signal,
};
if let Some(name) = name {
self.component_names.insert(component_id.clone(), name);
}
assert!(
self.monitored_components
.insert(component_id, component)
.is_none(),
"This component was already registered for monitoring."
);
},
(component_id, MonitoredComponentMsg::Unregister) => {
let (_, mut component) = self
.monitored_components
.remove_entry(&component_id)
.expect("Received Unregister for an unknown component");
// Prevent `signal_to_exit` from being called
component.exit_signal.release();
},
(component_id, MonitoredComponentMsg::NotifyActivity(annotation)) => {
let component = self
.monitored_components
.get_mut(&component_id)
.expect("Received NotifyActivity for an unknown component");
component.last_activity = Instant::now();
component.last_annotation = Some(annotation);
component.sent_transient_alert = false;
component.sent_permanent_alert = false;
component.is_waiting = false;
},
(component_id, MonitoredComponentMsg::NotifyWait) => {
let component = self
.monitored_components
.get_mut(&component_id)
.expect("Received NotifyWait for an unknown component");
component.last_activity = Instant::now();
component.sent_transient_alert = false;
component.sent_permanent_alert = false;
component.is_waiting = true;
},
}
}
fn perform_a_hang_monitor_checkpoint(&mut self) {
for (component_id, monitored) in self.monitored_components.iter_mut() {
if monitored.is_waiting {
continue;
}
let last_annotation = monitored.last_annotation.unwrap();
if monitored.last_activity.elapsed() > monitored.permanent_hang_timeout {
if monitored.sent_permanent_alert {
continue;
}
let profile = match monitored.sampler.suspend_and_sample_thread() {
Ok(native_stack) => Some(native_stack.to_hangprofile()),
Err(()) => None,
};
let _ = self
.constellation_chan
.send(HangMonitorAlert::Hang(HangAlert::Permanent(
component_id.clone(),
last_annotation,
profile,
)));
monitored.sent_permanent_alert = true;
continue;
}
if monitored.last_activity.elapsed() > monitored.transient_hang_timeout {
if monitored.sent_transient_alert {
continue;
}
let _ = self
.constellation_chan
.send(HangMonitorAlert::Hang(HangAlert::Transient(
component_id.clone(),
last_annotation,
)));
monitored.sent_transient_alert = true;
}
}
}
fn sample(&mut self) {
for (component_id, monitored) in self.monitored_components.iter_mut() {
let instant = Instant::now();
if let Ok(stack) = monitored.sampler.suspend_and_sample_thread() {
if self.sampling_baseline.elapsed() >
self.sampling_max_duration
.expect("Max duration has been set")
{
// Buffer is full, start discarding older samples.
self.samples.pop_front();
}
self.samples
.push_back(Sample(component_id.clone(), instant, stack));
}
}
}
}

View file

@ -0,0 +1,24 @@
/* 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/. */
#![deny(unsafe_code)]
#[macro_use]
extern crate crossbeam_channel;
#[macro_use]
extern crate log;
pub mod background_hang_monitor;
mod sampler;
#[cfg(all(
target_os = "linux",
not(any(target_arch = "arm", target_arch = "aarch64"))
))]
mod sampler_linux;
#[cfg(target_os = "macos")]
mod sampler_mac;
#[cfg(target_os = "windows")]
mod sampler_windows;
pub use self::background_hang_monitor::*;

View file

@ -0,0 +1,99 @@
/* 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 backtrace;
use msg::constellation_msg::{HangProfile, HangProfileSymbol};
use std::ptr;
const MAX_NATIVE_FRAMES: usize = 1024;
pub trait Sampler: Send {
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()>;
}
#[allow(dead_code)]
pub struct DummySampler;
impl DummySampler {
#[allow(dead_code)]
pub fn new() -> Box<dyn Sampler> {
Box::new(DummySampler)
}
}
impl Sampler for DummySampler {
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
Err(())
}
}
// Several types in this file are currently not used in a Linux or Windows build.
#[allow(dead_code)]
pub type Address = *const u8;
/// The registers used for stack unwinding
#[allow(dead_code)]
pub struct Registers {
/// Instruction pointer.
pub instruction_ptr: Address,
/// Stack pointer.
pub stack_ptr: Address,
/// Frame pointer.
pub frame_ptr: Address,
}
pub struct NativeStack {
instruction_ptrs: [*mut std::ffi::c_void; MAX_NATIVE_FRAMES],
stack_ptrs: [*mut std::ffi::c_void; MAX_NATIVE_FRAMES],
count: usize,
}
impl NativeStack {
pub fn new() -> Self {
NativeStack {
instruction_ptrs: [ptr::null_mut(); MAX_NATIVE_FRAMES],
stack_ptrs: [ptr::null_mut(); MAX_NATIVE_FRAMES],
count: 0,
}
}
pub fn process_register(
&mut self,
instruction_ptr: *mut std::ffi::c_void,
stack_ptr: *mut std::ffi::c_void,
) -> Result<(), ()> {
if !(self.count < MAX_NATIVE_FRAMES) {
return Err(());
}
self.instruction_ptrs[self.count] = instruction_ptr;
self.stack_ptrs[self.count] = stack_ptr;
self.count = self.count + 1;
Ok(())
}
pub fn to_hangprofile(&self) -> HangProfile {
let mut profile = HangProfile {
backtrace: Vec::new(),
};
for ip in self.instruction_ptrs.iter().rev() {
if ip.is_null() {
continue;
}
backtrace::resolve(*ip, |symbol| {
// TODO: use the demangled or C++ demangled symbols if available.
let name = symbol
.name()
.map(|n| String::from_utf8_lossy(&n.as_bytes()).to_string());
let filename = symbol.filename().map(|n| n.to_string_lossy().to_string());
let lineno = symbol.lineno();
profile.backtrace.push(HangProfileSymbol {
name,
filename,
lineno,
});
});
}
profile
}
}

View file

@ -0,0 +1,314 @@
/* 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/. */
#![allow(unsafe_code)]
use crate::sampler::{NativeStack, Sampler};
use libc;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
use std::cell::UnsafeCell;
use std::io;
use std::mem;
use std::process;
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::thread;
use unwind_sys::{
unw_cursor_t, unw_get_reg, unw_init_local, unw_step, UNW_ESUCCESS, UNW_REG_IP, UNW_REG_SP,
};
// Hack to workaround broken libunwind pkg-config contents for <1.1-3ubuntu.1.
// https://bugs.launchpad.net/ubuntu/+source/libunwind/+bug/1336912
#[link(name = "lzma")]
extern "C" {}
struct UncheckedSyncUnsafeCell<T>(std::cell::UnsafeCell<T>);
/// Safety: dereferencing the pointer from `UnsafeCell::get` must involve external synchronization
unsafe impl<T> Sync for UncheckedSyncUnsafeCell<T> {}
static SHARED_STATE: UncheckedSyncUnsafeCell<SharedState> =
UncheckedSyncUnsafeCell(std::cell::UnsafeCell::new(SharedState {
msg2: None,
msg3: None,
msg4: None,
}));
static CONTEXT: AtomicPtr<libc::ucontext_t> = AtomicPtr::new(ptr::null_mut());
type MonitoredThreadId = libc::pid_t;
struct SharedState {
// "msg1" is the signal.
msg2: Option<PosixSemaphore>,
msg3: Option<PosixSemaphore>,
msg4: Option<PosixSemaphore>,
}
fn clear_shared_state() {
// Safety: this is only called from the sampling thread (theres only one)
// Sampled threads only access SHARED_STATE in their signal handler.
// This signal and the semaphores in SHARED_STATE provide the necessary synchronization.
unsafe {
let shared_state = &mut *SHARED_STATE.0.get();
shared_state.msg2 = None;
shared_state.msg3 = None;
shared_state.msg4 = None;
}
CONTEXT.store(ptr::null_mut(), Ordering::SeqCst);
}
fn reset_shared_state() {
// Safety: same as clear_shared_state
unsafe {
let shared_state = &mut *SHARED_STATE.0.get();
shared_state.msg2 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
shared_state.msg3 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
shared_state.msg4 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
}
CONTEXT.store(ptr::null_mut(), Ordering::SeqCst);
}
struct PosixSemaphore {
sem: UnsafeCell<libc::sem_t>,
}
impl PosixSemaphore {
pub fn new(value: u32) -> io::Result<Self> {
let mut sem = mem::MaybeUninit::uninit();
let r = unsafe {
libc::sem_init(sem.as_mut_ptr(), 0 /* not shared */, value)
};
if r == -1 {
return Err(io::Error::last_os_error());
}
Ok(PosixSemaphore {
sem: UnsafeCell::new(unsafe { sem.assume_init() }),
})
}
pub fn post(&self) -> io::Result<()> {
if unsafe { libc::sem_post(self.sem.get()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn wait(&self) -> io::Result<()> {
if unsafe { libc::sem_wait(self.sem.get()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
/// Retries the wait if it returned due to EINTR.
/// Returns Ok on success and the error on any other return value.
pub fn wait_through_intr(&self) -> io::Result<()> {
loop {
match self.wait() {
Err(os_error) => {
let err = os_error.raw_os_error().expect("no os error");
if err == libc::EINTR {
thread::yield_now();
continue;
}
return Err(os_error);
},
_ => return Ok(()),
}
}
}
}
unsafe impl Sync for PosixSemaphore {}
impl Drop for PosixSemaphore {
/// Destroys the semaphore.
fn drop(&mut self) {
unsafe { libc::sem_destroy(self.sem.get()) };
}
}
#[allow(dead_code)]
pub struct LinuxSampler {
thread_id: MonitoredThreadId,
old_handler: SigAction,
}
impl LinuxSampler {
#[allow(unsafe_code, dead_code)]
pub fn new() -> Box<dyn Sampler> {
let thread_id = unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t };
let handler = SigHandler::SigAction(sigprof_handler);
let action = SigAction::new(
handler,
SaFlags::SA_RESTART | SaFlags::SA_SIGINFO,
SigSet::empty(),
);
let old_handler =
unsafe { sigaction(Signal::SIGPROF, &action).expect("signal handler set") };
Box::new(LinuxSampler {
thread_id,
old_handler,
})
}
}
enum RegNum {
Ip = UNW_REG_IP as isize,
Sp = UNW_REG_SP as isize,
}
fn get_register(cursor: *mut unw_cursor_t, num: RegNum) -> Result<u64, i32> {
unsafe {
let mut val = 0;
let ret = unw_get_reg(cursor, num as i32, &mut val);
if ret == UNW_ESUCCESS {
Ok(val)
} else {
Err(ret)
}
}
}
fn step(cursor: *mut unw_cursor_t) -> Result<bool, i32> {
unsafe {
// libunwind 1.1 seems to get confused and walks off the end of the stack. The last IP
// it reports is 0, so we'll stop if we're there.
if get_register(cursor, RegNum::Ip).unwrap_or(1) == 0 {
return Ok(false);
}
let ret = unw_step(cursor);
if ret > 0 {
Ok(true)
} else if ret == 0 {
Ok(false)
} else {
Err(ret)
}
}
}
impl Sampler for LinuxSampler {
#[allow(unsafe_code)]
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
// Warning: The "critical section" begins here.
// In the critical section:
// we must not do any dynamic memory allocation,
// nor try to acquire any lock
// or any other unshareable resource.
// first we reinitialize the semaphores
reset_shared_state();
// signal the thread, wait for it to tell us state was copied.
send_sigprof(self.thread_id);
// Safety: non-exclusive reference only
// since sampled threads are accessing this concurrently
let shared_state = unsafe { &*SHARED_STATE.0.get() };
shared_state
.msg2
.as_ref()
.unwrap()
.wait_through_intr()
.expect("msg2 failed");
let context = CONTEXT.load(Ordering::SeqCst);
let mut cursor = mem::MaybeUninit::uninit();
let ret = unsafe { unw_init_local(cursor.as_mut_ptr(), context) };
let result = if ret == UNW_ESUCCESS {
let mut native_stack = NativeStack::new();
loop {
let ip = match get_register(cursor.as_mut_ptr(), RegNum::Ip) {
Ok(ip) => ip,
Err(_) => break,
};
let sp = match get_register(cursor.as_mut_ptr(), RegNum::Sp) {
Ok(sp) => sp,
Err(_) => break,
};
if native_stack
.process_register(ip as *mut _, sp as *mut _)
.is_err() ||
!step(cursor.as_mut_ptr()).unwrap_or(false)
{
break;
}
}
Ok(native_stack)
} else {
Err(())
};
// signal the thread to continue.
shared_state
.msg3
.as_ref()
.unwrap()
.post()
.expect("msg3 failed");
// wait for thread to continue.
shared_state
.msg4
.as_ref()
.unwrap()
.wait_through_intr()
.expect("msg4 failed");
// No-op, but marks the end of the shared borrow
drop(shared_state);
clear_shared_state();
// NOTE: End of "critical section".
result
}
}
impl Drop for LinuxSampler {
fn drop(&mut self) {
unsafe {
sigaction(Signal::SIGPROF, &self.old_handler).expect("previous signal handler restored")
};
}
}
extern "C" fn sigprof_handler(
sig: libc::c_int,
_info: *mut libc::siginfo_t,
ctx: *mut libc::c_void,
) {
assert_eq!(sig, libc::SIGPROF);
// copy the context.
CONTEXT.store(ctx as *mut libc::ucontext_t, Ordering::SeqCst);
// Safety: non-exclusive reference only
// since the sampling thread is accessing this concurrently
let shared_state = unsafe { &*SHARED_STATE.0.get() };
// Tell the sampler we copied the context.
shared_state.msg2.as_ref().unwrap().post().expect("posted");
// Wait for sampling to finish.
shared_state
.msg3
.as_ref()
.unwrap()
.wait_through_intr()
.expect("msg3 wait succeeded");
// OK we are done!
shared_state.msg4.as_ref().unwrap().post().expect("posted");
// DO NOT TOUCH shared state here onwards.
}
fn send_sigprof(to: libc::pid_t) {
unsafe {
libc::syscall(libc::SYS_tgkill, process::id(), to, libc::SIGPROF);
}
}

View file

@ -0,0 +1,129 @@
/* 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 crate::sampler::{Address, NativeStack, Registers, Sampler};
use libc;
use mach;
use std::panic;
use std::process;
type MonitoredThreadId = mach::mach_types::thread_act_t;
pub struct MacOsSampler {
thread_id: MonitoredThreadId,
}
impl MacOsSampler {
#[allow(unsafe_code)]
pub fn new() -> Box<dyn Sampler> {
let thread_id = unsafe { mach::mach_init::mach_thread_self() };
Box::new(MacOsSampler { thread_id })
}
}
impl Sampler for MacOsSampler {
#[allow(unsafe_code)]
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
// Warning: The "critical section" begins here.
// In the critical section:
// we must not do any dynamic memory allocation,
// nor try to acquire any lock
// or any other unshareable resource.
let current_hook = panic::take_hook();
panic::set_hook(Box::new(|_| {
// Avoiding any allocation or locking as part of standard panicking.
process::abort();
}));
let native_stack = unsafe {
if let Err(()) = suspend_thread(self.thread_id) {
panic::set_hook(current_hook);
return Err(());
};
let native_stack = match get_registers(self.thread_id) {
Ok(regs) => Ok(frame_pointer_stack_walk(regs)),
Err(()) => Err(()),
};
if let Err(()) = resume_thread(self.thread_id) {
process::abort();
}
native_stack
};
panic::set_hook(current_hook);
// NOTE: End of "critical section".
native_stack
}
}
fn check_kern_return(kret: mach::kern_return::kern_return_t) -> Result<(), ()> {
if kret != mach::kern_return::KERN_SUCCESS {
return Err(());
}
Ok(())
}
#[allow(unsafe_code)]
unsafe fn suspend_thread(thread_id: MonitoredThreadId) -> Result<(), ()> {
check_kern_return(mach::thread_act::thread_suspend(thread_id))
}
#[allow(unsafe_code)]
unsafe fn get_registers(thread_id: MonitoredThreadId) -> Result<Registers, ()> {
let mut state = mach::structs::x86_thread_state64_t::new();
let mut state_count = mach::structs::x86_thread_state64_t::count();
let kret = mach::thread_act::thread_get_state(
thread_id,
mach::thread_status::x86_THREAD_STATE64,
(&mut state) as *mut _ as *mut _,
&mut state_count,
);
check_kern_return(kret)?;
Ok(Registers {
instruction_ptr: state.__rip as Address,
stack_ptr: state.__rsp as Address,
frame_ptr: state.__rbp as Address,
})
}
#[allow(unsafe_code)]
unsafe fn resume_thread(thread_id: MonitoredThreadId) -> Result<(), ()> {
check_kern_return(mach::thread_act::thread_resume(thread_id))
}
#[allow(unsafe_code)]
unsafe fn frame_pointer_stack_walk(regs: Registers) -> NativeStack {
// Note: this function will only work with code build with:
// --dev,
// or --with-frame-pointer.
let pthread_t = libc::pthread_self();
let stackaddr = libc::pthread_get_stackaddr_np(pthread_t);
let stacksize = libc::pthread_get_stacksize_np(pthread_t);
let mut native_stack = NativeStack::new();
let pc = regs.instruction_ptr as *mut std::ffi::c_void;
let stack = regs.stack_ptr as *mut std::ffi::c_void;
let _ = native_stack.process_register(pc, stack);
let mut current = regs.frame_ptr as *mut *mut std::ffi::c_void;
while !current.is_null() {
if (current as usize) < stackaddr as usize {
// Reached the end of the stack.
break;
}
if current as usize >= stackaddr.add(stacksize * 8) as usize {
// Reached the beginning of the stack.
// Assumining 64 bit mac(see the stacksize * 8).
break;
}
let next = *current as *mut *mut std::ffi::c_void;
let pc = current.add(1);
let stack = current.add(2);
if let Err(()) = native_stack.process_register(*pc, *stack) {
break;
}
if (next <= current) || (next as usize & 3 != 0) {
break;
}
current = next;
}
native_stack
}

View file

@ -0,0 +1,40 @@
/* 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 crate::sampler::{NativeStack, Sampler};
type MonitoredThreadId = usize; // TODO: use winapi
#[allow(dead_code)]
pub struct WindowsSampler {
thread_id: MonitoredThreadId,
}
impl WindowsSampler {
#[allow(unsafe_code, dead_code)]
pub fn new() -> Box<dyn Sampler> {
let thread_id = 0; // TODO: use winapi::um::processthreadsapi::GetThreadId
Box::new(WindowsSampler { thread_id })
}
}
impl Sampler for WindowsSampler {
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
// Warning: The "critical section" begins here.
// In the critical section:
// we must not do any dynamic memory allocation,
// nor try to acquire any lock
// or any other unshareable resource.
// TODO:
// 1: use winapi::um::processthreadsapi::SuspendThread
// 2: use winapi::um::processthreadsapi::GetThreadContext
// 3: populate registers using the context, see
// https://dxr.mozilla.org/mozilla-central/source/tools/profiler/core/platform-win32.cpp#129
// 4: use winapi::um::processthreadsapi::ResumeThread
// NOTE: End of "critical section".
Err(())
}
}

View file

@ -0,0 +1,267 @@
/* 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/. */
#![allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
use background_hang_monitor::HangMonitorRegister;
use ipc_channel::ipc;
use msg::constellation_msg::ScriptHangAnnotation;
use msg::constellation_msg::TEST_PIPELINE_ID;
use msg::constellation_msg::{
BackgroundHangMonitorControlMsg, BackgroundHangMonitorExitSignal, HangAlert, HangAnnotation,
HangMonitorAlert,
};
use msg::constellation_msg::{MonitoredComponentId, MonitoredComponentType};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
lazy_static! {
static ref SERIAL: Mutex<()> = Mutex::new(());
}
#[test]
fn test_hang_monitoring() {
let _lock = SERIAL.lock().unwrap();
let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) =
ipc::channel().expect("ipc channel failure");
let (_sampler_sender, sampler_receiver) = ipc::channel().expect("ipc channel failure");
let background_hang_monitor_register = HangMonitorRegister::init(
background_hang_monitor_ipc_sender.clone(),
sampler_receiver,
true,
);
let background_hang_monitor = background_hang_monitor_register.register_component(
MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script),
Duration::from_millis(10),
Duration::from_millis(1000),
None,
);
// Start an activity.
let hang_annotation = HangAnnotation::Script(ScriptHangAnnotation::AttachLayout);
background_hang_monitor.notify_activity(hang_annotation);
// Sleep until the "transient" timeout has been reached.
thread::sleep(Duration::from_millis(10));
// Check for a transient hang alert.
match background_hang_monitor_receiver.recv().unwrap() {
HangMonitorAlert::Hang(HangAlert::Transient(component_id, _annotation)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id);
},
_ => unreachable!(),
}
// Sleep until the "permanent" timeout has been reached.
thread::sleep(Duration::from_millis(1000));
// Check for a permanent hang alert.
match background_hang_monitor_receiver.recv().unwrap() {
HangMonitorAlert::Hang(HangAlert::Permanent(component_id, _annotation, _profile)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id);
},
_ => unreachable!(),
}
// Now the component is not hanging anymore.
background_hang_monitor.notify_activity(hang_annotation);
assert!(background_hang_monitor_receiver.try_recv().is_err());
// Sleep for a while.
thread::sleep(Duration::from_millis(10));
// Check for a transient hang alert.
match background_hang_monitor_receiver.recv().unwrap() {
HangMonitorAlert::Hang(HangAlert::Transient(component_id, _annotation)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id);
},
_ => unreachable!(),
}
// Now the component is waiting for a new task.
background_hang_monitor.notify_wait();
// Sleep for a while.
thread::sleep(Duration::from_millis(100));
// The component is still waiting, but not hanging.
assert!(background_hang_monitor_receiver.try_recv().is_err());
// New task handling starts.
background_hang_monitor.notify_activity(hang_annotation);
// Sleep for a while.
thread::sleep(Duration::from_millis(10));
// We're getting new hang alerts for the latest task.
match background_hang_monitor_receiver.recv().unwrap() {
HangMonitorAlert::Hang(HangAlert::Transient(component_id, _annotation)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id);
},
_ => unreachable!(),
}
// No new alert yet
assert!(background_hang_monitor_receiver.try_recv().is_err());
// Shut-down the hang monitor
drop(background_hang_monitor_register);
drop(background_hang_monitor);
// Sleep until the "max-timeout" has been reached.
thread::sleep(Duration::from_millis(1000));
// Still no new alerts because the hang monitor has shut-down already.
assert!(background_hang_monitor_receiver.try_recv().is_err());
}
#[test]
// https://github.com/servo/servo/issues/28270
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
fn test_hang_monitoring_unregister() {
let _lock = SERIAL.lock().unwrap();
let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) =
ipc::channel().expect("ipc channel failure");
let (_sampler_sender, sampler_receiver) = ipc::channel().expect("ipc channel failure");
let background_hang_monitor_register = HangMonitorRegister::init(
background_hang_monitor_ipc_sender.clone(),
sampler_receiver,
true,
);
let background_hang_monitor = background_hang_monitor_register.register_component(
MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script),
Duration::from_millis(10),
Duration::from_millis(1000),
None,
);
// Start an activity.
let hang_annotation = HangAnnotation::Script(ScriptHangAnnotation::AttachLayout);
background_hang_monitor.notify_activity(hang_annotation);
// Unregister the component.
background_hang_monitor.unregister();
// Sleep until the "transient" timeout has been reached.
thread::sleep(Duration::from_millis(10));
// No new alert yet
assert!(background_hang_monitor_receiver.try_recv().is_err());
}
// Perform two certain steps in `test_hang_monitoring_exit_signal_inner` in
// different orders to check for the race condition that
// caused <https://github.com/servo/servo/issues/28270> and
// <https://github.com/servo/servo/issues/27191>.
#[test]
fn test_hang_monitoring_exit_signal1() {
test_hang_monitoring_exit_signal_inner(|e1, e2| {
e1();
thread::sleep(Duration::from_millis(100));
e2();
});
}
#[test]
fn test_hang_monitoring_exit_signal2() {
test_hang_monitoring_exit_signal_inner(|e1, e2| {
e1();
e2();
});
}
#[test]
fn test_hang_monitoring_exit_signal3() {
test_hang_monitoring_exit_signal_inner(|e1, e2| {
e2();
e1();
});
}
#[test]
fn test_hang_monitoring_exit_signal4() {
test_hang_monitoring_exit_signal_inner(|e1, e2| {
e2();
thread::sleep(Duration::from_millis(100));
e1();
});
}
fn test_hang_monitoring_exit_signal_inner(op_order: fn(&mut dyn FnMut(), &mut dyn FnMut())) {
let _lock = SERIAL.lock().unwrap();
let (background_hang_monitor_ipc_sender, _background_hang_monitor_receiver) =
ipc::channel().expect("ipc channel failure");
let (control_sender, control_receiver) = ipc::channel().expect("ipc channel failure");
struct BHMExitSignal {
closing: Arc<AtomicBool>,
}
impl BackgroundHangMonitorExitSignal for BHMExitSignal {
fn signal_to_exit(&self) {
self.closing.store(true, Ordering::SeqCst);
}
}
let closing = Arc::new(AtomicBool::new(false));
let mut signal = Some(Box::new(BHMExitSignal {
closing: closing.clone(),
}));
// Init a worker, without active monitoring.
let background_hang_monitor_register = HangMonitorRegister::init(
background_hang_monitor_ipc_sender.clone(),
control_receiver,
false,
);
let mut background_hang_monitor = None;
let (exit_sender, exit_receiver) = ipc::channel().expect("Failed to create IPC channel!");
let mut exit_sender = Some(exit_sender);
// `op_order` determines the order in which these two closures are
// executed.
op_order(
&mut || {
// Register a component.
background_hang_monitor = Some(background_hang_monitor_register.register_component(
MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script),
Duration::from_millis(10),
Duration::from_millis(1000),
Some(signal.take().unwrap()),
));
},
&mut || {
// Send the exit message.
control_sender
.send(BackgroundHangMonitorControlMsg::Exit(
exit_sender.take().unwrap(),
))
.unwrap();
},
);
// Assert we receive a confirmation back.
assert!(exit_receiver.recv().is_ok());
// Assert we get the exit signal.
while !closing.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(10));
}
}

View file

@ -3,6 +3,7 @@ name = "bluetooth"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
@ -10,13 +11,15 @@ name = "bluetooth"
path = "lib.rs"
[dependencies]
bitflags = "0.7"
bluetooth_traits = {path = "../bluetooth_traits"}
device = {git = "https://github.com/servo/devices", features = ["bluetooth-test"]}
ipc-channel = "0.7"
servo_config = {path = "../config"}
servo_rand = {path = "../rand"}
uuid = {version = "0.4", features = ["v4"]}
bitflags = "1.0"
bluetooth_traits = { path = "../bluetooth_traits" }
device = { git = "https://github.com/servo/devices", features = ["bluetooth-test"], rev = "cb28c4725ffbfece99dab842d17d3e8c50774778" }
embedder_traits = { path = "../embedder_traits" }
ipc-channel = "0.14"
log = "0.4"
servo_config = { path = "../config" }
servo_rand = { path = "../rand" }
uuid = { version = "0.8", features = ["v4"] }
[target.'cfg(target_os = "linux")'.dependencies]
tinyfiledialogs = "2.5.9"
[features]
native-bluetooth = ["device/bluetooth"]

View file

@ -1,32 +1,27 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#[macro_use]
extern crate bitflags;
extern crate bluetooth_traits;
extern crate device;
extern crate ipc_channel;
#[cfg(target_os = "linux")]
extern crate servo_config;
extern crate servo_rand;
#[cfg(target_os = "linux")]
extern crate tinyfiledialogs;
extern crate uuid;
#[macro_use]
extern crate log;
pub mod test;
use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
use bluetooth_traits::scanfilter::{
BluetoothScanfilter, BluetoothScanfilterSequence, RequestDeviceoptions,
};
use bluetooth_traits::{BluetoothCharacteristicMsg, BluetoothDescriptorMsg, BluetoothServiceMsg};
use bluetooth_traits::{BluetoothDeviceMsg, BluetoothRequest, BluetoothResponse, GATTType};
use bluetooth_traits::{BluetoothError, BluetoothResponseResult, BluetoothResult};
use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence, RequestDeviceoptions};
use device::bluetooth::{BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic};
use device::bluetooth::{BluetoothGATTDescriptor, BluetoothGATTService};
use embedder_traits::{EmbedderMsg, EmbedderProxy};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
#[cfg(target_os = "linux")]
use servo_config::opts;
use servo_rand::Rng;
use servo_config::pref;
use servo_rand::{self, Rng};
use std::borrow::ToOwned;
use std::collections::{HashMap, HashSet};
use std::string::String;
@ -39,24 +34,18 @@ const MAXIMUM_TRANSACTION_TIME: u8 = 30;
const CONNECTION_TIMEOUT_MS: u64 = 1000;
// The discovery session needs some time to find any nearby devices
const DISCOVERY_TIMEOUT_MS: u64 = 1500;
#[cfg(target_os = "linux")]
const DIALOG_TITLE: &'static str = "Choose a device";
#[cfg(target_os = "linux")]
const DIALOG_COLUMN_ID: &'static str = "Id";
#[cfg(target_os = "linux")]
const DIALOG_COLUMN_NAME: &'static str = "Name";
bitflags! {
flags Flags: u32 {
const BROADCAST = 0b000000001,
const READ = 0b000000010,
const WRITE_WITHOUT_RESPONSE = 0b000000100,
const WRITE = 0b000001000,
const NOTIFY = 0b000010000,
const INDICATE = 0b000100000,
const AUTHENTICATED_SIGNED_WRITES = 0b001000000,
const RELIABLE_WRITE = 0b010000000,
const WRITABLE_AUXILIARIES = 0b100000000,
struct Flags: u32 {
const BROADCAST = 0b000000001;
const READ = 0b000000010;
const WRITE_WITHOUT_RESPONSE = 0b000000100;
const WRITE = 0b000001000;
const NOTIFY = 0b000010000;
const INDICATE = 0b000100000;
const AUTHENTICATED_SIGNED_WRITES = 0b001000000;
const RELIABLE_WRITE = 0b010000000;
const WRITABLE_AUXILIARIES = 0b100000000;
}
}
@ -69,16 +58,24 @@ macro_rules! return_if_cached(
);
pub trait BluetoothThreadFactory {
fn new() -> Self;
fn new(embedder_proxy: EmbedderProxy) -> Self;
}
impl BluetoothThreadFactory for IpcSender<BluetoothRequest> {
fn new() -> IpcSender<BluetoothRequest> {
fn new(embedder_proxy: EmbedderProxy) -> IpcSender<BluetoothRequest> {
let (sender, receiver) = ipc::channel().unwrap();
let adapter = BluetoothAdapter::init().ok();
thread::Builder::new().name("BluetoothThread".to_owned()).spawn(move || {
BluetoothManager::new(receiver, adapter).start();
}).expect("Thread spawning failed");
let adapter = if pref!(dom.bluetooth.enabled) {
BluetoothAdapter::init()
} else {
BluetoothAdapter::init_mock()
}
.ok();
thread::Builder::new()
.name("BluetoothThread".to_owned())
.spawn(move || {
BluetoothManager::new(receiver, adapter, embedder_proxy).start();
})
.expect("Thread spawning failed");
sender
}
}
@ -180,7 +177,7 @@ fn matches_filters(device: &BluetoothDevice, filters: &BluetoothScanfilterSequen
return false;
}
return filters.iter().any(|f| matches_filter(device, f))
return filters.iter().any(|f| matches_filter(device, f));
}
fn is_mock_adapter(adapter: &BluetoothAdapter) -> bool {
@ -202,13 +199,18 @@ pub struct BluetoothManager {
cached_characteristics: HashMap<String, BluetoothGATTCharacteristic>,
cached_descriptors: HashMap<String, BluetoothGATTDescriptor>,
allowed_services: HashMap<String, HashSet<String>>,
embedder_proxy: EmbedderProxy,
}
impl BluetoothManager {
pub fn new(receiver: IpcReceiver<BluetoothRequest>, adapter: Option<BluetoothAdapter>) -> BluetoothManager {
pub fn new(
receiver: IpcReceiver<BluetoothRequest>,
adapter: Option<BluetoothAdapter>,
embedder_proxy: EmbedderProxy,
) -> BluetoothManager {
BluetoothManager {
receiver: receiver,
adapter: adapter,
receiver,
adapter,
address_to_id: HashMap::new(),
service_to_device: HashMap::new(),
characteristic_to_service: HashMap::new(),
@ -218,6 +220,7 @@ impl BluetoothManager {
cached_characteristics: HashMap::new(),
cached_descriptors: HashMap::new(),
allowed_services: HashMap::new(),
embedder_proxy,
}
}
@ -251,9 +254,11 @@ impl BluetoothManager {
BluetoothRequest::Test(data_set_name, sender) => {
let _ = sender.send(self.test(data_set_name));
},
BluetoothRequest::SetRepresentedToNull(service_ids, characteristic_ids, descriptor_ids) => {
self.remove_ids_from_caches(service_ids, characteristic_ids, descriptor_ids)
},
BluetoothRequest::SetRepresentedToNull(
service_ids,
characteristic_ids,
descriptor_ids,
) => self.remove_ids_from_caches(service_ids, characteristic_ids, descriptor_ids),
BluetoothRequest::IsRepresentedDeviceNull(id, sender) => {
let _ = sender.send(!self.device_is_cached(&id));
},
@ -263,9 +268,7 @@ impl BluetoothManager {
BluetoothRequest::MatchesFilter(id, filters, sender) => {
let _ = sender.send(self.device_matches_filter(&id, &filters));
},
BluetoothRequest::Exit => {
break
},
BluetoothRequest::Exit => break,
}
}
}
@ -285,14 +288,16 @@ impl BluetoothManager {
self.adapter = BluetoothAdapter::init_mock().ok();
match test::test(self, data_set_name) {
Ok(_) => return Ok(()),
Err(error) => Err(BluetoothError::Type(error.description().to_owned())),
Err(error) => Err(BluetoothError::Type(error.to_string())),
}
}
fn remove_ids_from_caches(&mut self,
service_ids: Vec<String>,
characteristic_ids: Vec<String>,
descriptor_ids: Vec<String>) {
fn remove_ids_from_caches(
&mut self,
service_ids: Vec<String>,
characteristic_ids: Vec<String>,
descriptor_ids: Vec<String>,
) {
for id in service_ids {
self.cached_services.remove(&id);
self.service_to_device.remove(&id);
@ -312,15 +317,15 @@ impl BluetoothManager {
// Adapter
pub fn get_or_create_adapter(&mut self) -> Option<BluetoothAdapter> {
let adapter_valid = self.adapter.as_ref().map_or(false, |a| a.get_address().is_ok());
let adapter_valid = self
.adapter
.as_ref()
.map_or(false, |a| a.get_address().is_ok());
if !adapter_valid {
self.adapter = BluetoothAdapter::init().ok();
}
let adapter = match self.adapter.as_ref() {
Some(adapter) => adapter,
None => return None,
};
let adapter = self.adapter.as_ref()?;
if is_mock_adapter(adapter) && !adapter.is_present().unwrap_or(false) {
return None;
@ -344,13 +349,14 @@ impl BluetoothManager {
// Device
fn get_and_cache_devices(&mut self, adapter: &mut BluetoothAdapter) -> Vec<BluetoothDevice> {
let devices = adapter.get_devices().unwrap_or(vec!());
let devices = adapter.get_devices().unwrap_or(vec![]);
for device in &devices {
if let Ok(address) = device.get_address() {
if !self.address_to_id.contains_key(&address) {
let generated_id = self.generate_device_id();
self.address_to_id.insert(address, generated_id.clone());
self.cached_devices.insert(generated_id.clone(), device.clone());
self.cached_devices
.insert(generated_id.clone(), device.clone());
self.allowed_services.insert(generated_id, HashSet::new());
}
}
@ -358,17 +364,24 @@ impl BluetoothManager {
self.cached_devices.iter().map(|(_, d)| d.clone()).collect()
}
fn get_device(&mut self, adapter: &mut BluetoothAdapter, device_id: &str) -> Option<&BluetoothDevice> {
fn get_device(
&mut self,
adapter: &mut BluetoothAdapter,
device_id: &str,
) -> Option<&BluetoothDevice> {
return_if_cached!(self.cached_devices, device_id);
self.get_and_cache_devices(adapter);
return_if_cached!(self.cached_devices, device_id);
None
}
#[cfg(target_os = "linux")]
fn select_device(&mut self, devices: Vec<BluetoothDevice>, adapter: &BluetoothAdapter) -> Option<String> {
if is_mock_adapter(adapter) || opts::get().headless {
for device in devices {
fn select_device(
&mut self,
devices: Vec<BluetoothDevice>,
adapter: &BluetoothAdapter,
) -> Option<String> {
if is_mock_adapter(adapter) {
for device in &devices {
if let Ok(address) = device.get_address() {
return Some(address);
}
@ -376,33 +389,28 @@ impl BluetoothManager {
return None;
}
let mut dialog_rows: Vec<String> = vec!();
let mut dialog_rows: Vec<String> = vec![];
for device in devices {
dialog_rows.extend_from_slice(&[device.get_address().unwrap_or("".to_string()),
device.get_name().unwrap_or("".to_string())]);
dialog_rows.extend_from_slice(&[
device.get_address().unwrap_or("".to_string()),
device.get_name().unwrap_or("".to_string()),
]);
}
let dialog_rows: Vec<&str> = dialog_rows.iter()
.map(|s| s.as_ref())
.collect();
let dialog_rows: &[&str] = dialog_rows.as_slice();
if let Some(device) = tinyfiledialogs::list_dialog(DIALOG_TITLE,
&[DIALOG_COLUMN_ID, DIALOG_COLUMN_NAME],
Some(dialog_rows)) {
// The device string format will be "Address|Name". We need the first part of it.
return device.split("|").next().map(|s| s.to_string());
}
None
}
let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!");
let msg = (
None,
EmbedderMsg::GetSelectedBluetoothDevice(dialog_rows, ipc_sender),
);
self.embedder_proxy.send(msg);
#[cfg(not(target_os = "linux"))]
fn select_device(&mut self, devices: Vec<BluetoothDevice>, _adapter: &BluetoothAdapter) -> Option<String> {
for device in devices {
if let Ok(address) = device.get_address() {
return Some(address);
}
match ipc_receiver.recv() {
Ok(result) => result,
Err(e) => {
warn!("Failed to receive files from embedder ({:?}).", e);
None
},
}
None
}
fn generate_device_id(&mut self) -> String {
@ -418,25 +426,21 @@ impl BluetoothManager {
}
fn device_from_service_id(&self, service_id: &str) -> Option<BluetoothDevice> {
let device_id = match self.service_to_device.get(service_id) {
Some(id) => id,
None => return None,
};
match self.cached_devices.get(device_id) {
Some(d) => Some(d.clone()),
None => None,
}
let device_id = self.service_to_device.get(service_id)?;
self.cached_devices.get(device_id).cloned()
}
fn device_is_cached(&self, device_id: &str) -> bool {
self.cached_devices.contains_key(device_id) && self.address_to_id.values().any(|v| v == device_id)
self.cached_devices.contains_key(device_id) &&
self.address_to_id.values().any(|v| v == device_id)
}
fn device_matches_filter(&mut self,
device_id: &str,
filters: &BluetoothScanfilterSequence)
-> BluetoothResult<bool> {
let mut adapter = try!(self.get_adapter());
fn device_matches_filter(
&mut self,
device_id: &str,
filters: &BluetoothScanfilterSequence,
) -> BluetoothResult<bool> {
let mut adapter = self.get_adapter()?;
match self.get_device(&mut adapter, device_id) {
Some(ref device) => Ok(matches_filters(device, filters)),
None => Ok(false),
@ -445,69 +449,82 @@ impl BluetoothManager {
// Service
fn get_and_cache_gatt_services(&mut self,
adapter: &mut BluetoothAdapter,
device_id: &str)
-> Vec<BluetoothGATTService> {
fn get_and_cache_gatt_services(
&mut self,
adapter: &mut BluetoothAdapter,
device_id: &str,
) -> Vec<BluetoothGATTService> {
let mut services = match self.get_device(adapter, device_id) {
Some(d) => d.get_gatt_services().unwrap_or(vec!()),
None => vec!(),
Some(d) => d.get_gatt_services().unwrap_or(vec![]),
None => vec![],
};
services.retain(|s| !uuid_is_blocklisted(&s.get_uuid().unwrap_or(String::new()), Blocklist::All) &&
self.allowed_services
.get(device_id)
.map_or(false, |uuids| uuids.contains(&s.get_uuid().unwrap_or(String::new()))));
services.retain(|s| {
!uuid_is_blocklisted(&s.get_uuid().unwrap_or(String::new()), Blocklist::All) &&
self.allowed_services.get(device_id).map_or(false, |uuids| {
uuids.contains(&s.get_uuid().unwrap_or(String::new()))
})
});
for service in &services {
self.cached_services.insert(service.get_id(), service.clone());
self.service_to_device.insert(service.get_id(), device_id.to_owned());
self.cached_services
.insert(service.get_id(), service.clone());
self.service_to_device
.insert(service.get_id(), device_id.to_owned());
}
services
}
fn get_gatt_service(&mut self, adapter: &mut BluetoothAdapter, service_id: &str) -> Option<&BluetoothGATTService> {
fn get_gatt_service(
&mut self,
adapter: &mut BluetoothAdapter,
service_id: &str,
) -> Option<&BluetoothGATTService> {
return_if_cached!(self.cached_services, service_id);
let device_id = match self.service_to_device.get(service_id) {
Some(d) => d.clone(),
None => return None,
};
let device_id = self.service_to_device.get(service_id)?.clone();
self.get_and_cache_gatt_services(adapter, &device_id);
return_if_cached!(self.cached_services, service_id);
None
}
fn service_is_cached(&self, service_id: &str) -> bool {
self.cached_services.contains_key(service_id) && self.service_to_device.contains_key(service_id)
self.cached_services.contains_key(service_id) &&
self.service_to_device.contains_key(service_id)
}
// Characteristic
fn get_and_cache_gatt_characteristics(&mut self,
adapter: &mut BluetoothAdapter,
service_id: &str)
-> Vec<BluetoothGATTCharacteristic> {
fn get_and_cache_gatt_characteristics(
&mut self,
adapter: &mut BluetoothAdapter,
service_id: &str,
) -> Vec<BluetoothGATTCharacteristic> {
let mut characteristics = match self.get_gatt_service(adapter, service_id) {
Some(s) => s.get_gatt_characteristics().unwrap_or(vec!()),
None => vec!(),
Some(s) => s.get_gatt_characteristics().unwrap_or(vec![]),
None => vec![],
};
characteristics.retain(|c| !uuid_is_blocklisted(&c.get_uuid().unwrap_or(String::new()), Blocklist::All));
characteristics.retain(|c| {
!uuid_is_blocklisted(&c.get_uuid().unwrap_or(String::new()), Blocklist::All)
});
for characteristic in &characteristics {
self.cached_characteristics.insert(characteristic.get_id(), characteristic.clone());
self.characteristic_to_service.insert(characteristic.get_id(), service_id.to_owned());
self.cached_characteristics
.insert(characteristic.get_id(), characteristic.clone());
self.characteristic_to_service
.insert(characteristic.get_id(), service_id.to_owned());
}
characteristics
}
fn get_gatt_characteristic(&mut self,
adapter: &mut BluetoothAdapter,
characteristic_id: &str)
-> Option<&BluetoothGATTCharacteristic> {
fn get_gatt_characteristic(
&mut self,
adapter: &mut BluetoothAdapter,
characteristic_id: &str,
) -> Option<&BluetoothGATTCharacteristic> {
return_if_cached!(self.cached_characteristics, characteristic_id);
let service_id = match self.characteristic_to_service.get(characteristic_id) {
Some(s) => s.clone(),
None => return None,
};
let service_id = self
.characteristic_to_service
.get(characteristic_id)?
.clone();
self.get_and_cache_gatt_characteristics(adapter, &service_id);
return_if_cached!(self.cached_characteristics, characteristic_id);
None
@ -515,18 +532,18 @@ impl BluetoothManager {
fn get_characteristic_properties(&self, characteristic: &BluetoothGATTCharacteristic) -> Flags {
let mut props: Flags = Flags::empty();
let flags = characteristic.get_flags().unwrap_or(vec!());
let flags = characteristic.get_flags().unwrap_or(vec![]);
for flag in flags {
match flag.as_ref() {
"broadcast" => props.insert(BROADCAST),
"read" => props.insert(READ),
"write-without-response" => props.insert(WRITE_WITHOUT_RESPONSE),
"write" => props.insert(WRITE),
"notify" => props.insert(NOTIFY),
"indicate" => props.insert(INDICATE),
"authenticated-signed-writes" => props.insert(AUTHENTICATED_SIGNED_WRITES),
"reliable-write" => props.insert(RELIABLE_WRITE),
"writable-auxiliaries" => props.insert(WRITABLE_AUXILIARIES),
"broadcast" => props.insert(Flags::BROADCAST),
"read" => props.insert(Flags::READ),
"write-without-response" => props.insert(Flags::WRITE_WITHOUT_RESPONSE),
"write" => props.insert(Flags::WRITE),
"notify" => props.insert(Flags::NOTIFY),
"indicate" => props.insert(Flags::INDICATE),
"authenticated-signed-writes" => props.insert(Flags::AUTHENTICATED_SIGNED_WRITES),
"reliable-write" => props.insert(Flags::RELIABLE_WRITE),
"writable-auxiliaries" => props.insert(Flags::WRITABLE_AUXILIARIES),
_ => (),
}
}
@ -535,37 +552,44 @@ impl BluetoothManager {
fn characteristic_is_cached(&self, characteristic_id: &str) -> bool {
self.cached_characteristics.contains_key(characteristic_id) &&
self.characteristic_to_service.contains_key(characteristic_id)
self.characteristic_to_service
.contains_key(characteristic_id)
}
// Descriptor
fn get_and_cache_gatt_descriptors(&mut self,
adapter: &mut BluetoothAdapter,
characteristic_id: &str)
-> Vec<BluetoothGATTDescriptor> {
fn get_and_cache_gatt_descriptors(
&mut self,
adapter: &mut BluetoothAdapter,
characteristic_id: &str,
) -> Vec<BluetoothGATTDescriptor> {
let mut descriptors = match self.get_gatt_characteristic(adapter, characteristic_id) {
Some(c) => c.get_gatt_descriptors().unwrap_or(vec!()),
None => vec!(),
Some(c) => c.get_gatt_descriptors().unwrap_or(vec![]),
None => vec![],
};
descriptors.retain(|d| !uuid_is_blocklisted(&d.get_uuid().unwrap_or(String::new()), Blocklist::All));
descriptors.retain(|d| {
!uuid_is_blocklisted(&d.get_uuid().unwrap_or(String::new()), Blocklist::All)
});
for descriptor in &descriptors {
self.cached_descriptors.insert(descriptor.get_id(), descriptor.clone());
self.descriptor_to_characteristic.insert(descriptor.get_id(), characteristic_id.to_owned());
self.cached_descriptors
.insert(descriptor.get_id(), descriptor.clone());
self.descriptor_to_characteristic
.insert(descriptor.get_id(), characteristic_id.to_owned());
}
descriptors
}
fn get_gatt_descriptor(&mut self,
adapter: &mut BluetoothAdapter,
descriptor_id: &str)
-> Option<&BluetoothGATTDescriptor> {
fn get_gatt_descriptor(
&mut self,
adapter: &mut BluetoothAdapter,
descriptor_id: &str,
) -> Option<&BluetoothGATTDescriptor> {
return_if_cached!(self.cached_descriptors, descriptor_id);
let characteristic_id = match self.descriptor_to_characteristic.get(descriptor_id) {
Some(c) => c.clone(),
None => return None,
};
let characteristic_id = self
.descriptor_to_characteristic
.get(descriptor_id)?
.clone();
self.get_and_cache_gatt_descriptors(adapter, &characteristic_id);
return_if_cached!(self.cached_descriptors, descriptor_id);
None
@ -574,11 +598,9 @@ impl BluetoothManager {
// Methods
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
fn request_device(&mut self,
options: RequestDeviceoptions)
-> BluetoothResponseResult {
fn request_device(&mut self, options: RequestDeviceoptions) -> BluetoothResponseResult {
// Step 6.
let mut adapter = try!(self.get_adapter());
let mut adapter = self.get_adapter()?;
// Step 7.
// Note: There are no requiredServiceUUIDS, we scan for all devices.
@ -595,9 +617,10 @@ impl BluetoothManager {
// Step 8.
if !options.is_accepting_all_devices() {
matched_devices = matched_devices.into_iter()
.filter(|d| matches_filters(d, options.get_filters()))
.collect();
matched_devices = matched_devices
.into_iter()
.filter(|d| matches_filters(d, options.get_filters()))
.collect();
}
// Step 9.
@ -630,7 +653,7 @@ impl BluetoothManager {
if !self.device_is_cached(&device_id) {
return Err(BluetoothError::Network);
}
let mut adapter = try!(self.get_adapter());
let mut adapter = self.get_adapter()?;
// Step 5.1.1.
match self.get_device(&mut adapter, &device_id) {
@ -640,16 +663,15 @@ impl BluetoothManager {
}
let _ = d.connect();
for _ in 0..MAXIMUM_TRANSACTION_TIME {
match d.is_connected().unwrap_or(false) {
true => return Ok(BluetoothResponse::GATTServerConnect(true)),
false => {
if is_mock_adapter(&adapter) {
break;
}
thread::sleep(Duration::from_millis(CONNECTION_TIMEOUT_MS));
},
if d.is_connected().unwrap_or(false) {
return Ok(BluetoothResponse::GATTServerConnect(true));
} else {
if is_mock_adapter(&adapter) {
break;
}
thread::sleep(Duration::from_millis(CONNECTION_TIMEOUT_MS));
}
// TODO: Step 5.1.4: Use the exchange MTU procedure.
// TODO: Step 5.1.4: Use the exchange MTU procedure.
}
// Step 5.1.3.
return Err(BluetoothError::Network);
@ -660,7 +682,7 @@ impl BluetoothManager {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-disconnect
fn gatt_server_disconnect(&mut self, device_id: String) -> BluetoothResult<()> {
let mut adapter = try!(self.get_adapter());
let mut adapter = self.get_adapter()?;
match self.get_device(&mut adapter, &device_id) {
Some(d) => {
// Step 2.
@ -669,9 +691,10 @@ impl BluetoothManager {
}
let _ = d.disconnect();
for _ in 0..MAXIMUM_TRANSACTION_TIME {
match d.is_connected().unwrap_or(true) {
true => thread::sleep(Duration::from_millis(CONNECTION_TIMEOUT_MS)),
false => return Ok(()),
if d.is_connected().unwrap_or(true) {
thread::sleep(Duration::from_millis(CONNECTION_TIMEOUT_MS))
} else {
return Ok(());
}
}
return Err(BluetoothError::Network);
@ -681,13 +704,14 @@ impl BluetoothManager {
}
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
fn get_gatt_children(&mut self,
id: String,
uuid: Option<String>,
single: bool,
child_type: GATTType)
-> BluetoothResponseResult {
let mut adapter = try!(self.get_adapter());
fn get_gatt_children(
&mut self,
id: String,
uuid: Option<String>,
single: bool,
child_type: GATTType,
) -> BluetoothResponseResult {
let mut adapter = self.get_adapter()?;
match child_type {
GATTType::PrimaryService => {
// Step 5.
@ -696,7 +720,11 @@ impl BluetoothManager {
}
// Step 6.
if let Some(ref uuid) = uuid {
if !self.allowed_services.get(&id).map_or(false, |s| s.contains(uuid)) {
if !self
.allowed_services
.get(&id)
.map_or(false, |s| s.contains(uuid))
{
return Err(BluetoothError::Security);
}
}
@ -704,17 +732,15 @@ impl BluetoothManager {
if let Some(uuid) = uuid {
services.retain(|ref e| e.get_uuid().unwrap_or(String::new()) == uuid);
}
let mut services_vec = vec!();
let mut services_vec = vec![];
for service in services {
if service.is_primary().unwrap_or(false) {
if let Ok(uuid) = service.get_uuid() {
services_vec.push(
BluetoothServiceMsg {
uuid: uuid,
is_primary: true,
instance_id: service.get_id(),
}
);
services_vec.push(BluetoothServiceMsg {
uuid: uuid,
is_primary: true,
instance_id: service.get_id(),
});
}
}
}
@ -731,29 +757,30 @@ impl BluetoothManager {
return Err(BluetoothError::InvalidState);
}
// Step 6.
let mut characteristics = self.get_and_cache_gatt_characteristics(&mut adapter, &id);
let mut characteristics =
self.get_and_cache_gatt_characteristics(&mut adapter, &id);
if let Some(uuid) = uuid {
characteristics.retain(|ref e| e.get_uuid().unwrap_or(String::new()) == uuid);
}
let mut characteristics_vec = vec!();
let mut characteristics_vec = vec![];
for characteristic in characteristics {
if let Ok(uuid) = characteristic.get_uuid() {
let properties = self.get_characteristic_properties(&characteristic);
characteristics_vec.push(
BluetoothCharacteristicMsg {
uuid: uuid,
instance_id: characteristic.get_id(),
broadcast: properties.contains(BROADCAST),
read: properties.contains(READ),
write_without_response: properties.contains(WRITE_WITHOUT_RESPONSE),
write: properties.contains(WRITE),
notify: properties.contains(NOTIFY),
indicate: properties.contains(INDICATE),
authenticated_signed_writes: properties.contains(AUTHENTICATED_SIGNED_WRITES),
reliable_write: properties.contains(RELIABLE_WRITE),
writable_auxiliaries: properties.contains(WRITABLE_AUXILIARIES),
}
);
characteristics_vec.push(BluetoothCharacteristicMsg {
uuid: uuid,
instance_id: characteristic.get_id(),
broadcast: properties.contains(Flags::BROADCAST),
read: properties.contains(Flags::READ),
write_without_response: properties
.contains(Flags::WRITE_WITHOUT_RESPONSE),
write: properties.contains(Flags::WRITE),
notify: properties.contains(Flags::NOTIFY),
indicate: properties.contains(Flags::INDICATE),
authenticated_signed_writes: properties
.contains(Flags::AUTHENTICATED_SIGNED_WRITES),
reliable_write: properties.contains(Flags::RELIABLE_WRITE),
writable_auxiliaries: properties.contains(Flags::WRITABLE_AUXILIARIES),
});
}
}
@ -762,7 +789,10 @@ impl BluetoothManager {
return Err(BluetoothError::NotFound);
}
return Ok(BluetoothResponse::GetCharacteristics(characteristics_vec, single));
return Ok(BluetoothResponse::GetCharacteristics(
characteristics_vec,
single,
));
},
GATTType::IncludedService => {
// Step 5.
@ -778,17 +808,15 @@ impl BluetoothManager {
Some(s) => s,
None => return Err(BluetoothError::NotFound),
};
let services = primary_service.get_includes(device).unwrap_or(vec!());
let mut services_vec = vec!();
let services = primary_service.get_includes(device).unwrap_or(vec![]);
let mut services_vec = vec![];
for service in services {
if let Ok(service_uuid) = service.get_uuid() {
services_vec.push(
BluetoothServiceMsg {
uuid: service_uuid,
is_primary: service.is_primary().unwrap_or(false),
instance_id: service.get_id(),
}
);
services_vec.push(BluetoothServiceMsg {
uuid: service_uuid,
is_primary: service.is_primary().unwrap_or(false),
instance_id: service.get_id(),
});
}
}
if let Some(uuid) = uuid {
@ -813,15 +841,13 @@ impl BluetoothManager {
if let Some(uuid) = uuid {
descriptors.retain(|ref e| e.get_uuid().unwrap_or(String::new()) == uuid);
}
let mut descriptors_vec = vec!();
let mut descriptors_vec = vec![];
for descriptor in descriptors {
if let Ok(uuid) = descriptor.get_uuid() {
descriptors_vec.push(
BluetoothDescriptorMsg {
uuid: uuid,
instance_id: descriptor.get_id(),
}
);
descriptors_vec.push(BluetoothDescriptorMsg {
uuid: uuid,
instance_id: descriptor.get_id(),
});
}
}
@ -839,18 +865,20 @@ impl BluetoothManager {
fn read_value(&mut self, id: String) -> BluetoothResponseResult {
// (Characteristic) Step 5.2: Missing because it is optional.
// (Descriptor) Step 5.1: Missing because it is optional.
let mut adapter = try!(self.get_adapter());
let mut adapter = self.get_adapter()?;
// (Characteristic) Step 5.3.
let mut value = self.get_gatt_characteristic(&mut adapter, &id)
.map(|c| c.read_value().unwrap_or(vec![]));
let mut value = self
.get_gatt_characteristic(&mut adapter, &id)
.map(|c| c.read_value().unwrap_or(vec![]));
// (Characteristic) TODO: Step 5.4: Handle all the errors returned from the read_value call.
// (Descriptor) Step 5.2.
if value.is_none() {
value = self.get_gatt_descriptor(&mut adapter, &id)
.map(|d| d.read_value().unwrap_or(vec![]));
value = self
.get_gatt_descriptor(&mut adapter, &id)
.map(|d| d.read_value().unwrap_or(vec![]));
}
// (Descriptor) TODO: Step 5.3: Handle all the errors returned from the read_value call.
@ -871,18 +899,20 @@ impl BluetoothManager {
fn write_value(&mut self, id: String, value: Vec<u8>) -> BluetoothResponseResult {
// (Characteristic) Step 7.2: Missing because it is optional.
// (Descriptor) Step 7.1: Missing because it is optional.
let mut adapter = try!(self.get_adapter());
let mut adapter = self.get_adapter()?;
// (Characteristic) Step 7.3.
let mut result = self.get_gatt_characteristic(&mut adapter, &id)
.map(|c| c.write_value(value.clone()));
let mut result = self
.get_gatt_characteristic(&mut adapter, &id)
.map(|c| c.write_value(value.clone()));
// (Characteristic) TODO: Step 7.4: Handle all the errors returned from the write_value call.
// (Descriptor) Step 7.2.
if result.is_none() {
result = self.get_gatt_descriptor(&mut adapter, &id)
.map(|d| d.write_value(value.clone()));
result = self
.get_gatt_descriptor(&mut adapter, &id)
.map(|d| d.write_value(value.clone()));
}
// (Descriptor) TODO: Step 7.3: Handle all the errors returned from the write_value call.
@ -913,16 +943,16 @@ impl BluetoothManager {
}
// (StartNotification) TODO: Step 7: Missing because it is optional.
let mut adapter = try!(self.get_adapter());
let mut adapter = self.get_adapter()?;
match self.get_gatt_characteristic(&mut adapter, &id) {
Some(c) => {
let result = match enable {
let result = if enable {
// (StartNotification) Step 8.
// TODO: Handle all the errors returned from the start_notify call.
true => c.start_notify(),
c.start_notify()
} else {
// (StopNotification) Step 4.
false => c.stop_notify(),
c.stop_notify()
};
match result {
// (StartNotification) Step 11.
@ -947,6 +977,8 @@ impl BluetoothManager {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability
fn get_availability(&mut self) -> BluetoothResponseResult {
Ok(BluetoothResponse::GetAvailability(self.get_adapter().is_ok()))
Ok(BluetoothResponse::GetAvailability(
self.get_adapter().is_ok(),
))
}
}

View file

@ -1,13 +1,15 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use BluetoothManager;
use crate::BluetoothManager;
use device::bluetooth::{BluetoothAdapter, BluetoothDevice};
use device::bluetooth::{BluetoothGATTCharacteristic, BluetoothGATTDescriptor, BluetoothGATTService};
use device::bluetooth::{
BluetoothGATTCharacteristic, BluetoothGATTDescriptor, BluetoothGATTService,
};
use std::borrow::ToOwned;
use std::cell::RefCell;
use std::collections::{HashSet, HashMap};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::string::String;
use uuid::Uuid;
@ -34,7 +36,8 @@ const UNICODE_DEVICE_ADAPTER: &'static str = "UnicodeDeviceAdapter";
// https://cs.chromium.org/chromium/src/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h?l=205
const MISSING_SERVICE_HEART_RATE_ADAPTER: &'static str = "MissingServiceHeartRateAdapter";
// https://cs.chromium.org/chromium/src/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h?l=219
const MISSING_CHARACTERISTIC_HEART_RATE_ADAPTER: &'static str = "MissingCharacteristicHeartRateAdapter";
const MISSING_CHARACTERISTIC_HEART_RATE_ADAPTER: &'static str =
"MissingCharacteristicHeartRateAdapter";
const MISSING_DESCRIPTOR_HEART_RATE_ADAPTER: &'static str = "MissingDescriptorHeartRateAdapter";
// https://cs.chromium.org/chromium/src/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h?l=234
const HEART_RATE_ADAPTER: &'static str = "HeartRateAdapter";
@ -80,32 +83,38 @@ const HUMAN_INTERFACE_DEVICE_SERVICE_UUID: &'static str = "00001812-0000-1000-80
const TX_POWER_SERVICE_UUID: &'static str = "00001804-0000-1000-8000-00805f9b34fb";
// Characteristic UUIDs
const BLOCKLIST_EXCLUDE_READS_CHARACTERISTIC_UUID: &'static str = "bad1c9a2-9a5b-4015-8b60-1579bbbf2135";
const BLOCKLIST_EXCLUDE_READS_CHARACTERISTIC_UUID: &'static str =
"bad1c9a2-9a5b-4015-8b60-1579bbbf2135";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
const BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID: &'static str = "00002a38-0000-1000-8000-00805f9b34fb";
const BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID: &'static str =
"00002a38-0000-1000-8000-00805f9b34fb";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.gap.device_name.xml
const DEVICE_NAME_CHARACTERISTIC_UUID: &'static str = "00002a00-0000-1000-8000-00805f9b34fb";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml
const HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID: &'static str = "00002a37-0000-1000-8000-00805f9b34fb";
const HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID: &'static str =
"00002a37-0000-1000-8000-00805f9b34fb";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.gap.peripheral_privacy_flag.xml
const PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC_UUID: &'static str = "00002a02-0000-1000-8000-00805f9b34fb";
const PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC_UUID: &'static str =
"00002a02-0000-1000-8000-00805f9b34fb";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.serial_number_string.xml
const SERIAL_NUMBER_STRING_UUID: &'static str = "00002a25-0000-1000-8000-00805f9b34fb";
// Descriptor UUIDs
const BLOCKLIST_EXCLUDE_READS_DESCRIPTOR_UUID: &'static str = "aaaaaaaa-aaaa-1181-0510-810819516110";
const BLOCKLIST_EXCLUDE_READS_DESCRIPTOR_UUID: &'static str =
"aaaaaaaa-aaaa-1181-0510-810819516110";
const BLOCKLIST_DESCRIPTOR_UUID: &'static str = "07711111-6104-0970-7011-1107105110aa";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml
const CHARACTERISTIC_USER_DESCRIPTION_UUID: &'static str = "00002901-0000-1000-8000-00805f9b34fb";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
const CLIENT_CHARACTERISTIC_CONFIGURATION_UUID: &'static str = "00002902-0000-1000-8000-00805f9b34fb";
const CLIENT_CHARACTERISTIC_CONFIGURATION_UUID: &'static str =
"00002902-0000-1000-8000-00805f9b34fb";
// https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.descriptor.number_of_digitals.xml
const NUMBER_OF_DIGITALS_UUID: &'static str = "00002909-0000-1000-8000-00805f9b34fb";
@ -117,335 +126,398 @@ fn generate_id() -> Uuid {
let mut generated = false;
while !generated {
id = Uuid::new_v4();
CACHED_IDS.with(|cache|
CACHED_IDS.with(|cache| {
if !cache.borrow().contains(&id) {
cache.borrow_mut().insert(id.clone());
generated = true;
}
);
});
}
id
}
// Set the adapter's name, is_powered and is_discoverable attributes
fn set_adapter(adapter: &BluetoothAdapter, adapter_name: String) -> Result<(), Box<Error>> {
try!(adapter.set_name(adapter_name));
try!(adapter.set_powered(true));
try!(adapter.set_discoverable(true));
fn set_adapter(adapter: &BluetoothAdapter, adapter_name: String) -> Result<(), Box<dyn Error>> {
adapter.set_name(adapter_name)?;
adapter.set_powered(true)?;
adapter.set_discoverable(true)?;
Ok(())
}
// Create Device
fn create_device(adapter: &BluetoothAdapter,
name: String,
address: String)
-> Result<BluetoothDevice, Box<Error>> {
let device = try!(BluetoothDevice::create_mock_device(adapter.clone(), generate_id().to_string()));
try!(device.set_name(Some(name)));
try!(device.set_address(address));
try!(device.set_connectable(true));
fn create_device(
adapter: &BluetoothAdapter,
name: String,
address: String,
) -> Result<BluetoothDevice, Box<dyn Error>> {
let device = BluetoothDevice::create_mock_device(adapter.clone(), generate_id().to_string())?;
device.set_name(Some(name))?;
device.set_address(address)?;
device.set_connectable(true)?;
Ok(device)
}
// Create Device with UUIDs
fn create_device_with_uuids(adapter: &BluetoothAdapter,
name: String,
address: String,
uuids: Vec<String>)
-> Result<BluetoothDevice, Box<Error>> {
let device = try!(create_device(adapter, name, address));
try!(device.set_uuids(uuids));
fn create_device_with_uuids(
adapter: &BluetoothAdapter,
name: String,
address: String,
uuids: Vec<String>,
) -> Result<BluetoothDevice, Box<dyn Error>> {
let device = create_device(adapter, name, address)?;
device.set_uuids(uuids)?;
Ok(device)
}
// Create Service
fn create_service(device: &BluetoothDevice,
uuid: String)
-> Result<BluetoothGATTService, Box<Error>> {
let service = try!(BluetoothGATTService::create_mock_service(device.clone(), generate_id().to_string()));
try!(service.set_uuid(uuid));
fn create_service(
device: &BluetoothDevice,
uuid: String,
) -> Result<BluetoothGATTService, Box<dyn Error>> {
let service =
BluetoothGATTService::create_mock_service(device.clone(), generate_id().to_string())?;
service.set_uuid(uuid)?;
Ok(service)
}
// Create Characteristic
fn create_characteristic(service: &BluetoothGATTService,
uuid: String)
-> Result<BluetoothGATTCharacteristic, Box<Error>> {
let characteristic =
try!(BluetoothGATTCharacteristic::create_mock_characteristic(service.clone(), generate_id().to_string()));
try!(characteristic.set_uuid(uuid));
fn create_characteristic(
service: &BluetoothGATTService,
uuid: String,
) -> Result<BluetoothGATTCharacteristic, Box<dyn Error>> {
let characteristic = BluetoothGATTCharacteristic::create_mock_characteristic(
service.clone(),
generate_id().to_string(),
)?;
characteristic.set_uuid(uuid)?;
Ok(characteristic)
}
// Create Characteristic with value
fn create_characteristic_with_value(service: &BluetoothGATTService,
uuid: String,
value: Vec<u8>)
-> Result<BluetoothGATTCharacteristic, Box<Error>> {
let characteristic = try!(create_characteristic(service, uuid));
try!(characteristic.set_value(value));
fn create_characteristic_with_value(
service: &BluetoothGATTService,
uuid: String,
value: Vec<u8>,
) -> Result<BluetoothGATTCharacteristic, Box<dyn Error>> {
let characteristic = create_characteristic(service, uuid)?;
characteristic.set_value(value)?;
Ok(characteristic)
}
// Create Descriptor
fn create_descriptor(characteristic: &BluetoothGATTCharacteristic,
uuid: String)
-> Result<BluetoothGATTDescriptor, Box<Error>> {
let descriptor =
try!(BluetoothGATTDescriptor::create_mock_descriptor(characteristic.clone(), generate_id().to_string()));
try!(descriptor.set_uuid(uuid));
fn create_descriptor(
characteristic: &BluetoothGATTCharacteristic,
uuid: String,
) -> Result<BluetoothGATTDescriptor, Box<dyn Error>> {
let descriptor = BluetoothGATTDescriptor::create_mock_descriptor(
characteristic.clone(),
generate_id().to_string(),
)?;
descriptor.set_uuid(uuid)?;
Ok(descriptor)
}
// Create Descriptor with value
fn create_descriptor_with_value(characteristic: &BluetoothGATTCharacteristic,
uuid: String,
value: Vec<u8>)
-> Result<BluetoothGATTDescriptor, Box<Error>> {
let descriptor = try!(create_descriptor(characteristic, uuid));
try!(descriptor.set_value(value));
fn create_descriptor_with_value(
characteristic: &BluetoothGATTCharacteristic,
uuid: String,
value: Vec<u8>,
) -> Result<BluetoothGATTDescriptor, Box<dyn Error>> {
let descriptor = create_descriptor(characteristic, uuid)?;
descriptor.set_value(value)?;
Ok(descriptor)
}
fn create_heart_rate_service(device: &BluetoothDevice,
empty: bool)
-> Result<BluetoothGATTService, Box<Error>> {
fn create_heart_rate_service(
device: &BluetoothDevice,
empty: bool,
) -> Result<BluetoothGATTService, Box<dyn Error>> {
// Heart Rate Service
let heart_rate_service = try!(create_service(device, HEART_RATE_SERVICE_UUID.to_owned()));
let heart_rate_service = create_service(device, HEART_RATE_SERVICE_UUID.to_owned())?;
if empty {
return Ok(heart_rate_service)
return Ok(heart_rate_service);
}
// Heart Rate Measurement Characteristic
let heart_rate_measurement_characteristic =
try!(create_characteristic_with_value(&heart_rate_service,
HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID.to_owned(),
vec![0]));
try!(heart_rate_measurement_characteristic.set_flags(vec![NOTIFY_FLAG.to_string(),
READ_FLAG.to_string(),
WRITE_FLAG.to_string()]));
let heart_rate_measurement_characteristic = create_characteristic_with_value(
&heart_rate_service,
HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID.to_owned(),
vec![0],
)?;
heart_rate_measurement_characteristic.set_flags(vec![
NOTIFY_FLAG.to_string(),
READ_FLAG.to_string(),
WRITE_FLAG.to_string(),
])?;
// Body Sensor Location Characteristic 1
let body_sensor_location_characteristic_1 =
try!(create_characteristic_with_value(&heart_rate_service,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![49]));
try!(body_sensor_location_characteristic_1.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let body_sensor_location_characteristic_1 = create_characteristic_with_value(
&heart_rate_service,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![49],
)?;
body_sensor_location_characteristic_1
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
// Body Sensor Location Characteristic 2
let body_sensor_location_characteristic_2 =
try!(create_characteristic_with_value(&heart_rate_service,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![50]));
try!(body_sensor_location_characteristic_2.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let body_sensor_location_characteristic_2 = create_characteristic_with_value(
&heart_rate_service,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![50],
)?;
body_sensor_location_characteristic_2
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
Ok(heart_rate_service)
}
fn create_generic_access_service(device: &BluetoothDevice,
empty: bool)
-> Result<BluetoothGATTService, Box<Error>> {
fn create_generic_access_service(
device: &BluetoothDevice,
empty: bool,
) -> Result<BluetoothGATTService, Box<dyn Error>> {
// Generic Access Service
let generic_access_service =
try!(create_service(device, GENERIC_ACCESS_SERVICE_UUID.to_owned()));
let generic_access_service = create_service(device, GENERIC_ACCESS_SERVICE_UUID.to_owned())?;
if empty {
return Ok(generic_access_service)
return Ok(generic_access_service);
}
// Device Name Characteristic
let device_name_characteristic =
try!(create_characteristic_with_value(&generic_access_service,
DEVICE_NAME_CHARACTERISTIC_UUID.to_owned(),
HEART_RATE_DEVICE_NAME.as_bytes().to_vec()));
try!(device_name_characteristic.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let device_name_characteristic = create_characteristic_with_value(
&generic_access_service,
DEVICE_NAME_CHARACTERISTIC_UUID.to_owned(),
HEART_RATE_DEVICE_NAME.as_bytes().to_vec(),
)?;
device_name_characteristic.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
// Number of Digitals descriptor
let number_of_digitals_descriptor_1 =
try!(create_descriptor_with_value(&device_name_characteristic,
NUMBER_OF_DIGITALS_UUID.to_owned(),
vec![49]));
try!(number_of_digitals_descriptor_1.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let number_of_digitals_descriptor_1 = create_descriptor_with_value(
&device_name_characteristic,
NUMBER_OF_DIGITALS_UUID.to_owned(),
vec![49],
)?;
number_of_digitals_descriptor_1
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
let number_of_digitals_descriptor_2 =
try!(create_descriptor_with_value(&device_name_characteristic,
NUMBER_OF_DIGITALS_UUID.to_owned(),
vec![50]));
try!(number_of_digitals_descriptor_2.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let number_of_digitals_descriptor_2 = create_descriptor_with_value(
&device_name_characteristic,
NUMBER_OF_DIGITALS_UUID.to_owned(),
vec![50],
)?;
number_of_digitals_descriptor_2
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
// Characteristic User Description Descriptor
let _characteristic_user_description =
try!(create_descriptor_with_value(&device_name_characteristic,
CHARACTERISTIC_USER_DESCRIPTION_UUID.to_owned(),
HEART_RATE_DEVICE_NAME_DESCRIPTION.as_bytes().to_vec()));
let _characteristic_user_description = create_descriptor_with_value(
&device_name_characteristic,
CHARACTERISTIC_USER_DESCRIPTION_UUID.to_owned(),
HEART_RATE_DEVICE_NAME_DESCRIPTION.as_bytes().to_vec(),
)?;
// Client Characteristic Configuration descriptor
let _client_characteristic_configuration =
try!(create_descriptor_with_value(&device_name_characteristic,
CLIENT_CHARACTERISTIC_CONFIGURATION_UUID.to_owned(),
vec![0]));
let _client_characteristic_configuration = create_descriptor_with_value(
&device_name_characteristic,
CLIENT_CHARACTERISTIC_CONFIGURATION_UUID.to_owned(),
vec![0],
)?;
// Peripheral Privacy Flag Characteristic
let peripheral_privacy_flag_characteristic =
try!(create_characteristic(&generic_access_service, PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC_UUID.to_owned()));
try!(peripheral_privacy_flag_characteristic
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let peripheral_privacy_flag_characteristic = create_characteristic(
&generic_access_service,
PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC_UUID.to_owned(),
)?;
peripheral_privacy_flag_characteristic
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
Ok(generic_access_service)
}
// Create Heart Rate Device
fn create_heart_rate_device(adapter: &BluetoothAdapter,
empty: bool)
-> Result<BluetoothDevice, Box<Error>> {
fn create_heart_rate_device(
adapter: &BluetoothAdapter,
empty: bool,
) -> Result<BluetoothDevice, Box<dyn Error>> {
// Heart Rate Device
let heart_rate_device =
try!(create_device_with_uuids(adapter,
HEART_RATE_DEVICE_NAME.to_owned(),
HEART_RATE_DEVICE_ADDRESS.to_owned(),
vec![GENERIC_ACCESS_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned()]));
let heart_rate_device = create_device_with_uuids(
adapter,
HEART_RATE_DEVICE_NAME.to_owned(),
HEART_RATE_DEVICE_ADDRESS.to_owned(),
vec![
GENERIC_ACCESS_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned(),
],
)?;
if empty {
return Ok(heart_rate_device);
}
// Generic Access Service
let _generic_access_service = try!(create_generic_access_service(&heart_rate_device, false));
let _generic_access_service = create_generic_access_service(&heart_rate_device, false)?;
// Heart Rate Service
let _heart_rate_service = try!(create_heart_rate_service(&heart_rate_device, false));
let _heart_rate_service = create_heart_rate_service(&heart_rate_device, false)?;
Ok(heart_rate_device)
}
fn create_missing_characterisitc_heart_rate_device(adapter: &BluetoothAdapter) -> Result<(), Box<Error>> {
let heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
fn create_missing_characterisitc_heart_rate_device(
adapter: &BluetoothAdapter,
) -> Result<(), Box<dyn Error>> {
let heart_rate_device_empty = create_heart_rate_device(adapter, true)?;
let _generic_access_service_empty = try!(create_generic_access_service(&heart_rate_device_empty, true));
let _generic_access_service_empty =
create_generic_access_service(&heart_rate_device_empty, true)?;
let _heart_rate_service_empty = try!(create_heart_rate_service(&heart_rate_device_empty, true));
let _heart_rate_service_empty = create_heart_rate_service(&heart_rate_device_empty, true)?;
Ok(())
}
fn create_missing_descriptor_heart_rate_device(adapter: &BluetoothAdapter) -> Result<(), Box<Error>> {
let heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
fn create_missing_descriptor_heart_rate_device(
adapter: &BluetoothAdapter,
) -> Result<(), Box<dyn Error>> {
let heart_rate_device_empty = create_heart_rate_device(adapter, true)?;
let generic_access_service_empty = try!(create_generic_access_service(&heart_rate_device_empty, true));
let generic_access_service_empty =
create_generic_access_service(&heart_rate_device_empty, true)?;
let _device_name_characteristic =
try!(create_characteristic_with_value(&generic_access_service_empty,
DEVICE_NAME_CHARACTERISTIC_UUID.to_owned(),
HEART_RATE_DEVICE_NAME.as_bytes().to_vec()));
let _device_name_characteristic = create_characteristic_with_value(
&generic_access_service_empty,
DEVICE_NAME_CHARACTERISTIC_UUID.to_owned(),
HEART_RATE_DEVICE_NAME.as_bytes().to_vec(),
)?;
let peripheral_privacy_flag_characteristic =
try!(create_characteristic(&generic_access_service_empty,
PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC_UUID.to_owned()));
try!(peripheral_privacy_flag_characteristic.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let peripheral_privacy_flag_characteristic = create_characteristic(
&generic_access_service_empty,
PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC_UUID.to_owned(),
)?;
peripheral_privacy_flag_characteristic
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
let _heart_rate_service = try!(create_heart_rate_service(&heart_rate_device_empty, false));
let _heart_rate_service = create_heart_rate_service(&heart_rate_device_empty, false)?;
Ok(())
}
fn create_two_heart_rate_services_device(adapter: &BluetoothAdapter) -> Result<(), Box<Error>> {
let heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
fn create_two_heart_rate_services_device(adapter: &BluetoothAdapter) -> Result<(), Box<dyn Error>> {
let heart_rate_device_empty = create_heart_rate_device(adapter, true)?;
try!(heart_rate_device_empty.set_uuids(vec![GENERIC_ACCESS_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned()]));
heart_rate_device_empty.set_uuids(vec![
GENERIC_ACCESS_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned(),
])?;
let _generic_access_service = try!(create_generic_access_service(&heart_rate_device_empty, false));
let _generic_access_service = create_generic_access_service(&heart_rate_device_empty, false)?;
let heart_rate_service_empty_1 = try!(create_heart_rate_service(&heart_rate_device_empty, true));
let heart_rate_service_empty_1 = create_heart_rate_service(&heart_rate_device_empty, true)?;
let heart_rate_service_empty_2 = try!(create_heart_rate_service(&heart_rate_device_empty, true));
let heart_rate_service_empty_2 = create_heart_rate_service(&heart_rate_device_empty, true)?;
let heart_rate_measurement_characteristic =
try!(create_characteristic_with_value(&heart_rate_service_empty_1,
HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID.to_owned(),
vec![0]));
try!(heart_rate_measurement_characteristic.set_flags(vec![NOTIFY_FLAG.to_string()]));
let heart_rate_measurement_characteristic = create_characteristic_with_value(
&heart_rate_service_empty_1,
HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID.to_owned(),
vec![0],
)?;
heart_rate_measurement_characteristic.set_flags(vec![NOTIFY_FLAG.to_string()])?;
let _body_sensor_location_characteristic_1 =
try!(create_characteristic_with_value(&heart_rate_service_empty_1,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![49]));
let _body_sensor_location_characteristic_1 = create_characteristic_with_value(
&heart_rate_service_empty_1,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![49],
)?;
let _body_sensor_location_characteristic_2 =
try!(create_characteristic_with_value(&heart_rate_service_empty_2,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![50]));
let _body_sensor_location_characteristic_2 = create_characteristic_with_value(
&heart_rate_service_empty_2,
BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID.to_owned(),
vec![50],
)?;
Ok(())
}
fn create_blocklisted_device(adapter: &BluetoothAdapter) -> Result<(), Box<Error>> {
let connectable_device =
try!(create_device_with_uuids(adapter,
CONNECTABLE_DEVICE_NAME.to_owned(),
CONNECTABLE_DEVICE_ADDRESS.to_owned(),
vec![BLOCKLIST_TEST_SERVICE_UUID.to_owned(),
DEVICE_INFORMATION_UUID.to_owned(),
GENERIC_ACCESS_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned(),
HUMAN_INTERFACE_DEVICE_SERVICE_UUID.to_owned()]));
fn create_blocklisted_device(adapter: &BluetoothAdapter) -> Result<(), Box<dyn Error>> {
let connectable_device = create_device_with_uuids(
adapter,
CONNECTABLE_DEVICE_NAME.to_owned(),
CONNECTABLE_DEVICE_ADDRESS.to_owned(),
vec![
BLOCKLIST_TEST_SERVICE_UUID.to_owned(),
DEVICE_INFORMATION_UUID.to_owned(),
GENERIC_ACCESS_SERVICE_UUID.to_owned(),
HEART_RATE_SERVICE_UUID.to_owned(),
HUMAN_INTERFACE_DEVICE_SERVICE_UUID.to_owned(),
],
)?;
let blocklist_test_service = try!(create_service(&connectable_device, BLOCKLIST_TEST_SERVICE_UUID.to_owned()));
let blocklist_test_service =
create_service(&connectable_device, BLOCKLIST_TEST_SERVICE_UUID.to_owned())?;
let blocklist_exclude_reads_characteristic =
try!(create_characteristic(&blocklist_test_service,
BLOCKLIST_EXCLUDE_READS_CHARACTERISTIC_UUID.to_owned()));
try!(blocklist_exclude_reads_characteristic
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()]));
let blocklist_exclude_reads_characteristic = create_characteristic(
&blocklist_test_service,
BLOCKLIST_EXCLUDE_READS_CHARACTERISTIC_UUID.to_owned(),
)?;
blocklist_exclude_reads_characteristic
.set_flags(vec![READ_FLAG.to_string(), WRITE_FLAG.to_string()])?;
let _blocklist_exclude_reads_descriptor =
try!(create_descriptor_with_value(&blocklist_exclude_reads_characteristic,
BLOCKLIST_EXCLUDE_READS_DESCRIPTOR_UUID.to_owned(),
vec![54; 3]));
let _blocklist_exclude_reads_descriptor = create_descriptor_with_value(
&blocklist_exclude_reads_characteristic,
BLOCKLIST_EXCLUDE_READS_DESCRIPTOR_UUID.to_owned(),
vec![54; 3],
)?;
let _blocklist_descriptor =
try!(create_descriptor_with_value(&blocklist_exclude_reads_characteristic,
BLOCKLIST_DESCRIPTOR_UUID.to_owned(),
vec![54; 3]));
let _blocklist_descriptor = create_descriptor_with_value(
&blocklist_exclude_reads_characteristic,
BLOCKLIST_DESCRIPTOR_UUID.to_owned(),
vec![54; 3],
)?;
let device_information_service = try!(create_service(&connectable_device, DEVICE_INFORMATION_UUID.to_owned()));
let device_information_service =
create_service(&connectable_device, DEVICE_INFORMATION_UUID.to_owned())?;
let _serial_number_string_characteristic =
try!(create_characteristic(&device_information_service, SERIAL_NUMBER_STRING_UUID.to_owned()));
let _serial_number_string_characteristic = create_characteristic(
&device_information_service,
SERIAL_NUMBER_STRING_UUID.to_owned(),
)?;
let _generic_access_service = try!(create_generic_access_service(&connectable_device, false));
let _generic_access_service = create_generic_access_service(&connectable_device, false)?;
let _heart_rate_service = try!(create_heart_rate_service(&connectable_device, false));
let _heart_rate_service = create_heart_rate_service(&connectable_device, false)?;
let _human_interface_device_service =
try!(create_service(&connectable_device, HUMAN_INTERFACE_DEVICE_SERVICE_UUID.to_owned()));
let _human_interface_device_service = create_service(
&connectable_device,
HUMAN_INTERFACE_DEVICE_SERVICE_UUID.to_owned(),
)?;
Ok(())
}
fn create_glucose_heart_rate_devices(adapter: &BluetoothAdapter) -> Result<(), Box<Error>> {
let glucose_devie = try!(create_device_with_uuids(adapter,
GLUCOSE_DEVICE_NAME.to_owned(),
GLUCOSE_DEVICE_ADDRESS.to_owned(),
vec![GLUCOSE_SERVICE_UUID.to_owned(),
TX_POWER_SERVICE_UUID.to_owned()]));
fn create_glucose_heart_rate_devices(adapter: &BluetoothAdapter) -> Result<(), Box<dyn Error>> {
let glucose_devie = create_device_with_uuids(
adapter,
GLUCOSE_DEVICE_NAME.to_owned(),
GLUCOSE_DEVICE_ADDRESS.to_owned(),
vec![
GLUCOSE_SERVICE_UUID.to_owned(),
TX_POWER_SERVICE_UUID.to_owned(),
],
)?;
let heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
let heart_rate_device_empty = create_heart_rate_device(adapter, true)?;
let mut manufacturer_dta = HashMap::new();
manufacturer_dta.insert(17, vec![1, 2, 3]);
try!(glucose_devie.set_manufacturer_data(manufacturer_dta));
glucose_devie.set_manufacturer_data(manufacturer_dta)?;
let mut service_data = HashMap::new();
service_data.insert(GLUCOSE_SERVICE_UUID.to_owned(), vec![1, 2, 3]);
try!(glucose_devie.set_service_data(service_data));
glucose_devie.set_service_data(service_data)?;
service_data = HashMap::new();
service_data.insert(HEART_RATE_SERVICE_UUID.to_owned(), vec![1, 2, 3]);
try!(heart_rate_device_empty.set_service_data(service_data));
heart_rate_device_empty.set_service_data(service_data)?;
Ok(())
}
pub fn test(manager: &mut BluetoothManager, data_set_name: String) -> Result<(), Box<Error>> {
pub fn test(manager: &mut BluetoothManager, data_set_name: String) -> Result<(), Box<dyn Error>> {
let may_existing_adapter = manager.get_or_create_adapter();
let adapter = match may_existing_adapter.as_ref() {
Some(adapter) => adapter,
@ -453,68 +525,73 @@ pub fn test(manager: &mut BluetoothManager, data_set_name: String) -> Result<(),
};
match data_set_name.as_str() {
NOT_PRESENT_ADAPTER => {
try!(set_adapter(adapter, NOT_PRESENT_ADAPTER.to_owned()));
try!(adapter.set_present(false));
set_adapter(adapter, NOT_PRESENT_ADAPTER.to_owned())?;
adapter.set_present(false)?;
},
NOT_POWERED_ADAPTER => {
try!(set_adapter(adapter, NOT_POWERED_ADAPTER.to_owned()));
try!(adapter.set_powered(false));
set_adapter(adapter, NOT_POWERED_ADAPTER.to_owned())?;
adapter.set_powered(false)?;
},
EMPTY_ADAPTER => {
try!(set_adapter(adapter, EMPTY_ADAPTER.to_owned()));
set_adapter(adapter, EMPTY_ADAPTER.to_owned())?;
},
GLUCOSE_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, GLUCOSE_HEART_RATE_ADAPTER.to_owned()));
let _ = try!(create_glucose_heart_rate_devices(adapter));
set_adapter(adapter, GLUCOSE_HEART_RATE_ADAPTER.to_owned())?;
let _ = create_glucose_heart_rate_devices(adapter)?;
},
UNICODE_DEVICE_ADAPTER => {
try!(set_adapter(adapter, UNICODE_DEVICE_ADAPTER.to_owned()));
set_adapter(adapter, UNICODE_DEVICE_ADAPTER.to_owned())?;
let _unicode_device = try!(create_device(adapter,
UNICODE_DEVICE_NAME.to_owned(),
UNICODE_DEVICE_ADDRESS.to_owned()));
let _unicode_device = create_device(
adapter,
UNICODE_DEVICE_NAME.to_owned(),
UNICODE_DEVICE_ADDRESS.to_owned(),
)?;
},
MISSING_SERVICE_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, MISSING_SERVICE_HEART_RATE_ADAPTER.to_owned()));
set_adapter(adapter, MISSING_SERVICE_HEART_RATE_ADAPTER.to_owned())?;
let _heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
let _heart_rate_device_empty = create_heart_rate_device(adapter, true)?;
},
MISSING_CHARACTERISTIC_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, MISSING_CHARACTERISTIC_HEART_RATE_ADAPTER.to_owned()));
set_adapter(
adapter,
MISSING_CHARACTERISTIC_HEART_RATE_ADAPTER.to_owned(),
)?;
let _ = try!(create_missing_characterisitc_heart_rate_device(adapter));
let _ = create_missing_characterisitc_heart_rate_device(adapter)?;
},
MISSING_DESCRIPTOR_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, MISSING_DESCRIPTOR_HEART_RATE_ADAPTER.to_owned()));
set_adapter(adapter, MISSING_DESCRIPTOR_HEART_RATE_ADAPTER.to_owned())?;
let _ = try!(create_missing_descriptor_heart_rate_device(adapter));
let _ = create_missing_descriptor_heart_rate_device(adapter)?;
},
HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, HEART_RATE_ADAPTER.to_owned()));
set_adapter(adapter, HEART_RATE_ADAPTER.to_owned())?;
let _heart_rate_device = try!(create_heart_rate_device(adapter, false));
let _heart_rate_device = create_heart_rate_device(adapter, false)?;
},
EMPTY_NAME_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, EMPTY_NAME_HEART_RATE_ADAPTER.to_owned()));
set_adapter(adapter, EMPTY_NAME_HEART_RATE_ADAPTER.to_owned())?;
let heart_rate_device = try!(create_heart_rate_device(adapter, false));
try!(heart_rate_device.set_name(Some(EMPTY_DEVICE_NAME.to_owned())));
let heart_rate_device = create_heart_rate_device(adapter, false)?;
heart_rate_device.set_name(Some(EMPTY_DEVICE_NAME.to_owned()))?;
},
NO_NAME_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, NO_NAME_HEART_RATE_ADAPTER.to_owned()));
set_adapter(adapter, NO_NAME_HEART_RATE_ADAPTER.to_owned())?;
let heart_rate_device = try!(create_heart_rate_device(adapter, false));
try!(heart_rate_device.set_name(None));
let heart_rate_device = create_heart_rate_device(adapter, false)?;
heart_rate_device.set_name(None)?;
},
TWO_HEART_RATE_SERVICES_ADAPTER => {
try!(set_adapter(adapter, TWO_HEART_RATE_SERVICES_ADAPTER.to_owned()));
set_adapter(adapter, TWO_HEART_RATE_SERVICES_ADAPTER.to_owned())?;
let _ = try!(create_two_heart_rate_services_device(adapter));
let _ = create_two_heart_rate_services_device(adapter)?;
},
BLOCKLIST_TEST_ADAPTER => {
try!(set_adapter(adapter, BLOCKLIST_TEST_ADAPTER.to_owned()));
set_adapter(adapter, BLOCKLIST_TEST_ADAPTER.to_owned())?;
let _ = try!(create_blocklisted_device(adapter));
let _ = create_blocklisted_device(adapter)?;
},
_ => return Err(Box::from(WRONG_DATA_SET_ERROR.to_string())),
}

View file

@ -3,6 +3,7 @@ name = "bluetooth_traits"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
@ -10,8 +11,7 @@ name = "bluetooth_traits"
path = "lib.rs"
[dependencies]
ipc-channel = "0.7"
regex = "0.2"
serde = "0.9"
serde_derive = "0.9"
servo_config = {path = "../config"}
embedder_traits = { path = "../embedder_traits" }
ipc-channel = "0.14"
regex = "1.1"
serde = "1.0"

View file

@ -1,36 +1,26 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use embedder_traits::resources::{self, Resource};
use regex::Regex;
use servo_config::resource_files::read_resource_file;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::BufRead;
use std::string::String;
const BLOCKLIST_FILE: &'static str = "gatt_blocklist.txt";
const BLOCKLIST_FILE_NOT_FOUND: &'static str = "Could not find gatt_blocklist.txt file";
const EXCLUDE_READS: &'static str = "exclude-reads";
const EXCLUDE_WRITES: &'static str = "exclude-writes";
const VALID_UUID_REGEX: &'static str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
const VALID_UUID_REGEX: &'static str =
"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
thread_local!(pub static BLUETOOTH_BLOCKLIST: RefCell<BluetoothBlocklist> =
RefCell::new(BluetoothBlocklist(parse_blocklist())));
pub fn uuid_is_blocklisted(uuid: &str, exclude_type: Blocklist) -> bool {
BLUETOOTH_BLOCKLIST.with(|blist| {
match exclude_type {
Blocklist::All => {
blist.borrow().is_blocklisted(uuid)
},
Blocklist::Reads => {
blist.borrow().is_blocklisted_for_reads(uuid)
}
Blocklist::Writes => {
blist.borrow().is_blocklisted_for_writes(uuid)
}
}
BLUETOOTH_BLOCKLIST.with(|blist| match exclude_type {
Blocklist::All => blist.borrow().is_blocklisted(uuid),
Blocklist::Reads => blist.borrow().is_blocklisted_for_reads(uuid),
Blocklist::Writes => blist.borrow().is_blocklisted_for_writes(uuid),
})
}
@ -55,8 +45,9 @@ impl BluetoothBlocklist {
// https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-reads
pub fn is_blocklisted_for_reads(&self, uuid: &str) -> bool {
match self.0 {
Some(ref map) => map.get(uuid).map_or(false, |et| et.eq(&Blocklist::All) ||
et.eq(&Blocklist::Reads)),
Some(ref map) => map.get(uuid).map_or(false, |et| {
et.eq(&Blocklist::All) || et.eq(&Blocklist::Reads)
}),
None => false,
}
}
@ -64,8 +55,9 @@ impl BluetoothBlocklist {
// https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-writes
pub fn is_blocklisted_for_writes(&self, uuid: &str) -> bool {
match self.0 {
Some(ref map) => map.get(uuid).map_or(false, |et| et.eq(&Blocklist::All) ||
et.eq(&Blocklist::Writes)),
Some(ref map) => map.get(uuid).map_or(false, |et| {
et.eq(&Blocklist::All) || et.eq(&Blocklist::Writes)
}),
None => false,
}
}
@ -75,15 +67,11 @@ impl BluetoothBlocklist {
fn parse_blocklist() -> Option<HashMap<String, Blocklist>> {
// Step 1 missing, currently we parse ./resources/gatt_blocklist.txt.
let valid_uuid_regex = Regex::new(VALID_UUID_REGEX).unwrap();
let content = read_resource_file(BLOCKLIST_FILE).expect(BLOCKLIST_FILE_NOT_FOUND);
let content = resources::read_string(Resource::BluetoothBlocklist);
// Step 3
let mut result = HashMap::new();
// Step 2 and 4
for line in content.lines() {
let line = match line {
Ok(l) => l,
Err(_) => return None,
};
// Step 4.1
if line.is_empty() || line.starts_with('#') {
continue;
@ -105,7 +93,7 @@ fn parse_blocklist() -> Option<HashMap<String, Blocklist>> {
exclude_type = Blocklist::Reads;
},
Some(EXCLUDE_WRITES) => {
exclude_type = Blocklist::Writes;
exclude_type = Blocklist::Writes;
},
// Step 4.4
_ => {

View file

@ -1,20 +1,17 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
extern crate ipc_channel;
extern crate regex;
#[macro_use]
extern crate serde_derive;
extern crate servo_config;
extern crate serde;
pub mod blocklist;
pub mod scanfilter;
use crate::scanfilter::{BluetoothScanfilterSequence, RequestDeviceoptions};
use ipc_channel::ipc::IpcSender;
use scanfilter::{BluetoothScanfilterSequence, RequestDeviceoptions};
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub enum BluetoothError {
Type(String),
Network,
@ -24,7 +21,7 @@ pub enum BluetoothError {
InvalidState,
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub enum GATTType {
PrimaryService,
Characteristic,
@ -32,21 +29,21 @@ pub enum GATTType {
Descriptor,
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BluetoothDeviceMsg {
// Bluetooth Device properties
pub id: String,
pub name: Option<String>,
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BluetoothServiceMsg {
pub uuid: String,
pub is_primary: bool,
pub instance_id: String,
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BluetoothCharacteristicMsg {
// Characteristic
pub uuid: String,
@ -63,7 +60,7 @@ pub struct BluetoothCharacteristicMsg {
pub writable_auxiliaries: bool,
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BluetoothDescriptorMsg {
pub uuid: String,
pub instance_id: String,
@ -79,12 +76,18 @@ pub type BluetoothResult<T> = Result<T, BluetoothError>;
pub type BluetoothResponseResult = Result<BluetoothResponse, BluetoothError>;
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub enum BluetoothRequest {
RequestDevice(RequestDeviceoptions, IpcSender<BluetoothResponseResult>),
GATTServerConnect(String, IpcSender<BluetoothResponseResult>),
GATTServerDisconnect(String, IpcSender<BluetoothResult<()>>),
GetGATTChildren(String, Option<String>, bool, GATTType, IpcSender<BluetoothResponseResult>),
GetGATTChildren(
String,
Option<String>,
bool,
GATTType,
IpcSender<BluetoothResponseResult>,
),
ReadValue(String, IpcSender<BluetoothResponseResult>),
WriteValue(String, Vec<u8>, IpcSender<BluetoothResponseResult>),
EnableNotification(String, bool, IpcSender<BluetoothResponseResult>),
@ -92,12 +95,16 @@ pub enum BluetoothRequest {
SetRepresentedToNull(Vec<String>, Vec<String>, Vec<String>),
IsRepresentedDeviceNull(String, IpcSender<bool>),
GetAvailability(IpcSender<BluetoothResponseResult>),
MatchesFilter(String, BluetoothScanfilterSequence, IpcSender<BluetoothResult<bool>>),
MatchesFilter(
String,
BluetoothScanfilterSequence,
IpcSender<BluetoothResult<bool>>,
),
Test(String, IpcSender<BluetoothResult<()>>),
Exit,
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub enum BluetoothResponse {
RequestDevice(BluetoothDeviceMsg),
GATTServerConnect(bool),

View file

@ -1,6 +1,6 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::{HashMap, HashSet};
use std::slice::Iter;
@ -10,7 +10,7 @@ use std::slice::Iter;
// That leaves 29 bytes for the name.
const MAX_NAME_LENGTH: usize = 29;
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ServiceUUIDSequence(Vec<String>);
impl ServiceUUIDSequence {
@ -26,7 +26,7 @@ impl ServiceUUIDSequence {
type ManufacturerData = HashMap<u16, (Vec<u8>, Vec<u8>)>;
type ServiceData = HashMap<String, (Vec<u8>, Vec<u8>)>;
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BluetoothScanfilter {
name: Option<String>,
name_prefix: String,
@ -36,12 +36,13 @@ pub struct BluetoothScanfilter {
}
impl BluetoothScanfilter {
pub fn new(name: Option<String>,
name_prefix: String,
services: Vec<String>,
manufacturer_data: Option<ManufacturerData>,
service_data: Option<ServiceData>)
-> BluetoothScanfilter {
pub fn new(
name: Option<String>,
name_prefix: String,
services: Vec<String>,
manufacturer_data: Option<ManufacturerData>,
service_data: Option<ServiceData>,
) -> BluetoothScanfilter {
BluetoothScanfilter {
name: name,
name_prefix: name_prefix,
@ -73,16 +74,16 @@ impl BluetoothScanfilter {
pub fn is_empty_or_invalid(&self) -> bool {
(self.name.is_none() &&
self.name_prefix.is_empty() &&
self.get_services().is_empty() &&
self.manufacturer_data.is_none() &&
self.service_data.is_none()) ||
self.get_name().unwrap_or("").len() > MAX_NAME_LENGTH ||
self.name_prefix.len() > MAX_NAME_LENGTH
self.name_prefix.is_empty() &&
self.get_services().is_empty() &&
self.manufacturer_data.is_none() &&
self.service_data.is_none()) ||
self.get_name().unwrap_or("").len() > MAX_NAME_LENGTH ||
self.name_prefix.len() > MAX_NAME_LENGTH
}
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BluetoothScanfilterSequence(Vec<BluetoothScanfilter>);
impl BluetoothScanfilterSequence {
@ -99,7 +100,9 @@ impl BluetoothScanfilterSequence {
}
fn get_services_set(&self) -> HashSet<String> {
self.iter().flat_map(|filter| filter.services.get_services_set()).collect()
self.iter()
.flat_map(|filter| filter.services.get_services_set())
.collect()
}
fn is_empty(&self) -> bool {
@ -107,16 +110,17 @@ impl BluetoothScanfilterSequence {
}
}
#[derive(Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct RequestDeviceoptions {
filters: BluetoothScanfilterSequence,
optional_services: ServiceUUIDSequence,
}
impl RequestDeviceoptions {
pub fn new(filters: BluetoothScanfilterSequence,
services: ServiceUUIDSequence)
-> RequestDeviceoptions {
pub fn new(
filters: BluetoothScanfilterSequence,
services: ServiceUUIDSequence,
) -> RequestDeviceoptions {
RequestDeviceoptions {
filters: filters,
optional_services: services,

View file

@ -3,21 +3,50 @@ name = "canvas"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
name = "canvas"
path = "lib.rs"
[features]
no-wgl = ["surfman/sm-no-wgl"]
webgl_backtrace = ["canvas_traits/webgl_backtrace"]
xr-profile = ["webxr-api/profile", "time"]
[dependencies]
azure = {git = "https://github.com/servo/rust-azure"}
canvas_traits = {path = "../canvas_traits"}
cssparser = "0.13"
euclid = "0.11"
gleam = "0.4"
ipc-channel = "0.7"
log = "0.3.5"
num-traits = "0.1.32"
offscreen_gl_context = "0.8"
servo_config = {path = "../config"}
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
bitflags = "1.0"
byteorder = "1"
canvas_traits = { path = "../canvas_traits" }
crossbeam-channel = "0.4"
cssparser = "0.28"
euclid = "0.20"
font-kit = "0.10"
fnv = "1.0"
gfx = { path = "../gfx" }
gleam = "0.12"
half = "1"
ipc-channel = "0.14"
log = "0.4"
lyon_geom = "0.14"
num-traits = "0.2"
pathfinder_geometry = "0.5"
pixels = { path = "../pixels" }
raqote = { git = "https://github.com/jrmuizel/raqote", features = ["text"] }
servo_arc = { path = "../servo_arc" }
servo_config = { path = "../config" }
sparkle = "0.1.25"
style = { path = "../style" }
style_traits = { path = "../style_traits" }
# NOTE: the sm-angle feature only enables angle on windows, not other platforms!
surfman = { version = "0.4", features = ["sm-angle","sm-angle-default"] }
surfman-chains = "0.6"
surfman-chains-api = "0.2"
time = { version = "0.1.41", optional = true }
webrender = { git = "https://github.com/servo/webrender" }
webrender_api = { git = "https://github.com/servo/webrender" }
webrender_surfman = { path = "../webrender_surfman" }
webrender_traits = { path = "../webrender_traits" }
webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] }
webxr = { git = "https://github.com/servo/webxr", features = ["ipc"] }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,20 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
extern crate azure;
extern crate canvas_traits;
extern crate cssparser;
extern crate euclid;
extern crate gleam;
extern crate ipc_channel;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate log;
extern crate num_traits;
extern crate offscreen_gl_context;
extern crate servo_config;
extern crate webrender_traits;
mod raqote_backend;
pub use webgl_mode::WebGLComm;
pub mod canvas_data;
pub mod canvas_paint_thread;
pub mod webgl_paint_thread;
mod webgl_limits;
mod webgl_mode;
pub mod webgl_thread;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,249 @@
/* 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 canvas_traits::webgl::{GLLimits, WebGLVersion};
use sparkle::gl;
use sparkle::gl::GLenum;
use sparkle::gl::Gl;
use sparkle::gl::GlType;
pub trait GLLimitsDetect {
fn detect(gl: &Gl, webgl_version: WebGLVersion) -> Self;
}
impl GLLimitsDetect for GLLimits {
fn detect(gl: &Gl, webgl_version: WebGLVersion) -> GLLimits {
let max_vertex_attribs = gl.get_integer(gl::MAX_VERTEX_ATTRIBS);
let max_tex_size = gl.get_integer(gl::MAX_TEXTURE_SIZE);
let max_cube_map_tex_size = gl.get_integer(gl::MAX_CUBE_MAP_TEXTURE_SIZE);
let max_combined_texture_image_units = gl.get_integer(gl::MAX_COMBINED_TEXTURE_IMAGE_UNITS);
let max_renderbuffer_size = gl.get_integer(gl::MAX_RENDERBUFFER_SIZE);
let max_texture_image_units = gl.get_integer(gl::MAX_TEXTURE_IMAGE_UNITS);
let max_vertex_texture_image_units = gl.get_integer(gl::MAX_VERTEX_TEXTURE_IMAGE_UNITS);
// TODO: better value for this?
let max_client_wait_timeout_webgl = std::time::Duration::new(1, 0);
// Based on:
// https://searchfox.org/mozilla-central/rev/5a744713370ec47969595e369fd5125f123e6d24/dom/canvas/WebGLContextValidate.cpp#523-558
let (
max_fragment_uniform_vectors,
max_varying_vectors,
max_vertex_uniform_vectors,
max_vertex_output_vectors,
max_fragment_input_vectors,
);
if gl.get_type() == GlType::Gles {
max_fragment_uniform_vectors = gl.get_integer(gl::MAX_FRAGMENT_UNIFORM_VECTORS);
max_varying_vectors = gl.get_integer(gl::MAX_VARYING_VECTORS);
max_vertex_uniform_vectors = gl.get_integer(gl::MAX_VERTEX_UNIFORM_VECTORS);
max_vertex_output_vectors = gl
.try_get_integer(gl::MAX_VERTEX_OUTPUT_COMPONENTS)
.map(|c| c / 4)
.unwrap_or(max_varying_vectors);
max_fragment_input_vectors = gl
.try_get_integer(gl::MAX_FRAGMENT_INPUT_COMPONENTS)
.map(|c| c / 4)
.unwrap_or(max_vertex_output_vectors);
} else {
max_fragment_uniform_vectors = gl.get_integer(gl::MAX_FRAGMENT_UNIFORM_COMPONENTS) / 4;
max_vertex_uniform_vectors = gl.get_integer(gl::MAX_VERTEX_UNIFORM_COMPONENTS) / 4;
max_fragment_input_vectors = gl
.try_get_integer(gl::MAX_FRAGMENT_INPUT_COMPONENTS)
.or_else(|| gl.try_get_integer(gl::MAX_VARYING_COMPONENTS))
.map(|c| c / 4)
.unwrap_or_else(|| gl.get_integer(gl::MAX_VARYING_VECTORS));
max_vertex_output_vectors = gl
.try_get_integer(gl::MAX_VERTEX_OUTPUT_COMPONENTS)
.map(|c| c / 4)
.unwrap_or(max_fragment_input_vectors);
max_varying_vectors = max_vertex_output_vectors
.min(max_fragment_input_vectors)
.max(4);
};
let (
max_uniform_block_size,
max_uniform_buffer_bindings,
min_program_texel_offset,
max_program_texel_offset,
max_transform_feedback_separate_attribs,
max_draw_buffers,
max_color_attachments,
max_combined_uniform_blocks,
max_combined_vertex_uniform_components,
max_combined_fragment_uniform_components,
max_vertex_uniform_blocks,
max_vertex_uniform_components,
max_fragment_uniform_blocks,
max_fragment_uniform_components,
max_3d_texture_size,
max_array_texture_layers,
uniform_buffer_offset_alignment,
max_element_index,
max_elements_indices,
max_elements_vertices,
max_fragment_input_components,
max_samples,
max_server_wait_timeout,
max_texture_lod_bias,
max_varying_components,
max_vertex_output_components,
);
if webgl_version == WebGLVersion::WebGL2 {
max_uniform_block_size = gl.get_integer64(gl::MAX_UNIFORM_BLOCK_SIZE);
max_uniform_buffer_bindings = gl.get_integer(gl::MAX_UNIFORM_BUFFER_BINDINGS);
min_program_texel_offset = gl.get_signed_integer(gl::MIN_PROGRAM_TEXEL_OFFSET);
max_program_texel_offset = gl.get_integer(gl::MAX_PROGRAM_TEXEL_OFFSET);
max_transform_feedback_separate_attribs =
gl.get_integer(gl::MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS);
max_color_attachments = gl.get_integer(gl::MAX_COLOR_ATTACHMENTS);
max_draw_buffers = gl
.get_integer(gl::MAX_DRAW_BUFFERS)
.min(max_color_attachments);
max_combined_uniform_blocks = gl.get_integer(gl::MAX_COMBINED_UNIFORM_BLOCKS);
max_combined_vertex_uniform_components =
gl.get_integer64(gl::MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS);
max_combined_fragment_uniform_components =
gl.get_integer64(gl::MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS);
max_vertex_uniform_blocks = gl.get_integer(gl::MAX_VERTEX_UNIFORM_BLOCKS);
max_vertex_uniform_components = gl.get_integer(gl::MAX_VERTEX_UNIFORM_COMPONENTS);
max_fragment_uniform_blocks = gl.get_integer(gl::MAX_FRAGMENT_UNIFORM_BLOCKS);
max_fragment_uniform_components = gl.get_integer(gl::MAX_FRAGMENT_UNIFORM_COMPONENTS);
uniform_buffer_offset_alignment = gl.get_integer(gl::UNIFORM_BUFFER_OFFSET_ALIGNMENT);
max_3d_texture_size = gl.get_integer(gl::MAX_3D_TEXTURE_SIZE);
max_array_texture_layers = gl.get_integer(gl::MAX_ARRAY_TEXTURE_LAYERS);
max_element_index = gl
.try_get_integer64(gl::MAX_ELEMENT_INDEX)
.unwrap_or(u32::MAX as u64); // requires GL 4.3
max_elements_indices = gl.get_integer(gl::MAX_ELEMENTS_INDICES);
max_elements_vertices = gl.get_integer(gl::MAX_ELEMENTS_VERTICES);
max_fragment_input_components = gl.get_integer(gl::MAX_FRAGMENT_INPUT_COMPONENTS);
max_samples = gl.get_integer(gl::MAX_SAMPLES);
max_server_wait_timeout =
std::time::Duration::from_nanos(gl.get_integer64(gl::MAX_SERVER_WAIT_TIMEOUT));
max_texture_lod_bias = gl.get_float(gl::MAX_TEXTURE_LOD_BIAS);
max_varying_components = gl.try_get_integer(gl::MAX_VARYING_COMPONENTS).unwrap_or(
// macOS Core Profile is buggy. The spec says this value is 4 * MAX_VARYING_VECTORS.
max_varying_vectors * 4,
);
max_vertex_output_components = gl.get_integer(gl::MAX_VERTEX_OUTPUT_COMPONENTS);
} else {
max_uniform_block_size = 0;
max_uniform_buffer_bindings = 0;
min_program_texel_offset = 0;
max_program_texel_offset = 0;
max_transform_feedback_separate_attribs = 0;
max_color_attachments = 1;
max_draw_buffers = 1;
max_combined_uniform_blocks = 0;
max_combined_vertex_uniform_components = 0;
max_combined_fragment_uniform_components = 0;
max_vertex_uniform_blocks = 0;
max_vertex_uniform_components = 0;
max_fragment_uniform_blocks = 0;
max_fragment_uniform_components = 0;
uniform_buffer_offset_alignment = 0;
max_3d_texture_size = 0;
max_array_texture_layers = 0;
max_element_index = 0;
max_elements_indices = 0;
max_elements_vertices = 0;
max_fragment_input_components = 0;
max_samples = 0;
max_server_wait_timeout = std::time::Duration::default();
max_texture_lod_bias = 0.0;
max_varying_components = 0;
max_vertex_output_components = 0;
}
GLLimits {
max_vertex_attribs,
max_tex_size,
max_cube_map_tex_size,
max_combined_texture_image_units,
max_fragment_uniform_vectors,
max_renderbuffer_size,
max_texture_image_units,
max_varying_vectors,
max_vertex_texture_image_units,
max_vertex_uniform_vectors,
max_client_wait_timeout_webgl,
max_transform_feedback_separate_attribs,
max_vertex_output_vectors,
max_fragment_input_vectors,
max_uniform_buffer_bindings,
min_program_texel_offset,
max_program_texel_offset,
max_color_attachments,
max_draw_buffers,
max_uniform_block_size,
max_combined_uniform_blocks,
max_combined_vertex_uniform_components,
max_combined_fragment_uniform_components,
max_vertex_uniform_blocks,
max_vertex_uniform_components,
max_fragment_uniform_blocks,
max_fragment_uniform_components,
max_3d_texture_size,
max_array_texture_layers,
uniform_buffer_offset_alignment,
max_element_index,
max_elements_indices,
max_elements_vertices,
max_fragment_input_components,
max_samples,
max_server_wait_timeout,
max_texture_lod_bias,
max_varying_components,
max_vertex_output_components,
}
}
}
trait GLExt {
fn try_get_integer(self, parameter: GLenum) -> Option<u32>;
fn try_get_integer64(self, parameter: GLenum) -> Option<u64>;
fn try_get_signed_integer(self, parameter: GLenum) -> Option<i32>;
fn try_get_float(self, parameter: GLenum) -> Option<f32>;
fn get_integer(self, parameter: GLenum) -> u32;
fn get_integer64(self, parameter: GLenum) -> u64;
fn get_signed_integer(self, parameter: GLenum) -> i32;
fn get_float(self, parameter: GLenum) -> f32;
}
macro_rules! create_fun {
($tryer:ident, $getter:ident, $gltype:ty, $glcall:ident, $rstype:ty) => {
#[allow(unsafe_code)]
fn $tryer(self, parameter: GLenum) -> Option<$rstype> {
let mut value = [<$gltype>::default()];
unsafe {
self.$glcall(parameter, &mut value);
}
if self.get_error() != gl::NO_ERROR {
None
} else {
Some(value[0] as $rstype)
}
}
fn $getter(self, parameter: GLenum) -> $rstype {
self.$tryer(parameter).unwrap()
}
};
}
impl<'a> GLExt for &'a Gl {
create_fun!(try_get_integer, get_integer, i32, get_integer_v, u32);
create_fun!(try_get_integer64, get_integer64, i64, get_integer64_v, u64);
create_fun!(
try_get_signed_integer,
get_signed_integer,
i32,
get_integer_v,
i32
);
create_fun!(try_get_float, get_float, f32, get_float_v, f32);
}

View file

@ -0,0 +1,186 @@
/* 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 crate::webgl_thread::{WebGLThread, WebGLThreadInit, WebXRBridgeInit};
use canvas_traits::webgl::webgl_channel;
use canvas_traits::webgl::{WebGLContextId, WebGLMsg, WebGLThreads};
use euclid::default::Size2D;
use fnv::FnvHashMap;
use gleam;
use servo_config::pref;
use sparkle::gl;
use sparkle::gl::GlType;
use std::default::Default;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use surfman::Device;
use surfman::SurfaceInfo;
use surfman::SurfaceTexture;
use surfman_chains::SwapChains;
use surfman_chains_api::SwapChainAPI;
use surfman_chains_api::SwapChainsAPI;
use webrender_surfman::WebrenderSurfman;
use webrender_traits::{
WebrenderExternalImageApi, WebrenderExternalImageRegistry, WebrenderImageSource,
};
use webxr::SurfmanGL as WebXRSurfman;
use webxr_api::LayerGrandManager as WebXRLayerGrandManager;
pub struct WebGLComm {
pub webgl_threads: WebGLThreads,
pub image_handler: Box<dyn WebrenderExternalImageApi>,
pub output_handler: Option<Box<dyn webrender_api::OutputImageHandler>>,
pub webxr_layer_grand_manager: WebXRLayerGrandManager<WebXRSurfman>,
}
impl WebGLComm {
/// Creates a new `WebGLComm` object.
pub fn new(
surfman: WebrenderSurfman,
webrender_gl: Rc<dyn gleam::gl::Gl>,
webrender_api_sender: webrender_api::RenderApiSender,
webrender_doc: webrender_api::DocumentId,
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
api_type: GlType,
) -> WebGLComm {
debug!("WebGLThreads::new()");
let (sender, receiver) = webgl_channel::<WebGLMsg>().unwrap();
let webrender_swap_chains = SwapChains::new();
let webxr_init = WebXRBridgeInit::new(sender.clone());
let webxr_layer_grand_manager = webxr_init.layer_grand_manager();
// This implementation creates a single `WebGLThread` for all the pipelines.
let init = WebGLThreadInit {
webrender_api_sender,
webrender_doc,
external_images,
sender: sender.clone(),
receiver,
webrender_swap_chains: webrender_swap_chains.clone(),
connection: surfman.connection(),
adapter: surfman.adapter(),
api_type,
webxr_init,
};
let output_handler = if pref!(dom.webgl.dom_to_texture.enabled) {
Some(Box::new(OutputHandler::new(webrender_gl.clone())))
} else {
None
};
let external = WebGLExternalImages::new(surfman, webrender_swap_chains);
WebGLThread::run_on_own_thread(init);
WebGLComm {
webgl_threads: WebGLThreads(sender),
image_handler: Box::new(external),
output_handler: output_handler.map(|b| b as Box<_>),
webxr_layer_grand_manager: webxr_layer_grand_manager,
}
}
}
/// Bridge between the webrender::ExternalImage callbacks and the WebGLThreads.
struct WebGLExternalImages {
surfman: WebrenderSurfman,
swap_chains: SwapChains<WebGLContextId, Device>,
locked_front_buffers: FnvHashMap<WebGLContextId, SurfaceTexture>,
}
impl WebGLExternalImages {
fn new(surfman: WebrenderSurfman, swap_chains: SwapChains<WebGLContextId, Device>) -> Self {
Self {
surfman,
swap_chains,
locked_front_buffers: FnvHashMap::default(),
}
}
fn lock_swap_chain(&mut self, id: WebGLContextId) -> Option<(u32, Size2D<i32>)> {
debug!("... locking chain {:?}", id);
let front_buffer = self.swap_chains.get(id)?.take_surface()?;
let SurfaceInfo {
id: front_buffer_id,
size,
..
} = self.surfman.surface_info(&front_buffer);
debug!("... getting texture for surface {:?}", front_buffer_id);
let front_buffer_texture = self.surfman.create_surface_texture(front_buffer).unwrap();
let gl_texture = self.surfman.surface_texture_object(&front_buffer_texture);
self.locked_front_buffers.insert(id, front_buffer_texture);
Some((gl_texture, size))
}
fn unlock_swap_chain(&mut self, id: WebGLContextId) -> Option<()> {
let locked_front_buffer = self.locked_front_buffers.remove(&id)?;
let locked_front_buffer = self
.surfman
.destroy_surface_texture(locked_front_buffer)
.unwrap();
debug!("... unlocked chain {:?}", id);
self.swap_chains
.get(id)?
.recycle_surface(locked_front_buffer);
Some(())
}
}
impl WebrenderExternalImageApi for WebGLExternalImages {
fn lock(&mut self, id: u64) -> (WebrenderImageSource, Size2D<i32>) {
let id = WebGLContextId(id);
let (texture_id, size) = self.lock_swap_chain(id).unwrap_or_default();
(WebrenderImageSource::TextureHandle(texture_id), size)
}
fn unlock(&mut self, id: u64) {
let id = WebGLContextId(id);
self.unlock_swap_chain(id);
}
}
/// struct used to implement DOMToTexture feature and webrender::OutputImageHandler trait.
struct OutputHandler {
webrender_gl: Rc<dyn gleam::gl::Gl>,
sync_objects: FnvHashMap<webrender_api::PipelineId, gleam::gl::GLsync>,
}
impl OutputHandler {
fn new(webrender_gl: Rc<dyn gleam::gl::Gl>) -> Self {
OutputHandler {
webrender_gl,
sync_objects: Default::default(),
}
}
}
/// Bridge between the WR frame outputs and WebGL to implement DOMToTexture synchronization.
impl webrender_api::OutputImageHandler for OutputHandler {
fn lock(
&mut self,
id: webrender_api::PipelineId,
) -> Option<(u32, webrender_api::units::FramebufferIntSize)> {
// Insert a fence in the WR command queue
let gl_sync = self
.webrender_gl
.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
self.sync_objects.insert(id, gl_sync);
// https://github.com/servo/servo/issues/24615
None
}
fn unlock(&mut self, id: webrender_api::PipelineId) {
if let Some(gl_sync) = self.sync_objects.remove(&id) {
// Flush the Sync object into the GPU's command queue to guarantee that it it's signaled.
self.webrender_gl.flush();
// Mark the sync object for deletion.
self.webrender_gl.delete_sync(gl_sync);
}
}
}

View file

@ -0,0 +1,7 @@
/* 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/. */
mod inprocess;
pub use self::inprocess::WebGLComm;

View file

@ -1,327 +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 http://mozilla.org/MPL/2.0/. */
use canvas_traits::{CanvasCommonMsg, CanvasData, CanvasMsg, CanvasImageData};
use canvas_traits::{FromLayoutMsg, FromScriptMsg, byte_swap};
use euclid::size::Size2D;
use gleam::gl;
use ipc_channel::ipc::{self, IpcSender};
use offscreen_gl_context::{ColorAttachmentType, GLContext, GLLimits};
use offscreen_gl_context::{GLContextAttributes, NativeGLContext, OSMesaContext};
use servo_config::opts;
use std::borrow::ToOwned;
use std::sync::Arc;
use std::sync::mpsc::channel;
use std::thread;
use webrender_traits;
enum GLContextWrapper {
Native(GLContext<NativeGLContext>),
OSMesa(GLContext<OSMesaContext>),
}
impl GLContextWrapper {
fn new(size: Size2D<i32>,
attributes: GLContextAttributes,
gl_type: gl::GlType) -> Result<GLContextWrapper, &'static str> {
if opts::get().should_use_osmesa() {
let ctx = GLContext::<OSMesaContext>::new(size,
attributes,
ColorAttachmentType::Texture,
gl_type,
None);
ctx.map(GLContextWrapper::OSMesa)
} else {
let ctx = GLContext::<NativeGLContext>::new(size,
attributes,
ColorAttachmentType::Texture,
gl_type,
None);
ctx.map(GLContextWrapper::Native)
}
}
pub fn get_limits(&self) -> GLLimits {
match *self {
GLContextWrapper::Native(ref ctx) => {
ctx.borrow_limits().clone()
}
GLContextWrapper::OSMesa(ref ctx) => {
ctx.borrow_limits().clone()
}
}
}
fn resize(&mut self, size: Size2D<i32>) -> Result<Size2D<i32>, &'static str> {
match *self {
GLContextWrapper::Native(ref mut ctx) => {
try!(ctx.resize(size));
Ok(ctx.borrow_draw_buffer().unwrap().size())
}
GLContextWrapper::OSMesa(ref mut ctx) => {
try!(ctx.resize(size));
Ok(ctx.borrow_draw_buffer().unwrap().size())
}
}
}
fn gl(&self) -> &gl::Gl {
match *self {
GLContextWrapper::Native(ref ctx) => {
ctx.gl()
}
GLContextWrapper::OSMesa(ref ctx) => {
ctx.gl()
}
}
}
pub fn make_current(&self) {
match *self {
GLContextWrapper::Native(ref ctx) => {
ctx.make_current().unwrap();
}
GLContextWrapper::OSMesa(ref ctx) => {
ctx.make_current().unwrap();
}
}
}
pub fn apply_command(&self, cmd: webrender_traits::WebGLCommand) {
match *self {
GLContextWrapper::Native(ref ctx) => {
cmd.apply(ctx);
}
GLContextWrapper::OSMesa(ref ctx) => {
cmd.apply(ctx);
}
}
}
}
enum WebGLPaintTaskData {
WebRender(webrender_traits::RenderApi, webrender_traits::WebGLContextId),
Readback(GLContextWrapper, webrender_traits::RenderApi, Option<webrender_traits::ImageKey>),
}
pub struct WebGLPaintThread {
size: Size2D<i32>,
data: WebGLPaintTaskData,
}
fn create_readback_painter(size: Size2D<i32>,
attrs: GLContextAttributes,
webrender_api: webrender_traits::RenderApi,
gl_type: gl::GlType)
-> Result<(WebGLPaintThread, GLLimits), String> {
let context = try!(GLContextWrapper::new(size, attrs, gl_type));
let limits = context.get_limits();
let painter = WebGLPaintThread {
size: size,
data: WebGLPaintTaskData::Readback(context, webrender_api, None)
};
Ok((painter, limits))
}
impl WebGLPaintThread {
fn new(size: Size2D<i32>,
attrs: GLContextAttributes,
webrender_api_sender: webrender_traits::RenderApiSender,
gl_type: gl::GlType)
-> Result<(WebGLPaintThread, GLLimits), String> {
let wr_api = webrender_api_sender.create_api();
let device_size = webrender_traits::DeviceIntSize::from_untyped(&size);
match wr_api.request_webgl_context(&device_size, attrs) {
Ok((id, limits)) => {
let painter = WebGLPaintThread {
data: WebGLPaintTaskData::WebRender(wr_api, id),
size: size
};
Ok((painter, limits))
},
Err(msg) => {
warn!("Initial context creation failed, falling back to readback: {}", msg);
create_readback_painter(size, attrs, wr_api, gl_type)
}
}
}
fn handle_webgl_message(&self, message: webrender_traits::WebGLCommand) {
debug!("WebGL message: {:?}", message);
match self.data {
WebGLPaintTaskData::WebRender(ref api, id) => {
api.send_webgl_command(id, message);
}
WebGLPaintTaskData::Readback(ref ctx, _, _) => {
ctx.apply_command(message);
}
}
}
fn handle_webvr_message(&self, message: webrender_traits::VRCompositorCommand) {
match self.data {
WebGLPaintTaskData::WebRender(ref api, id) => {
api.send_vr_compositor_command(id, message);
}
WebGLPaintTaskData::Readback(..) => {
error!("Webrender is required for WebVR implementation");
}
}
}
/// Creates a new `WebGLPaintThread` and returns an `IpcSender` to
/// communicate with it.
pub fn start(size: Size2D<i32>,
attrs: GLContextAttributes,
webrender_api_sender: webrender_traits::RenderApiSender)
-> Result<(IpcSender<CanvasMsg>, GLLimits), String> {
let (sender, receiver) = ipc::channel::<CanvasMsg>().unwrap();
let (result_chan, result_port) = channel();
thread::Builder::new().name("WebGLThread".to_owned()).spawn(move || {
let gl_type = gl::GlType::default();
let mut painter = match WebGLPaintThread::new(size, attrs, webrender_api_sender, gl_type) {
Ok((thread, limits)) => {
result_chan.send(Ok(limits)).unwrap();
thread
},
Err(e) => {
result_chan.send(Err(e)).unwrap();
return
}
};
painter.init();
loop {
match receiver.recv().unwrap() {
CanvasMsg::WebGL(message) => painter.handle_webgl_message(message),
CanvasMsg::Common(message) => {
match message {
CanvasCommonMsg::Close => break,
// TODO(emilio): handle error nicely
CanvasCommonMsg::Recreate(size) => painter.recreate(size).unwrap(),
}
},
CanvasMsg::FromScript(message) => {
match message {
FromScriptMsg::SendPixels(chan) =>{
// Read the comment on
// HTMLCanvasElement::fetch_all_data.
chan.send(None).unwrap();
}
}
}
CanvasMsg::FromLayout(message) => {
match message {
FromLayoutMsg::SendData(chan) =>
painter.send_data(chan),
}
}
CanvasMsg::Canvas2d(_) => panic!("Wrong message sent to WebGLThread"),
CanvasMsg::WebVR(message) => painter.handle_webvr_message(message)
}
}
}).expect("Thread spawning failed");
result_port.recv().unwrap().map(|limits| (sender, limits))
}
fn send_data(&mut self, chan: IpcSender<CanvasData>) {
match self.data {
WebGLPaintTaskData::Readback(ref ctx, ref webrender_api, ref mut image_key) => {
let width = self.size.width as usize;
let height = self.size.height as usize;
let mut pixels = ctx.gl().read_pixels(0, 0,
self.size.width as gl::GLsizei,
self.size.height as gl::GLsizei,
gl::RGBA, gl::UNSIGNED_BYTE);
// flip image vertically (texture is upside down)
let orig_pixels = pixels.clone();
let stride = width * 4;
for y in 0..height {
let dst_start = y * stride;
let src_start = (height - y - 1) * stride;
let src_slice = &orig_pixels[src_start .. src_start + stride];
(&mut pixels[dst_start .. dst_start + stride]).clone_from_slice(&src_slice[..stride]);
}
// rgba -> bgra
byte_swap(&mut pixels);
let descriptor = webrender_traits::ImageDescriptor {
width: width as u32,
height: height as u32,
stride: None,
format: webrender_traits::ImageFormat::RGBA8,
offset: 0,
is_opaque: false,
};
let data = webrender_traits::ImageData::Raw(Arc::new(pixels));
match *image_key {
Some(image_key) => {
webrender_api.update_image(image_key,
descriptor,
data,
None);
}
None => {
*image_key = Some(webrender_api.generate_image_key());
webrender_api.add_image(image_key.unwrap(),
descriptor,
data,
None);
}
}
let image_data = CanvasImageData {
image_key: image_key.unwrap(),
};
chan.send(CanvasData::Image(image_data)).unwrap();
}
WebGLPaintTaskData::WebRender(_, id) => {
chan.send(CanvasData::WebGL(id)).unwrap();
}
}
}
#[allow(unsafe_code)]
fn recreate(&mut self, size: Size2D<i32>) -> Result<(), &'static str> {
match self.data {
WebGLPaintTaskData::Readback(ref mut context, _, _) => {
if size.width > self.size.width ||
size.height > self.size.height {
self.size = try!(context.resize(size));
} else {
self.size = size;
context.gl().scissor(0, 0, size.width, size.height);
}
}
WebGLPaintTaskData::WebRender(ref api, id) => {
let device_size = webrender_traits::DeviceIntSize::from_untyped(&size);
api.resize_webgl_context(id, &device_size);
}
}
Ok(())
}
fn init(&mut self) {
if let WebGLPaintTaskData::Readback(ref context, _, _) = self.data {
context.make_current();
}
}
}
impl Drop for WebGLPaintThread {
fn drop(&mut self) {
if let WebGLPaintTaskData::Readback(_, ref mut wr, image_key) = self.data {
if let Some(image_key) = image_key {
wr.delete_image(image_key);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,18 +3,31 @@ name = "canvas_traits"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
name = "canvas_traits"
path = "lib.rs"
[features]
webgl_backtrace = []
xr-profile = ["webxr-api/profile", "time"]
[dependencies]
cssparser = "0.13"
euclid = "0.11"
heapsize = "0.3.0"
heapsize_derive = "0.1"
ipc-channel = "0.7"
serde = {version = "0.9", features = ["unstable"]}
serde_derive = "0.9"
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
crossbeam-channel = "0.4"
cssparser = "0.28"
euclid = "0.20"
ipc-channel = "0.14"
lazy_static = "1"
malloc_size_of = { path = "../malloc_size_of" }
malloc_size_of_derive = "0.1"
pixels = { path = "../pixels" }
serde = "1.0"
serde_bytes = "0.11"
servo_config = { path = "../config" }
sparkle = "0.1"
style = { path = "../style" }
time = { version = "0.1.41", optional = true }
webrender_api = { git = "https://github.com/servo/webrender" }
webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] }

View file

@ -0,0 +1,486 @@
/* 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 cssparser::RGBA;
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender, IpcSender, IpcSharedMemory};
use serde_bytes::ByteBuf;
use std::default::Default;
use std::str::FromStr;
use style::properties::style_structs::Font as FontStyleStruct;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FillRule {
Nonzero,
Evenodd,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct CanvasId(pub u64);
#[derive(Deserialize, Serialize)]
pub enum CanvasMsg {
Canvas2d(Canvas2dMsg, CanvasId),
FromLayout(FromLayoutMsg, CanvasId),
FromScript(FromScriptMsg, CanvasId),
Recreate(Size2D<u64>, CanvasId),
Close(CanvasId),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CanvasImageData {
pub image_key: webrender_api::ImageKey,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum Canvas2dMsg {
Arc(Point2D<f32>, f32, f32, f32, bool),
ArcTo(Point2D<f32>, Point2D<f32>, f32),
DrawImage(Option<ByteBuf>, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
DrawImageInOther(CanvasId, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
BeginPath,
BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
ClearRect(Rect<f32>),
Clip,
ClosePath,
Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool),
Fill(FillOrStrokeStyle),
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
FillRect(Rect<f32>, FillOrStrokeStyle),
GetImageData(Rect<u64>, Size2D<u64>, IpcBytesSender),
GetTransform(IpcSender<Transform2D<f32>>),
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
LineTo(Point2D<f32>),
MoveTo(Point2D<f32>),
PutImageData(Rect<u64>, IpcBytesReceiver),
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
Rect(Rect<f32>),
RestoreContext,
SaveContext,
StrokeRect(Rect<f32>, FillOrStrokeStyle),
Stroke(FillOrStrokeStyle),
SetLineWidth(f32),
SetLineCap(LineCapStyle),
SetLineJoin(LineJoinStyle),
SetMiterLimit(f32),
SetGlobalAlpha(f32),
SetGlobalComposition(CompositionOrBlending),
SetTransform(Transform2D<f32>),
SetShadowOffsetX(f64),
SetShadowOffsetY(f64),
SetShadowBlur(f64),
SetShadowColor(RGBA),
SetFont(FontStyleStruct),
SetTextAlign(TextAlign),
SetTextBaseline(TextBaseline),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FromLayoutMsg {
SendData(IpcSender<CanvasImageData>),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FromScriptMsg {
SendPixels(IpcSender<IpcSharedMemory>),
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct CanvasGradientStop {
pub offset: f64,
pub color: RGBA,
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct LinearGradientStyle {
pub x0: f64,
pub y0: f64,
pub x1: f64,
pub y1: f64,
pub stops: Vec<CanvasGradientStop>,
}
impl LinearGradientStyle {
pub fn new(
x0: f64,
y0: f64,
x1: f64,
y1: f64,
stops: Vec<CanvasGradientStop>,
) -> LinearGradientStyle {
LinearGradientStyle {
x0: x0,
y0: y0,
x1: x1,
y1: y1,
stops: stops,
}
}
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RadialGradientStyle {
pub x0: f64,
pub y0: f64,
pub r0: f64,
pub x1: f64,
pub y1: f64,
pub r1: f64,
pub stops: Vec<CanvasGradientStop>,
}
impl RadialGradientStyle {
pub fn new(
x0: f64,
y0: f64,
r0: f64,
x1: f64,
y1: f64,
r1: f64,
stops: Vec<CanvasGradientStop>,
) -> RadialGradientStyle {
RadialGradientStyle {
x0: x0,
y0: y0,
r0: r0,
x1: x1,
y1: y1,
r1: r1,
stops: stops,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SurfaceStyle {
pub surface_data: ByteBuf,
pub surface_size: Size2D<u32>,
pub repeat_x: bool,
pub repeat_y: bool,
}
impl SurfaceStyle {
pub fn new(
surface_data: Vec<u8>,
surface_size: Size2D<u32>,
repeat_x: bool,
repeat_y: bool,
) -> Self {
Self {
surface_data: ByteBuf::from(surface_data),
surface_size,
repeat_x,
repeat_y,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FillOrStrokeStyle {
Color(RGBA),
LinearGradient(LinearGradientStyle),
RadialGradient(RadialGradientStyle),
Surface(SurfaceStyle),
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum LineCapStyle {
Butt = 0,
Round = 1,
Square = 2,
}
impl FromStr for LineCapStyle {
type Err = ();
fn from_str(string: &str) -> Result<LineCapStyle, ()> {
match string {
"butt" => Ok(LineCapStyle::Butt),
"round" => Ok(LineCapStyle::Round),
"square" => Ok(LineCapStyle::Square),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum LineJoinStyle {
Round = 0,
Bevel = 1,
Miter = 2,
}
impl FromStr for LineJoinStyle {
type Err = ();
fn from_str(string: &str) -> Result<LineJoinStyle, ()> {
match string {
"round" => Ok(LineJoinStyle::Round),
"bevel" => Ok(LineJoinStyle::Bevel),
"miter" => Ok(LineJoinStyle::Miter),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum RepetitionStyle {
Repeat,
RepeatX,
RepeatY,
NoRepeat,
}
impl FromStr for RepetitionStyle {
type Err = ();
fn from_str(string: &str) -> Result<RepetitionStyle, ()> {
match string {
"repeat" => Ok(RepetitionStyle::Repeat),
"repeat-x" => Ok(RepetitionStyle::RepeatX),
"repeat-y" => Ok(RepetitionStyle::RepeatY),
"no-repeat" => Ok(RepetitionStyle::NoRepeat),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum CompositionStyle {
SrcIn,
SrcOut,
SrcOver,
SrcAtop,
DestIn,
DestOut,
DestOver,
DestAtop,
Copy,
Lighter,
Xor,
Clear,
}
impl FromStr for CompositionStyle {
type Err = ();
fn from_str(string: &str) -> Result<CompositionStyle, ()> {
match string {
"source-in" => Ok(CompositionStyle::SrcIn),
"source-out" => Ok(CompositionStyle::SrcOut),
"source-over" => Ok(CompositionStyle::SrcOver),
"source-atop" => Ok(CompositionStyle::SrcAtop),
"destination-in" => Ok(CompositionStyle::DestIn),
"destination-out" => Ok(CompositionStyle::DestOut),
"destination-over" => Ok(CompositionStyle::DestOver),
"destination-atop" => Ok(CompositionStyle::DestAtop),
"copy" => Ok(CompositionStyle::Copy),
"lighter" => Ok(CompositionStyle::Lighter),
"xor" => Ok(CompositionStyle::Xor),
"clear" => Ok(CompositionStyle::Clear),
_ => Err(()),
}
}
}
impl CompositionStyle {
pub fn to_str(&self) -> &str {
match *self {
CompositionStyle::SrcIn => "source-in",
CompositionStyle::SrcOut => "source-out",
CompositionStyle::SrcOver => "source-over",
CompositionStyle::SrcAtop => "source-atop",
CompositionStyle::DestIn => "destination-in",
CompositionStyle::DestOut => "destination-out",
CompositionStyle::DestOver => "destination-over",
CompositionStyle::DestAtop => "destination-atop",
CompositionStyle::Copy => "copy",
CompositionStyle::Lighter => "lighter",
CompositionStyle::Xor => "xor",
CompositionStyle::Clear => "clear",
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum BlendingStyle {
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl FromStr for BlendingStyle {
type Err = ();
fn from_str(string: &str) -> Result<BlendingStyle, ()> {
match string {
"multiply" => Ok(BlendingStyle::Multiply),
"screen" => Ok(BlendingStyle::Screen),
"overlay" => Ok(BlendingStyle::Overlay),
"darken" => Ok(BlendingStyle::Darken),
"lighten" => Ok(BlendingStyle::Lighten),
"color-dodge" => Ok(BlendingStyle::ColorDodge),
"color-burn" => Ok(BlendingStyle::ColorBurn),
"hard-light" => Ok(BlendingStyle::HardLight),
"soft-light" => Ok(BlendingStyle::SoftLight),
"difference" => Ok(BlendingStyle::Difference),
"exclusion" => Ok(BlendingStyle::Exclusion),
"hue" => Ok(BlendingStyle::Hue),
"saturation" => Ok(BlendingStyle::Saturation),
"color" => Ok(BlendingStyle::Color),
"luminosity" => Ok(BlendingStyle::Luminosity),
_ => Err(()),
}
}
}
impl BlendingStyle {
pub fn to_str(&self) -> &str {
match *self {
BlendingStyle::Multiply => "multiply",
BlendingStyle::Screen => "screen",
BlendingStyle::Overlay => "overlay",
BlendingStyle::Darken => "darken",
BlendingStyle::Lighten => "lighten",
BlendingStyle::ColorDodge => "color-dodge",
BlendingStyle::ColorBurn => "color-burn",
BlendingStyle::HardLight => "hard-light",
BlendingStyle::SoftLight => "soft-light",
BlendingStyle::Difference => "difference",
BlendingStyle::Exclusion => "exclusion",
BlendingStyle::Hue => "hue",
BlendingStyle::Saturation => "saturation",
BlendingStyle::Color => "color",
BlendingStyle::Luminosity => "luminosity",
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum CompositionOrBlending {
Composition(CompositionStyle),
Blending(BlendingStyle),
}
impl Default for CompositionOrBlending {
fn default() -> CompositionOrBlending {
CompositionOrBlending::Composition(CompositionStyle::SrcOver)
}
}
impl FromStr for CompositionOrBlending {
type Err = ();
fn from_str(string: &str) -> Result<CompositionOrBlending, ()> {
if let Ok(op) = CompositionStyle::from_str(string) {
return Ok(CompositionOrBlending::Composition(op));
}
if let Ok(op) = BlendingStyle::from_str(string) {
return Ok(CompositionOrBlending::Blending(op));
}
Err(())
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum TextAlign {
Start,
End,
Left,
Right,
Center,
}
impl FromStr for TextAlign {
type Err = ();
fn from_str(string: &str) -> Result<TextAlign, ()> {
match string {
"start" => Ok(TextAlign::Start),
"end" => Ok(TextAlign::End),
"left" => Ok(TextAlign::Left),
"right" => Ok(TextAlign::Right),
"center" => Ok(TextAlign::Center),
_ => Err(()),
}
}
}
impl Default for TextAlign {
fn default() -> TextAlign {
TextAlign::Start
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum TextBaseline {
Top,
Hanging,
Middle,
Alphabetic,
Ideographic,
Bottom,
}
impl FromStr for TextBaseline {
type Err = ();
fn from_str(string: &str) -> Result<TextBaseline, ()> {
match string {
"top" => Ok(TextBaseline::Top),
"hanging" => Ok(TextBaseline::Hanging),
"middle" => Ok(TextBaseline::Middle),
"alphabetic" => Ok(TextBaseline::Alphabetic),
"ideographic" => Ok(TextBaseline::Ideographic),
"bottom" => Ok(TextBaseline::Bottom),
_ => Err(()),
}
}
}
impl Default for TextBaseline {
fn default() -> TextBaseline {
TextBaseline::Alphabetic
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum Direction {
Ltr,
Rtl,
Inherit,
}
impl FromStr for Direction {
type Err = ();
fn from_str(string: &str) -> Result<Direction, ()> {
match string {
"ltr" => Ok(Direction::Ltr),
"rtl" => Ok(Direction::Rtl),
"inherit" => Ok(Direction::Inherit),
_ => Err(()),
}
}
}
impl Default for Direction {
fn default() -> Direction {
Direction::Inherit
}
}

View file

@ -1,438 +1,32 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![crate_name = "canvas_traits"]
#![crate_type = "rlib"]
#![deny(unsafe_code)]
extern crate cssparser;
extern crate euclid;
extern crate heapsize;
#[macro_use] extern crate heapsize_derive;
extern crate ipc_channel;
#[macro_use] extern crate serde_derive;
extern crate webrender_traits;
use crate::canvas::CanvasId;
use crossbeam_channel::Sender;
use euclid::default::Size2D;
use cssparser::RGBA;
use euclid::matrix2d::Matrix2D;
use euclid::point::Point2D;
use euclid::rect::Rect;
use euclid::size::Size2D;
use ipc_channel::ipc::IpcSender;
use std::default::Default;
use std::str::FromStr;
use webrender_traits::{WebGLCommand, WebGLContextId, VRCompositorCommand};
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate malloc_size_of_derive;
#[macro_use]
extern crate serde;
#[derive(Clone, Deserialize, Serialize)]
pub enum FillRule {
Nonzero,
Evenodd,
}
#[derive(Clone, Deserialize, Serialize)]
pub enum CanvasMsg {
Canvas2d(Canvas2dMsg),
Common(CanvasCommonMsg),
FromLayout(FromLayoutMsg),
FromScript(FromScriptMsg),
WebGL(WebGLCommand),
WebVR(VRCompositorCommand)
}
#[derive(Clone, Deserialize, Serialize)]
pub enum CanvasCommonMsg {
Close,
Recreate(Size2D<i32>),
}
#[derive(Clone, Deserialize, Serialize)]
pub enum CanvasData {
Image(CanvasImageData),
WebGL(WebGLContextId),
}
#[derive(Clone, Deserialize, Serialize)]
pub struct CanvasImageData {
pub image_key: webrender_traits::ImageKey,
}
#[derive(Clone, Deserialize, Serialize)]
pub enum FromLayoutMsg {
SendData(IpcSender<CanvasData>),
}
#[derive(Clone, Deserialize, Serialize)]
pub enum FromScriptMsg {
SendPixels(IpcSender<Option<Vec<u8>>>),
}
#[derive(Clone, Deserialize, Serialize)]
pub enum Canvas2dMsg {
Arc(Point2D<f32>, f32, f32, f32, bool),
ArcTo(Point2D<f32>, Point2D<f32>, f32),
DrawImage(Vec<u8>, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
DrawImageSelf(Size2D<f64>, Rect<f64>, Rect<f64>, bool),
DrawImageInOther(
IpcSender<CanvasMsg>, Size2D<f64>, Rect<f64>, Rect<f64>, bool, IpcSender<()>),
BeginPath,
BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
ClearRect(Rect<f32>),
Clip,
ClosePath,
Fill,
FillRect(Rect<f32>),
GetImageData(Rect<i32>, Size2D<f64>, IpcSender<Vec<u8>>),
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
LineTo(Point2D<f32>),
MoveTo(Point2D<f32>),
PutImageData(Vec<u8>, Point2D<f64>, Size2D<f64>, Rect<f64>),
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
Rect(Rect<f32>),
RestoreContext,
SaveContext,
StrokeRect(Rect<f32>),
Stroke,
SetFillStyle(FillOrStrokeStyle),
SetStrokeStyle(FillOrStrokeStyle),
SetLineWidth(f32),
SetLineCap(LineCapStyle),
SetLineJoin(LineJoinStyle),
SetMiterLimit(f32),
SetGlobalAlpha(f32),
SetGlobalComposition(CompositionOrBlending),
SetTransform(Matrix2D<f32>),
SetShadowOffsetX(f64),
SetShadowOffsetY(f64),
SetShadowBlur(f64),
SetShadowColor(RGBA),
}
#[derive(Clone, Deserialize, Serialize, HeapSizeOf)]
pub struct CanvasGradientStop {
pub offset: f64,
pub color: RGBA,
}
#[derive(Clone, Deserialize, Serialize, HeapSizeOf)]
pub struct LinearGradientStyle {
pub x0: f64,
pub y0: f64,
pub x1: f64,
pub y1: f64,
pub stops: Vec<CanvasGradientStop>
}
impl LinearGradientStyle {
pub fn new(x0: f64, y0: f64, x1: f64, y1: f64, stops: Vec<CanvasGradientStop>)
-> LinearGradientStyle {
LinearGradientStyle {
x0: x0,
y0: y0,
x1: x1,
y1: y1,
stops: stops,
}
}
}
#[derive(Clone, Deserialize, Serialize, HeapSizeOf)]
pub struct RadialGradientStyle {
pub x0: f64,
pub y0: f64,
pub r0: f64,
pub x1: f64,
pub y1: f64,
pub r1: f64,
pub stops: Vec<CanvasGradientStop>
}
impl RadialGradientStyle {
pub fn new(x0: f64, y0: f64, r0: f64, x1: f64, y1: f64, r1: f64, stops: Vec<CanvasGradientStop>)
-> RadialGradientStyle {
RadialGradientStyle {
x0: x0,
y0: y0,
r0: r0,
x1: x1,
y1: y1,
r1: r1,
stops: stops,
}
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct SurfaceStyle {
pub surface_data: Vec<u8>,
pub surface_size: Size2D<i32>,
pub repeat_x: bool,
pub repeat_y: bool,
}
impl SurfaceStyle {
pub fn new(surface_data: Vec<u8>, surface_size: Size2D<i32>, repeat_x: bool, repeat_y: bool)
-> SurfaceStyle {
SurfaceStyle {
surface_data: surface_data,
surface_size: surface_size,
repeat_x: repeat_x,
repeat_y: repeat_y,
}
}
}
#[derive(Clone, Deserialize, Serialize)]
pub enum FillOrStrokeStyle {
Color(RGBA),
LinearGradient(LinearGradientStyle),
RadialGradient(RadialGradientStyle),
Surface(SurfaceStyle),
}
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, HeapSizeOf)]
pub enum LineCapStyle {
Butt = 0,
Round = 1,
Square = 2,
}
impl FromStr for LineCapStyle {
type Err = ();
fn from_str(string: &str) -> Result<LineCapStyle, ()> {
match string {
"butt" => Ok(LineCapStyle::Butt),
"round" => Ok(LineCapStyle::Round),
"square" => Ok(LineCapStyle::Square),
_ => Err(()),
}
}
}
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, HeapSizeOf)]
pub enum LineJoinStyle {
Round = 0,
Bevel = 1,
Miter = 2,
}
impl FromStr for LineJoinStyle {
type Err = ();
fn from_str(string: &str) -> Result<LineJoinStyle, ()> {
match string {
"round" => Ok(LineJoinStyle::Round),
"bevel" => Ok(LineJoinStyle::Bevel),
"miter" => Ok(LineJoinStyle::Miter),
_ => Err(()),
}
}
}
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum RepetitionStyle {
Repeat,
RepeatX,
RepeatY,
NoRepeat,
}
impl FromStr for RepetitionStyle {
type Err = ();
fn from_str(string: &str) -> Result<RepetitionStyle, ()> {
match string {
"repeat" => Ok(RepetitionStyle::Repeat),
"repeat-x" => Ok(RepetitionStyle::RepeatX),
"repeat-y" => Ok(RepetitionStyle::RepeatY),
"no-repeat" => Ok(RepetitionStyle::NoRepeat),
_ => Err(()),
}
}
}
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, HeapSizeOf)]
pub enum CompositionStyle {
SrcIn,
SrcOut,
SrcOver,
SrcAtop,
DestIn,
DestOut,
DestOver,
DestAtop,
Copy,
Lighter,
Xor,
}
impl FromStr for CompositionStyle {
type Err = ();
fn from_str(string: &str) -> Result<CompositionStyle, ()> {
match string {
"source-in" => Ok(CompositionStyle::SrcIn),
"source-out" => Ok(CompositionStyle::SrcOut),
"source-over" => Ok(CompositionStyle::SrcOver),
"source-atop" => Ok(CompositionStyle::SrcAtop),
"destination-in" => Ok(CompositionStyle::DestIn),
"destination-out" => Ok(CompositionStyle::DestOut),
"destination-over" => Ok(CompositionStyle::DestOver),
"destination-atop" => Ok(CompositionStyle::DestAtop),
"copy" => Ok(CompositionStyle::Copy),
"lighter" => Ok(CompositionStyle::Lighter),
"xor" => Ok(CompositionStyle::Xor),
_ => Err(())
}
}
}
impl CompositionStyle {
pub fn to_str(&self) -> &str {
match *self {
CompositionStyle::SrcIn => "source-in",
CompositionStyle::SrcOut => "source-out",
CompositionStyle::SrcOver => "source-over",
CompositionStyle::SrcAtop => "source-atop",
CompositionStyle::DestIn => "destination-in",
CompositionStyle::DestOut => "destination-out",
CompositionStyle::DestOver => "destination-over",
CompositionStyle::DestAtop => "destination-atop",
CompositionStyle::Copy => "copy",
CompositionStyle::Lighter => "lighter",
CompositionStyle::Xor => "xor",
}
}
}
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, HeapSizeOf)]
pub enum BlendingStyle {
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl FromStr for BlendingStyle {
type Err = ();
fn from_str(string: &str) -> Result<BlendingStyle, ()> {
match string {
"multiply" => Ok(BlendingStyle::Multiply),
"screen" => Ok(BlendingStyle::Screen),
"overlay" => Ok(BlendingStyle::Overlay),
"darken" => Ok(BlendingStyle::Darken),
"lighten" => Ok(BlendingStyle::Lighten),
"color-dodge" => Ok(BlendingStyle::ColorDodge),
"color-burn" => Ok(BlendingStyle::ColorBurn),
"hard-light" => Ok(BlendingStyle::HardLight),
"soft-light" => Ok(BlendingStyle::SoftLight),
"difference" => Ok(BlendingStyle::Difference),
"exclusion" => Ok(BlendingStyle::Exclusion),
"hue" => Ok(BlendingStyle::Hue),
"saturation" => Ok(BlendingStyle::Saturation),
"color" => Ok(BlendingStyle::Color),
"luminosity" => Ok(BlendingStyle::Luminosity),
_ => Err(())
}
}
}
impl BlendingStyle {
pub fn to_str(&self) -> &str {
match *self {
BlendingStyle::Multiply => "multiply",
BlendingStyle::Screen => "screen",
BlendingStyle::Overlay => "overlay",
BlendingStyle::Darken => "darken",
BlendingStyle::Lighten => "lighten",
BlendingStyle::ColorDodge => "color-dodge",
BlendingStyle::ColorBurn => "color-burn",
BlendingStyle::HardLight => "hard-light",
BlendingStyle::SoftLight => "soft-light",
BlendingStyle::Difference => "difference",
BlendingStyle::Exclusion => "exclusion",
BlendingStyle::Hue => "hue",
BlendingStyle::Saturation => "saturation",
BlendingStyle::Color => "color",
BlendingStyle::Luminosity => "luminosity",
}
}
}
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, HeapSizeOf)]
pub enum CompositionOrBlending {
Composition(CompositionStyle),
Blending(BlendingStyle),
}
impl Default for CompositionOrBlending {
fn default() -> CompositionOrBlending {
CompositionOrBlending::Composition(CompositionStyle::SrcOver)
}
}
impl FromStr for CompositionOrBlending {
type Err = ();
fn from_str(string: &str) -> Result<CompositionOrBlending, ()> {
if let Ok(op) = CompositionStyle::from_str(string) {
return Ok(CompositionOrBlending::Composition(op));
}
if let Ok(op) = BlendingStyle::from_str(string) {
return Ok(CompositionOrBlending::Blending(op));
}
Err(())
}
}
// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
pub fn byte_swap(data: &mut [u8]) {
let length = data.len();
// FIXME(rust #27741): Range::step_by is not stable yet as of this writing.
let mut i = 0;
while i < length {
let r = data[i + 2];
data[i + 2] = data[i + 0];
data[i + 0] = r;
i += 4;
}
}
pub fn multiply_u8_pixel(a: u8, b: u8) -> u8 {
return (a as u32 * b as u32 / 255) as u8;
}
pub fn byte_swap_and_premultiply(data: &mut [u8]) {
let length = data.len();
let mut i = 0;
while i < length {
let r = data[i + 2];
let g = data[i + 1];
let b = data[i + 0];
let a = data[i + 3];
data[i + 0] = multiply_u8_pixel(r, a);
data[i + 1] = multiply_u8_pixel(g, a);
data[i + 2] = multiply_u8_pixel(b, a);
i += 4;
}
pub mod canvas;
#[macro_use]
pub mod webgl;
mod webgl_channel;
pub enum ConstellationCanvasMsg {
Create {
id_sender: Sender<CanvasId>,
size: Size2D<u64>,
antialias: bool,
},
Exit,
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
/* 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 serde::{Deserialize, Serialize};
use std::io;
pub type WebGLSender<T> = ipc_channel::ipc::IpcSender<T>;
pub type WebGLReceiver<T> = ipc_channel::ipc::IpcReceiver<T>;
pub fn webgl_channel<T: Serialize + for<'de> Deserialize<'de>>(
) -> Result<(WebGLSender<T>, WebGLReceiver<T>), io::Error> {
ipc_channel::ipc::channel()
}

View file

@ -0,0 +1,146 @@
/* 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/. */
//! Enum wrappers to be able to select different channel implementations at runtime.
mod ipc;
mod mpsc;
use crate::webgl::WebGLMsg;
use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
use serde::{Deserialize, Serialize};
use servo_config::opts;
use std::fmt;
lazy_static! {
static ref IS_MULTIPROCESS: bool = opts::multiprocess();
}
#[derive(Deserialize, Serialize)]
pub enum WebGLSender<T: Serialize> {
Ipc(ipc::WebGLSender<T>),
Mpsc(mpsc::WebGLSender<T>),
}
impl<T> Clone for WebGLSender<T>
where
T: Serialize,
{
fn clone(&self) -> Self {
match *self {
WebGLSender::Ipc(ref chan) => WebGLSender::Ipc(chan.clone()),
WebGLSender::Mpsc(ref chan) => WebGLSender::Mpsc(chan.clone()),
}
}
}
impl<T: Serialize> fmt::Debug for WebGLSender<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "WebGLSender(..)")
}
}
impl<T: Serialize> WebGLSender<T> {
#[inline]
pub fn send(&self, msg: T) -> WebGLSendResult {
match *self {
WebGLSender::Ipc(ref sender) => sender.send(msg).map_err(|_| ()),
WebGLSender::Mpsc(ref sender) => sender.send(msg).map_err(|_| ()),
}
}
}
pub type WebGLSendResult = Result<(), ()>;
pub enum WebGLReceiver<T>
where
T: for<'de> Deserialize<'de> + Serialize,
{
Ipc(ipc::WebGLReceiver<T>),
Mpsc(mpsc::WebGLReceiver<T>),
}
impl<T> WebGLReceiver<T>
where
T: for<'de> Deserialize<'de> + Serialize,
{
pub fn recv(&self) -> Result<T, ()> {
match *self {
WebGLReceiver::Ipc(ref receiver) => receiver.recv().map_err(|_| ()),
WebGLReceiver::Mpsc(ref receiver) => receiver.recv().map_err(|_| ()),
}
}
pub fn try_recv(&self) -> Result<T, ()> {
match *self {
WebGLReceiver::Ipc(ref receiver) => receiver.try_recv().map_err(|_| ()),
WebGLReceiver::Mpsc(ref receiver) => receiver.try_recv().map_err(|_| ()),
}
}
pub fn into_inner(self) -> crossbeam_channel::Receiver<T>
where
T: Send + 'static,
{
match self {
WebGLReceiver::Ipc(receiver) => {
ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(receiver)
},
WebGLReceiver::Mpsc(receiver) => receiver.into_inner(),
}
}
}
pub fn webgl_channel<T>() -> Result<(WebGLSender<T>, WebGLReceiver<T>), ()>
where
T: for<'de> Deserialize<'de> + Serialize,
{
if *IS_MULTIPROCESS {
ipc::webgl_channel()
.map(|(tx, rx)| (WebGLSender::Ipc(tx), WebGLReceiver::Ipc(rx)))
.map_err(|_| ())
} else {
mpsc::webgl_channel().map(|(tx, rx)| (WebGLSender::Mpsc(tx), WebGLReceiver::Mpsc(rx)))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WebGLChan(pub WebGLSender<WebGLMsg>);
impl WebGLChan {
#[inline]
pub fn send(&self, msg: WebGLMsg) -> WebGLSendResult {
self.0.send(msg)
}
pub fn to_ipc(&self) -> IpcSender<WebGLMsg> {
match self.0 {
WebGLSender::Ipc(ref sender) => sender.clone(),
WebGLSender::Mpsc(ref mpsc_sender) => {
let (sender, receiver) =
ipc_channel::ipc::channel().expect("IPC Channel creation failed");
let mpsc_sender = mpsc_sender.clone();
ipc_channel::router::ROUTER.add_route(
receiver.to_opaque(),
Box::new(move |message| {
if let Ok(message) = message.to() {
let _ = mpsc_sender.send(message);
}
}),
);
sender
},
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WebGLPipeline(pub WebGLChan);
impl WebGLPipeline {
pub fn channel(&self) -> WebGLChan {
self.0.clone()
}
}

View file

@ -0,0 +1,63 @@
/* 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 serde::{Deserialize, Serialize};
use serde::{Deserializer, Serializer};
macro_rules! unreachable_serializable {
($name:ident) => {
impl<T> Serialize for $name<T> {
fn serialize<S: Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
unreachable!();
}
}
impl<'a, T> Deserialize<'a> for $name<T> {
fn deserialize<D>(_: D) -> Result<$name<T>, D::Error>
where
D: Deserializer<'a>,
{
unreachable!();
}
}
};
}
pub struct WebGLSender<T>(crossbeam_channel::Sender<T>);
pub struct WebGLReceiver<T>(crossbeam_channel::Receiver<T>);
impl<T> Clone for WebGLSender<T> {
fn clone(&self) -> Self {
WebGLSender(self.0.clone())
}
}
impl<T> WebGLSender<T> {
#[inline]
pub fn send(&self, data: T) -> Result<(), crossbeam_channel::SendError<T>> {
self.0.send(data)
}
}
impl<T> WebGLReceiver<T> {
#[inline]
pub fn recv(&self) -> Result<T, crossbeam_channel::RecvError> {
self.0.recv()
}
#[inline]
pub fn try_recv(&self) -> Result<T, crossbeam_channel::TryRecvError> {
self.0.try_recv()
}
pub fn into_inner(self) -> crossbeam_channel::Receiver<T> {
self.0
}
}
pub fn webgl_channel<T>() -> Result<(WebGLSender<T>, WebGLReceiver<T>), ()> {
let (sender, receiver) = crossbeam_channel::unbounded();
Ok((WebGLSender(sender), WebGLReceiver(receiver)))
}
unreachable_serializable!(WebGLReceiver);
unreachable_serializable!(WebGLSender);

View file

@ -3,27 +3,45 @@ name = "compositing"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
build = "build.rs"
[lib]
name = "compositing"
path = "lib.rs"
[features]
default = []
gl = ["gleam", "pixels"]
[dependencies]
euclid = "0.11"
gfx_traits = {path = "../gfx_traits"}
gleam = "0.4"
image = "0.12"
ipc-channel = "0.7"
log = "0.3.5"
msg = {path = "../msg"}
net_traits = {path = "../net_traits"}
profile_traits = {path = "../profile_traits"}
script_traits = {path = "../script_traits"}
servo_config = {path = "../config"}
servo_geometry = {path = "../geometry", features = ["servo"]}
servo_url = {path = "../url"}
style_traits = {path = "../style_traits"}
time = "0.1.17"
webrender = {git = "https://github.com/servo/webrender"}
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
canvas = { path = "../canvas" }
crossbeam-channel = "0.4"
embedder_traits = { path = "../embedder_traits" }
euclid = "0.20"
gfx_traits = { path = "../gfx_traits" }
gleam = { version = "0.12", optional = true }
image = "0.23"
ipc-channel = "0.14"
keyboard-types = "0.5"
libc = "0.2"
log = "0.4"
msg = { path = "../msg" }
net_traits = { path = "../net_traits" }
num-traits = "0.2"
pixels = { path = "../pixels", optional = true }
profile_traits = { path = "../profile_traits" }
script_traits = { path = "../script_traits" }
servo-media = { git = "https://github.com/servo/media" }
servo_geometry = { path = "../geometry" }
servo_url = { path = "../url" }
style_traits = { path = "../style_traits" }
time = "0.1.41"
webrender = { git = "https://github.com/servo/webrender", features = ["capture"] }
webrender_api = { git = "https://github.com/servo/webrender" }
webrender_surfman = { path = "../webrender_surfman" }
webxr = { git = "https://github.com/servo/webxr" }
[build-dependencies]
toml = "0.5"

View file

@ -0,0 +1,52 @@
/* 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 std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
fn main() {
let lockfile_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
.join("..")
.join("..")
.join("Cargo.lock");
let revision_file_path =
Path::new(&env::var_os("OUT_DIR").unwrap()).join("webrender_revision.rs");
let mut lockfile = String::new();
File::open(lockfile_path)
.expect("Cannot open lockfile")
.read_to_string(&mut lockfile)
.expect("Failed to read lockfile");
match toml::from_str::<toml::value::Table>(&lockfile) {
Ok(result) => {
let packages = result
.get("package")
.expect("Cargo lockfile should contain package list");
match *packages {
toml::Value::Array(ref arr) => {
let source = arr
.iter()
.find(|pkg| {
pkg.get("name").and_then(|name| name.as_str()).unwrap_or("") ==
"webrender"
})
.and_then(|pkg| pkg.get("source").and_then(|source| source.as_str()))
.unwrap_or("unknown");
let parsed: Vec<&str> = source.split("#").collect();
let revision = if parsed.len() > 1 { parsed[1] } else { source };
let mut revision_module_file = File::create(&revision_file_path).unwrap();
write!(&mut revision_module_file, "{}", format!("\"{}\"", revision)).unwrap();
},
_ => panic!("Cannot find package definitions in lockfile"),
}
},
Err(e) => panic!("{}", e),
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,120 +1,94 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Communication with the compositor thread.
use SendableFrameTree;
use compositor::CompositingReason;
use euclid::point::Point2D;
use euclid::size::Size2D;
use crate::compositor::CompositingReason;
use crate::{ConstellationMsg, SendableFrameTree};
use canvas::canvas_paint_thread::ImageUpdate;
use crossbeam_channel::{Receiver, Sender};
use embedder_traits::EventLoopWaker;
use euclid::Rect;
use gfx_traits::Epoch;
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::{Key, KeyModifiers, KeyState, PipelineId};
use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId};
use net_traits::image::base::Image;
use profile_traits::mem;
use profile_traits::time;
use script_traits::{AnimationState, ConstellationMsg, EventResult, LoadData};
use servo_url::ServoUrl;
use script_traits::{AnimationState, EventResult, MouseButton, MouseEventType};
use std::fmt::{Debug, Error, Formatter};
use std::sync::mpsc::{Receiver, Sender};
use style_traits::cursor::Cursor;
use std::rc::Rc;
use style_traits::viewport::ViewportConstraints;
use webrender;
use webrender_traits;
use style_traits::CSSPixel;
use webrender_api;
use webrender_api::units::{DeviceIntPoint, DeviceIntSize};
use webrender_surfman::WebrenderSurfman;
/// Sends messages to the compositor. This is a trait supplied by the port because the method used
/// to communicate with the compositor may have to kick OS event loops awake, communicate cross-
/// process, and so forth.
pub trait CompositorProxy : 'static + Send {
/// Sends a message to the compositor.
fn send(&self, msg: Msg);
/// Clones the compositor proxy.
fn clone_compositor_proxy(&self) -> Box<CompositorProxy + 'static + Send>;
/// Sends messages to the compositor.
pub struct CompositorProxy {
pub sender: Sender<Msg>,
pub event_loop_waker: Box<dyn EventLoopWaker>,
}
/// The port that the compositor receives messages on. As above, this is a trait supplied by the
/// Servo port.
pub trait CompositorReceiver : 'static {
/// Receives the next message inbound for the compositor. This must not block.
fn try_recv_compositor_msg(&mut self) -> Option<Msg>;
/// Synchronously waits for, and returns, the next message inbound for the compositor.
fn recv_compositor_msg(&mut self) -> Msg;
}
/// A convenience implementation of `CompositorReceiver` for a plain old Rust `Receiver`.
impl CompositorReceiver for Receiver<Msg> {
fn try_recv_compositor_msg(&mut self) -> Option<Msg> {
self.try_recv().ok()
}
fn recv_compositor_msg(&mut self) -> Msg {
self.recv().unwrap()
impl CompositorProxy {
pub fn send(&self, msg: Msg) {
if let Err(err) = self.sender.send(msg) {
warn!("Failed to send response ({:?}).", err);
}
self.event_loop_waker.wake();
}
}
pub trait RenderListener {
fn recomposite(&mut self, reason: CompositingReason);
impl Clone for CompositorProxy {
fn clone(&self) -> CompositorProxy {
CompositorProxy {
sender: self.sender.clone(),
event_loop_waker: self.event_loop_waker.clone(),
}
}
}
impl RenderListener for Box<CompositorProxy + 'static> {
fn recomposite(&mut self, reason: CompositingReason) {
/// The port that the compositor receives messages on.
pub struct CompositorReceiver {
pub receiver: Receiver<Msg>,
}
impl CompositorReceiver {
pub fn try_recv_compositor_msg(&mut self) -> Option<Msg> {
self.receiver.try_recv().ok()
}
pub fn recv_compositor_msg(&mut self) -> Msg {
self.receiver.recv().unwrap()
}
}
impl CompositorProxy {
pub fn recomposite(&self, reason: CompositingReason) {
self.send(Msg::Recomposite(reason));
}
}
/// Messages from the painting thread and the constellation thread to the compositor thread.
pub enum Msg {
/// Requests that the compositor shut down.
Exit,
/// Informs the compositor that the constellation has completed shutdown.
/// Required because the constellation can have pending calls to make
/// (e.g. SetFrameTree) at the time that we send it an ExitMsg.
ShutdownComplete,
/// Scroll a page in a window
ScrollFragmentPoint(webrender_traits::ClipId, Point2D<f32>, bool),
/// Alerts the compositor that the current page has changed its title.
ChangePageTitle(PipelineId, Option<String>),
/// Alerts the compositor that the given pipeline has changed whether it is running animations.
ChangeRunningAnimationsState(PipelineId, AnimationState),
/// Replaces the current frame tree, typically called during main frame navigation.
SetFrameTree(SendableFrameTree, IpcSender<()>),
/// The load of a page has begun
LoadStart,
/// The load of a page has completed
LoadComplete,
/// The history state has changed.
HistoryChanged(Vec<LoadData>, usize),
/// Wether or not to follow a link
AllowNavigation(ServoUrl, IpcSender<bool>),
/// We hit the delayed composition timeout. (See `delayed_composition.rs`.)
DelayedCompositionTimeout(u64),
SetFrameTree(SendableFrameTree),
/// Composite.
Recomposite(CompositingReason),
/// Sends an unconsumed key event back to the compositor.
KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
/// Script has handled a touch event, and either prevented or allowed default actions.
TouchEventProcessed(EventResult),
/// Changes the cursor.
SetCursor(Cursor),
/// Composite to a PNG file and return the Image over a passed channel.
CreatePng(IpcSender<Option<Image>>),
CreatePng(Option<Rect<f32, CSSPixel>>, IpcSender<Option<Image>>),
/// Alerts the compositor that the viewport has been constrained in some manner
ViewportConstrained(PipelineId, ViewportConstraints),
/// A reply to the compositor asking if the output image is stable.
IsReadyToSaveImageReply(bool),
/// A favicon was detected
NewFavicon(ServoUrl),
/// <head> tag finished parsing
HeadParsed,
/// A status message to be displayed by the browser chrome.
Status(Option<String>),
/// Get Window Informations size and position
GetClientWindow(IpcSender<(Size2D<u32>, Point2D<i32>)>),
/// Move the window to a point
MoveTo(Point2D<i32>),
/// Resize the window to size
ResizeTo(Size2D<u32>),
/// Pipeline visibility changed
PipelineVisibilityChanged(PipelineId, bool),
/// WebRender has successfully processed a scroll. The boolean specifies whether a composite is
@ -129,43 +103,75 @@ pub enum Msg {
/// Runs a closure in the compositor thread.
/// It's used to dispatch functions from webrender to the main thread's event loop.
/// Required to allow WGL GLContext sharing in Windows.
Dispatch(Box<Fn() + Send>),
/// Enter or exit fullscreen
SetFullscreenState(bool),
Dispatch(Box<dyn Fn() + Send>),
/// Indicates to the compositor that it needs to record the time when the frame with
/// the given ID (epoch) is painted and report it to the layout thread of the given
/// pipeline ID.
PendingPaintMetric(PipelineId, Epoch),
/// The load of a page has completed
LoadComplete(TopLevelBrowsingContextId),
/// WebDriver mouse button event
WebDriverMouseButtonEvent(MouseEventType, MouseButton, f32, f32),
/// WebDriver mouse move event
WebDriverMouseMoveEvent(f32, f32),
/// Get Window Informations size and position.
GetClientWindow(IpcSender<(DeviceIntSize, DeviceIntPoint)>),
/// Get screen size.
GetScreenSize(IpcSender<DeviceIntSize>),
/// Get screen available size.
GetScreenAvailSize(IpcSender<DeviceIntSize>),
/// Webrender operations requested from non-compositor threads.
Webrender(WebrenderMsg),
}
pub enum WebrenderFontMsg {
AddFontInstance(
webrender_api::FontKey,
f32,
Sender<webrender_api::FontInstanceKey>,
),
AddFont(gfx_traits::FontData, Sender<webrender_api::FontKey>),
}
pub enum WebrenderCanvasMsg {
GenerateKey(Sender<webrender_api::ImageKey>),
UpdateImages(Vec<ImageUpdate>),
}
pub enum WebrenderMsg {
Layout(script_traits::WebrenderMsg),
Net(net_traits::WebrenderImageMsg),
Font(WebrenderFontMsg),
Canvas(WebrenderCanvasMsg),
}
impl Debug for Msg {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match *self {
Msg::Exit => write!(f, "Exit"),
Msg::ShutdownComplete => write!(f, "ShutdownComplete"),
Msg::ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"),
Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
Msg::ChangeRunningAnimationsState(_, state) => {
write!(f, "ChangeRunningAnimationsState({:?})", state)
},
Msg::SetFrameTree(..) => write!(f, "SetFrameTree"),
Msg::LoadComplete => write!(f, "LoadComplete"),
Msg::AllowNavigation(..) => write!(f, "AllowNavigation"),
Msg::LoadStart => write!(f, "LoadStart"),
Msg::HistoryChanged(..) => write!(f, "HistoryChanged"),
Msg::DelayedCompositionTimeout(..) => write!(f, "DelayedCompositionTimeout"),
Msg::Recomposite(..) => write!(f, "Recomposite"),
Msg::KeyEvent(..) => write!(f, "KeyEvent"),
Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"),
Msg::SetCursor(..) => write!(f, "SetCursor"),
Msg::CreatePng(..) => write!(f, "CreatePng"),
Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"),
Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"),
Msg::NewFavicon(..) => write!(f, "NewFavicon"),
Msg::HeadParsed => write!(f, "HeadParsed"),
Msg::Status(..) => write!(f, "Status"),
Msg::GetClientWindow(..) => write!(f, "GetClientWindow"),
Msg::MoveTo(..) => write!(f, "MoveTo"),
Msg::ResizeTo(..) => write!(f, "ResizeTo"),
Msg::PipelineVisibilityChanged(..) => write!(f, "PipelineVisibilityChanged"),
Msg::PipelineExited(..) => write!(f, "PipelineExited"),
Msg::NewScrollFrameReady(..) => write!(f, "NewScrollFrameReady"),
Msg::Dispatch(..) => write!(f, "Dispatch"),
Msg::SetFullscreenState(..) => write!(f, "SetFullscreenState"),
Msg::PendingPaintMetric(..) => write!(f, "PendingPaintMetric"),
Msg::LoadComplete(..) => write!(f, "LoadComplete"),
Msg::WebDriverMouseButtonEvent(..) => write!(f, "WebDriverMouseButtonEvent"),
Msg::WebDriverMouseMoveEvent(..) => write!(f, "WebDriverMouseMoveEvent"),
Msg::GetClientWindow(..) => write!(f, "GetClientWindow"),
Msg::GetScreenSize(..) => write!(f, "GetScreenSize"),
Msg::GetScreenAvailSize(..) => write!(f, "GetScreenAvailSize"),
Msg::Webrender(..) => write!(f, "Webrender"),
}
}
}
@ -173,9 +179,9 @@ impl Debug for Msg {
/// Data used to construct a compositor.
pub struct InitialCompositorState {
/// A channel to the compositor.
pub sender: Box<CompositorProxy + Send>,
pub sender: CompositorProxy,
/// A port on which messages inbound to the compositor can be received.
pub receiver: Box<CompositorReceiver>,
pub receiver: CompositorReceiver,
/// A channel to the constellation.
pub constellation_chan: Sender<ConstellationMsg>,
/// A channel to the time profiler thread.
@ -184,5 +190,9 @@ pub struct InitialCompositorState {
pub mem_profiler_chan: mem::ProfilerChan,
/// Instance of webrender API
pub webrender: webrender::Renderer,
pub webrender_api_sender: webrender_traits::RenderApiSender,
pub webrender_document: webrender_api::DocumentId,
pub webrender_api: webrender_api::RenderApi,
pub webrender_surfman: WebrenderSurfman,
pub webrender_gl: Rc<dyn gleam::gl::Gl>,
pub webxr_main_thread: webxr::MainThreadRegistry,
}

View file

@ -1,107 +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 http://mozilla.org/MPL/2.0/. */
//! A timer thread that composites near the end of the frame.
//!
//! This is useful when we need to composite next frame but we want to opportunistically give the
//! painting thread time to paint if it can.
use compositor_thread::{CompositorProxy, Msg};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread::{self, Builder};
use std::time::Duration;
use std::u32;
use time;
/// The amount of time in nanoseconds that we give to the painting thread to paint. When this
/// expires, we give up and composite anyway.
static TIMEOUT: u64 = 12_000_000;
pub struct DelayedCompositionTimerProxy {
sender: Sender<ToDelayedCompositionTimerMsg>,
}
struct DelayedCompositionTimer {
compositor_proxy: Box<CompositorProxy>,
receiver: Receiver<ToDelayedCompositionTimerMsg>,
}
enum ToDelayedCompositionTimerMsg {
Exit,
ScheduleComposite(u64),
}
impl DelayedCompositionTimerProxy {
pub fn new(compositor_proxy: Box<CompositorProxy + Send>) -> DelayedCompositionTimerProxy {
let (to_timer_sender, to_timer_receiver) = channel();
Builder::new().spawn(move || {
let mut timer = DelayedCompositionTimer {
compositor_proxy: compositor_proxy,
receiver: to_timer_receiver,
};
timer.run();
}).unwrap();
DelayedCompositionTimerProxy {
sender: to_timer_sender,
}
}
pub fn schedule_composite(&mut self, timestamp: u64) {
self.sender.send(ToDelayedCompositionTimerMsg::ScheduleComposite(timestamp)).unwrap()
}
pub fn shutdown(&mut self) {
self.sender.send(ToDelayedCompositionTimerMsg::Exit).unwrap()
}
}
impl DelayedCompositionTimer {
fn run(&mut self) {
'outer: loop {
let mut timestamp;
loop {
match self.receiver.recv() {
Ok(ToDelayedCompositionTimerMsg::ScheduleComposite(this_timestamp)) => {
timestamp = this_timestamp;
break
}
Ok(ToDelayedCompositionTimerMsg::Exit) => break 'outer,
_ => break 'outer,
}
}
// Drain all messages from the queue.
loop {
match self.receiver.try_recv() {
Ok(ToDelayedCompositionTimerMsg::ScheduleComposite(this_timestamp)) => {
timestamp = this_timestamp;
break
}
_ => break,
}
}
let target = timestamp + TIMEOUT;
let now = time::precise_time_ns();
if target > now {
let delta_ns = target - now;
thread::sleep(duration_from_nanoseconds(delta_ns));
}
self.compositor_proxy.send(Msg::DelayedCompositionTimeout(timestamp));
}
}
}
fn duration_from_nanoseconds(nanos: u64) -> Duration {
pub const NANOS_PER_SEC: u32 = 1_000_000_000;
// Get number of seconds.
let secs = nanos / NANOS_PER_SEC as u64;
// Get number of extra nanoseconds. This should always fit in a u32, but check anyway.
let subsec_nanos = nanos % NANOS_PER_SEC as u64;
assert!(subsec_nanos <= u32::MAX as u64);
Duration::new(secs, subsec_nanos as u32)
}

View file

@ -0,0 +1,126 @@
/* 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 gleam::gl;
use image::RgbImage;
use servo_geometry::FramebufferUintLength;
#[derive(Default)]
pub struct RenderTargetInfo {
framebuffer_ids: Vec<gl::GLuint>,
renderbuffer_ids: Vec<gl::GLuint>,
texture_ids: Vec<gl::GLuint>,
}
pub fn initialize_png(
gl: &dyn gl::Gl,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> RenderTargetInfo {
let framebuffer_ids = gl.gen_framebuffers(1);
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
let texture_ids = gl.gen_textures(1);
gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]);
gl.tex_image_2d(
gl::TEXTURE_2D,
0,
gl::RGB as gl::GLint,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
0,
gl::RGB,
gl::UNSIGNED_BYTE,
None,
);
gl.tex_parameter_i(
gl::TEXTURE_2D,
gl::TEXTURE_MAG_FILTER,
gl::NEAREST as gl::GLint,
);
gl.tex_parameter_i(
gl::TEXTURE_2D,
gl::TEXTURE_MIN_FILTER,
gl::NEAREST as gl::GLint,
);
gl.framebuffer_texture_2d(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture_ids[0],
0,
);
gl.bind_texture(gl::TEXTURE_2D, 0);
let renderbuffer_ids = gl.gen_renderbuffers(1);
let depth_rb = renderbuffer_ids[0];
gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
gl.renderbuffer_storage(
gl::RENDERBUFFER,
gl::DEPTH_COMPONENT24,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
);
gl.framebuffer_renderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_ATTACHMENT,
gl::RENDERBUFFER,
depth_rb,
);
RenderTargetInfo {
framebuffer_ids,
renderbuffer_ids,
texture_ids,
}
}
pub fn draw_img(
gl: &dyn gl::Gl,
render_target_info: RenderTargetInfo,
x: i32,
y: i32,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> RgbImage {
let width = width.get() as usize;
let height = height.get() as usize;
// For some reason, OSMesa fails to render on the 3rd
// attempt in headless mode, under some conditions.
// I think this can only be some kind of synchronization
// bug in OSMesa, but explicitly un-binding any vertex
// array here seems to work around that bug.
// See https://github.com/servo/servo/issues/18606.
gl.bind_vertex_array(0);
let mut pixels = gl.read_pixels(
x,
y,
width as gl::GLsizei,
height as gl::GLsizei,
gl::RGB,
gl::UNSIGNED_BYTE,
);
gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
gl.delete_textures(&render_target_info.texture_ids);
gl.delete_renderbuffers(&render_target_info.renderbuffer_ids);
gl.delete_framebuffers(&render_target_info.framebuffer_ids);
// flip image vertically (texture is upside down)
let orig_pixels = pixels.clone();
let stride = width * 3;
for y in 0..height {
let dst_start = y * stride;
let src_start = (height - y - 1) * stride;
let src_slice = &orig_pixels[src_start..src_start + stride];
(&mut pixels[dst_start..dst_start + stride]).clone_from_slice(&src_slice[..stride]);
}
RgbImage::from_raw(width as u32, height as u32, pixels).expect("Flipping image failed!")
}

View file

@ -1,46 +1,43 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
#![feature(box_syntax)]
extern crate euclid;
extern crate gfx_traits;
extern crate gleam;
extern crate image;
extern crate ipc_channel;
#[macro_use]
extern crate log;
extern crate msg;
extern crate net_traits;
extern crate profile_traits;
extern crate script_traits;
extern crate servo_config;
extern crate servo_geometry;
extern crate servo_url;
extern crate style_traits;
extern crate time;
extern crate webrender;
extern crate webrender_traits;
pub use compositor_thread::CompositorProxy;
pub use compositor::IOCompositor;
use euclid::size::TypedSize2D;
pub use crate::compositor::CompositingReason;
pub use crate::compositor::IOCompositor;
pub use crate::compositor::ShutdownState;
pub use crate::compositor_thread::CompositorProxy;
use embedder_traits::Cursor;
use gfx_traits::Epoch;
use ipc_channel::ipc::IpcSender;
use keyboard_types::KeyboardEvent;
use msg::constellation_msg::PipelineId;
use script_traits::{ConstellationControlMsg, LayoutControlMsg};
use style_traits::CSSPixel;
use msg::constellation_msg::TopLevelBrowsingContextId;
use msg::constellation_msg::{BrowsingContextId, TraversalDirection};
use script_traits::{
AnimationTickType, LogEntry, WebDriverCommandMsg, WindowSizeData, WindowSizeType,
};
use script_traits::{
CompositorEvent, ConstellationControlMsg, LayoutControlMsg, MediaSessionActionType,
};
use servo_url::ServoUrl;
use std::collections::HashMap;
use std::fmt;
use std::time::Duration;
mod compositor;
pub mod compositor_thread;
mod delayed_composition;
#[cfg(feature = "gl")]
mod gl;
mod touch;
pub mod windowing;
pub struct SendableFrameTree {
pub pipeline: CompositionPipeline,
pub size: Option<TypedSize2D<f32, CSSPixel>>,
pub children: Vec<SendableFrameTree>,
}
@ -48,6 +45,108 @@ pub struct SendableFrameTree {
#[derive(Clone)]
pub struct CompositionPipeline {
pub id: PipelineId,
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
pub script_chan: IpcSender<ConstellationControlMsg>,
pub layout_chan: IpcSender<LayoutControlMsg>,
}
/// Messages to the constellation.
pub enum ConstellationMsg {
/// Exit the constellation.
Exit,
/// Request that the constellation send the BrowsingContextId corresponding to the document
/// with the provided pipeline id
GetBrowsingContext(PipelineId, IpcSender<Option<BrowsingContextId>>),
/// Request that the constellation send the current pipeline id for the provided
/// browsing context id, over a provided channel.
GetPipeline(BrowsingContextId, IpcSender<Option<PipelineId>>),
/// Request that the constellation send the current focused top-level browsing context id,
/// over a provided channel.
GetFocusTopLevelBrowsingContext(IpcSender<Option<TopLevelBrowsingContextId>>),
/// Query the constellation to see if the current compositor output is stable
IsReadyToSaveImage(HashMap<PipelineId, Epoch>),
/// Inform the constellation of a key event.
Keyboard(KeyboardEvent),
/// Whether to allow script to navigate.
AllowNavigationResponse(PipelineId, bool),
/// Request to load a page.
LoadUrl(TopLevelBrowsingContextId, ServoUrl),
/// Clear the network cache.
ClearCache,
/// Request to traverse the joint session history of the provided browsing context.
TraverseHistory(TopLevelBrowsingContextId, TraversalDirection),
/// Inform the constellation of a window being resized.
WindowSize(
Option<TopLevelBrowsingContextId>,
WindowSizeData,
WindowSizeType,
),
/// Requests that the constellation instruct layout to begin a new tick of the animation.
TickAnimation(PipelineId, AnimationTickType),
/// Dispatch a webdriver command
WebDriverCommand(WebDriverCommandMsg),
/// Reload a top-level browsing context.
Reload(TopLevelBrowsingContextId),
/// A log entry, with the top-level browsing context id and thread name
LogEntry(Option<TopLevelBrowsingContextId>, Option<String>, LogEntry),
/// Create a new top level browsing context.
NewBrowser(ServoUrl, TopLevelBrowsingContextId),
/// Close a top level browsing context.
CloseBrowser(TopLevelBrowsingContextId),
/// Panic a top level browsing context.
SendError(Option<TopLevelBrowsingContextId>, String),
/// Make browser visible.
SelectBrowser(TopLevelBrowsingContextId),
/// Forward an event to the script task of the given pipeline.
ForwardEvent(PipelineId, CompositorEvent),
/// Requesting a change to the onscreen cursor.
SetCursor(Cursor),
/// Enable the sampling profiler, with a given sampling rate and max total sampling duration.
EnableProfiler(Duration, Duration),
/// Disable the sampling profiler.
DisableProfiler,
/// Request to exit from fullscreen mode
ExitFullScreen(TopLevelBrowsingContextId),
/// Media session action.
MediaSessionAction(MediaSessionActionType),
/// Toggle browser visibility.
ChangeBrowserVisibility(TopLevelBrowsingContextId, bool),
/// Virtual keyboard was dismissed
IMEDismissed,
}
impl fmt::Debug for ConstellationMsg {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
use self::ConstellationMsg::*;
let variant = match *self {
Exit => "Exit",
GetBrowsingContext(..) => "GetBrowsingContext",
GetPipeline(..) => "GetPipeline",
GetFocusTopLevelBrowsingContext(..) => "GetFocusTopLevelBrowsingContext",
IsReadyToSaveImage(..) => "IsReadyToSaveImage",
Keyboard(..) => "Keyboard",
AllowNavigationResponse(..) => "AllowNavigationResponse",
LoadUrl(..) => "LoadUrl",
TraverseHistory(..) => "TraverseHistory",
WindowSize(..) => "WindowSize",
TickAnimation(..) => "TickAnimation",
WebDriverCommand(..) => "WebDriverCommand",
Reload(..) => "Reload",
LogEntry(..) => "LogEntry",
NewBrowser(..) => "NewBrowser",
CloseBrowser(..) => "CloseBrowser",
SendError(..) => "SendError",
SelectBrowser(..) => "SelectBrowser",
ForwardEvent(..) => "ForwardEvent",
SetCursor(..) => "SetCursor",
EnableProfiler(..) => "EnableProfiler",
DisableProfiler => "DisableProfiler",
ExitFullScreen(..) => "ExitFullScreen",
MediaSessionAction(..) => "MediaSessionAction",
ChangeBrowserVisibility(..) => "ChangeBrowserVisibility",
IMEDismissed => "IMEDismissed",
ClearCache => "ClearCache",
};
write!(formatter, "ConstellationMsg::{}", variant)
}
}

View file

@ -1,11 +1,11 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use euclid::point::TypedPoint2D;
use euclid::scale_factor::ScaleFactor;
use script_traits::{DevicePixel, EventResult, TouchId};
use self::TouchState::*;
use euclid::{Point2D, Scale, Vector2D};
use script_traits::{EventResult, TouchId};
use style_traits::DevicePixel;
/// Minimum number of `DeviceIndependentPixel` to begin touch scrolling.
const TOUCH_PAN_MIN_SCREEN_PX: f32 = 20.0;
@ -18,12 +18,15 @@ pub struct TouchHandler {
#[derive(Clone, Copy, Debug)]
pub struct TouchPoint {
pub id: TouchId,
pub point: TypedPoint2D<f32, DevicePixel>
pub point: Point2D<f32, DevicePixel>,
}
impl TouchPoint {
pub fn new(id: TouchId, point: TypedPoint2D<f32, DevicePixel>) -> Self {
TouchPoint { id: id, point: point }
pub fn new(id: TouchId, point: Point2D<f32, DevicePixel>) -> Self {
TouchPoint {
id: id,
point: point,
}
}
}
@ -56,9 +59,9 @@ pub enum TouchAction {
/// Simulate a mouse click.
Click,
/// Scroll by the provided offset.
Scroll(TypedPoint2D<f32, DevicePixel>),
Scroll(Vector2D<f32, DevicePixel>),
/// Zoom by a magnification factor and scroll by the provided offset.
Zoom(f32, TypedPoint2D<f32, DevicePixel>),
Zoom(f32, Vector2D<f32, DevicePixel>),
/// Send a JavaScript event to content.
DispatchEvent,
/// Don't do anything.
@ -73,27 +76,26 @@ impl TouchHandler {
}
}
pub fn on_touch_down(&mut self, id: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
pub fn on_touch_down(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
let point = TouchPoint::new(id, point);
self.active_touch_points.push(point);
self.state = match self.state {
Nothing => WaitingForScript,
Touching | Panning => Pinching,
WaitingForScript => WaitingForScript,
DefaultPrevented => DefaultPrevented,
Nothing => WaitingForScript,
Touching | Panning => Pinching,
WaitingForScript => WaitingForScript,
DefaultPrevented => DefaultPrevented,
Pinching | MultiTouch => MultiTouch,
};
}
pub fn on_touch_move(&mut self, id: TouchId, point: TypedPoint2D<f32, DevicePixel>)
-> TouchAction {
pub fn on_touch_move(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) -> TouchAction {
let idx = match self.active_touch_points.iter_mut().position(|t| t.id == id) {
Some(i) => i,
None => {
warn!("Got a touchmove event for a non-active touch point");
return TouchAction::NoAction;
}
},
};
let old_point = self.active_touch_points[idx].point;
@ -102,31 +104,29 @@ impl TouchHandler {
let delta = point - old_point;
if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
{
self.state = Panning;
TouchAction::Scroll(delta)
} else {
TouchAction::NoAction
}
}
},
Panning => {
let delta = point - old_point;
TouchAction::Scroll(delta)
}
DefaultPrevented => {
TouchAction::DispatchEvent
}
},
DefaultPrevented => TouchAction::DispatchEvent,
Pinching => {
let (d0, c0) = self.pinch_distance_and_center();
self.active_touch_points[idx].point = point;
let (d1, c1) = self.pinch_distance_and_center();
let magnification = d1 / d0;
let scroll_delta = c1 - c0 * ScaleFactor::new(magnification);
let scroll_delta = c1 - c0 * Scale::new(magnification);
TouchAction::Zoom(magnification, scroll_delta)
}
},
WaitingForScript => TouchAction::NoAction,
MultiTouch => TouchAction::NoAction,
Nothing => unreachable!(),
@ -140,15 +140,14 @@ impl TouchHandler {
action
}
pub fn on_touch_up(&mut self, id: TouchId, _point: TypedPoint2D<f32, DevicePixel>)
-> TouchAction {
pub fn on_touch_up(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) -> TouchAction {
match self.active_touch_points.iter().position(|t| t.id == id) {
Some(i) => {
self.active_touch_points.swap_remove(i);
}
},
None => {
warn!("Got a touch up event for a non-active touch point");
}
},
}
match self.state {
Touching => {
@ -156,47 +155,47 @@ impl TouchHandler {
// FIXME: Don't send a click if preventDefault is called on the touchend event.
self.state = Nothing;
TouchAction::Click
}
},
Nothing | Panning => {
self.state = Nothing;
TouchAction::NoAction
}
},
Pinching => {
self.state = Panning;
TouchAction::NoAction
}
},
WaitingForScript | DefaultPrevented | MultiTouch => {
if self.active_touch_points.is_empty() {
self.state = Nothing;
}
TouchAction::NoAction
}
},
}
}
pub fn on_touch_cancel(&mut self, id: TouchId, _point: TypedPoint2D<f32, DevicePixel>) {
pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) {
match self.active_touch_points.iter().position(|t| t.id == id) {
Some(i) => {
self.active_touch_points.swap_remove(i);
}
},
None => {
warn!("Got a touchcancel event for a non-active touch point");
return;
}
},
}
match self.state {
Nothing => {}
Nothing => {},
Touching | Panning => {
self.state = Nothing;
}
},
Pinching => {
self.state = Panning;
}
},
WaitingForScript | DefaultPrevented | MultiTouch => {
if self.active_touch_points.is_empty() {
self.state = Nothing;
}
}
},
}
}
@ -208,7 +207,7 @@ impl TouchHandler {
1 => Touching,
2 => Pinching,
_ => MultiTouch,
}
},
}
}
}
@ -217,14 +216,12 @@ impl TouchHandler {
self.active_touch_points.len()
}
fn pinch_distance_and_center(&self) -> (f32, TypedPoint2D<f32, DevicePixel>) {
debug_assert!(self.touch_count() == 2);
fn pinch_distance_and_center(&self) -> (f32, Point2D<f32, DevicePixel>) {
debug_assert_eq!(self.touch_count(), 2);
let p0 = self.active_touch_points[0].point;
let p1 = self.active_touch_points[1].point;
let center = (p0 + p1) / ScaleFactor::new(2.0);
let d = p0 - p1;
let distance = f32::sqrt(d.x * d.x + d.y * d.y);
let center = p0.lerp(p1, 0.5);
let distance = (p0 - p1).length();
(distance, center)
}

View file

@ -1,37 +1,39 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
use compositor_thread::{CompositorProxy, CompositorReceiver};
use euclid::{Point2D, Size2D};
use euclid::point::TypedPoint2D;
use euclid::rect::TypedRect;
use euclid::scale_factor::ScaleFactor;
use euclid::size::TypedSize2D;
use gleam::gl;
use msg::constellation_msg::{Key, KeyModifiers, KeyState};
use net_traits::net_error_list::NetError;
use script_traits::{DevicePixel, LoadData, MouseButton, TouchEventType, TouchId, TouchpadPressurePhase};
use embedder_traits::{EmbedderProxy, EventLoopWaker};
use euclid::Scale;
use keyboard_types::KeyboardEvent;
use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId, TraversalDirection};
use script_traits::{MediaSessionActionType, MouseButton, TouchEventType, TouchId, WheelDelta};
use servo_geometry::DeviceIndependentPixel;
use servo_media::player::context::{GlApi, GlContext, NativeDisplay};
use servo_url::ServoUrl;
use std::fmt::{Debug, Error, Formatter};
use std::rc::Rc;
use style_traits::cursor::Cursor;
use webrender_traits::ScrollLocation;
use std::time::Duration;
use style_traits::DevicePixel;
use webrender_api::units::DevicePoint;
use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
use webrender_api::ScrollLocation;
use webrender_surfman::WebrenderSurfman;
#[derive(Clone)]
pub enum MouseWindowEvent {
Click(MouseButton, TypedPoint2D<f32, DevicePixel>),
MouseDown(MouseButton, TypedPoint2D<f32, DevicePixel>),
MouseUp(MouseButton, TypedPoint2D<f32, DevicePixel>),
Click(MouseButton, DevicePoint),
MouseDown(MouseButton, DevicePoint),
MouseUp(MouseButton, DevicePoint),
}
/// Various debug and profiling flags that WebRender supports.
#[derive(Clone)]
pub enum WindowNavigateMsg {
Forward,
Back,
pub enum WebRenderDebugOption {
Profiler,
TextureCacheDebug,
RenderTargetDebug,
}
/// Events that the windowing system sends to Servo.
@ -47,26 +49,23 @@ pub enum WindowEvent {
/// Sent when part of the window is marked dirty and needs to be redrawn. Before sending this
/// message, the window must make the same GL context as in `PrepareRenderingEvent` current.
Refresh,
/// Sent to initialize the GL context. The windowing system must have a valid, current GL
/// context when this message is sent.
InitializeCompositing,
/// Sent when the window is resized.
Resize(TypedSize2D<u32, DevicePixel>),
/// Touchpad Pressure
TouchpadPressure(TypedPoint2D<f32, DevicePixel>, f32, TouchpadPressurePhase),
/// Sent when you want to override the viewport.
Viewport(TypedPoint2D<u32, DevicePixel>, TypedSize2D<u32, DevicePixel>),
Resize,
/// Sent when a navigation request from script is allowed/refused.
AllowNavigationResponse(PipelineId, bool),
/// Sent when a new URL is to be loaded.
LoadUrl(String),
LoadUrl(TopLevelBrowsingContextId, ServoUrl),
/// Sent when a mouse hit test is to be performed.
MouseWindowEventClass(MouseWindowEvent),
/// Sent when a mouse move.
MouseWindowMoveEventClass(TypedPoint2D<f32, DevicePixel>),
MouseWindowMoveEventClass(DevicePoint),
/// Touch event: type, identifier, point
Touch(TouchEventType, TouchId, TypedPoint2D<f32, DevicePixel>),
Touch(TouchEventType, TouchId, DevicePoint),
/// Sent when user moves the mouse wheel.
Wheel(WheelDelta, DevicePoint),
/// Sent when the user scrolls. The first point is the delta and the second point is the
/// origin.
Scroll(ScrollLocation, TypedPoint2D<i32, DevicePixel>, TouchEventType),
Scroll(ScrollLocation, DeviceIntPoint, TouchEventType),
/// Sent when the user zooms.
Zoom(f32),
/// Simulated "pinch zoom" gesture for non-touch platforms (e.g. ctrl-scrollwheel).
@ -74,13 +73,39 @@ pub enum WindowEvent {
/// Sent when the user resets zoom to default.
ResetZoom,
/// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace).
Navigation(WindowNavigateMsg),
Navigation(TopLevelBrowsingContextId, TraversalDirection),
/// Sent when the user quits the application
Quit,
/// Sent when the user exits from fullscreen mode
ExitFullScreen(TopLevelBrowsingContextId),
/// Sent when a key input state changes
KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
Keyboard(KeyboardEvent),
/// Sent when Ctr+R/Apple+R is called to reload the current page.
Reload,
Reload(TopLevelBrowsingContextId),
/// Create a new top level browsing context
NewBrowser(ServoUrl, TopLevelBrowsingContextId),
/// Close a top level browsing context
CloseBrowser(TopLevelBrowsingContextId),
/// Panic a top level browsing context.
SendError(Option<TopLevelBrowsingContextId>, String),
/// Make a top level browsing context visible, hiding the previous
/// visible one.
SelectBrowser(TopLevelBrowsingContextId),
/// Toggles a debug flag in WebRender
ToggleWebRenderDebug(WebRenderDebugOption),
/// Capture current WebRender
CaptureWebRender,
/// Clear the network cache.
ClearCache,
/// Toggle sampling profiler with the given sampling rate and max duration.
ToggleSamplingProfiler(Duration, Duration),
/// Sent when the user triggers a media action through the UA exposed media UI
/// (play, pause, seek, etc.).
MediaSessionAction(MediaSessionActionType),
/// Set browser visibility. A hidden browser will not tick the animations.
ChangeBrowserVisibility(TopLevelBrowsingContextId, bool),
/// Virtual keyboard was dismissed
IMEDismissed,
}
impl Debug for WindowEvent {
@ -88,89 +113,98 @@ impl Debug for WindowEvent {
match *self {
WindowEvent::Idle => write!(f, "Idle"),
WindowEvent::Refresh => write!(f, "Refresh"),
WindowEvent::InitializeCompositing => write!(f, "InitializeCompositing"),
WindowEvent::Resize(..) => write!(f, "Resize"),
WindowEvent::TouchpadPressure(..) => write!(f, "TouchpadPressure"),
WindowEvent::Viewport(..) => write!(f, "Viewport"),
WindowEvent::KeyEvent(..) => write!(f, "Key"),
WindowEvent::Resize => write!(f, "Resize"),
WindowEvent::Keyboard(..) => write!(f, "Keyboard"),
WindowEvent::AllowNavigationResponse(..) => write!(f, "AllowNavigationResponse"),
WindowEvent::LoadUrl(..) => write!(f, "LoadUrl"),
WindowEvent::MouseWindowEventClass(..) => write!(f, "Mouse"),
WindowEvent::MouseWindowMoveEventClass(..) => write!(f, "MouseMove"),
WindowEvent::Touch(..) => write!(f, "Touch"),
WindowEvent::Wheel(..) => write!(f, "Wheel"),
WindowEvent::Scroll(..) => write!(f, "Scroll"),
WindowEvent::Zoom(..) => write!(f, "Zoom"),
WindowEvent::PinchZoom(..) => write!(f, "PinchZoom"),
WindowEvent::ResetZoom => write!(f, "ResetZoom"),
WindowEvent::Navigation(..) => write!(f, "Navigation"),
WindowEvent::Quit => write!(f, "Quit"),
WindowEvent::Reload => write!(f, "Reload"),
WindowEvent::Reload(..) => write!(f, "Reload"),
WindowEvent::NewBrowser(..) => write!(f, "NewBrowser"),
WindowEvent::SendError(..) => write!(f, "SendError"),
WindowEvent::CloseBrowser(..) => write!(f, "CloseBrowser"),
WindowEvent::SelectBrowser(..) => write!(f, "SelectBrowser"),
WindowEvent::ToggleWebRenderDebug(..) => write!(f, "ToggleWebRenderDebug"),
WindowEvent::CaptureWebRender => write!(f, "CaptureWebRender"),
WindowEvent::ToggleSamplingProfiler(..) => write!(f, "ToggleSamplingProfiler"),
WindowEvent::ExitFullScreen(..) => write!(f, "ExitFullScreen"),
WindowEvent::MediaSessionAction(..) => write!(f, "MediaSessionAction"),
WindowEvent::ChangeBrowserVisibility(..) => write!(f, "ChangeBrowserVisibility"),
WindowEvent::IMEDismissed => write!(f, "IMEDismissed"),
WindowEvent::ClearCache => write!(f, "ClearCache"),
}
}
}
pub trait WindowMethods {
/// Returns the rendering area size in hardware pixels.
fn framebuffer_size(&self) -> TypedSize2D<u32, DevicePixel>;
/// Returns the position and size of the window within the rendering area.
fn window_rect(&self) -> TypedRect<u32, DevicePixel>;
/// Returns the size of the window in density-independent "px" units.
fn size(&self) -> TypedSize2D<f32, DeviceIndependentPixel>;
/// Presents the window to the screen (perhaps by page flipping).
fn present(&self);
/// Return the size of the window with head and borders and position of the window values
fn client_window(&self) -> (Size2D<u32>, Point2D<i32>);
/// Set the size inside of borders and head
fn set_inner_size(&self, size: Size2D<u32>);
/// Set the window position
fn set_position(&self, point: Point2D<i32>);
/// Set fullscreen state
fn set_fullscreen_state(&self, state: bool);
/// Sets the page title for the current page.
fn set_page_title(&self, title: Option<String>);
/// Called when the browser chrome should display a status message.
fn status(&self, Option<String>);
/// Called when the browser has started loading a frame.
fn load_start(&self);
/// Called when the browser is done loading a frame.
fn load_end(&self);
/// Called when the browser encounters an error while loading a URL
fn load_error(&self, code: NetError, url: String);
/// Wether or not to follow a link
fn allow_navigation(&self, url: ServoUrl) -> bool;
/// Called when the <head> tag has finished parsing
fn head_parsed(&self);
/// Called when the history state has changed.
fn history_changed(&self, Vec<LoadData>, usize);
/// Returns the scale factor of the system (device pixels / device independent pixels).
fn hidpi_factor(&self) -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel>;
/// Creates a channel to the compositor. The dummy parameter is needed because we don't have
/// UFCS in Rust yet.
///
/// This is part of the windowing system because its implementation often involves OS-specific
/// magic to wake the up window's event loop.
fn create_compositor_channel(&self) -> (Box<CompositorProxy + Send>, Box<CompositorReceiver>);
/// Requests that the window system prepare a composite. Typically this will involve making
/// some type of platform-specific graphics context current. Returns true if the composite may
/// proceed and false if it should not.
fn prepare_for_composite(&self, width: usize, height: usize) -> bool;
/// Sets the cursor to be used in the window.
fn set_cursor(&self, cursor: Cursor);
/// Process a key event.
fn handle_key(&self, ch: Option<char>, key: Key, mods: KeyModifiers);
/// Does this window support a clipboard
fn supports_clipboard(&self) -> bool;
/// Add a favicon
fn set_favicon(&self, url: ServoUrl);
/// Return the GL function pointer trait.
fn gl(&self) -> Rc<gl::Gl>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AnimationState {
Idle,
Animating,
}
// TODO: this trait assumes that the window is responsible
// for creating the GL context, making it current, buffer
// swapping, etc. Really that should all be done by surfman.
pub trait WindowMethods {
/// Get the coordinates of the native window, the screen and the framebuffer.
fn get_coordinates(&self) -> EmbedderCoordinates;
/// Set whether the application is currently animating.
/// Typically, when animations are active, the window
/// 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);
/// Get the media GL context
fn get_gl_context(&self) -> GlContext;
/// Get the media native display
fn get_native_display(&self) -> NativeDisplay;
/// Get the GL api
fn get_gl_api(&self) -> GlApi;
/// Get the webrender surfman instance
fn webrender_surfman(&self) -> WebrenderSurfman;
}
pub trait EmbedderMethods {
/// Returns a thread-safe object to wake up the window's event loop.
fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker>;
/// Register services with a WebXR Registry.
fn register_webxr(&mut self, _: &mut webxr::MainThreadRegistry, _: EmbedderProxy) {}
/// Returns the user agent string to report in network requests.
fn get_user_agent_string(&self) -> Option<String> {
None
}
}
#[derive(Clone, Copy, Debug)]
pub struct EmbedderCoordinates {
/// The pixel density of the display.
pub hidpi_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
/// Size of the screen.
pub screen: DeviceIntSize,
/// Size of the available screen space (screen without toolbars and docks).
pub screen_avail: DeviceIntSize,
/// Size of the native window.
pub window: (DeviceIntSize, DeviceIntPoint),
/// Size of the GL buffer in the window.
pub framebuffer: DeviceIntSize,
/// Coordinates of the document within the framebuffer.
pub viewport: DeviceIntRect,
}
impl EmbedderCoordinates {
pub fn get_flipped_viewport(&self) -> DeviceIntRect {
let fb_height = self.framebuffer.height;
let mut view = self.viewport.clone();
view.origin.y = fb_height - view.origin.y - view.size.height;
DeviceIntRect::from_untyped(&view.to_untyped())
}
}

View file

@ -3,30 +3,32 @@ name = "servo_config"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[features]
uwp = []
[lib]
name = "servo_config"
path = "lib.rs"
[dependencies]
euclid = "0.11"
embedder_traits = { path = "../embedder_traits" }
euclid = "0.20"
getopts = "0.2.11"
lazy_static = "0.2"
log = "0.3.5"
lazy_static = "1"
log = "0.4"
num_cpus = "1.1.0"
rustc-serialize = "0.3"
serde = {version = "0.9"}
serde_derive = {version = "0.9"}
servo_geometry = {path = "../geometry"}
servo_url = {path = "../url"}
url = "1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
servo_config_plugins = { path = "../config_plugins" }
servo_geometry = { path = "../geometry" }
servo_url = { path = "../url" }
url = "2.0"
[dev-dependencies]
env_logger = "0.4"
std_test_override = { path = "../std_test_override" }
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies]
xdg = "2.0"
[target.'cfg(target_os = "android")'.dependencies]
android_injected_glue = {git = "https://github.com/mmatyas/android-rs-injected-glue"}
[target.'cfg(not(target_os = "android"))'.dependencies]
dirs = "2.0.2"

View file

@ -1,90 +1,48 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Contains routines for retrieving default config directories.
//! For linux based platforms, it uses the XDG base directory spec but provides
//! similar abstractions for non-linux platforms.
#[cfg(target_os = "android")]
use android_injected_glue;
#[cfg(any(target_os = "macos", target_os = "windows"))]
use std::env;
#[cfg(target_os = "android")]
use std::ffi::CStr;
use std::path::PathBuf;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
use xdg;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
#[cfg(all(
unix,
not(target_os = "macos"),
not(target_os = "ios"),
not(target_os = "android")
))]
pub fn default_config_dir() -> Option<PathBuf> {
let xdg_dirs = xdg::BaseDirectories::with_profile("servo", "default").unwrap();
let config_dir = xdg_dirs.get_config_home();
let mut config_dir = ::dirs::config_dir().unwrap();
config_dir.push("servo");
config_dir.push("default");
Some(config_dir)
}
#[cfg(target_os = "android")]
#[allow(unsafe_code)]
pub fn default_config_dir() -> Option<PathBuf> {
let dir = unsafe {
CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath)
};
Some(PathBuf::from(dir.to_str().unwrap()))
}
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn default_data_dir() -> Option<PathBuf> {
let xdg_dirs = xdg::BaseDirectories::with_profile("servo", "default").unwrap();
let data_dir = xdg_dirs.get_data_home();
Some(data_dir)
}
#[cfg(target_os = "android")]
#[allow(unsafe_code)]
pub fn default_data_dir() -> Option<PathBuf> {
let dir = unsafe {
CStr::from_ptr((*android_injected_glue::get_app().activity).internalDataPath)
};
Some(PathBuf::from(dir.to_str().unwrap()))
}
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn default_cache_dir() -> Option<PathBuf> {
let xdg_dirs = xdg::BaseDirectories::with_profile("servo", "default").unwrap();
let cache_dir = xdg_dirs.get_cache_home();
Some(cache_dir)
}
#[cfg(target_os = "android")]
#[allow(unsafe_code)]
pub fn default_cache_dir() -> Option<PathBuf> {
// TODO: Use JNI to call context.getCacheDir().
// There is no equivalent function in NDK/NativeActivity.
let dir = unsafe {
CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath)
};
Some(PathBuf::from(dir.to_str().unwrap()))
None
}
#[cfg(target_os = "macos")]
pub fn default_config_dir() -> Option<PathBuf> {
let mut config_dir = env::home_dir().unwrap();
config_dir.push("Library");
config_dir.push("Application Support");
// FIXME: use `config_dir()` ($HOME/Library/Preferences)
// instead of `data_dir()` ($HOME/Library/Application Support) ?
let mut config_dir = ::dirs::data_dir().unwrap();
config_dir.push("Servo");
Some(config_dir)
}
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", not(feature = "uwp")))]
pub fn default_config_dir() -> Option<PathBuf> {
let mut config_dir = match env::var("APPDATA") {
Ok(appdata_path) => PathBuf::from(appdata_path),
Err(_) => { let mut dir = env::home_dir().unwrap();
dir.push("Appdata");
dir.push("Roaming");
dir
}
};
let mut config_dir = ::dirs::config_dir().unwrap();
config_dir.push("Servo");
Some(config_dir)
}
#[cfg(all(target_os = "windows", feature = "uwp"))]
pub fn default_config_dir() -> Option<PathBuf> {
None
}

View file

@ -1,28 +1,23 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
#[cfg(target_os = "android")]
extern crate android_injected_glue;
extern crate euclid;
extern crate getopts;
#[macro_use] extern crate lazy_static;
#[macro_use] extern crate log;
extern crate num_cpus;
extern crate rustc_serialize;
#[macro_use] extern crate serde_derive;
extern crate servo_geometry;
extern crate servo_url;
extern crate url;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
extern crate xdg;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde;
pub mod pref_util;
#[macro_use]
pub mod prefs;
pub mod basedir;
#[allow(unsafe_code)] pub mod opts;
pub mod prefs;
pub mod resource_files;
#[allow(unsafe_code)]
pub mod opts;
pub fn servo_version() -> String {
let cargo_version = env!("CARGO_PKG_VERSION");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,288 @@
/* 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 serde_json::Value;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use std::sync::{Arc, RwLock};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum PrefValue {
Float(f64),
Int(i64),
Str(String),
Bool(bool),
Missing,
}
impl PrefValue {
pub fn as_str(&self) -> Option<&str> {
if let PrefValue::Str(val) = self {
Some(val)
} else {
None
}
}
pub fn as_i64(&self) -> Option<i64> {
if let PrefValue::Int(val) = self {
Some(*val)
} else {
None
}
}
pub fn as_f64(&self) -> Option<f64> {
if let PrefValue::Float(val) = self {
Some(*val)
} else {
None
}
}
pub fn as_bool(&self) -> Option<bool> {
if let PrefValue::Bool(val) = self {
Some(*val)
} else {
None
}
}
pub fn is_missing(&self) -> bool {
match self {
PrefValue::Missing => true,
_ => false,
}
}
pub fn from_json_value(value: &Value) -> Option<Self> {
match value {
Value::Bool(b) => Some(PrefValue::Bool(*b)),
Value::Number(n) if n.is_i64() => Some(PrefValue::Int(n.as_i64().unwrap())),
Value::Number(n) if n.is_f64() => Some(PrefValue::Float(n.as_f64().unwrap())),
Value::String(s) => Some(PrefValue::Str(s.to_owned())),
_ => None,
}
}
}
impl FromStr for PrefValue {
type Err = PrefError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "false" {
Ok(PrefValue::Bool(false))
} else if s == "true" {
Ok(PrefValue::Bool(true))
} else if let Ok(float) = s.parse::<f64>() {
Ok(PrefValue::Float(float))
} else if let Ok(integer) = s.parse::<i64>() {
Ok(PrefValue::Int(integer))
} else {
Ok(PrefValue::from(s))
}
}
}
macro_rules! impl_pref_from {
($($t: ty => $variant: path,)*) => {
$(
impl From<$t> for PrefValue {
fn from(other: $t) -> Self {
$variant(other.into())
}
}
)+
$(
impl From<Option<$t>> for PrefValue {
fn from(other: Option<$t>) -> Self {
other.map(|val| $variant(val.into())).unwrap_or(PrefValue::Missing)
}
}
)+
}
}
macro_rules! impl_from_pref {
($($variant: path => $t: ty,)*) => {
$(
impl From<PrefValue> for $t {
#[allow(unsafe_code)]
fn from(other: PrefValue) -> Self {
if let $variant(value) = other {
value.into()
} else {
panic!("Cannot convert {:?} to {:?}", other, std::any::type_name::<$t>())
}
}
}
)+
$(
impl From<PrefValue> for Option<$t> {
fn from(other: PrefValue) -> Self {
if let PrefValue::Missing = other {
None
} else {
Some(other.into())
}
}
}
)+
}
}
impl_pref_from! {
f64 => PrefValue::Float,
i64 => PrefValue::Int,
String => PrefValue::Str,
&str => PrefValue::Str,
bool => PrefValue::Bool,
}
impl_from_pref! {
PrefValue::Float => f64,
PrefValue::Int => i64,
PrefValue::Str => String,
PrefValue::Bool => bool,
}
#[derive(Debug)]
pub enum PrefError {
NoSuchPref(String),
InvalidValue(String),
JsonParseErr(serde_json::error::Error),
}
impl fmt::Display for PrefError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PrefError::NoSuchPref(s) | PrefError::InvalidValue(s) => f.write_str(&s),
PrefError::JsonParseErr(e) => e.fmt(f),
}
}
}
impl std::error::Error for PrefError {}
pub struct Accessor<P, V> {
pub getter: Box<dyn Fn(&P) -> V + Sync>,
pub setter: Box<dyn Fn(&mut P, V) + Sync>,
}
impl<P, V> Accessor<P, V> {
pub fn new<G, S>(getter: G, setter: S) -> Self
where
G: Fn(&P) -> V + Sync + 'static,
S: Fn(&mut P, V) + Sync + 'static,
{
Accessor {
getter: Box::new(getter),
setter: Box::new(setter),
}
}
}
pub struct Preferences<'m, P> {
user_prefs: Arc<RwLock<P>>,
default_prefs: P,
accessors: &'m HashMap<String, Accessor<P, PrefValue>>,
}
impl<'m, P: Clone> Preferences<'m, P> {
/// Create a new `Preferences` object. The values provided in `default_prefs` are immutable and
/// can always be restored using `reset` or `reset_all`.
pub fn new(default_prefs: P, accessors: &'m HashMap<String, Accessor<P, PrefValue>>) -> Self {
Self {
user_prefs: Arc::new(RwLock::new(default_prefs.clone())),
default_prefs,
accessors,
}
}
/// Access to the data structure holding the preference values.
pub fn values(&self) -> Arc<RwLock<P>> {
Arc::clone(&self.user_prefs)
}
/// Retrieve a preference using its key
pub fn get(&self, key: &str) -> PrefValue {
if let Some(accessor) = self.accessors.get(key) {
let prefs = self.user_prefs.read().unwrap();
(accessor.getter)(&prefs)
} else {
PrefValue::Missing
}
}
/// Has the preference been modified from its original value?
pub fn is_default(&self, key: &str) -> Result<bool, PrefError> {
if let Some(accessor) = self.accessors.get(key) {
let user = (accessor.getter)(&self.default_prefs);
let default = (accessor.getter)(&self.user_prefs.read().unwrap());
Ok(default == user)
} else {
Err(PrefError::NoSuchPref(String::from(key)))
}
}
/// Creates an iterator over all keys and values
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (String, PrefValue)> + 'a {
let prefs = self.user_prefs.read().unwrap();
self.accessors
.iter()
.map(move |(k, accessor)| (k.clone(), (accessor.getter)(&prefs)))
}
/// Creates an iterator over all keys
pub fn keys<'a>(&'a self) -> impl Iterator<Item = &'a str> + 'a {
self.accessors.keys().map(String::as_str)
}
fn set_inner<V>(&self, key: &str, mut prefs: &mut P, val: V) -> Result<(), PrefError>
where
V: Into<PrefValue>,
{
if let Some(accessor) = self.accessors.get(key) {
Ok((accessor.setter)(&mut prefs, val.into()))
} else {
Err(PrefError::NoSuchPref(String::from(key)))
}
}
/// Set a new value for a preference, using its key.
pub fn set<V>(&self, key: &str, val: V) -> Result<(), PrefError>
where
V: Into<PrefValue>,
{
let mut prefs = self.user_prefs.write().unwrap();
self.set_inner(key, &mut prefs, val)
}
pub fn set_all<M>(&self, values: M) -> Result<(), PrefError>
where
M: IntoIterator<Item = (String, PrefValue)>,
{
let mut prefs = self.user_prefs.write().unwrap();
for (k, v) in values.into_iter() {
self.set_inner(&k, &mut prefs, v)?;
}
Ok(())
}
pub fn reset(&self, key: &str) -> Result<PrefValue, PrefError> {
if let Some(accessor) = self.accessors.get(key) {
let mut prefs = self.user_prefs.write().unwrap();
let old_pref = (accessor.getter)(&prefs);
let default_pref = (accessor.getter)(&self.default_prefs);
(accessor.setter)(&mut prefs, default_pref);
Ok(old_pref)
} else {
Err(PrefError::NoSuchPref(String::from(key)))
}
}
pub fn reset_all(&self) {
*self.user_prefs.write().unwrap() = self.default_prefs.clone();
}
}

View file

@ -1,264 +1,507 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use basedir::default_config_dir;
use opts;
use resource_files::resources_dir_path;
use rustc_serialize::json::{Json, ToJson};
use embedder_traits::resources::{self, Resource};
use serde_json::{self, Value};
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Write, stderr};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use crate::pref_util::Preferences;
pub use crate::pref_util::{PrefError, PrefValue};
use gen::Prefs;
lazy_static! {
pub static ref PREFS: Preferences = {
let prefs = read_prefs().ok().unwrap_or_else(HashMap::new);
Preferences(Arc::new(RwLock::new(prefs)))
static ref PREFS: Preferences<'static, Prefs> = {
let def_prefs: Prefs = serde_json::from_str(&resources::read_string(Resource::Preferences))
.expect("Failed to initialize config preferences.");
Preferences::new(def_prefs, &gen::PREF_ACCESSORS)
};
}
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub enum PrefValue {
Boolean(bool),
String(String),
Number(f64),
Missing
/// A convenience macro for accessing a preference value using its static path.
/// Passing an invalid path is a compile-time error.
#[macro_export]
macro_rules! pref {
($($segment: ident).+) => {{
let values = $crate::prefs::pref_map().values();
let lock = values.read()
.map(|prefs| prefs $(.$segment)+.clone());
lock.unwrap()
}};
}
impl PrefValue {
pub fn from_json(data: Json) -> Result<PrefValue, ()> {
let value = match data {
Json::Boolean(x) => PrefValue::Boolean(x),
Json::String(x) => PrefValue::String(x),
Json::F64(x) => PrefValue::Number(x),
Json::I64(x) => PrefValue::Number(x as f64),
Json::U64(x) => PrefValue::Number(x as f64),
_ => return Err(())
};
Ok(value)
}
/// A convenience macro for updating a preference value using its static path.
/// Passing an invalid path is a compile-time error.
#[macro_export]
macro_rules! set_pref {
($($segment: ident).+, $value: expr) => {{
let values = $crate::prefs::pref_map().values();
let mut lock = values.write().unwrap();
lock$ (.$segment)+ = $value;
}};
}
pub fn as_boolean(&self) -> Option<bool> {
match *self {
PrefValue::Boolean(value) => {
Some(value)
},
_ => None
}
}
/// Access preferences using their `String` keys. Note that the key may be different from the
/// static path because legacy keys contain hyphens, or because a preference name has been renamed.
///
/// When retrieving a preference, the value will always be a `PrefValue`. When setting a value, it
/// may be a `PrefValue` or the type that converts into the correct underlying value; one of `bool`,
/// `i64`, `f64` or `String`.
#[inline]
pub fn pref_map() -> &'static Preferences<'static, Prefs> {
&PREFS
}
pub fn as_string(&self) -> Option<&str> {
match *self {
PrefValue::String(ref value) => {
Some(&value)
},
_ => None
}
}
pub fn as_i64(&self) -> Option<i64> {
match *self {
PrefValue::Number(x) => Some(x as i64),
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match *self {
PrefValue::Number(x) => Some(x as u64),
_ => None,
}
pub fn add_user_prefs(prefs: HashMap<String, PrefValue>) {
if let Err(error) = PREFS.set_all(prefs.into_iter()) {
panic!("Error setting preference: {:?}", error);
}
}
impl ToJson for PrefValue {
fn to_json(&self) -> Json {
match *self {
PrefValue::Boolean(x) => {
Json::Boolean(x)
},
PrefValue::String(ref x) => {
Json::String(x.clone())
},
PrefValue::Number(x) => {
Json::F64(x)
},
PrefValue::Missing => Json::Null
}
}
pub fn read_prefs_map(txt: &str) -> Result<HashMap<String, PrefValue>, PrefError> {
let prefs: HashMap<String, Value> =
serde_json::from_str(txt).map_err(|e| PrefError::JsonParseErr(e))?;
prefs
.into_iter()
.map(|(k, pref_value)| {
Ok({
let v = match &pref_value {
Value::Bool(b) => PrefValue::Bool(*b),
Value::Number(n) if n.is_i64() => PrefValue::Int(n.as_i64().unwrap()),
Value::Number(n) if n.is_f64() => PrefValue::Float(n.as_f64().unwrap()),
Value::String(s) => PrefValue::Str(s.to_owned()),
_ => {
return Err(PrefError::InvalidValue(format!(
"Invalid value: {}",
pref_value
)));
},
};
(k.to_owned(), v)
})
})
.collect()
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Pref {
NoDefault(Arc<PrefValue>),
WithDefault(Arc<PrefValue>, Option<Arc<PrefValue>>)
}
mod gen {
use servo_config_plugins::build_structs;
impl Pref {
pub fn new(value: PrefValue) -> Pref {
Pref::NoDefault(Arc::new(value))
// The number of layout threads is calculated if it is not present in `prefs.json`.
fn default_layout_threads() -> i64 {
std::cmp::max(num_cpus::get() * 3 / 4, 1) as i64
}
fn new_default(value: PrefValue) -> Pref {
Pref::WithDefault(Arc::new(value), None)
fn black() -> i64 {
0x000000
}
fn from_json(data: Json) -> Result<Pref, ()> {
let value = try!(PrefValue::from_json(data));
Ok(Pref::new_default(value))
fn white() -> i64 {
0xFFFFFF
}
pub fn value(&self) -> &Arc<PrefValue> {
match *self {
Pref::NoDefault(ref x) => x,
Pref::WithDefault(ref default, ref override_value) => {
match *override_value {
Some(ref x) => x,
None => default
build_structs! {
// type of the accessors
accessor_type = crate::pref_util::Accessor::<Prefs, crate::pref_util::PrefValue>,
// name of the constant, which will hold a HashMap of preference accessors
gen_accessors = PREF_ACCESSORS,
// tree of structs to generate
gen_types = Prefs {
browser: {
display: {
#[serde(default = "white")]
background_color: i64,
#[serde(default = "black")]
foreground_color: i64,
}
}
}
}
fn set(&mut self, value: PrefValue) {
// TODO - this should error if we try to override a pref of one type
// with a value of a different type
match *self {
Pref::NoDefault(ref mut pref_value) => {
*pref_value = Arc::new(value)
},
Pref::WithDefault(_, ref mut override_value) => {
*override_value = Some(Arc::new(value))
}
}
}
}
impl ToJson for Pref {
fn to_json(&self) -> Json {
self.value().to_json()
}
}
pub fn read_prefs_from_file<T>(mut file: T)
-> Result<HashMap<String, Pref>, ()> where T: Read {
let json = try!(Json::from_reader(&mut file).or_else(|e| {
println!("Ignoring invalid JSON in preferences: {:?}.", e);
Err(())
}));
let mut prefs = HashMap::new();
if let Json::Object(obj) = json {
for (name, value) in obj.into_iter() {
match Pref::from_json(value) {
Ok(x) => {
prefs.insert(name, x);
css: {
animations: {
testing: {
#[serde(default)]
enabled: bool,
},
},
Err(_) => println!("Ignoring non-boolean/string/i64 preference value for {:?}", name),
}
}
}
Ok(prefs)
}
pub fn add_user_prefs() {
match opts::get().config_dir {
Some(ref config_path) => {
let mut path = PathBuf::from(config_path);
init_user_prefs(&mut path);
}
None => {
let mut path = default_config_dir().unwrap();
if path.join("prefs.json").exists() {
init_user_prefs(&mut path);
}
}
}
}
fn init_user_prefs(path: &mut PathBuf) {
path.push("prefs.json");
if let Ok(file) = File::open(path) {
if let Ok(prefs) = read_prefs_from_file(file) {
PREFS.extend(prefs);
}
} else {
writeln!(&mut stderr(), "Error opening prefs.json from config directory")
.expect("failed printing to stderr");
}
}
fn read_prefs() -> Result<HashMap<String, Pref>, ()> {
let mut path = try!(resources_dir_path().map_err(|_| ()));
path.push("prefs.json");
let file = try!(File::open(path).or_else(|e| {
writeln!(&mut stderr(), "Error opening preferences: {:?}.", e)
.expect("failed printing to stderr");
Err(())
}));
read_prefs_from_file(file)
}
pub struct Preferences(Arc<RwLock<HashMap<String, Pref>>>);
impl Preferences {
pub fn get(&self, name: &str) -> Arc<PrefValue> {
self.0.read().unwrap().get(name).map_or(Arc::new(PrefValue::Missing), |x| x.value().clone())
}
pub fn cloned(&self) -> HashMap<String, Pref> {
self.0.read().unwrap().clone()
}
pub fn is_mozbrowser_enabled(&self) -> bool {
self.get("dom.mozbrowser.enabled").as_boolean().unwrap_or(false)
}
pub fn set(&self, name: &str, value: PrefValue) {
let mut prefs = self.0.write().unwrap();
if let Some(pref) = prefs.get_mut(name) {
pref.set(value);
return;
}
prefs.insert(name.to_owned(), Pref::new(value));
}
pub fn reset(&self, name: &str) -> Arc<PrefValue> {
let mut prefs = self.0.write().unwrap();
let result = match prefs.get_mut(name) {
None => return Arc::new(PrefValue::Missing),
Some(&mut Pref::NoDefault(_)) => Arc::new(PrefValue::Missing),
Some(&mut Pref::WithDefault(ref default, ref mut set_value)) => {
*set_value = None;
default.clone()
},
};
if *result == PrefValue::Missing {
prefs.remove(name);
devtools: {
server: {
enabled: bool,
port: i64,
},
},
dom: {
webgpu: {
enabled: bool,
},
bluetooth: {
enabled: bool,
testing: {
enabled: bool,
}
},
canvas_capture: {
enabled: bool,
},
canvas_text: {
enabled: bool,
},
composition_event: {
#[serde(rename = "dom.compositionevent.enabled")]
enabled: bool,
},
custom_elements: {
#[serde(rename = "dom.customelements.enabled")]
enabled: bool,
},
document: {
dblclick_timeout: i64,
dblclick_dist: i64,
},
forcetouch: {
enabled: bool,
},
fullscreen: {
test: bool,
},
gamepad: {
enabled: bool,
},
imagebitmap: {
enabled: bool,
},
microdata: {
testing: {
enabled: bool,
}
},
mouse_event: {
which: {
#[serde(rename = "dom.mouseevent.which.enabled")]
enabled: bool,
}
},
mutation_observer: {
enabled: bool,
},
offscreen_canvas: {
enabled: bool,
},
permissions: {
enabled: bool,
testing: {
allowed_in_nonsecure_contexts: bool,
}
},
script: {
asynch: bool,
},
serviceworker: {
enabled: bool,
timeout_seconds: i64,
},
servo_helpers: {
enabled: bool,
},
servoparser: {
async_html_tokenizer: {
enabled: bool,
}
},
shadowdom: {
enabled: bool,
},
svg: {
enabled: bool,
},
testable_crash: {
enabled: bool,
},
testbinding: {
enabled: bool,
prefcontrolled: {
#[serde(default)]
enabled: bool,
},
prefcontrolled2: {
#[serde(default)]
enabled: bool,
},
preference_value: {
#[serde(default)]
falsy: bool,
#[serde(default)]
quote_string_test: String,
#[serde(default)]
space_string_test: String,
#[serde(default)]
string_empty: String,
#[serde(default)]
string_test: String,
#[serde(default)]
truthy: bool,
},
},
testing: {
element: {
activation: {
#[serde(default)]
enabled: bool,
}
},
html_input_element: {
select_files: {
#[serde(rename = "dom.testing.htmlinputelement.select_files.enabled")]
enabled: bool,
}
},
},
testperf: {
#[serde(default)]
enabled: bool,
},
webgl: {
dom_to_texture: {
enabled: bool,
}
},
webgl2: {
enabled: bool,
},
webrtc: {
transceiver: {
enabled: bool,
},
#[serde(default)]
enabled: bool,
},
webvtt: {
enabled: bool,
},
webxr: {
#[serde(default)]
enabled: bool,
#[serde(default)]
test: bool,
first_person_observer_view: bool,
glwindow: {
#[serde(default)]
enabled: bool,
#[serde(rename = "dom.webxr.glwindow.left-right")]
left_right: bool,
#[serde(rename = "dom.webxr.glwindow.red-cyan")]
red_cyan: bool,
spherical: bool,
cubemap: bool,
},
hands: {
#[serde(default)]
enabled: bool,
},
layers: {
enabled: bool,
},
sessionavailable: bool,
#[serde(rename = "dom.webxr.unsafe-assume-user-intent")]
unsafe_assume_user_intent: bool,
},
worklet: {
blockingsleep: {
#[serde(default)]
enabled: bool,
},
#[serde(default)]
enabled: bool,
testing: {
#[serde(default)]
enabled: bool,
},
timeout_ms: i64,
},
},
gfx: {
subpixel_text_antialiasing: {
#[serde(rename = "gfx.subpixel-text-antialiasing.enabled")]
enabled: bool,
},
texture_swizzling: {
#[serde(rename = "gfx.texture-swizzling.enabled")]
enabled: bool,
},
},
js: {
asmjs: {
enabled: bool,
},
asyncstack: {
enabled: bool,
},
baseline: {
enabled: bool,
unsafe_eager_compilation: {
enabled: bool,
},
},
discard_system_source: {
enabled: bool,
},
dump_stack_on_debuggee_would_run: {
enabled: bool,
},
ion: {
enabled: bool,
offthread_compilation: {
enabled: bool,
},
unsafe_eager_compilation: {
enabled: bool,
},
},
mem: {
gc: {
allocation_threshold_mb: i64,
allocation_threshold_factor: i64,
allocation_threshold_avoid_interrupt_factor: i64,
compacting: {
enabled: bool,
},
decommit_threshold_mb: i64,
dynamic_heap_growth: {
enabled: bool,
},
dynamic_mark_slice: {
enabled: bool,
},
empty_chunk_count_max: i64,
empty_chunk_count_min: i64,
high_frequency_heap_growth_max: i64,
high_frequency_heap_growth_min: i64,
high_frequency_high_limit_mb: i64,
high_frequency_low_limit_mb: i64,
high_frequency_time_limit_ms: i64,
incremental: {
enabled: bool,
slice_ms: i64,
},
low_frequency_heap_growth: i64,
per_zone: {
enabled: bool,
},
zeal: {
frequency: i64,
level: i64,
},
},
max: i64,
},
native_regex: {
enabled: bool,
},
offthread_compilation: {
enabled: bool,
},
parallel_parsing: {
enabled: bool,
},
shared_memory: {
enabled: bool,
},
strict: {
debug: {
enabled: bool,
},
enabled: bool,
},
throw_on_asmjs_validation_failure: {
enabled: bool,
},
throw_on_debuggee_would_run: {
enabled: bool,
},
timers: {
minimum_duration: i64,
},
wasm: {
baseline: {
enabled: bool,
},
enabled: bool,
ion: {
enabled: bool,
}
},
werror: {
enabled: bool,
},
},
layout: {
animations: {
test: {
enabled: bool,
}
},
columns: {
enabled: bool,
},
flexbox: {
enabled: bool,
},
#[serde(default = "default_layout_threads")]
threads: i64,
viewport: {
enabled: bool,
},
writing_mode: {
#[serde(rename = "layout.writing-mode.enabled")]
enabled: bool,
}
},
media: {
glvideo: {
enabled: bool,
},
testing: {
enabled: bool,
}
},
network: {
enforce_tls: {
enabled: bool,
localhost: bool,
onion: bool,
},
http_cache: {
#[serde(rename = "network.http-cache.disabled")]
disabled: bool,
},
mime: {
sniff: bool,
}
},
session_history: {
#[serde(rename = "session-history.max-length")]
max_length: i64,
},
shell: {
crash_reporter: {
enabled: bool,
},
homepage: String,
keep_screen_on: {
enabled: bool,
},
#[serde(rename = "shell.native-orientation")]
native_orientation: String,
native_titlebar: {
#[serde(rename = "shell.native-titlebar.enabled")]
enabled: bool,
},
searchpage: String,
},
webgl: {
testing: {
context_creation_error: bool,
}
},
}
result
}
pub fn reset_all(&self) {
let names = {
self.0.read().unwrap().keys().cloned().collect::<Vec<String>>()
};
for name in names.iter() {
self.reset(name);
}
}
pub fn extend(&self, extension: HashMap<String, Pref>) {
self.0.write().unwrap().extend(extension);
}
pub fn is_webvr_enabled(&self) -> bool {
self.get("dom.webvr.enabled").as_boolean().unwrap_or(false)
}
}

View file

@ -1,75 +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 http://mozilla.org/MPL/2.0/. */
#[cfg(target_os = "android")]
use android_injected_glue;
#[cfg(not(target_os = "android"))]
use std::env;
#[cfg(target_os = "android")]
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
lazy_static! {
static ref CMD_RESOURCE_DIR: Arc<Mutex<Option<String>>> = {
Arc::new(Mutex::new(None))
};
}
pub fn set_resources_path(path: Option<String>) {
let mut dir = CMD_RESOURCE_DIR.lock().unwrap();
*dir = path;
}
#[cfg(target_os = "android")]
#[allow(unsafe_code)]
pub fn resources_dir_path() -> io::Result<PathBuf> {
let dir = unsafe {
CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath)
};
Ok(PathBuf::from(dir.to_str().unwrap()))
}
#[cfg(not(target_os = "android"))]
pub fn resources_dir_path() -> io::Result<PathBuf> {
let mut dir = CMD_RESOURCE_DIR.lock().unwrap();
if let Some(ref path) = *dir {
return Ok(PathBuf::from(path));
}
// FIXME: Find a way to not rely on the executable being
// under `<servo source>[/$target_triple]/target/debug`
// or `<servo source>[/$target_triple]/target/release`.
let mut path = try!(env::current_exe());
// Follow symlink
path = try!(path.canonicalize());
while path.pop() {
path.push("resources");
if path.is_dir() {
break;
}
path.pop();
// Check for Resources on mac when using a case sensitive filesystem.
path.push("Resources");
if path.is_dir() {
break;
}
path.pop();
}
*dir = Some(path.to_str().unwrap().to_owned());
Ok(path)
}
pub fn read_resource_file<P: AsRef<Path>>(relative_path: P) -> io::Result<Vec<u8>> {
let mut path = try!(resources_dir_path());
path.push(relative_path);
let mut file = try!(File::open(&path));
let mut data = Vec::new();
try!(file.read_to_end(&mut data));
Ok(data)
}

View file

@ -0,0 +1,86 @@
/* 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 servo_config::opts::parse_url_or_filename;
use std::path::Path;
#[cfg(not(target_os = "windows"))]
const FAKE_CWD: &'static str = "/fake/cwd";
#[cfg(target_os = "windows")]
const FAKE_CWD: &'static str = "C:/fake/cwd";
#[test]
fn test_argument_parsing() {
let fake_cwd = Path::new(FAKE_CWD);
assert!(parse_url_or_filename(fake_cwd, "http://example.net:invalid").is_err());
let url = parse_url_or_filename(fake_cwd, "http://example.net").unwrap();
assert_eq!(url.scheme(), "http");
let url = parse_url_or_filename(fake_cwd, "file:///foo/bar.html").unwrap();
assert_eq!(url.scheme(), "file");
assert_eq!(
url.path_segments().unwrap().collect::<Vec<_>>(),
["foo", "bar.html"]
);
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_file_path_parsing() {
let fake_cwd = Path::new(FAKE_CWD);
let url = parse_url_or_filename(fake_cwd, "bar.html").unwrap();
assert_eq!(url.scheme(), "file");
assert_eq!(
url.path_segments().unwrap().collect::<Vec<_>>(),
["fake", "cwd", "bar.html"]
);
}
#[test]
#[cfg(target_os = "windows")]
fn test_file_path_parsing() {
let fake_cwd = Path::new(FAKE_CWD);
let url = parse_url_or_filename(fake_cwd, "bar.html").unwrap();
assert_eq!(url.scheme(), "file");
assert_eq!(
url.path_segments().unwrap().collect::<Vec<_>>(),
["C:", "fake", "cwd", "bar.html"]
);
}
#[test]
#[cfg(not(target_os = "windows"))]
// Windows file paths can't contain ?
fn test_argument_parsing_special() {
let fake_cwd = Path::new(FAKE_CWD);
// '?' and '#' have a special meaning in URLs...
let url = parse_url_or_filename(fake_cwd, "file:///foo/bar?baz#buzz.html").unwrap();
assert_eq!(&*url.to_file_path().unwrap(), Path::new("/foo/bar"));
assert_eq!(url.scheme(), "file");
assert_eq!(
url.path_segments().unwrap().collect::<Vec<_>>(),
["foo", "bar"]
);
assert_eq!(url.query(), Some("baz"));
assert_eq!(url.fragment(), Some("buzz.html"));
// but not in file names.
let url = parse_url_or_filename(fake_cwd, "./bar?baz#buzz.html").unwrap();
assert_eq!(
&*url.to_file_path().unwrap(),
Path::new("/fake/cwd/bar?baz#buzz.html")
);
assert_eq!(url.scheme(), "file");
assert_eq!(
url.path_segments().unwrap().collect::<Vec<_>>(),
["fake", "cwd", "bar%3Fbaz%23buzz.html"]
);
assert_eq!(url.query(), None);
assert_eq!(url.fragment(), None);
}

View file

@ -0,0 +1,334 @@
/* 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/. */
#[macro_use]
extern crate serde;
use servo_config::basedir;
use servo_config::pref_util::Preferences;
use servo_config::prefs::{read_prefs_map, PrefValue};
use std::collections::HashMap;
use std::error::Error;
use std::fs::{self, File};
use std::io::{Read, Write};
#[test]
fn test_create_prefs_map() {
let json_str = "{
\"layout.writing-mode.enabled\": true,
\"network.mime.sniff\": false,
\"shell.homepage\": \"https://servo.org\"
}";
let prefs = read_prefs_map(json_str);
assert!(prefs.is_ok());
let prefs = prefs.unwrap();
assert_eq!(prefs.len(), 3);
}
#[test]
fn test_generated_accessors_get() {
let prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR).unwrap();
let map: HashMap<String, PrefValue> = gen::TEST_PREF_ACCESSORS
.iter()
.map(move |(key, accessor)| {
let pref_value = (accessor.getter)(&prefs);
(key.clone(), pref_value)
})
.collect();
assert_eq!(&PrefValue::from("hello"), map.get("pref_string").unwrap());
assert_eq!(&PrefValue::from(23_i64), map.get("pref_i64").unwrap());
assert_eq!(&PrefValue::from(1.5_f64), map.get("pref_f64").unwrap());
assert_eq!(&PrefValue::from(true), map.get("pref_bool").unwrap());
assert_eq!(
&PrefValue::from(333_i64),
map.get("group.nested.nested_i64").unwrap()
);
assert_eq!(&PrefValue::from(42_i64), map.get("a.renamed.pref").unwrap());
}
#[test]
fn test_generated_accessors_set() {
let mut prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR).unwrap();
let setters: HashMap<String, _> = gen::TEST_PREF_ACCESSORS
.iter()
.map(|(key, accessor)| (key.clone(), &accessor.setter))
.collect();
(setters.get("pref_string").unwrap())(&mut prefs, PrefValue::Str(String::from("boo")));
(setters.get("pref_i64").unwrap())(&mut prefs, PrefValue::Int(-25));
(setters.get("pref_f64").unwrap())(&mut prefs, PrefValue::Float(-1.9));
(setters.get("pref_bool").unwrap())(&mut prefs, PrefValue::Bool(false));
(setters.get("group.nested.nested_i64").unwrap())(&mut prefs, PrefValue::Int(10));
(setters.get("a.renamed.pref").unwrap())(&mut prefs, PrefValue::Int(11));
assert_eq!("boo", prefs.pref_string);
assert_eq!(-25, prefs.pref_i64);
assert_eq!(-1.9, prefs.pref_f64);
assert_eq!(false, prefs.pref_bool);
assert_eq!(10, prefs.group.nested.nested_i64);
assert_eq!(11, prefs.group.nested.renamed);
}
#[test]
fn test_static_struct() {
let prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR).unwrap();
assert_eq!("hello", prefs.pref_string);
assert_eq!(23, prefs.pref_i64);
assert_eq!(1.5, prefs.pref_f64);
assert_eq!(true, prefs.pref_bool);
assert_eq!(333, prefs.group.nested.nested_i64);
assert_eq!(42, prefs.group.nested.renamed);
}
#[test]
fn test_set_pref() {
let prefs = Preferences::new(gen::TestPrefs::default(), &gen::TEST_PREF_ACCESSORS);
assert_eq!(Some(0), prefs.get("group.nested.nested_i64").as_i64());
let result = prefs.set("group.nested.nested_i64", 1);
assert_eq!(true, result.is_ok());
assert_eq!(Some(1), prefs.get("group.nested.nested_i64").as_i64());
assert_eq!(1, prefs.values().read().unwrap().group.nested.nested_i64);
}
#[test]
fn test_set_unknown_pref_is_err() -> Result<(), Box<dyn Error>> {
let prefs = Preferences::new(gen::TestPrefs::default(), &gen::TEST_PREF_ACCESSORS);
let result = prefs.set("unknown_pref", 1);
assert_eq!(true, result.is_err());
Ok(())
}
#[test]
fn test_reset_pref() -> Result<(), Box<dyn Error>> {
let mut def_prefs = gen::TestPrefs::default();
def_prefs.group.nested.nested_i64 = 999;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
assert_eq!(Some(999), prefs.get("group.nested.nested_i64").as_i64());
prefs.set("group.nested.nested_i64", 1)?;
assert_eq!(Some(1), prefs.get("group.nested.nested_i64").as_i64());
prefs.reset("group.nested.nested_i64")?;
assert_eq!(Some(999), prefs.get("group.nested.nested_i64").as_i64());
assert_eq!(999, prefs.values().read().unwrap().group.nested.nested_i64);
Ok(())
}
#[test]
fn test_default_values() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
assert_eq!(Some(0), prefs.get("default_value").as_i64());
assert_eq!(Some(555), prefs.get("computed_default_value").as_i64());
Ok(())
}
#[test]
fn test_override_default_values() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(WITHOUT_DEFAULTS_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
assert_eq!(Some(-1), prefs.get("default_value").as_i64());
assert_eq!(Some(-1), prefs.get("computed_default_value").as_i64());
Ok(())
}
#[test]
fn test_update_reset_default_values() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
prefs.set("default_value", 99)?;
prefs.set("computed_default_value", 199)?;
assert_eq!(Some(99), prefs.get("default_value").as_i64());
assert_eq!(Some(199), prefs.get("computed_default_value").as_i64());
prefs.reset("default_value")?;
prefs.reset("computed_default_value")?;
assert_eq!(Some(0), prefs.get("default_value").as_i64());
assert_eq!(Some(555), prefs.get("computed_default_value").as_i64());
Ok(())
}
#[test]
fn test_update_reset_overridden_default_values() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(WITHOUT_DEFAULTS_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
prefs.set("default_value", 99)?;
prefs.set("computed_default_value", 199)?;
assert_eq!(Some(99), prefs.get("default_value").as_i64());
assert_eq!(Some(199), prefs.get("computed_default_value").as_i64());
prefs.reset("default_value")?;
prefs.reset("computed_default_value")?;
assert_eq!(Some(-1), prefs.get("default_value").as_i64());
assert_eq!(Some(-1), prefs.get("computed_default_value").as_i64());
Ok(())
}
#[test]
fn test_user_prefs_override_and_reset() -> Result<(), Box<dyn Error>> {
let mut def_prefs = gen::TestPrefs::default();
def_prefs.group.nested.nested_i64 = 999;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
prefs.set("group.nested.nested_i64", 45)?;
assert_eq!(Some(45), prefs.get("group.nested.nested_i64").as_i64());
prefs.reset("group.nested.nested_i64")?;
assert_eq!(Some(999), prefs.get("group.nested.nested_i64").as_i64());
Ok(())
}
#[test]
fn test_reset_all() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
prefs.set_all(read_prefs_map(USER_JSON_STR)?)?;
let values = prefs.values();
assert_eq!("bye", values.read().unwrap().pref_string);
assert_eq!(-1, values.read().unwrap().pref_i64);
assert_eq!(-1.0, values.read().unwrap().pref_f64);
assert_eq!(false, values.read().unwrap().pref_bool);
assert_eq!(-1, values.read().unwrap().group.nested.nested_i64);
assert_eq!(-1, values.read().unwrap().group.nested.renamed);
prefs.reset_all();
let values = prefs.values();
assert_eq!("hello", values.read().unwrap().pref_string);
assert_eq!(23, values.read().unwrap().pref_i64);
assert_eq!(1.5, values.read().unwrap().pref_f64);
assert_eq!(true, values.read().unwrap().pref_bool);
assert_eq!(333, values.read().unwrap().group.nested.nested_i64);
assert_eq!(42, values.read().unwrap().group.nested.renamed);
Ok(())
}
#[test]
fn test_set_all_from_map() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
prefs.set_all(read_prefs_map(USER_JSON_STR)?)?;
let mut overrides = HashMap::new();
overrides.insert(String::from("pref_string"), PrefValue::from("new value"));
overrides.insert(
String::from("group.nested.nested_i64"),
PrefValue::from(1001),
);
overrides.insert(String::from("a.renamed.pref"), PrefValue::from(47));
let result = prefs.set_all(overrides.into_iter());
assert_eq!(true, result.is_ok());
let values = prefs.values();
assert_eq!("new value", values.read().unwrap().pref_string);
assert_eq!(1001, values.read().unwrap().group.nested.nested_i64);
assert_eq!(47, values.read().unwrap().group.nested.renamed);
Ok(())
}
#[test]
fn test_set_all_error_on_unknown_field() -> Result<(), Box<dyn Error>> {
let def_prefs: gen::TestPrefs = serde_json::from_str(DEF_JSON_STR)?;
let prefs = Preferences::new(def_prefs, &gen::TEST_PREF_ACCESSORS);
let mut overrides = HashMap::new();
overrides.insert(String::from("doesnt_exist"), PrefValue::from(1001));
let result = prefs.set_all(overrides.into_iter());
assert_eq!(true, result.is_err());
Ok(())
}
#[cfg(not(any(target_os = "android", feature = "uwp")))]
#[test]
fn test_default_config_dir_create_read_write() {
let json_str = "{\
\"layout.writing-mode.enabled\": true,\
\"extra.stuff\": false,\
\"shell.homepage\": \"https://google.com\"\
}";
let mut expected_json = String::new();
let config_path = basedir::default_config_dir().unwrap();
if !config_path.exists() {
fs::create_dir_all(&config_path).unwrap();
}
let json_path = config_path.join("test_config.json");
let mut fd = File::create(&json_path).unwrap();
assert_eq!(json_path.exists(), true);
fd.write_all(json_str.as_bytes()).unwrap();
let mut fd = File::open(&json_path).unwrap();
fd.read_to_string(&mut expected_json).unwrap();
assert_eq!(json_str, expected_json);
fs::remove_file(&json_path).unwrap();
}
static DEF_JSON_STR: &'static str = r#"{
"pref_string": "hello",
"pref_i64": 23,
"pref_f64": 1.5,
"pref_bool": true,
"group.nested.nested_i64": 333,
"a.renamed.pref": 42
}"#;
static USER_JSON_STR: &'static str = r#"{
"pref_string": "bye",
"pref_i64": -1,
"pref_f64": -1.0,
"pref_bool": false,
"group.nested.nested_i64": -1,
"a.renamed.pref": -1
}"#;
static WITHOUT_DEFAULTS_JSON_STR: &'static str = r#"{
"pref_string": "bye",
"pref_i64": -1,
"pref_f64": -1.0,
"pref_bool": false,
"group.nested.nested_i64": -1,
"a.renamed.pref": -1,
"computed_default_value": -1,
"default_value": -1
}"#;
mod gen {
use servo_config::pref_util::{Accessor, PrefValue};
use servo_config_plugins::build_structs;
fn compute_default() -> i64 {
555
}
build_structs! {
accessor_type = Accessor::<TestPrefs, PrefValue>,
gen_accessors = TEST_PREF_ACCESSORS,
gen_types = TestPrefs {
pref_string: String,
pref_i64: i64,
pref_f64: f64,
pref_bool: bool,
#[serde(default)]
default_value: i64,
#[serde(default = "compute_default")]
computed_default_value: i64,
group: {
nested: {
nested_i64: i64,
#[serde(rename = "a.renamed.pref")]
renamed: i64,
}
}
}
}
}

View file

@ -0,0 +1,18 @@
[package]
name = "servo_config_plugins"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
name = "servo_config_plugins"
proc-macro = true
path = "lib.rs"
[dependencies]
itertools = "0.8"
proc-macro2 = "1"
quote = "1"
syn = { version = "1", default-features = false, features = ["clone-impls", "parsing"] }

View file

@ -0,0 +1,219 @@
/* 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/. */
#![feature(proc_macro_diagnostic)]
use itertools::Itertools;
use proc_macro2::{Span, TokenStream};
use quote::*;
use std::collections::{hash_map, HashMap};
use std::{fmt::Write, iter};
use syn::{
parse::Result, parse_macro_input, spanned::Spanned, Attribute, Ident, Lit, LitStr, Meta,
MetaList, MetaNameValue, NestedMeta, Path,
};
mod parse;
use parse::*;
#[proc_macro]
pub fn build_structs(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: MacroInput = parse_macro_input!(tokens);
let out = Build::new(&input)
.build(&input.type_def)
.unwrap_or_else(|e| {
proc_macro::Diagnostic::spanned(
e.span().unwrap(),
proc_macro::Level::Error,
format!("{}", e),
)
.emit();
TokenStream::new()
});
out.into()
}
struct Build {
root_type_name: Ident,
gen_accessors: Ident,
accessor_type: Path,
output: TokenStream,
path_stack: Vec<Ident>,
path_map: HashMap<String, Vec<Ident>>,
}
impl Build {
fn new(input: &MacroInput) -> Self {
Build {
root_type_name: input.type_def.type_name.clone(),
gen_accessors: input.gen_accessors.clone(),
accessor_type: input.accessor_type.clone(),
output: TokenStream::new(),
path_stack: Vec::new(),
path_map: HashMap::new(),
}
}
fn build(mut self, type_def: &RootTypeDef) -> Result<TokenStream> {
self.walk(&type_def.type_def)?;
self.build_accessors();
Ok(self.output)
}
fn walk(&mut self, type_def: &NewTypeDef) -> Result<()> {
self.define_pref_struct(type_def)?;
for field in type_def.fields.iter() {
self.path_stack.push(field.name.clone());
if let FieldType::NewTypeDef(new_def) = &field.field_type {
self.walk(&new_def)?;
} else {
let pref_name =
self.pref_name(field, &self.path_stack[..self.path_stack.len() - 1]);
if let hash_map::Entry::Vacant(slot) = self.path_map.entry(pref_name) {
slot.insert(self.path_stack.clone());
} else {
return Err(err(&field.name, "duplicate preference name"));
}
}
self.path_stack.pop();
}
Ok(())
}
fn define_pref_struct(&mut self, type_def: &NewTypeDef) -> Result<()> {
let struct_name = self.path_to_name(self.path_stack.iter());
let field_defs = type_def
.fields
.iter()
.map(|field| self.field_to_tokens(field, &self.path_stack))
.collect::<Result<Vec<_>>>()?;
self.output.extend(quote! {
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct #struct_name {
#(#field_defs), *
}
});
Ok(())
}
fn build_accessors(&mut self) {
let accessor_type = &self.accessor_type;
let values = self.path_map.iter().map(|(key, path)| {
quote! {
map.insert(String::from(#key),
#accessor_type::new(
|prefs| prefs #(.#path)*.clone().into(),
|prefs, value| prefs #(.#path)* = value.into()
)
);
}
});
let gen_accessors = &self.gen_accessors;
let num_prefs = self.path_map.len();
self.output.extend(quote! {
lazy_static::lazy_static! {
pub static ref #gen_accessors: std::collections::HashMap<String, #accessor_type> = {
let mut map = std::collections::HashMap::with_capacity(#num_prefs);
#(#values)*
map
};
}
});
}
fn pref_name(&self, field: &Field, path_stack: &[Ident]) -> String {
field
.get_field_name_mapping()
.map(|pref_attr| pref_attr.value())
.unwrap_or_else(|| {
Itertools::intersperse(
path_stack
.iter()
.chain(iter::once(&field.name))
.map(Ident::to_string),
String::from("."),
)
.collect()
})
}
fn field_to_tokens(&self, field: &Field, path_stack: &[Ident]) -> Result<TokenStream> {
let name = &field.name;
Ok(match &field.field_type {
FieldType::NewTypeDef(_) => {
let type_name = self.path_to_name(path_stack.iter().chain(iter::once(name)));
quote! {
#[serde(flatten)]
pub #name: #type_name
}
},
FieldType::Existing(type_name) => {
let pref_name = self.pref_name(field, &path_stack);
let attributes = field.get_attributes(&pref_name);
quote! {
#attributes
pub #name: #type_name
}
},
})
}
fn path_to_name<'p, P: Iterator<Item = &'p Ident> + 'p>(&self, path: P) -> Ident {
let mut name = format!("{}", self.root_type_name);
for part in path {
name.write_fmt(format_args!("__{}", part)).unwrap();
}
Ident::new(&name, Span::call_site())
}
}
impl Field {
fn get_attributes(&self, pref_name: &str) -> TokenStream {
let mut tokens = TokenStream::new();
for attr in self
.attributes
.iter()
.filter(|attr| attr_to_pref_name(attr).is_none())
{
attr.to_tokens(&mut tokens);
}
tokens.extend(quote! {
#[serde(rename = #pref_name)]
});
tokens
}
fn get_field_name_mapping(&self) -> Option<LitStr> {
self.attributes.iter().filter_map(attr_to_pref_name).next()
}
}
fn attr_to_pref_name(attr: &Attribute) -> Option<LitStr> {
attr.parse_meta().ok().and_then(|meta| {
if let Meta::List(MetaList { path, nested, .. }) = meta {
if path.is_ident("serde") {
if let Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
ref path,
lit: Lit::Str(val),
..
}))) = nested.iter().next()
{
if path.is_ident("rename") {
return Some(val.clone());
}
}
}
}
None
})
}
fn err<S: Spanned>(s: S, msg: &str) -> syn::Error {
syn::Error::new(s.span(), msg)
}

View file

@ -0,0 +1,149 @@
/* 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 proc_macro2::Span;
use syn::parse::{Parse, ParseStream, Result};
use syn::{braced, punctuated::Punctuated, token, Attribute, Ident, Path, Token, Type};
#[allow(non_camel_case_types)]
mod kw {
syn::custom_keyword!(accessor_type);
syn::custom_keyword!(gen_accessors);
syn::custom_keyword!(gen_types);
}
pub struct MacroInput {
pub type_def: RootTypeDef,
pub gen_accessors: Ident,
pub accessor_type: Path,
}
enum MacroArg {
GenAccessors(ArgInner<kw::gen_accessors, Ident>),
AccessorType(ArgInner<kw::accessor_type, Path>),
Types(ArgInner<kw::gen_types, RootTypeDef>),
}
struct ArgInner<K, V> {
_field_kw: K,
_equals: Token![=],
value: V,
}
pub struct Field {
pub attributes: Vec<Attribute>,
pub name: Ident,
_colon: Token![:],
pub field_type: FieldType,
}
pub enum FieldType {
Existing(Type),
NewTypeDef(NewTypeDef),
}
pub struct NewTypeDef {
_braces: token::Brace,
pub fields: Punctuated<Field, Token![, ]>,
}
pub struct RootTypeDef {
pub type_name: Ident,
pub type_def: NewTypeDef,
}
impl Parse for MacroInput {
fn parse(input: ParseStream) -> Result<Self> {
let fields: Punctuated<MacroArg, Token![, ]> = input.parse_terminated(MacroArg::parse)?;
let mut gen_accessors = None;
let mut type_def = None;
let mut accessor_type = None;
for arg in fields.into_iter() {
match arg {
MacroArg::GenAccessors(ArgInner { value, .. }) => gen_accessors = Some(value),
MacroArg::AccessorType(ArgInner { value, .. }) => accessor_type = Some(value),
MacroArg::Types(ArgInner { value, .. }) => type_def = Some(value),
}
}
fn missing_attr(att_name: &str) -> syn::Error {
syn::Error::new(
Span::call_site(),
format!("Expected `{}` attribute", att_name),
)
}
Ok(MacroInput {
type_def: type_def.ok_or_else(|| missing_attr("gen_types"))?,
gen_accessors: gen_accessors.ok_or_else(|| missing_attr("gen_accessors"))?,
accessor_type: accessor_type.ok_or_else(|| missing_attr("accessor_type"))?,
})
}
}
impl Parse for MacroArg {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::gen_types) {
Ok(MacroArg::Types(input.parse()?))
} else if lookahead.peek(kw::gen_accessors) {
Ok(MacroArg::GenAccessors(input.parse()?))
} else if lookahead.peek(kw::accessor_type) {
Ok(MacroArg::AccessorType(input.parse()?))
} else {
Err(lookahead.error())
}
}
}
impl<K: Parse, V: Parse> Parse for ArgInner<K, V> {
fn parse(input: ParseStream) -> Result<Self> {
Ok(ArgInner {
_field_kw: input.parse()?,
_equals: input.parse()?,
value: input.parse()?,
})
}
}
impl Parse for Field {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Field {
attributes: input.call(Attribute::parse_outer)?,
name: input.parse()?,
_colon: input.parse()?,
field_type: input.parse()?,
})
}
}
impl Parse for RootTypeDef {
fn parse(input: ParseStream) -> Result<Self> {
Ok(RootTypeDef {
type_name: input.parse()?,
type_def: input.parse()?,
})
}
}
impl Parse for NewTypeDef {
fn parse(input: ParseStream) -> Result<Self> {
let content;
#[allow(clippy::eval_order_dependence)]
Ok(NewTypeDef {
_braces: braced!(content in input),
fields: content.parse_terminated(Field::parse)?,
})
}
}
impl Parse for FieldType {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(token::Brace) {
Ok(FieldType::NewTypeDef(input.parse()?))
} else {
Ok(FieldType::Existing(input.parse()?))
}
}
}

View file

@ -3,6 +3,7 @@ name = "constellation"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
@ -10,35 +11,39 @@ name = "constellation"
path = "lib.rs"
[dependencies]
background_hang_monitor = { path = "../background_hang_monitor" }
backtrace = "0.3"
bluetooth_traits = { path = "../bluetooth_traits" }
canvas = {path = "../canvas"}
canvas_traits = {path = "../canvas_traits"}
compositing = {path = "../compositing"}
debugger = {path = "../debugger"}
devtools_traits = {path = "../devtools_traits"}
euclid = "0.11"
gfx = {path = "../gfx"}
gfx_traits = {path = "../gfx_traits"}
ipc-channel = "0.7"
itertools = "0.5"
layout_traits = {path = "../layout_traits"}
log = "0.3.5"
msg = {path = "../msg"}
net = {path = "../net"}
net_traits = {path = "../net_traits"}
offscreen_gl_context = "0.8"
profile_traits = {path = "../profile_traits"}
script_traits = {path = "../script_traits"}
serde = "0.9"
serde_derive = "0.9"
style_traits = {path = "../style_traits"}
servo_config = {path = "../config"}
servo_rand = {path = "../rand"}
servo_remutex = {path = "../remutex"}
servo_url = {path = "../url"}
webvr_traits = {path = "../webvr_traits"}
webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
canvas_traits = { path = "../canvas_traits" }
compositing = { path = "../compositing" }
crossbeam-channel = "0.4"
devtools_traits = { path = "../devtools_traits" }
embedder_traits = { path = "../embedder_traits" }
euclid = "0.20"
gfx = { path = "../gfx" }
gfx_traits = { path = "../gfx_traits" }
http = "0.1"
ipc-channel = "0.14"
keyboard-types = "0.5"
layout_traits = { path = "../layout_traits" }
log = "0.4"
media = { path = "../media" }
metrics = { path = "../metrics" }
msg = { path = "../msg" }
net = { path = "../net" }
net_traits = { path = "../net_traits" }
profile_traits = { path = "../profile_traits" }
script_traits = { path = "../script_traits" }
serde = "1.0"
servo_config = { path = "../config" }
servo_rand = {path = "../rand" }
servo_remutex = { path = "../remutex" }
servo_url = { path = "../url" }
style_traits = { path = "../style_traits" }
webgpu = { path = "../webgpu" }
webrender_api = { git = "https://github.com/servo/webrender" }
webrender_traits = { path = "../webrender_traits" }
webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] }
[target.'cfg(not(target_os = "windows"))'.dependencies]
gaol = {git = "https://github.com/servo/gaol"}
[target.'cfg(any(target_os="macos", all(not(target_os = "windows"), not(target_os = "ios"), not(target_os="android"), not(target_arch="arm"), not(target_arch="aarch64"))))'.dependencies]
gaol = "0.2.1"

View file

@ -0,0 +1,204 @@
/* 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 crate::pipeline::Pipeline;
use euclid::Size2D;
use msg::constellation_msg::{
BrowsingContextGroupId, BrowsingContextId, PipelineId, TopLevelBrowsingContextId,
};
use std::collections::{HashMap, HashSet};
use style_traits::CSSPixel;
/// Because a browsing context is only constructed once the document that's
/// going to be in it becomes active (i.e. not when a pipeline is spawned), some
/// values needed in browsing context are not easily available at the point of
/// constructing it. Thus, every time a pipeline is created for a browsing
/// context which doesn't exist yet, these values needed for the new browsing
/// context are stored here so that they may be available later.
pub struct NewBrowsingContextInfo {
/// The parent pipeline that contains this browsing context. `None` if this
/// is a top level browsing context.
pub parent_pipeline_id: Option<PipelineId>,
/// Whether this browsing context is in private browsing mode.
pub is_private: bool,
/// Whether this browsing context inherits a secure context.
pub inherited_secure_context: Option<bool>,
/// Whether this browsing context should be treated as visible for the
/// purposes of scheduling and resource management.
pub is_visible: bool,
}
/// The constellation's view of a browsing context.
/// Each browsing context has a session history, caused by navigation and
/// traversing the history. Each browsing context has its current entry, plus
/// past and future entries. The past is sorted chronologically, the future is
/// sorted reverse chronologically: in particular prev.pop() is the latest
/// past entry, and next.pop() is the earliest future entry.
pub struct BrowsingContext {
/// The browsing context group id where the top-level of this bc is found.
pub bc_group_id: BrowsingContextGroupId,
/// The browsing context id.
pub id: BrowsingContextId,
/// The top-level browsing context ancestor
pub top_level_id: TopLevelBrowsingContextId,
/// The size of the frame.
pub size: Size2D<f32, CSSPixel>,
/// Whether this browsing context is in private browsing mode.
pub is_private: bool,
/// Whether this browsing context inherits a secure context.
pub inherited_secure_context: Option<bool>,
/// Whether this browsing context should be treated as visible for the
/// purposes of scheduling and resource management.
pub is_visible: bool,
/// The pipeline for the current session history entry.
pub pipeline_id: PipelineId,
/// The parent pipeline that contains this browsing context. `None` if this
/// is a top level browsing context.
pub parent_pipeline_id: Option<PipelineId>,
/// All the pipelines that have been presented or will be presented in
/// this browsing context.
pub pipelines: HashSet<PipelineId>,
}
impl BrowsingContext {
/// Create a new browsing context.
/// Note this just creates the browsing context, it doesn't add it to the constellation's set of browsing contexts.
pub fn new(
bc_group_id: BrowsingContextGroupId,
id: BrowsingContextId,
top_level_id: TopLevelBrowsingContextId,
pipeline_id: PipelineId,
parent_pipeline_id: Option<PipelineId>,
size: Size2D<f32, CSSPixel>,
is_private: bool,
inherited_secure_context: Option<bool>,
is_visible: bool,
) -> BrowsingContext {
let mut pipelines = HashSet::new();
pipelines.insert(pipeline_id);
BrowsingContext {
bc_group_id,
id,
top_level_id,
size,
is_private,
inherited_secure_context,
is_visible,
pipeline_id,
parent_pipeline_id,
pipelines,
}
}
pub fn update_current_entry(&mut self, pipeline_id: PipelineId) {
self.pipeline_id = pipeline_id;
}
/// Is this a top-level browsing context?
pub fn is_top_level(&self) -> bool {
self.id == self.top_level_id
}
}
/// An iterator over browsing contexts, returning the descendant
/// contexts whose active documents are fully active, in depth-first
/// order.
pub struct FullyActiveBrowsingContextsIterator<'a> {
/// The browsing contexts still to iterate over.
pub stack: Vec<BrowsingContextId>,
/// The set of all browsing contexts.
pub browsing_contexts: &'a HashMap<BrowsingContextId, BrowsingContext>,
/// The set of all pipelines. We use this to find the active
/// children of a frame, which are the iframes in the currently
/// active document.
pub pipelines: &'a HashMap<PipelineId, Pipeline>,
}
impl<'a> Iterator for FullyActiveBrowsingContextsIterator<'a> {
type Item = &'a BrowsingContext;
fn next(&mut self) -> Option<&'a BrowsingContext> {
loop {
let browsing_context_id = self.stack.pop()?;
let browsing_context = match self.browsing_contexts.get(&browsing_context_id) {
Some(browsing_context) => browsing_context,
None => {
warn!(
"BrowsingContext {:?} iterated after closure.",
browsing_context_id
);
continue;
},
};
let pipeline = match self.pipelines.get(&browsing_context.pipeline_id) {
Some(pipeline) => pipeline,
None => {
warn!(
"Pipeline {:?} iterated after closure.",
browsing_context.pipeline_id
);
continue;
},
};
self.stack.extend(pipeline.children.iter());
return Some(browsing_context);
}
}
}
/// An iterator over browsing contexts, returning all descendant
/// contexts in depth-first order. Note that this iterator returns all
/// contexts, not just the fully active ones.
pub struct AllBrowsingContextsIterator<'a> {
/// The browsing contexts still to iterate over.
pub stack: Vec<BrowsingContextId>,
/// The set of all browsing contexts.
pub browsing_contexts: &'a HashMap<BrowsingContextId, BrowsingContext>,
/// The set of all pipelines. We use this to find the
/// children of a browsing context, which are the iframes in all documents
/// in the session history.
pub pipelines: &'a HashMap<PipelineId, Pipeline>,
}
impl<'a> Iterator for AllBrowsingContextsIterator<'a> {
type Item = &'a BrowsingContext;
fn next(&mut self) -> Option<&'a BrowsingContext> {
let pipelines = self.pipelines;
loop {
let browsing_context_id = self.stack.pop()?;
let browsing_context = match self.browsing_contexts.get(&browsing_context_id) {
Some(browsing_context) => browsing_context,
None => {
warn!(
"BrowsingContext {:?} iterated after closure.",
browsing_context_id
);
continue;
},
};
let child_browsing_context_ids = browsing_context
.pipelines
.iter()
.filter_map(|pipeline_id| pipelines.get(&pipeline_id))
.flat_map(|pipeline| pipeline.children.iter());
self.stack.extend(child_browsing_context_ids);
return Some(browsing_context);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,18 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! This module contains the `EventLoop` type, which is the constellation's
//! view of a script thread. When an `EventLoop` is dropped, an `ExitScriptThread`
//! message is sent to the script thread, asking it to shut down.
use ipc_channel::Error;
use ipc_channel::ipc::IpcSender;
use ipc_channel::Error;
use script_traits::ConstellationControlMsg;
use std::marker::PhantomData;
use std::rc::Rc;
/// https://html.spec.whatwg.org/multipage/#event-loop
/// <https://html.spec.whatwg.org/multipage/#event-loop>
pub struct EventLoop {
script_chan: IpcSender<ConstellationControlMsg>,
dont_send_or_sync: PhantomData<Rc<()>>,
@ -20,7 +20,9 @@ pub struct EventLoop {
impl Drop for EventLoop {
fn drop(&mut self) {
let _ = self.script_chan.send(ConstellationControlMsg::ExitScriptThread);
let _ = self
.script_chan
.send(ConstellationControlMsg::ExitScriptThread);
}
}
@ -43,4 +45,3 @@ impl EventLoop {
self.script_chan.clone()
}
}

View file

@ -1,212 +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 http://mozilla.org/MPL/2.0/. */
use msg::constellation_msg::{FrameId, PipelineId};
use pipeline::Pipeline;
use script_traits::LoadData;
use std::collections::HashMap;
use std::iter::once;
use std::mem::replace;
use std::time::Instant;
/// A frame in the frame tree.
/// Each frame is the constellation's view of a browsing context.
/// Each browsing context has a session history, caused by
/// navigation and traversing the history. Each frame has its
/// current entry, plus past and future entries. The past is sorted
/// chronologically, the future is sorted reverse chronologically:
/// in particular prev.pop() is the latest past entry, and
/// next.pop() is the earliest future entry.
pub struct Frame {
/// The frame id.
pub id: FrameId,
/// The timestamp for the current session history entry
pub instant: Instant,
/// The pipeline for the current session history entry
pub pipeline_id: PipelineId,
/// The load data for the current session history entry
pub load_data: LoadData,
/// The past session history, ordered chronologically.
pub prev: Vec<FrameState>,
/// The future session history, ordered reverse chronologically.
pub next: Vec<FrameState>,
}
impl Frame {
/// Create a new frame.
/// Note this just creates the frame, it doesn't add it to the frame tree.
pub fn new(id: FrameId, pipeline_id: PipelineId, load_data: LoadData) -> Frame {
Frame {
id: id,
pipeline_id: pipeline_id,
instant: Instant::now(),
load_data: load_data,
prev: vec!(),
next: vec!(),
}
}
/// Get the current frame state.
pub fn current(&self) -> FrameState {
FrameState {
instant: self.instant,
frame_id: self.id,
pipeline_id: Some(self.pipeline_id),
load_data: self.load_data.clone(),
}
}
/// Set the current frame entry, and push the current frame entry into the past.
pub fn load(&mut self, pipeline_id: PipelineId, load_data: LoadData) {
let current = self.current();
self.prev.push(current);
self.instant = Instant::now();
self.pipeline_id = pipeline_id;
self.load_data = load_data;
}
/// Set the future to be empty.
pub fn remove_forward_entries(&mut self) -> Vec<FrameState> {
replace(&mut self.next, vec!())
}
/// Update the current entry of the Frame from an entry that has been traversed to.
pub fn update_current(&mut self, pipeline_id: PipelineId, entry: FrameState) {
self.pipeline_id = pipeline_id;
self.instant = entry.instant;
self.load_data = entry.load_data;
}
}
/// An entry in a frame's session history.
/// Each entry stores the pipeline id for a document in the session history.
///
/// When we operate on the joint session history, entries are sorted chronologically,
/// so we timestamp the entries by when the entry was added to the session history.
#[derive(Clone)]
pub struct FrameState {
/// The timestamp for when the session history entry was created
pub instant: Instant,
/// The pipeline for the document in the session history,
/// None if the entry has been discarded
pub pipeline_id: Option<PipelineId>,
/// The load data for this entry, used to reload the pipeline if it has been discarded
pub load_data: LoadData,
/// The frame that this session history entry is part of
pub frame_id: FrameId,
}
/// Represents a pending change in the frame tree, that will be applied
/// once the new pipeline has loaded and completed initial layout / paint.
pub struct FrameChange {
/// The frame to change.
pub frame_id: FrameId,
/// The pipeline for the document being loaded.
pub new_pipeline_id: PipelineId,
/// The data for the document being loaded.
pub load_data: LoadData,
/// Is the new document replacing the current document (e.g. a reload)
/// or pushing it into the session history (e.g. a navigation)?
/// If it is replacing an existing entry, we store its timestamp.
pub replace_instant: Option<Instant>,
}
/// An iterator over a frame tree, returning the fully active frames in
/// depth-first order. Note that this iterator only returns the fully
/// active frames, that is ones where every ancestor frame is
/// in the currently active pipeline of its parent frame.
pub struct FrameTreeIterator<'a> {
/// The frames still to iterate over.
pub stack: Vec<FrameId>,
/// The set of all frames.
pub frames: &'a HashMap<FrameId, Frame>,
/// The set of all pipelines. We use this to find the active
/// children of a frame, which are the iframes in the currently
/// active document.
pub pipelines: &'a HashMap<PipelineId, Pipeline>,
}
impl<'a> Iterator for FrameTreeIterator<'a> {
type Item = &'a Frame;
fn next(&mut self) -> Option<&'a Frame> {
loop {
let frame_id = match self.stack.pop() {
Some(frame_id) => frame_id,
None => return None,
};
let frame = match self.frames.get(&frame_id) {
Some(frame) => frame,
None => {
warn!("Frame {:?} iterated after closure.", frame_id);
continue;
},
};
let pipeline = match self.pipelines.get(&frame.pipeline_id) {
Some(pipeline) => pipeline,
None => {
warn!("Pipeline {:?} iterated after closure.", frame.pipeline_id);
continue;
},
};
self.stack.extend(pipeline.children.iter());
return Some(frame)
}
}
}
/// An iterator over a frame tree, returning all frames in depth-first
/// order. Note that this iterator returns all frames, not just the
/// fully active ones.
pub struct FullFrameTreeIterator<'a> {
/// The frames still to iterate over.
pub stack: Vec<FrameId>,
/// The set of all frames.
pub frames: &'a HashMap<FrameId, Frame>,
/// The set of all pipelines. We use this to find the
/// children of a frame, which are the iframes in all documents
/// in the session history.
pub pipelines: &'a HashMap<PipelineId, Pipeline>,
}
impl<'a> Iterator for FullFrameTreeIterator<'a> {
type Item = &'a Frame;
fn next(&mut self) -> Option<&'a Frame> {
let pipelines = self.pipelines;
loop {
let frame_id = match self.stack.pop() {
Some(frame_id) => frame_id,
None => return None,
};
let frame = match self.frames.get(&frame_id) {
Some(frame) => frame,
None => {
warn!("Frame {:?} iterated after closure.", frame_id);
continue;
},
};
let child_frame_ids = frame.prev.iter().chain(frame.next.iter())
.filter_map(|entry| entry.pipeline_id)
.chain(once(frame.pipeline_id))
.filter_map(|pipeline_id| pipelines.get(&pipeline_id))
.flat_map(|pipeline| pipeline.children.iter());
self.stack.extend(child_frame_ids);
return Some(frame)
}
}
}

View file

@ -1,55 +1,28 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
#![feature(box_syntax)]
#![feature(conservative_impl_trait)]
#![feature(mpsc_select)]
extern crate backtrace;
extern crate bluetooth_traits;
extern crate canvas;
extern crate canvas_traits;
extern crate compositing;
extern crate debugger;
extern crate devtools_traits;
extern crate euclid;
#[cfg(not(target_os = "windows"))]
extern crate gaol;
extern crate gfx;
extern crate gfx_traits;
extern crate ipc_channel;
extern crate itertools;
extern crate layout_traits;
#[macro_use]
extern crate crossbeam_channel;
#[macro_use]
extern crate log;
extern crate msg;
extern crate net;
extern crate net_traits;
extern crate offscreen_gl_context;
extern crate profile_traits;
extern crate script_traits;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate servo_config;
extern crate servo_rand;
extern crate servo_remutex;
extern crate servo_url;
extern crate style_traits;
extern crate webrender_traits;
extern crate webvr_traits;
extern crate serde;
mod browsingcontext;
mod constellation;
mod event_loop;
mod frame;
mod network_listener;
mod pipeline;
#[cfg(not(target_os = "windows"))]
mod sandboxing;
mod serviceworker;
mod session_history;
mod timer_scheduler;
pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState};
pub use pipeline::UnprivilegedPipelineContent;
#[cfg(not(target_os = "windows"))]
pub use sandboxing::content_process_sandbox_profile;
pub use crate::constellation::{
Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState,
};
pub use crate::pipeline::UnprivilegedPipelineContent;
pub use crate::sandboxing::{content_process_sandbox_profile, UnprivilegedContent};

View file

@ -0,0 +1,171 @@
/* 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/. */
//! The listener that encapsulates all state for an in-progress document request.
//! Any redirects that are encountered are followed. Whenever a non-redirect
//! response is received, it is forwarded to the appropriate script thread.
use crossbeam_channel::Sender;
use http::HeaderMap;
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use msg::constellation_msg::PipelineId;
use net::http_loader::{set_default_accept, set_default_accept_language};
use net_traits::request::{Destination, Referrer, RequestBuilder};
use net_traits::response::ResponseInit;
use net_traits::{CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseMsg};
use net_traits::{IpcSend, NetworkError, ResourceThreads};
pub struct NetworkListener {
res_init: Option<ResponseInit>,
request_builder: RequestBuilder,
pipeline_id: PipelineId,
resource_threads: ResourceThreads,
sender: Sender<(PipelineId, FetchResponseMsg)>,
should_send: bool,
}
impl NetworkListener {
pub fn new(
request_builder: RequestBuilder,
pipeline_id: PipelineId,
resource_threads: ResourceThreads,
sender: Sender<(PipelineId, FetchResponseMsg)>,
) -> NetworkListener {
NetworkListener {
res_init: None,
request_builder,
pipeline_id,
resource_threads,
sender,
should_send: false,
}
}
pub fn initiate_fetch(&self, cancel_chan: Option<ipc::IpcReceiver<()>>) {
let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!");
let mut listener = NetworkListener {
res_init: self.res_init.clone(),
request_builder: self.request_builder.clone(),
resource_threads: self.resource_threads.clone(),
sender: self.sender.clone(),
pipeline_id: self.pipeline_id.clone(),
should_send: false,
};
let msg = match self.res_init {
Some(ref res_init_) => CoreResourceMsg::FetchRedirect(
self.request_builder.clone(),
res_init_.clone(),
ipc_sender,
None,
),
None => {
set_default_accept(Destination::Document, &mut listener.request_builder.headers);
set_default_accept_language(&mut listener.request_builder.headers);
CoreResourceMsg::Fetch(
listener.request_builder.clone(),
FetchChannels::ResponseMsg(ipc_sender, cancel_chan),
)
},
};
ROUTER.add_route(
ipc_receiver.to_opaque(),
Box::new(move |message| {
let msg = message.to();
match msg {
Ok(FetchResponseMsg::ProcessResponse(res)) => listener.check_redirect(res),
Ok(msg_) => listener.send(msg_),
Err(e) => warn!("Error while receiving network listener message: {}", e),
};
}),
);
if let Err(e) = self.resource_threads.sender().send(msg) {
warn!("Resource thread unavailable ({})", e);
}
}
fn check_redirect(&mut self, message: Result<FetchMetadata, NetworkError>) {
match message {
Ok(res_metadata) => {
let metadata = match res_metadata {
FetchMetadata::Filtered { ref unsafe_, .. } => unsafe_,
FetchMetadata::Unfiltered(ref m) => m,
};
match metadata.location_url {
// https://html.spec.whatwg.org/multipage/#process-a-navigate-fetch
// Step 7-4.
Some(Ok(ref location_url))
if matches!(location_url.scheme(), "http" | "https") =>
{
if self.request_builder.url_list.is_empty() {
self.request_builder
.url_list
.push(self.request_builder.url.clone());
}
self.request_builder
.url_list
.push(metadata.final_url.clone());
self.request_builder.referrer = metadata
.referrer
.clone()
.map(|referrer_url| Referrer::ReferrerUrl(referrer_url))
.unwrap_or(Referrer::NoReferrer);
self.request_builder.referrer_policy = metadata.referrer_policy;
let headers = if let Some(ref headers) = metadata.headers {
headers.clone().into_inner()
} else {
HeaderMap::new()
};
self.res_init = Some(ResponseInit {
url: metadata.final_url.clone(),
location_url: metadata.location_url.clone(),
headers,
referrer: metadata.referrer.clone(),
status_code: metadata
.status
.as_ref()
.map(|&(code, _)| code)
.unwrap_or(200),
});
// XXXManishearth we don't have the cancel_chan anymore and
// can't use it here.
//
// Ideally the Fetch code would handle manual redirects on its own
self.initiate_fetch(None);
}
_ => {
// Response should be processed by script thread.
self.should_send = true;
self.send(FetchResponseMsg::ProcessResponse(Ok(res_metadata)));
},
};
},
Err(e) => {
self.should_send = true;
self.send(FetchResponseMsg::ProcessResponse(Err(e)))
},
};
}
fn send(&mut self, msg: FetchResponseMsg) {
if self.should_send {
if let Err(e) = self.sender.send((self.pipeline_id, msg)) {
warn!(
"Failed to forward network message to pipeline {}: {:?}",
self.pipeline_id, e
);
}
}
}
}

View file

@ -1,45 +1,53 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::event_loop::EventLoop;
use crate::sandboxing::{spawn_multiprocess, UnprivilegedContent};
use background_hang_monitor::HangMonitorRegister;
use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline;
use compositing::compositor_thread::Msg as CompositorMsg;
use compositing::CompositionPipeline;
use compositing::CompositorProxy;
use compositing::compositor_thread::Msg as CompositorMsg;
use crossbeam_channel::{unbounded, Sender};
use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg};
use euclid::scale_factor::ScaleFactor;
use euclid::size::TypedSize2D;
use event_loop::EventLoop;
use embedder_traits::EventLoopWaker;
use gfx::font_cache_thread::FontCacheThread;
use ipc_channel::Error;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use ipc_channel::Error;
use layout_traits::LayoutThreadFactory;
use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespaceId};
use media::WindowGLContext;
use metrics::PaintTimeMetrics;
use msg::constellation_msg::TopLevelBrowsingContextId;
use msg::constellation_msg::{
BackgroundHangMonitorControlMsg, BackgroundHangMonitorRegister, HangMonitorAlert,
};
use msg::constellation_msg::{BrowsingContextId, HistoryStateId};
use msg::constellation_msg::{
PipelineId, PipelineNamespace, PipelineNamespaceId, PipelineNamespaceRequest,
};
use net::image_cache::ImageCacheImpl;
use net_traits::{IpcSend, ResourceThreads};
use net_traits::image_cache::ImageCache;
use net_traits::ResourceThreads;
use profile_traits::mem as profile_mem;
use profile_traits::time;
use script_traits::{ConstellationControlMsg, DevicePixel, DiscardBrowsingContext};
use script_traits::{
AnimationState, ConstellationControlMsg, DiscardBrowsingContext, ScriptToConstellationChan,
};
use script_traits::{DocumentActivity, InitialScriptState};
use script_traits::{LayoutControlMsg, LayoutMsg, LoadData, MozBrowserEvent};
use script_traits::{NewLayoutInfo, SWManagerMsg, SWManagerSenders, ScriptMsg};
use script_traits::{LayoutControlMsg, LayoutMsg, LoadData};
use script_traits::{NewLayoutInfo, SWManagerMsg};
use script_traits::{ScriptThreadFactory, TimerSchedulerMsg, WindowSizeData};
use servo_config::opts::{self, Opts};
use servo_config::prefs::{PREFS, Pref};
use servo_config::{prefs, prefs::PrefValue};
use servo_url::ServoUrl;
use std::collections::HashMap;
#[cfg(not(windows))]
use std::env;
use std::ffi::OsStr;
use std::process;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use style_traits::CSSPixel;
use webrender_traits;
use webvr_traits::WebVRMsg;
/// A `Pipeline` is the constellation's view of a `Document`. Each pipeline has an
/// event loop (executed by a script thread) and a layout thread. A script thread
@ -49,15 +57,13 @@ pub struct Pipeline {
/// The ID of the pipeline.
pub id: PipelineId,
/// The ID of the frame that contains this Pipeline.
pub frame_id: FrameId,
/// The ID of the browsing context that contains this Pipeline.
pub browsing_context_id: BrowsingContextId,
/// The parent pipeline of this one. `None` if this is a root pipeline.
/// Note that because of mozbrowser iframes, even top-level pipelines
/// may have a parent (in which case the frame type will be
/// `MozbrowserIFrame`).
/// TODO: move this field to `Frame`.
pub parent_info: Option<(PipelineId, FrameType)>,
/// The ID of the top-level browsing context that contains this Pipeline.
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
pub opener: Option<BrowsingContextId>,
/// The event loop handling this pipeline.
pub event_loop: Rc<EventLoop>,
@ -66,34 +72,34 @@ pub struct Pipeline {
pub layout_chan: IpcSender<LayoutControlMsg>,
/// A channel to the compositor.
pub compositor_proxy: Box<CompositorProxy + 'static + Send>,
pub compositor_proxy: CompositorProxy,
/// The most recently loaded URL in this pipeline.
/// Note that this URL can change, for example if the page navigates
/// to a hash URL.
pub url: ServoUrl,
/// The title of the most recently-loaded page.
pub title: Option<String>,
/// The size of the frame.
/// TODO: move this field to `Frame`.
pub size: Option<TypedSize2D<f32, CSSPixel>>,
/// Whether this pipeline is currently running animations. Pipelines that are running
/// animations cause composites to be continually scheduled.
pub running_animations: bool,
pub animation_state: AnimationState,
/// The child frames of this pipeline (these are iframes in the document).
pub children: Vec<FrameId>,
/// The child browsing contexts of this pipeline (these are iframes in the document).
pub children: Vec<BrowsingContextId>,
/// Whether this pipeline is in private browsing mode.
/// TODO: move this field to `Frame`.
pub is_private: bool,
/// The Load Data used to create this pipeline.
pub load_data: LoadData,
/// Whether this pipeline should be treated as visible for the purposes of scheduling and
/// resource management.
pub visible: bool,
/// The active history state for this pipeline.
pub history_state_id: Option<HistoryStateId>,
/// The history states owned by this pipeline.
pub history_states: HashSet<HistoryStateId>,
/// Has this pipeline received a notification that it is completely loaded?
pub completely_loaded: bool,
/// The title of this pipeline's document.
pub title: String,
}
/// Initial setup data needed to construct a pipeline.
@ -104,18 +110,30 @@ pub struct InitialPipelineState {
/// The ID of the pipeline to create.
pub id: PipelineId,
/// The ID of the frame that contains this Pipeline.
pub frame_id: FrameId,
/// The ID of the browsing context that contains this Pipeline.
pub browsing_context_id: BrowsingContextId,
/// The ID of the top-level frame that contains this Pipeline.
pub top_level_frame_id: FrameId,
/// The ID of the top-level browsing context that contains this Pipeline.
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
/// The ID of the parent pipeline and frame type, if any.
/// If `None`, this is the root.
pub parent_info: Option<(PipelineId, FrameType)>,
pub parent_pipeline_id: Option<PipelineId>,
pub opener: Option<BrowsingContextId>,
/// A channel to the associated constellation.
pub constellation_chan: IpcSender<ScriptMsg>,
pub script_to_constellation_chan: ScriptToConstellationChan,
/// A sender to request pipeline namespace ids.
pub namespace_request_sender: IpcSender<PipelineNamespaceRequest>,
/// A handle to register components for hang monitoring.
/// None when in multiprocess mode.
pub background_monitor_register: Option<Box<dyn BackgroundHangMonitorRegister>>,
/// A channel for the background hang monitor to send messages to the constellation.
pub background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
/// A channel for the layout thread to send messages to the constellation.
pub layout_to_constellation_chan: IpcSender<LayoutMsg>,
@ -124,7 +142,7 @@ pub struct InitialPipelineState {
pub scheduler_chan: IpcSender<TimerSchedulerMsg>,
/// A channel to the compositor.
pub compositor_proxy: Box<CompositorProxy + 'static + Send>,
pub compositor_proxy: CompositorProxy,
/// A channel to the developer tools, if applicable.
pub devtools_chan: Option<Sender<DevtoolsControlMsg>>,
@ -148,10 +166,10 @@ pub struct InitialPipelineState {
pub mem_profiler_chan: profile_mem::ProfilerChan,
/// Information about the initial window size.
pub window_size: Option<TypedSize2D<f32, CSSPixel>>,
pub window_size: WindowSizeData,
/// Information about the device pixel ratio.
pub device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
/// The ID of the pipeline namespace for this script thread.
pub pipeline_namespace_id: PipelineNamespaceId,
/// The event loop to run in, if applicable.
pub event_loop: Option<Rc<EventLoop>>,
@ -159,92 +177,114 @@ pub struct InitialPipelineState {
/// Information about the page to load.
pub load_data: LoadData,
/// The ID of the pipeline namespace for this script thread.
pub pipeline_namespace_id: PipelineNamespaceId,
/// Pipeline visibility to be inherited
pub prev_visibility: Option<bool>,
/// Whether the browsing context in which pipeline is embedded is visible
/// for the purposes of scheduling and resource management. This field is
/// only used to notify script and compositor threads after spawning
/// a pipeline.
pub prev_visibility: bool,
/// Webrender api.
pub webrender_api_sender: webrender_traits::RenderApiSender,
pub webrender_image_api_sender: net_traits::WebrenderIpcSender,
/// Whether this pipeline is considered private.
pub is_private: bool,
/// A channel to the webvr thread.
pub webvr_thread: Option<IpcSender<WebVRMsg>>,
/// Webrender api.
pub webrender_api_sender: script_traits::WebrenderIpcSender,
/// The ID of the document processed by this script thread.
pub webrender_document: webrender_api::DocumentId,
/// A channel to the WebGL thread.
pub webgl_chan: Option<WebGLPipeline>,
/// The XR device registry
pub webxr_registry: webxr_api::Registry,
/// Application window's GL Context for Media player
pub player_context: WindowGLContext,
/// Mechanism to force the compositor to process events.
pub event_loop_waker: Option<Box<dyn EventLoopWaker>>,
/// User agent string to report in network requests.
pub user_agent: Cow<'static, str>,
}
pub struct NewPipeline {
pub pipeline: Pipeline,
pub bhm_control_chan: Option<IpcSender<BackgroundHangMonitorControlMsg>>,
}
impl Pipeline {
/// Starts a layout thread, and possibly a script thread, in
/// a new process if requested.
pub fn spawn<Message, LTF, STF>(state: InitialPipelineState) -> Result<Pipeline, Error>
where LTF: LayoutThreadFactory<Message=Message>,
STF: ScriptThreadFactory<Message=Message>
pub fn spawn<Message, LTF, STF>(state: InitialPipelineState) -> Result<NewPipeline, Error>
where
LTF: LayoutThreadFactory<Message = Message>,
STF: ScriptThreadFactory<Message = Message>,
{
// Note: we allow channel creation to panic, since recovering from this
// probably requires a general low-memory strategy.
let (pipeline_chan, pipeline_port) = ipc::channel()
.expect("Pipeline main chan");
let (pipeline_chan, pipeline_port) = ipc::channel().expect("Pipeline main chan");
let (layout_content_process_shutdown_chan, layout_content_process_shutdown_port) =
ipc::channel().expect("Pipeline layout content shutdown chan");
let device_pixel_ratio = state.device_pixel_ratio;
let window_size = state.window_size.map(|size| {
WindowSizeData {
initial_viewport: size,
device_pixel_ratio: device_pixel_ratio,
}
});
let url = state.load_data.url.clone();
let script_chan = match state.event_loop {
let (script_chan, bhm_control_chan) = match state.event_loop {
Some(script_chan) => {
let new_layout_info = NewLayoutInfo {
parent_info: state.parent_info,
parent_info: state.parent_pipeline_id,
new_pipeline_id: state.id,
frame_id: state.frame_id,
load_data: state.load_data,
window_size: window_size,
browsing_context_id: state.browsing_context_id,
top_level_browsing_context_id: state.top_level_browsing_context_id,
opener: state.opener,
load_data: state.load_data.clone(),
window_size: state.window_size,
pipeline_port: pipeline_port,
content_process_shutdown_chan: Some(layout_content_process_shutdown_chan.clone()),
layout_threads: PREFS.get("layout.threads").as_u64().expect("count") as usize,
};
if let Err(e) = script_chan.send(ConstellationControlMsg::AttachLayout(new_layout_info)) {
if let Err(e) =
script_chan.send(ConstellationControlMsg::AttachLayout(new_layout_info))
{
warn!("Sending to script during pipeline creation failed ({})", e);
}
script_chan
}
(script_chan, None)
},
None => {
let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan");
// Route messages coming from content to devtools as appropriate.
let script_to_devtools_chan = state.devtools_chan.as_ref().map(|devtools_chan| {
let (script_to_devtools_chan, script_to_devtools_port) = ipc::channel()
.expect("Pipeline script to devtools chan");
let (script_to_devtools_chan, script_to_devtools_port) =
ipc::channel().expect("Pipeline script to devtools chan");
let devtools_chan = (*devtools_chan).clone();
ROUTER.add_route(script_to_devtools_port.to_opaque(), box move |message| {
match message.to::<ScriptToDevtoolsControlMsg>() {
Err(e) => error!("Cast to ScriptToDevtoolsControlMsg failed ({}).", e),
Ok(message) => if let Err(e) = devtools_chan.send(DevtoolsControlMsg::FromScript(message)) {
warn!("Sending to devtools failed ({})", e)
ROUTER.add_route(
script_to_devtools_port.to_opaque(),
Box::new(
move |message| match message.to::<ScriptToDevtoolsControlMsg>() {
Err(e) => {
error!("Cast to ScriptToDevtoolsControlMsg failed ({}).", e)
},
Ok(message) => {
if let Err(e) =
devtools_chan.send(DevtoolsControlMsg::FromScript(message))
{
warn!("Sending to devtools failed ({:?})", e)
}
},
},
}
});
),
);
script_to_devtools_chan
});
let (script_content_process_shutdown_chan, script_content_process_shutdown_port) =
ipc::channel().expect("Pipeline script content process shutdown chan");
let unprivileged_pipeline_content = UnprivilegedPipelineContent {
let mut unprivileged_pipeline_content = UnprivilegedPipelineContent {
id: state.id,
frame_id: state.frame_id,
top_level_frame_id: state.top_level_frame_id,
parent_info: state.parent_info,
constellation_chan: state.constellation_chan,
browsing_context_id: state.browsing_context_id,
top_level_browsing_context_id: state.top_level_browsing_context_id,
parent_pipeline_id: state.parent_pipeline_id,
opener: state.opener,
script_to_constellation_chan: state.script_to_constellation_chan.clone(),
namespace_request_sender: state.namespace_request_sender,
background_hang_monitor_to_constellation_chan: state
.background_hang_monitor_to_constellation_chan
.clone(),
bhm_control_port: None,
scheduler_chan: state.scheduler_chan,
devtools_chan: script_to_devtools_chan,
bluetooth_thread: state.bluetooth_thread,
@ -253,78 +293,99 @@ impl Pipeline {
resource_threads: state.resource_threads,
time_profiler_chan: state.time_profiler_chan,
mem_profiler_chan: state.mem_profiler_chan,
window_size: window_size,
window_size: state.window_size,
layout_to_constellation_chan: state.layout_to_constellation_chan,
script_chan: script_chan.clone(),
load_data: state.load_data,
load_data: state.load_data.clone(),
script_port: script_port,
opts: (*opts::get()).clone(),
prefs: PREFS.cloned(),
prefs: prefs::pref_map().iter().collect(),
pipeline_port: pipeline_port,
pipeline_namespace_id: state.pipeline_namespace_id,
layout_content_process_shutdown_chan: layout_content_process_shutdown_chan,
layout_content_process_shutdown_port: layout_content_process_shutdown_port,
script_content_process_shutdown_chan: script_content_process_shutdown_chan,
script_content_process_shutdown_port: script_content_process_shutdown_port,
webrender_api_sender: state.webrender_api_sender,
webvr_thread: state.webvr_thread,
webrender_image_api_sender: state.webrender_image_api_sender,
webrender_document: state.webrender_document,
webgl_chan: state.webgl_chan,
webxr_registry: state.webxr_registry,
player_context: state.player_context,
user_agent: state.user_agent,
};
// Spawn the child process.
//
// Yes, that's all there is to it!
if opts::multiprocess() {
let _ = try!(unprivileged_pipeline_content.spawn_multiprocess());
let bhm_control_chan = if opts::multiprocess() {
let (bhm_control_chan, bhm_control_port) =
ipc::channel().expect("Sampler chan");
unprivileged_pipeline_content.bhm_control_port = Some(bhm_control_port);
let _ = unprivileged_pipeline_content.spawn_multiprocess()?;
Some(bhm_control_chan)
} else {
unprivileged_pipeline_content.start_all::<Message, LTF, STF>(false);
}
// Should not be None in single-process mode.
let register = state
.background_monitor_register
.expect("Couldn't start content, no background monitor has been initiated");
unprivileged_pipeline_content.start_all::<Message, LTF, STF>(
false,
register,
state.event_loop_waker,
);
None
};
EventLoop::new(script_chan)
}
(EventLoop::new(script_chan), bhm_control_chan)
},
};
Ok(Pipeline::new(state.id,
state.frame_id,
state.parent_info,
script_chan,
pipeline_chan,
state.compositor_proxy,
state.is_private,
url,
state.window_size,
state.prev_visibility.unwrap_or(true)))
let pipeline = Pipeline::new(
state.id,
state.browsing_context_id,
state.top_level_browsing_context_id,
state.opener,
script_chan,
pipeline_chan,
state.compositor_proxy,
state.prev_visibility,
state.load_data,
);
Ok(NewPipeline {
pipeline,
bhm_control_chan,
})
}
/// Creates a new `Pipeline`, after the script and layout threads have been
/// spawned.
pub fn new(id: PipelineId,
frame_id: FrameId,
parent_info: Option<(PipelineId, FrameType)>,
event_loop: Rc<EventLoop>,
layout_chan: IpcSender<LayoutControlMsg>,
compositor_proxy: Box<CompositorProxy + 'static + Send>,
is_private: bool,
url: ServoUrl,
size: Option<TypedSize2D<f32, CSSPixel>>,
visible: bool)
-> Pipeline {
pub fn new(
id: PipelineId,
browsing_context_id: BrowsingContextId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
opener: Option<BrowsingContextId>,
event_loop: Rc<EventLoop>,
layout_chan: IpcSender<LayoutControlMsg>,
compositor_proxy: CompositorProxy,
is_visible: bool,
load_data: LoadData,
) -> Pipeline {
let pipeline = Pipeline {
id: id,
frame_id: frame_id,
parent_info: parent_info,
browsing_context_id: browsing_context_id,
top_level_browsing_context_id: top_level_browsing_context_id,
opener: opener,
event_loop: event_loop,
layout_chan: layout_chan,
compositor_proxy: compositor_proxy,
url: url,
title: None,
children: vec!(),
size: size,
running_animations: false,
visible: visible,
is_private: is_private,
url: load_data.url.clone(),
children: vec![],
animation_state: AnimationState::NoAnimationsPresent,
load_data: load_data,
history_state_id: None,
history_states: HashSet::new(),
completely_loaded: false,
title: String::new(),
};
pipeline.notify_visibility();
pipeline.notify_visibility(is_visible);
pipeline
}
@ -340,9 +401,10 @@ impl Pipeline {
// It's OK for the constellation to block on the compositor,
// since the compositor never blocks on the constellation.
if let Ok((sender, receiver)) = ipc::channel() {
self.compositor_proxy.send(CompositorMsg::PipelineExited(self.id, sender));
self.compositor_proxy
.send(CompositorMsg::PipelineExited(self.id, sender));
if let Err(e) = receiver.recv() {
warn!("Sending exit message failed ({}).", e);
warn!("Sending exit message failed ({:?}).", e);
}
}
@ -378,60 +440,44 @@ impl Pipeline {
pub fn to_sendable(&self) -> CompositionPipeline {
CompositionPipeline {
id: self.id.clone(),
top_level_browsing_context_id: self.top_level_browsing_context_id.clone(),
script_chan: self.event_loop.sender(),
layout_chan: self.layout_chan.clone(),
}
}
/// Add a new child frame.
pub fn add_child(&mut self, frame_id: FrameId) {
self.children.push(frame_id);
/// Add a new child browsing context.
pub fn add_child(&mut self, browsing_context_id: BrowsingContextId) {
self.children.push(browsing_context_id);
}
/// Remove a child frame.
pub fn remove_child(&mut self, frame_id: FrameId) {
match self.children.iter().position(|id| *id == frame_id) {
None => return warn!("Pipeline remove child already removed ({:?}).", frame_id),
/// Remove a child browsing context.
pub fn remove_child(&mut self, browsing_context_id: BrowsingContextId) {
match self
.children
.iter()
.position(|id| *id == browsing_context_id)
{
None => {
return warn!(
"Pipeline remove child already removed ({:?}).",
browsing_context_id
);
},
Some(index) => self.children.remove(index),
};
}
/// Send a mozbrowser event to the script thread for this pipeline.
/// This will cause an event to be fired on an iframe in the document,
/// or on the `Window` if no frame is given.
pub fn trigger_mozbrowser_event(&self,
child_id: Option<FrameId>,
event: MozBrowserEvent) {
assert!(PREFS.is_mozbrowser_enabled());
let event = ConstellationControlMsg::MozBrowserEvent(self.id,
child_id,
event);
if let Err(e) = self.event_loop.send(event) {
warn!("Sending mozbrowser event to script failed ({}).", e);
}
}
/// Notify the script thread that this pipeline is visible.
fn notify_visibility(&self) {
let script_msg = ConstellationControlMsg::ChangeFrameVisibilityStatus(self.id, self.visible);
let compositor_msg = CompositorMsg::PipelineVisibilityChanged(self.id, self.visible);
pub fn notify_visibility(&self, is_visible: bool) {
let script_msg = ConstellationControlMsg::ChangeFrameVisibilityStatus(self.id, is_visible);
let compositor_msg = CompositorMsg::PipelineVisibilityChanged(self.id, is_visible);
let err = self.event_loop.send(script_msg);
if let Err(e) = err {
warn!("Sending visibility change failed ({}).", e);
}
self.compositor_proxy.send(compositor_msg);
}
/// Change the visibility of this pipeline.
pub fn change_visibility(&mut self, visible: bool) {
if visible == self.visible {
return;
}
self.visible = visible;
self.notify_visibility();
}
}
/// Creating a new pipeline may require creating a new event loop.
@ -440,10 +486,14 @@ impl Pipeline {
#[derive(Deserialize, Serialize)]
pub struct UnprivilegedPipelineContent {
id: PipelineId,
frame_id: FrameId,
top_level_frame_id: FrameId,
parent_info: Option<(PipelineId, FrameType)>,
constellation_chan: IpcSender<ScriptMsg>,
top_level_browsing_context_id: TopLevelBrowsingContextId,
browsing_context_id: BrowsingContextId,
parent_pipeline_id: Option<PipelineId>,
opener: Option<BrowsingContextId>,
namespace_request_sender: IpcSender<PipelineNamespaceRequest>,
script_to_constellation_chan: ScriptToConstellationChan,
background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
bhm_control_port: Option<IpcReceiver<BackgroundHangMonitorControlMsg>>,
layout_to_constellation_chan: IpcSender<LayoutMsg>,
scheduler_chan: IpcSender<TimerSchedulerMsg>,
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
@ -453,180 +503,153 @@ pub struct UnprivilegedPipelineContent {
resource_threads: ResourceThreads,
time_profiler_chan: time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
window_size: Option<WindowSizeData>,
window_size: WindowSizeData,
script_chan: IpcSender<ConstellationControlMsg>,
load_data: LoadData,
script_port: IpcReceiver<ConstellationControlMsg>,
opts: Opts,
prefs: HashMap<String, Pref>,
prefs: HashMap<String, PrefValue>,
pipeline_port: IpcReceiver<LayoutControlMsg>,
pipeline_namespace_id: PipelineNamespaceId,
layout_content_process_shutdown_chan: IpcSender<()>,
layout_content_process_shutdown_port: IpcReceiver<()>,
script_content_process_shutdown_chan: IpcSender<()>,
script_content_process_shutdown_port: IpcReceiver<()>,
webrender_api_sender: webrender_traits::RenderApiSender,
webvr_thread: Option<IpcSender<WebVRMsg>>,
webrender_api_sender: script_traits::WebrenderIpcSender,
webrender_image_api_sender: net_traits::WebrenderIpcSender,
webrender_document: webrender_api::DocumentId,
webgl_chan: Option<WebGLPipeline>,
webxr_registry: webxr_api::Registry,
player_context: WindowGLContext,
user_agent: Cow<'static, str>,
}
impl UnprivilegedPipelineContent {
pub fn start_all<Message, LTF, STF>(self, wait_for_completion: bool)
where LTF: LayoutThreadFactory<Message=Message>,
STF: ScriptThreadFactory<Message=Message>
pub fn start_all<Message, LTF, STF>(
self,
wait_for_completion: bool,
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
event_loop_waker: Option<Box<dyn EventLoopWaker>>,
) where
LTF: LayoutThreadFactory<Message = Message>,
STF: ScriptThreadFactory<Message = Message>,
{
let image_cache = Arc::new(ImageCacheImpl::new(self.webrender_api_sender.create_api()));
let layout_pair = STF::create(InitialScriptState {
id: self.id,
frame_id: self.frame_id,
top_level_frame_id: self.top_level_frame_id,
parent_info: self.parent_info,
control_chan: self.script_chan.clone(),
control_port: self.script_port,
constellation_chan: self.constellation_chan,
layout_to_constellation_chan: self.layout_to_constellation_chan.clone(),
scheduler_chan: self.scheduler_chan,
bluetooth_thread: self.bluetooth_thread,
resource_threads: self.resource_threads,
image_cache: image_cache.clone(),
time_profiler_chan: self.time_profiler_chan.clone(),
mem_profiler_chan: self.mem_profiler_chan.clone(),
devtools_chan: self.devtools_chan,
window_size: self.window_size,
pipeline_namespace_id: self.pipeline_namespace_id,
content_process_shutdown_chan: self.script_content_process_shutdown_chan,
webvr_thread: self.webvr_thread
}, self.load_data.clone());
// Setup pipeline-namespace-installing for all threads in this process.
// Idempotent in single-process mode.
PipelineNamespace::set_installer_sender(self.namespace_request_sender);
LTF::create(self.id,
Some(self.top_level_frame_id),
self.load_data.url,
self.parent_info.is_some(),
layout_pair,
self.pipeline_port,
self.layout_to_constellation_chan,
self.script_chan,
image_cache.clone(),
self.font_cache_thread,
self.time_profiler_chan,
self.mem_profiler_chan,
Some(self.layout_content_process_shutdown_chan),
self.webrender_api_sender,
self.prefs.get("layout.threads").expect("exists").value()
.as_u64().expect("count") as usize);
let image_cache = Arc::new(ImageCacheImpl::new(self.webrender_image_api_sender.clone()));
let paint_time_metrics = PaintTimeMetrics::new(
self.id,
self.time_profiler_chan.clone(),
self.layout_to_constellation_chan.clone(),
self.script_chan.clone(),
self.load_data.url.clone(),
);
let (content_process_shutdown_chan, content_process_shutdown_port) = unbounded();
let layout_thread_busy_flag = Arc::new(AtomicBool::new(false));
let layout_pair = STF::create(
InitialScriptState {
id: self.id,
browsing_context_id: self.browsing_context_id,
top_level_browsing_context_id: self.top_level_browsing_context_id,
parent_info: self.parent_pipeline_id,
opener: self.opener,
control_chan: self.script_chan.clone(),
control_port: self.script_port,
script_to_constellation_chan: self.script_to_constellation_chan.clone(),
background_hang_monitor_register: background_hang_monitor_register.clone(),
layout_to_constellation_chan: self.layout_to_constellation_chan.clone(),
scheduler_chan: self.scheduler_chan,
bluetooth_thread: self.bluetooth_thread,
resource_threads: self.resource_threads,
image_cache: image_cache.clone(),
time_profiler_chan: self.time_profiler_chan.clone(),
mem_profiler_chan: self.mem_profiler_chan.clone(),
devtools_chan: self.devtools_chan,
window_size: self.window_size,
pipeline_namespace_id: self.pipeline_namespace_id,
content_process_shutdown_chan: content_process_shutdown_chan,
webgl_chan: self.webgl_chan,
webxr_registry: self.webxr_registry,
webrender_document: self.webrender_document,
webrender_api_sender: self.webrender_api_sender.clone(),
layout_is_busy: layout_thread_busy_flag.clone(),
player_context: self.player_context.clone(),
event_loop_waker,
inherited_secure_context: self.load_data.inherited_secure_context.clone(),
},
self.load_data.clone(),
self.opts.profile_script_events,
self.opts.print_pwm,
self.opts.relayout_event,
self.opts.output_file.is_some() ||
self.opts.exit_after_load ||
self.opts.webdriver_port.is_some(),
self.opts.unminify_js,
self.opts.local_script_source,
self.opts.userscripts,
self.opts.headless,
self.opts.replace_surrogates,
self.user_agent,
);
LTF::create(
self.id,
self.top_level_browsing_context_id,
self.load_data.url,
self.parent_pipeline_id.is_some(),
layout_pair,
self.pipeline_port,
background_hang_monitor_register,
self.layout_to_constellation_chan,
self.script_chan,
image_cache,
self.font_cache_thread,
self.time_profiler_chan,
self.mem_profiler_chan,
self.webrender_api_sender,
paint_time_metrics,
layout_thread_busy_flag.clone(),
self.opts.load_webfonts_synchronously,
self.window_size,
self.opts.dump_display_list,
self.opts.dump_display_list_json,
self.opts.dump_style_tree,
self.opts.dump_rule_tree,
self.opts.relayout_event,
self.opts.nonincremental_layout,
self.opts.trace_layout,
self.opts.dump_flow_tree,
);
if wait_for_completion {
let _ = self.script_content_process_shutdown_port.recv();
let _ = self.layout_content_process_shutdown_port.recv();
}
}
#[cfg(not(target_os = "windows"))]
pub fn spawn_multiprocess(self) -> Result<(), Error> {
use gaol::sandbox::{self, Sandbox, SandboxMethods};
use ipc_channel::ipc::IpcOneShotServer;
use sandboxing::content_process_sandbox_profile;
impl CommandMethods for sandbox::Command {
fn arg<T>(&mut self, arg: T)
where T: AsRef<OsStr> {
self.arg(arg);
}
fn env<T, U>(&mut self, key: T, val: U)
where T: AsRef<OsStr>, U: AsRef<OsStr> {
self.env(key, val);
match content_process_shutdown_port.recv() {
Ok(()) => {},
Err(_) => error!("Script-thread shut-down unexpectedly"),
}
}
// Note that this function can panic, due to process creation,
// avoiding this panic would require a mechanism for dealing
// with low-resource scenarios.
let (server, token) =
IpcOneShotServer::<IpcSender<UnprivilegedPipelineContent>>::new()
.expect("Failed to create IPC one-shot server.");
// If there is a sandbox, use the `gaol` API to create the child process.
if opts::get().sandbox {
let mut command = sandbox::Command::me().expect("Failed to get current sandbox.");
self.setup_common(&mut command, token);
let profile = content_process_sandbox_profile();
let _ = Sandbox::new(profile)
.start(&mut command)
.expect("Failed to start sandboxed child process!");
} else {
let path_to_self = env::current_exe()
.expect("Failed to get current executor.");
let mut child_process = process::Command::new(path_to_self);
self.setup_common(&mut child_process, token);
let _ = child_process.spawn().expect("Failed to start unsandboxed child process!");
}
let (_receiver, sender) = server.accept().expect("Server failed to accept.");
try!(sender.send(self));
Ok(())
}
#[cfg(target_os = "windows")]
pub fn spawn_multiprocess(self) -> Result<(), Error> {
error!("Multiprocess is not supported on Windows.");
process::exit(1);
spawn_multiprocess(UnprivilegedContent::Pipeline(self))
}
#[cfg(not(windows))]
fn setup_common<C: CommandMethods>(&self, command: &mut C, token: String) {
C::arg(command, "--content-process");
C::arg(command, token);
if let Ok(value) = env::var("RUST_BACKTRACE") {
C::env(command, "RUST_BACKTRACE", value);
}
if let Ok(value) = env::var("RUST_LOG") {
C::env(command, "RUST_LOG", value);
}
pub fn register_with_background_hang_monitor(
&mut self,
) -> Box<dyn BackgroundHangMonitorRegister> {
HangMonitorRegister::init(
self.background_hang_monitor_to_constellation_chan.clone(),
self.bhm_control_port.take().expect("no sampling profiler?"),
opts::get().background_hang_monitor,
)
}
pub fn constellation_chan(&self) -> IpcSender<ScriptMsg> {
self.constellation_chan.clone()
pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan {
&self.script_to_constellation_chan
}
pub fn opts(&self) -> Opts {
self.opts.clone()
}
pub fn prefs(&self) -> HashMap<String, Pref> {
pub fn prefs(&self) -> HashMap<String, PrefValue> {
self.prefs.clone()
}
pub fn swmanager_senders(&self) -> SWManagerSenders {
SWManagerSenders {
swmanager_sender: self.swmanager_thread.clone(),
resource_sender: self.resource_threads.sender()
}
}
}
/// A trait to unify commands launched as multiprocess with or without a sandbox.
trait CommandMethods {
/// A command line argument.
fn arg<T>(&mut self, arg: T)
where T: AsRef<OsStr>;
/// An environment variable.
fn env<T, U>(&mut self, key: T, val: U)
where T: AsRef<OsStr>, U: AsRef<OsStr>;
}
impl CommandMethods for process::Command {
fn arg<T>(&mut self, arg: T)
where T: AsRef<OsStr> {
self.arg(arg);
}
fn env<T, U>(&mut self, key: T, val: U)
where T: AsRef<OsStr>, U: AsRef<OsStr> {
self.env(key, val);
}
}

View file

@ -1,42 +1,268 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::pipeline::UnprivilegedPipelineContent;
use crate::serviceworker::ServiceWorkerUnprivilegedContent;
#[cfg(any(
target_os = "macos",
all(
not(target_os = "windows"),
not(target_os = "ios"),
not(target_os = "android"),
not(target_arch = "arm"),
not(target_arch = "aarch64")
)
))]
use gaol::profile::{Operation, PathPattern, Profile};
use servo_config::resource_files;
use std::path::PathBuf;
use ipc_channel::Error;
use servo_config::opts::Opts;
use servo_config::prefs::PrefValue;
use std::collections::HashMap;
#[cfg(not(windows))]
use std::env;
use std::ffi::OsStr;
use std::process;
#[derive(Deserialize, Serialize)]
pub enum UnprivilegedContent {
Pipeline(UnprivilegedPipelineContent),
ServiceWorker(ServiceWorkerUnprivilegedContent),
}
impl UnprivilegedContent {
pub fn opts(&self) -> Opts {
match self {
UnprivilegedContent::Pipeline(content) => content.opts(),
UnprivilegedContent::ServiceWorker(content) => content.opts(),
}
}
pub fn prefs(&self) -> HashMap<String, PrefValue> {
match self {
UnprivilegedContent::Pipeline(content) => content.prefs(),
UnprivilegedContent::ServiceWorker(content) => content.prefs(),
}
}
}
/// Our content process sandbox profile on Mac. As restrictive as possible.
#[cfg(target_os = "macos")]
pub fn content_process_sandbox_profile() -> Profile {
use embedder_traits::resources;
use gaol::platform;
Profile::new(vec![
use std::path::PathBuf;
let mut operations = vec![
Operation::FileReadAll(PathPattern::Literal(PathBuf::from("/dev/urandom"))),
Operation::FileReadAll(PathPattern::Subpath(resource_files::resources_dir_path()
.expect("Cannot find resource dir"))),
Operation::FileReadAll(PathPattern::Subpath(PathBuf::from("/Library/Fonts"))),
Operation::FileReadAll(PathPattern::Subpath(PathBuf::from("/System/Library/Fonts"))),
Operation::FileReadAll(PathPattern::Subpath(PathBuf::from(
"/System/Library/Frameworks/ApplicationServices.framework"))),
"/System/Library/Frameworks/ApplicationServices.framework",
))),
Operation::FileReadAll(PathPattern::Subpath(PathBuf::from(
"/System/Library/Frameworks/CoreGraphics.framework"))),
"/System/Library/Frameworks/CoreGraphics.framework",
))),
Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/"))),
Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/Library"))),
Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/System"))),
Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/etc"))),
Operation::SystemInfoRead,
Operation::PlatformSpecific(platform::macos::Operation::MachLookup(
b"com.apple.FontServer".to_vec())),
]).expect("Failed to create sandbox profile!")
b"com.apple.FontServer".to_vec(),
)),
];
operations.extend(
resources::sandbox_access_files()
.into_iter()
.map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
);
operations.extend(
resources::sandbox_access_files_dirs()
.into_iter()
.map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
);
Profile::new(operations).expect("Failed to create sandbox profile!")
}
/// Our content process sandbox profile on Linux. As restrictive as possible.
#[cfg(not(target_os = "macos"))]
#[cfg(all(
not(target_os = "macos"),
not(target_os = "windows"),
not(target_os = "ios"),
not(target_os = "android"),
not(target_arch = "arm"),
not(target_arch = "aarch64")
))]
pub fn content_process_sandbox_profile() -> Profile {
Profile::new(vec![
Operation::FileReadAll(PathPattern::Literal(PathBuf::from("/dev/urandom"))),
Operation::FileReadAll(PathPattern::Subpath(resource_files::resources_dir_path()
.expect("Cannot find resource dir"))),
]).expect("Failed to create sandbox profile!")
use embedder_traits::resources;
use std::path::PathBuf;
let mut operations = vec![Operation::FileReadAll(PathPattern::Literal(PathBuf::from(
"/dev/urandom",
)))];
operations.extend(
resources::sandbox_access_files()
.into_iter()
.map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
);
operations.extend(
resources::sandbox_access_files_dirs()
.into_iter()
.map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
);
Profile::new(operations).expect("Failed to create sandbox profile!")
}
#[cfg(any(
target_os = "windows",
target_os = "ios",
target_os = "android",
target_arch = "arm",
// exclude apple arm devices
all(target_arch = "aarch64", not(target_os = "macos"))
))]
pub fn content_process_sandbox_profile() {
error!("Sandboxed multiprocess is not supported on this platform.");
process::exit(1);
}
#[cfg(any(
target_os = "android",
target_arch = "arm",
all(target_arch = "aarch64", not(target_os = "windows"))
))]
pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> {
use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
// Note that this function can panic, due to process creation,
// avoiding this panic would require a mechanism for dealing
// with low-resource scenarios.
let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
.expect("Failed to create IPC one-shot server.");
let path_to_self = env::current_exe().expect("Failed to get current executor.");
let mut child_process = process::Command::new(path_to_self);
setup_common(&mut child_process, token);
let _ = child_process
.spawn()
.expect("Failed to start unsandboxed child process!");
let (_receiver, sender) = server.accept().expect("Server failed to accept.");
sender.send(content)?;
Ok(())
}
#[cfg(all(
not(target_os = "windows"),
not(target_os = "ios"),
not(target_os = "android"),
not(target_arch = "arm"),
not(target_arch = "aarch64")
))]
pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<(), Error> {
use gaol::sandbox::{self, Sandbox, SandboxMethods};
use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
impl CommandMethods for sandbox::Command {
fn arg<T>(&mut self, arg: T)
where
T: AsRef<OsStr>,
{
self.arg(arg);
}
fn env<T, U>(&mut self, key: T, val: U)
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
{
self.env(key, val);
}
}
// Note that this function can panic, due to process creation,
// avoiding this panic would require a mechanism for dealing
// with low-resource scenarios.
let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
.expect("Failed to create IPC one-shot server.");
// If there is a sandbox, use the `gaol` API to create the child process.
if content.opts().sandbox {
let mut command = sandbox::Command::me().expect("Failed to get current sandbox.");
setup_common(&mut command, token);
let profile = content_process_sandbox_profile();
let _ = Sandbox::new(profile)
.start(&mut command)
.expect("Failed to start sandboxed child process!");
} else {
let path_to_self = env::current_exe().expect("Failed to get current executor.");
let mut child_process = process::Command::new(path_to_self);
setup_common(&mut child_process, token);
let _ = child_process
.spawn()
.expect("Failed to start unsandboxed child process!");
}
let (_receiver, sender) = server.accept().expect("Server failed to accept.");
sender.send(content)?;
Ok(())
}
#[cfg(any(target_os = "windows", target_os = "ios"))]
pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result<(), Error> {
error!("Multiprocess is not supported on Windows or iOS.");
process::exit(1);
}
#[cfg(not(windows))]
fn setup_common<C: CommandMethods>(command: &mut C, token: String) {
C::arg(command, "--content-process");
C::arg(command, token);
if let Ok(value) = env::var("RUST_BACKTRACE") {
C::env(command, "RUST_BACKTRACE", value);
}
if let Ok(value) = env::var("RUST_LOG") {
C::env(command, "RUST_LOG", value);
}
}
/// A trait to unify commands launched as multiprocess with or without a sandbox.
trait CommandMethods {
/// A command line argument.
fn arg<T>(&mut self, arg: T)
where
T: AsRef<OsStr>;
/// An environment variable.
fn env<T, U>(&mut self, key: T, val: U)
where
T: AsRef<OsStr>,
U: AsRef<OsStr>;
}
impl CommandMethods for process::Command {
fn arg<T>(&mut self, arg: T)
where
T: AsRef<OsStr>,
{
self.arg(arg);
}
fn env<T, U>(&mut self, key: T, val: U)
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
{
self.env(key, val);
}
}

View file

@ -0,0 +1,56 @@
/* 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 crate::sandboxing::{spawn_multiprocess, UnprivilegedContent};
use ipc_channel::Error;
use script_traits::{SWManagerSenders, ServiceWorkerManagerFactory};
use servo_config::opts::{self, Opts};
use servo_config::prefs::{self, PrefValue};
use servo_url::ImmutableOrigin;
use std::collections::HashMap;
/// Conceptually, this is glue to start an agent-cluster for a service worker agent.
/// <https://html.spec.whatwg.org/multipage/#obtain-a-service-worker-agent>
#[derive(Deserialize, Serialize)]
pub struct ServiceWorkerUnprivilegedContent {
opts: Opts,
prefs: HashMap<String, PrefValue>,
senders: SWManagerSenders,
origin: ImmutableOrigin,
}
impl ServiceWorkerUnprivilegedContent {
pub fn new(
senders: SWManagerSenders,
origin: ImmutableOrigin,
) -> ServiceWorkerUnprivilegedContent {
ServiceWorkerUnprivilegedContent {
opts: (*opts::get()).clone(),
prefs: prefs::pref_map().iter().collect(),
senders,
origin,
}
}
/// Start the agent-cluster.
pub fn start<SWF>(self)
where
SWF: ServiceWorkerManagerFactory,
{
SWF::create(self.senders, self.origin);
}
/// Start the agent-cluster in it's own process.
pub fn spawn_multiprocess(self) -> Result<(), Error> {
spawn_multiprocess(UnprivilegedContent::ServiceWorker(self))
}
pub fn opts(&self) -> Opts {
self.opts.clone()
}
pub fn prefs(&self) -> HashMap<String, PrefValue> {
self.prefs.clone()
}
}

View file

@ -0,0 +1,272 @@
/* 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 crate::browsingcontext::NewBrowsingContextInfo;
use euclid::Size2D;
use msg::constellation_msg::{
BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId,
};
use script_traits::LoadData;
use servo_url::ServoUrl;
use std::cmp::PartialEq;
use std::{fmt, mem};
use style_traits::CSSPixel;
/// Represents the joint session history
/// https://html.spec.whatwg.org/multipage/#joint-session-history
#[derive(Debug)]
pub struct JointSessionHistory {
/// Diffs used to traverse to past entries. Oldest entries are at the back,
/// the most recent entries are at the front.
pub past: Vec<SessionHistoryDiff>,
/// Diffs used to traverse to future entries. Oldest entries are at the back,
/// the most recent entries are at the front.
pub future: Vec<SessionHistoryDiff>,
}
impl JointSessionHistory {
pub fn new() -> JointSessionHistory {
JointSessionHistory {
past: Vec::new(),
future: Vec::new(),
}
}
pub fn history_length(&self) -> usize {
self.past.len() + 1 + self.future.len()
}
pub fn push_diff(&mut self, diff: SessionHistoryDiff) -> Vec<SessionHistoryDiff> {
debug!("pushing a past entry; removing future");
self.past.push(diff);
mem::replace(&mut self.future, vec![])
}
pub fn replace_reloader(&mut self, old_reloader: NeedsToReload, new_reloader: NeedsToReload) {
for diff in self.past.iter_mut().chain(self.future.iter_mut()) {
diff.replace_reloader(&old_reloader, &new_reloader);
}
}
pub fn replace_history_state(
&mut self,
pipeline_id: PipelineId,
history_state_id: HistoryStateId,
url: ServoUrl,
) {
if let Some(SessionHistoryDiff::PipelineDiff {
ref mut new_history_state_id,
ref mut new_url,
..
}) = self.past.iter_mut().find(|diff| match diff {
SessionHistoryDiff::PipelineDiff {
pipeline_reloader: NeedsToReload::No(id),
..
} => pipeline_id == *id,
_ => false,
}) {
*new_history_state_id = history_state_id;
*new_url = url.clone();
}
if let Some(SessionHistoryDiff::PipelineDiff {
ref mut old_history_state_id,
ref mut old_url,
..
}) = self.future.iter_mut().find(|diff| match diff {
SessionHistoryDiff::PipelineDiff {
pipeline_reloader: NeedsToReload::No(id),
..
} => pipeline_id == *id,
_ => false,
}) {
*old_history_state_id = Some(history_state_id);
*old_url = url;
}
}
pub fn remove_entries_for_browsing_context(&mut self, context_id: BrowsingContextId) {
debug!("removing entries for context {}", context_id);
self.past.retain(|diff| match diff {
SessionHistoryDiff::BrowsingContextDiff {
browsing_context_id,
..
} => *browsing_context_id != context_id,
_ => true,
});
self.future.retain(|diff| match diff {
SessionHistoryDiff::BrowsingContextDiff {
browsing_context_id,
..
} => *browsing_context_id != context_id,
_ => true,
});
}
}
/// Represents a pending change in a session history, that will be applied
/// once the new pipeline has loaded and completed initial layout / paint.
pub struct SessionHistoryChange {
/// The browsing context to change.
pub browsing_context_id: BrowsingContextId,
/// The top-level browsing context ancestor.
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
/// The pipeline for the document being loaded.
pub new_pipeline_id: PipelineId,
/// The old pipeline that the new pipeline should replace.
pub replace: Option<NeedsToReload>,
/// Holds data for not-yet constructed browsing contexts that are not
/// easily available when they need to be constructed.
pub new_browsing_context_info: Option<NewBrowsingContextInfo>,
/// The size of the viewport for the browsing context.
pub window_size: Size2D<f32, CSSPixel>,
}
/// Represents a pipeline or discarded pipeline in a history entry.
#[derive(Clone, Debug)]
pub enum NeedsToReload {
/// Represents a pipeline that has not been discarded
No(PipelineId),
/// Represents a pipeline that has been discarded and must be reloaded with the given `LoadData`
/// if ever traversed to.
Yes(PipelineId, LoadData),
}
impl fmt::Display for NeedsToReload {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
NeedsToReload::No(pipeline_id) => write!(fmt, "Alive({})", pipeline_id),
NeedsToReload::Yes(pipeline_id, ..) => write!(fmt, "Dead({})", pipeline_id),
}
}
}
impl NeedsToReload {
pub fn alive_pipeline_id(&self) -> Option<PipelineId> {
match *self {
NeedsToReload::No(pipeline_id) => Some(pipeline_id),
NeedsToReload::Yes(..) => None,
}
}
}
// Custom `PartialEq` that only compares the `PipelineId`s of the same variants while ignoring `LoadData`
impl PartialEq for NeedsToReload {
fn eq(&self, other: &NeedsToReload) -> bool {
match *self {
NeedsToReload::No(pipeline_id) => match *other {
NeedsToReload::No(other_pipeline_id) => pipeline_id == other_pipeline_id,
_ => false,
},
NeedsToReload::Yes(pipeline_id, _) => match *other {
NeedsToReload::Yes(other_pipeline_id, _) => pipeline_id == other_pipeline_id,
_ => false,
},
}
}
}
/// Represents a the difference between two adjacent session history entries.
#[derive(Debug)]
pub enum SessionHistoryDiff {
/// Represents a diff where the active pipeline of an entry changed.
BrowsingContextDiff {
/// The browsing context whose pipeline changed
browsing_context_id: BrowsingContextId,
/// The previous pipeline (used when traversing into the past)
old_reloader: NeedsToReload,
/// The next pipeline (used when traversing into the future)
new_reloader: NeedsToReload,
},
/// Represents a diff where the active state of a pipeline changed.
PipelineDiff {
/// The pipeline whose history state changed.
pipeline_reloader: NeedsToReload,
/// The old history state id.
old_history_state_id: Option<HistoryStateId>,
/// The old url
old_url: ServoUrl,
/// The new history state id.
new_history_state_id: HistoryStateId,
/// The new url
new_url: ServoUrl,
},
HashDiff {
pipeline_reloader: NeedsToReload,
old_url: ServoUrl,
new_url: ServoUrl,
},
}
impl SessionHistoryDiff {
/// Returns the old pipeline id if that pipeline is still alive, otherwise returns `None`
pub fn alive_old_pipeline(&self) -> Option<PipelineId> {
match *self {
SessionHistoryDiff::BrowsingContextDiff {
ref old_reloader, ..
} => match *old_reloader {
NeedsToReload::No(pipeline_id) => Some(pipeline_id),
NeedsToReload::Yes(..) => None,
},
_ => None,
}
}
/// Returns the new pipeline id if that pipeline is still alive, otherwise returns `None`
pub fn alive_new_pipeline(&self) -> Option<PipelineId> {
match *self {
SessionHistoryDiff::BrowsingContextDiff {
ref new_reloader, ..
} => match *new_reloader {
NeedsToReload::No(pipeline_id) => Some(pipeline_id),
NeedsToReload::Yes(..) => None,
},
_ => None,
}
}
/// Replaces all occurances of the replaced pipeline with a new pipeline
pub fn replace_reloader(
&mut self,
replaced_reloader: &NeedsToReload,
reloader: &NeedsToReload,
) {
match *self {
SessionHistoryDiff::BrowsingContextDiff {
ref mut old_reloader,
ref mut new_reloader,
..
} => {
if *old_reloader == *replaced_reloader {
*old_reloader = reloader.clone();
}
if *new_reloader == *replaced_reloader {
*new_reloader = reloader.clone();
}
},
SessionHistoryDiff::PipelineDiff {
ref mut pipeline_reloader,
..
} => {
if *pipeline_reloader == *replaced_reloader {
*pipeline_reloader = reloader.clone();
}
},
SessionHistoryDiff::HashDiff {
ref mut pipeline_reloader,
..
} => {
if *pipeline_reloader == *replaced_reloader {
*pipeline_reloader = reloader.clone();
}
},
}
}
}

View file

@ -1,17 +1,13 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use ipc_channel::ipc::{self, IpcSender};
use script_traits::{TimerEvent, TimerEventRequest, TimerSchedulerMsg};
use std::cmp::{self, Ord};
use std::collections::BinaryHeap;
use std::sync::mpsc;
use std::sync::mpsc::TryRecvError::{Disconnected, Empty};
use std::thread;
use std::time::{Duration, Instant};
pub struct TimerScheduler;
pub struct TimerScheduler(BinaryHeap<ScheduledEvent>);
struct ScheduledEvent {
request: TimerEventRequest,
@ -38,91 +34,40 @@ impl PartialEq for ScheduledEvent {
}
impl TimerScheduler {
pub fn start() -> IpcSender<TimerSchedulerMsg> {
let (req_ipc_sender, req_ipc_receiver) = ipc::channel().expect("Channel creation failed.");
let (req_sender, req_receiver) = mpsc::sync_channel(1);
pub fn new() -> Self {
TimerScheduler(BinaryHeap::<ScheduledEvent>::new())
}
// We could do this much more directly with recv_timeout
// (https://github.com/rust-lang/rfcs/issues/962).
/// Dispatch any events whose due time is past,
/// and return a timeout corresponding to the earliest scheduled event, if any.
pub fn check_timers(&mut self) -> Option<Duration> {
let now = Instant::now();
loop {
match self.0.peek() {
// Dispatch the event if its due time is past
Some(event) if event.for_time <= now => {
let TimerEventRequest(ref sender, source, id, _) = event.request;
let _ = sender.send(TimerEvent(source, id));
},
// Do not schedule a timeout.
None => return None,
// Schedule a timeout for the earliest event.
Some(event) => return Some(event.for_time - now),
}
// Remove the event from the priority queue
// (Note this only executes when the first event has been dispatched).
self.0.pop();
}
}
// util::thread doesn't give us access to the JoinHandle, which we need for park/unpark,
// so we use the builder directly.
let timeout_thread = thread::Builder::new()
.name(String::from("TimerScheduler"))
.spawn(move || {
// We maintain a priority queue of future events, sorted by due time.
let mut scheduled_events = BinaryHeap::<ScheduledEvent>::new();
loop {
let now = Instant::now();
// Dispatch any events whose due time is past
loop {
match scheduled_events.peek() {
// Dispatch the event if its due time is past
Some(event) if event.for_time <= now => {
let TimerEventRequest(ref sender, source, id, _) = event.request;
let _ = sender.send(TimerEvent(source, id));
},
// Otherwise, we're done dispatching events
_ => break,
}
// Remove the event from the priority queue
// (Note this only executes when the first event has been dispatched
scheduled_events.pop();
}
// Look to see if there are any incoming events
match req_receiver.try_recv() {
// If there is an event, add it to the priority queue
Ok(TimerSchedulerMsg::Request(req)) => {
let TimerEventRequest(_, _, _, delay) = req;
let schedule = Instant::now() + Duration::from_millis(delay.get());
let event = ScheduledEvent { request: req, for_time: schedule };
scheduled_events.push(event);
},
// If there is no incoming event, park the thread,
// it will either be unparked when a new event arrives,
// or by a timeout.
Err(Empty) => match scheduled_events.peek() {
None => thread::park(),
Some(event) => thread::park_timeout(event.for_time - now),
},
// If the channel is closed or we are shutting down, we are done.
Ok(TimerSchedulerMsg::Exit) |
Err(Disconnected) => break,
}
}
// This thread can terminate if the req_ipc_sender is dropped.
warn!("TimerScheduler thread terminated.");
})
.expect("Thread creation failed.")
.thread()
.clone();
// A proxy that just routes incoming IPC requests over the MPSC channel to the timeout thread,
// and unparks the timeout thread each time. Note that if unpark is called while the timeout
// thread isn't parked, this causes the next call to thread::park by the timeout thread
// not to block. This means that the timeout thread won't park when there is a request
// waiting in the MPSC channel buffer.
thread::Builder::new()
.name(String::from("TimerProxy"))
.spawn(move || {
while let Ok(req) = req_ipc_receiver.recv() {
let mut shutting_down = false;
match req {
TimerSchedulerMsg::Exit => shutting_down = true,
_ => {}
}
let _ = req_sender.send(req);
timeout_thread.unpark();
if shutting_down {
break;
}
}
// This thread can terminate if the req_ipc_sender is dropped.
warn!("TimerProxy thread terminated.");
})
.expect("Thread creation failed.");
// Return the IPC sender
req_ipc_sender
/// Handle an incoming timer request.
pub fn handle_timer_request(&mut self, request: TimerSchedulerMsg) {
let TimerEventRequest(_, _, _, delay) = request.0;
let schedule = Instant::now() + Duration::from_millis(delay.get());
let event = ScheduledEvent {
request: request.0,
for_time: schedule,
};
self.0.push(event);
}
}

View file

@ -1,68 +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 http://mozilla.org/MPL/2.0/. */
#[macro_use]
extern crate log;
extern crate ws;
use std::sync::mpsc;
use std::sync::mpsc::channel;
use std::thread;
use ws::{Builder, CloseCode, Handler, Handshake};
enum Message {
ShutdownServer,
}
pub struct Sender(mpsc::Sender<Message>);
struct Connection {
sender: ws::Sender
}
impl Handler for Connection {
fn on_open(&mut self, _: Handshake) -> ws::Result<()> {
debug!("Connection opened.");
Ok(())
}
fn on_close(&mut self, _: CloseCode, _: &str) {
debug!("Connection closed.");
}
fn on_message(&mut self, message: ws::Message) -> ws::Result<()> {
self.sender.send(message)
}
}
pub fn start_server(port: u16) -> Sender {
debug!("Starting server.");
let (sender, receiver) = channel();
thread::Builder::new().name("debugger".to_owned()).spawn(move || {
let socket = Builder::new().build(|sender: ws::Sender| {
Connection { sender: sender }
}).unwrap();
let sender = socket.broadcaster();
thread::Builder::new().name("debugger-websocket".to_owned()).spawn(move || {
socket.listen(("127.0.0.1", port)).unwrap();
}).expect("Thread spawning failed");
while let Ok(message) = receiver.recv() {
match message {
Message::ShutdownServer => {
break;
}
}
}
sender.shutdown().unwrap();
}).expect("Thread spawning failed");
Sender(sender)
}
pub fn shutdown_server(sender: &Sender) {
debug!("Shutting down server.");
let &Sender(ref sender) = sender;
if let Err(_) = sender.send(Message::ShutdownServer) {
warn!("Failed to shut down server.");
}
}

View file

@ -3,6 +3,7 @@ name = "deny_public_fields"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
@ -10,5 +11,5 @@ path = "lib.rs"
proc-macro = true
[dependencies]
syn = "0.11"
synstructure = "0.5"
syn = { version = "1", default-features = false, features = ["derive", "parsing"] }
synstructure = "0.12"

View file

@ -1,26 +1,23 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
extern crate proc_macro;
extern crate syn;
extern crate synstructure;
use std::str::FromStr;
use synstructure::{self, decl_derive};
#[proc_macro_derive(DenyPublicFields)]
pub fn expand_token_stream(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
expand_string(&input.to_string()).parse().unwrap()
}
decl_derive!([DenyPublicFields] => deny_public_fields_derive);
fn expand_string(input: &str) -> String {
let type_ = syn::parse_macro_input(input).unwrap();
let style = synstructure::BindStyle::Ref.into();
synstructure::each_field(&type_, &style, |binding| {
if binding.field.vis != syn::Visibility::Inherited {
panic!("Field {} should not be public", binding.ident);
fn deny_public_fields_derive(s: synstructure::Structure) -> proc_macro::TokenStream {
s.each(|binding| {
if binding.ast().vis != syn::Visibility::Inherited {
panic!(
"Field `{}` should not be public",
binding.ast().ident.as_ref().unwrap_or(&binding.binding)
);
}
"".to_owned()
});
"".to_owned()
proc_macro::TokenStream::from_str("").unwrap()
}

View file

@ -0,0 +1,16 @@
[package]
name = "derive_common"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
publish = false
[lib]
path = "lib.rs"
[dependencies]
darling = { version = "0.10", default-features = false }
proc-macro2 = "1"
quote = "1"
syn = { version = "1", default-features = false, features = ["clone-impls", "parsing"] }
synstructure = "0.12"

View file

@ -0,0 +1,388 @@
/* 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 darling::{FromDeriveInput, FromField, FromVariant};
use proc_macro2::{Span, TokenStream};
use quote::TokenStreamExt;
use syn::{self, AngleBracketedGenericArguments, Binding, DeriveInput, Field};
use syn::{GenericArgument, GenericParam, Ident, Path};
use syn::{PathArguments, PathSegment, QSelf, Type, TypeArray, TypeGroup};
use syn::{TypeParam, TypeParen, TypePath, TypeSlice, TypeTuple};
use syn::{Variant, WherePredicate};
use synstructure::{self, BindStyle, BindingInfo, VariantAst, VariantInfo};
/// Given an input type which has some where clauses already, like:
///
/// struct InputType<T>
/// where
/// T: Zero,
/// {
/// ...
/// }
///
/// Add the necessary `where` clauses so that the output type of a trait
/// fulfils them.
///
/// For example:
///
/// ```ignore
/// <T as ToComputedValue>::ComputedValue: Zero,
/// ```
///
/// This needs to run before adding other bounds to the type parameters.
pub fn propagate_clauses_to_output_type(
where_clause: &mut Option<syn::WhereClause>,
generics: &syn::Generics,
trait_path: &Path,
trait_output: &Ident,
) {
let where_clause = match *where_clause {
Some(ref mut clause) => clause,
None => return,
};
let mut extra_bounds = vec![];
for pred in &where_clause.predicates {
let ty = match *pred {
syn::WherePredicate::Type(ref ty) => ty,
ref predicate => panic!("Unhanded complex where predicate: {:?}", predicate),
};
let path = match ty.bounded_ty {
syn::Type::Path(ref p) => &p.path,
ref ty => panic!("Unhanded complex where type: {:?}", ty),
};
assert!(
ty.lifetimes.is_none(),
"Unhanded complex lifetime bound: {:?}",
ty,
);
let ident = match path_to_ident(path) {
Some(i) => i,
None => panic!("Unhanded complex where type path: {:?}", path),
};
if generics.type_params().any(|param| param.ident == *ident) {
extra_bounds.push(ty.clone());
}
}
for bound in extra_bounds {
let ty = bound.bounded_ty;
let bounds = bound.bounds;
where_clause
.predicates
.push(parse_quote!(<#ty as #trait_path>::#trait_output: #bounds))
}
}
pub fn add_predicate(where_clause: &mut Option<syn::WhereClause>, pred: WherePredicate) {
where_clause
.get_or_insert(parse_quote!(where))
.predicates
.push(pred);
}
pub fn fmap_match<F>(input: &DeriveInput, bind_style: BindStyle, f: F) -> TokenStream
where
F: FnMut(&BindingInfo) -> TokenStream,
{
fmap2_match(input, bind_style, f, |_| None)
}
pub fn fmap2_match<F, G>(
input: &DeriveInput,
bind_style: BindStyle,
mut f: F,
mut g: G,
) -> TokenStream
where
F: FnMut(&BindingInfo) -> TokenStream,
G: FnMut(&BindingInfo) -> Option<TokenStream>,
{
let mut s = synstructure::Structure::new(input);
s.variants_mut().iter_mut().for_each(|v| {
v.bind_with(|_| bind_style);
});
s.each_variant(|variant| {
let (mapped, mapped_fields) = value(variant, "mapped");
let fields_pairs = variant.bindings().iter().zip(mapped_fields.iter());
let mut computations = quote!();
computations.append_all(fields_pairs.map(|(field, mapped_field)| {
let expr = f(field);
quote! { let #mapped_field = #expr; }
}));
computations.append_all(
mapped_fields
.iter()
.map(|mapped_field| match g(mapped_field) {
Some(expr) => quote! { let #mapped_field = #expr; },
None => quote!(),
}),
);
computations.append_all(mapped);
Some(computations)
})
}
pub fn fmap_trait_output(input: &DeriveInput, trait_path: &Path, trait_output: &Ident) -> Path {
let segment = PathSegment {
ident: input.ident.clone(),
arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args: input
.generics
.params
.iter()
.map(|arg| match arg {
&GenericParam::Lifetime(ref data) => {
GenericArgument::Lifetime(data.lifetime.clone())
},
&GenericParam::Type(ref data) => {
let ident = &data.ident;
GenericArgument::Type(parse_quote!(<#ident as #trait_path>::#trait_output))
},
ref arg => panic!("arguments {:?} cannot be mapped yet", arg),
})
.collect(),
colon2_token: Default::default(),
gt_token: Default::default(),
lt_token: Default::default(),
}),
};
segment.into()
}
pub fn map_type_params<F>(ty: &Type, params: &[&TypeParam], self_type: &Path, f: &mut F) -> Type
where
F: FnMut(&Ident) -> Type,
{
match *ty {
Type::Slice(ref inner) => Type::from(TypeSlice {
elem: Box::new(map_type_params(&inner.elem, params, self_type, f)),
..inner.clone()
}),
Type::Array(ref inner) => {
//ref ty, ref expr) => {
Type::from(TypeArray {
elem: Box::new(map_type_params(&inner.elem, params, self_type, f)),
..inner.clone()
})
},
ref ty @ Type::Never(_) => ty.clone(),
Type::Tuple(ref inner) => Type::from(TypeTuple {
elems: inner
.elems
.iter()
.map(|ty| map_type_params(&ty, params, self_type, f))
.collect(),
..inner.clone()
}),
Type::Path(TypePath {
qself: None,
ref path,
}) => {
if let Some(ident) = path_to_ident(path) {
if params.iter().any(|ref param| &param.ident == ident) {
return f(ident);
}
if ident == "Self" {
return Type::from(TypePath {
qself: None,
path: self_type.clone(),
});
}
}
Type::from(TypePath {
qself: None,
path: map_type_params_in_path(path, params, self_type, f),
})
},
Type::Path(TypePath {
ref qself,
ref path,
}) => Type::from(TypePath {
qself: qself.as_ref().map(|qself| QSelf {
ty: Box::new(map_type_params(&qself.ty, params, self_type, f)),
position: qself.position,
..qself.clone()
}),
path: map_type_params_in_path(path, params, self_type, f),
}),
Type::Paren(ref inner) => Type::from(TypeParen {
elem: Box::new(map_type_params(&inner.elem, params, self_type, f)),
..inner.clone()
}),
Type::Group(ref inner) => Type::from(TypeGroup {
elem: Box::new(map_type_params(&inner.elem, params, self_type, f)),
..inner.clone()
}),
ref ty => panic!("type {:?} cannot be mapped yet", ty),
}
}
fn map_type_params_in_path<F>(
path: &Path,
params: &[&TypeParam],
self_type: &Path,
f: &mut F,
) -> Path
where
F: FnMut(&Ident) -> Type,
{
Path {
leading_colon: path.leading_colon,
segments: path
.segments
.iter()
.map(|segment| PathSegment {
ident: segment.ident.clone(),
arguments: match segment.arguments {
PathArguments::AngleBracketed(ref data) => {
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args: data
.args
.iter()
.map(|arg| match arg {
ty @ &GenericArgument::Lifetime(_) => ty.clone(),
&GenericArgument::Type(ref data) => GenericArgument::Type(
map_type_params(data, params, self_type, f),
),
&GenericArgument::Binding(ref data) => {
GenericArgument::Binding(Binding {
ty: map_type_params(&data.ty, params, self_type, f),
..data.clone()
})
},
ref arg => panic!("arguments {:?} cannot be mapped yet", arg),
})
.collect(),
..data.clone()
})
},
ref arg @ PathArguments::None => arg.clone(),
ref parameters => panic!("parameters {:?} cannot be mapped yet", parameters),
},
})
.collect(),
}
}
fn path_to_ident(path: &Path) -> Option<&Ident> {
match *path {
Path {
leading_colon: None,
ref segments,
} if segments.len() == 1 => {
if segments[0].arguments.is_empty() {
Some(&segments[0].ident)
} else {
None
}
},
_ => None,
}
}
pub fn parse_field_attrs<A>(field: &Field) -> A
where
A: FromField,
{
match A::from_field(field) {
Ok(attrs) => attrs,
Err(e) => panic!("failed to parse field attributes: {}", e),
}
}
pub fn parse_input_attrs<A>(input: &DeriveInput) -> A
where
A: FromDeriveInput,
{
match A::from_derive_input(input) {
Ok(attrs) => attrs,
Err(e) => panic!("failed to parse input attributes: {}", e),
}
}
pub fn parse_variant_attrs_from_ast<A>(variant: &VariantAst) -> A
where
A: FromVariant,
{
let v = Variant {
ident: variant.ident.clone(),
attrs: variant.attrs.to_vec(),
fields: variant.fields.clone(),
discriminant: variant.discriminant.clone(),
};
parse_variant_attrs(&v)
}
pub fn parse_variant_attrs<A>(variant: &Variant) -> A
where
A: FromVariant,
{
match A::from_variant(variant) {
Ok(attrs) => attrs,
Err(e) => panic!("failed to parse variant attributes: {}", e),
}
}
pub fn ref_pattern<'a>(
variant: &'a VariantInfo,
prefix: &str,
) -> (TokenStream, Vec<BindingInfo<'a>>) {
let mut v = variant.clone();
v.bind_with(|_| BindStyle::Ref);
v.bindings_mut().iter_mut().for_each(|b| {
b.binding = Ident::new(&format!("{}_{}", b.binding, prefix), Span::call_site())
});
(v.pat(), v.bindings().to_vec())
}
pub fn value<'a>(variant: &'a VariantInfo, prefix: &str) -> (TokenStream, Vec<BindingInfo<'a>>) {
let mut v = variant.clone();
v.bindings_mut().iter_mut().for_each(|b| {
b.binding = Ident::new(&format!("{}_{}", b.binding, prefix), Span::call_site())
});
v.bind_with(|_| BindStyle::Move);
(v.pat(), v.bindings().to_vec())
}
/// Transforms "FooBar" to "foo-bar".
///
/// If the first Camel segment is "Moz", "Webkit", or "Servo", the result string
/// is prepended with "-".
pub fn to_css_identifier(mut camel_case: &str) -> String {
camel_case = camel_case.trim_end_matches('_');
let mut first = true;
let mut result = String::with_capacity(camel_case.len());
while let Some(segment) = split_camel_segment(&mut camel_case) {
if first {
match segment {
"Moz" | "Webkit" | "Servo" => first = false,
_ => {},
}
}
if !first {
result.push_str("-");
}
first = false;
result.push_str(&segment.to_lowercase());
}
result
}
/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
let index = match camel_case.chars().next() {
None => return None,
Some(ch) => ch.len_utf8(),
};
let end_position = camel_case[index..]
.find(char::is_uppercase)
.map_or(camel_case.len(), |pos| index + pos);
let result = &camel_case[..end_position];
*camel_case = &camel_case[end_position..];
Some(result)
}

View file

@ -0,0 +1,13 @@
/* 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/. */
extern crate darling;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
extern crate synstructure;
pub mod cg;

View file

@ -3,6 +3,7 @@ name = "devtools"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
publish = false
[lib]
@ -10,14 +11,19 @@ name = "devtools"
path = "lib.rs"
[dependencies]
devtools_traits = {path = "../devtools_traits"}
encoding = "0.2"
hyper = "0.10"
hyper_serde = "0.6"
ipc-channel = "0.7"
log = "0.3.5"
msg = {path = "../msg"}
serde = "0.9"
serde_derive = "0.9"
serde_json = "0.9"
crossbeam-channel = "0.4"
devtools_traits = { path = "../devtools_traits" }
embedder_traits = { path = "../embedder_traits" }
headers = "0.2"
http = "0.1"
hyper = "0.12"
ipc-channel = "0.14"
log = "0.4"
msg = { path = "../msg" }
serde = "1.0"
serde_json = "1.0"
servo_config = { path = "../config" }
servo_rand = { path = "../rand" }
servo_url = { path = "../url" }
time = "0.1"
uuid = { version = "0.8", features = ["v4"] }

View file

@ -1,9 +1,9 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/// General actor system infrastructure.
use crate::StreamId;
use devtools_traits::PreciseTime;
use serde_json::{Map, Value};
use std::any::Any;
@ -22,29 +22,37 @@ pub enum ActorMessageStatus {
/// A common trait for all devtools actors that encompasses an immutable name
/// and the ability to process messages that are directed to particular actors.
/// TODO: ensure the name is immutable
pub trait Actor: Any + ActorAsAny {
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()>;
pub(crate) trait Actor: Any + ActorAsAny {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
id: StreamId,
) -> Result<ActorMessageStatus, ()>;
fn name(&self) -> String;
fn cleanup(&self, _id: StreamId) {}
}
pub trait ActorAsAny {
fn actor_as_any(&self) -> &Any;
fn actor_as_any_mut(&mut self) -> &mut Any;
pub(crate) trait ActorAsAny {
fn actor_as_any(&self) -> &dyn Any;
fn actor_as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Actor> ActorAsAny for T {
fn actor_as_any(&self) -> &Any { self }
fn actor_as_any_mut(&mut self) -> &mut Any { self }
fn actor_as_any(&self) -> &dyn Any {
self
}
fn actor_as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
/// A list of known, owned actors.
pub struct ActorRegistry {
actors: HashMap<String, Box<Actor + Send>>,
new_actors: RefCell<Vec<Box<Actor + Send>>>,
actors: HashMap<String, Box<dyn Actor + Send>>,
new_actors: RefCell<Vec<Box<dyn Actor + Send>>>,
old_actors: RefCell<Vec<String>>,
script_actors: RefCell<HashMap<String, String>>,
shareable: Option<Arc<Mutex<ActorRegistry>>>,
@ -57,8 +65,8 @@ impl ActorRegistry {
pub fn new() -> ActorRegistry {
ActorRegistry {
actors: HashMap::new(),
new_actors: RefCell::new(vec!()),
old_actors: RefCell::new(vec!()),
new_actors: RefCell::new(vec![]),
old_actors: RefCell::new(vec![]),
script_actors: RefCell::new(HashMap::new()),
shareable: None,
next: Cell::new(0),
@ -66,6 +74,12 @@ impl ActorRegistry {
}
}
pub(crate) fn cleanup(&self, id: StreamId) {
for actor in self.actors.values() {
actor.cleanup(id);
}
}
/// Creating shareable registry
pub fn create_shareable(self) -> Arc<Mutex<ActorRegistry>> {
if let Some(shareable) = self.shareable {
@ -126,11 +140,11 @@ impl ActorRegistry {
}
/// Add an actor to the registry of known actors that can receive messages.
pub fn register(&mut self, actor: Box<Actor + Send>) {
pub(crate) fn register(&mut self, actor: Box<dyn Actor + Send>) {
self.actors.insert(actor.name(), actor);
}
pub fn register_later(&self, actor: Box<Actor + Send>) {
pub(crate) fn register_later(&self, actor: Box<dyn Actor + Send>) {
let mut actors = self.new_actors.borrow_mut();
actors.push(actor);
}
@ -149,29 +163,40 @@ impl ActorRegistry {
/// Attempt to process a message as directed by its `to` property. If the actor is not
/// found or does not indicate that it knew how to process the message, ignore the failure.
pub fn handle_message(&mut self,
msg: &Map<String, Value>,
stream: &mut TcpStream)
-> Result<(), ()> {
let to = msg.get("to").unwrap().as_str().unwrap();
pub(crate) fn handle_message(
&mut self,
msg: &Map<String, Value>,
stream: &mut TcpStream,
id: StreamId,
) -> Result<(), ()> {
let to = match msg.get("to") {
Some(to) => to.as_str().unwrap(),
None => {
warn!("Received unexpected message: {:?}", msg);
return Err(());
},
};
match self.actors.get(to) {
None => debug!("message received for unknown actor \"{}\"", to),
Some(actor) => {
let msg_type = msg.get("type").unwrap().as_str().unwrap();
if try!(actor.handle_message(self, msg_type, msg, stream))
!= ActorMessageStatus::Processed {
debug!("unexpected message type \"{}\" found for actor \"{}\"",
msg_type, to);
if actor.handle_message(self, msg_type, msg, stream, id)? !=
ActorMessageStatus::Processed
{
debug!(
"unexpected message type \"{}\" found for actor \"{}\"",
msg_type, to
);
}
}
},
}
let new_actors = replace(&mut *self.new_actors.borrow_mut(), vec!());
let new_actors = replace(&mut *self.new_actors.borrow_mut(), vec![]);
for actor in new_actors.into_iter() {
self.actors.insert(actor.name().to_owned(), actor);
}
let old_actors = replace(&mut *self.old_actors.borrow_mut(), vec!());
let old_actors = replace(&mut *self.old_actors.borrow_mut(), vec![]);
for name in old_actors {
self.drop_actor(name);
}

View file

@ -0,0 +1,385 @@
/* 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/. */
//! Liberally derived from the [Firefox JS implementation]
//! (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webbrowser.js).
//! Connection point for remote devtools that wish to investigate a particular Browsing Context's contents.
//! Supports dynamic attaching and detaching which control notifications of navigation, etc.
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::emulation::EmulationActor;
use crate::actors::inspector::InspectorActor;
use crate::actors::performance::PerformanceActor;
use crate::actors::profiler::ProfilerActor;
use crate::actors::stylesheets::StyleSheetsActor;
use crate::actors::tab::TabDescriptorActor;
use crate::actors::thread::ThreadActor;
use crate::actors::timeline::TimelineActor;
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use devtools_traits::DevtoolScriptControlMsg::{self, WantsLiveNotifications};
use devtools_traits::DevtoolsPageInfo;
use devtools_traits::NavigationState;
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::{BrowsingContextId, PipelineId};
use serde_json::{Map, Value};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::net::TcpStream;
#[derive(Serialize)]
struct BrowsingContextTraits {
isBrowsingContext: bool,
}
#[derive(Serialize)]
struct AttachedTraits {
reconfigure: bool,
frames: bool,
logInPage: bool,
canRewind: bool,
watchpoints: bool,
}
#[derive(Serialize)]
struct BrowsingContextAttachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
threadActor: String,
cacheDisabled: bool,
javascriptEnabled: bool,
traits: AttachedTraits,
}
#[derive(Serialize)]
struct BrowsingContextDetachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
}
#[derive(Serialize)]
struct ReconfigureReply {
from: String,
}
#[derive(Serialize)]
struct ListFramesReply {
from: String,
frames: Vec<FrameMsg>,
}
#[derive(Serialize)]
struct FrameMsg {
id: u32,
url: String,
title: String,
parentID: u32,
}
#[derive(Serialize)]
struct ListWorkersReply {
from: String,
workers: Vec<WorkerMsg>,
}
#[derive(Serialize)]
struct WorkerMsg {
id: u32,
}
#[derive(Serialize)]
pub struct BrowsingContextActorMsg {
actor: String,
title: String,
url: String,
outerWindowID: u32,
browsingContextId: u32,
consoleActor: String,
/*emulationActor: String,
inspectorActor: String,
timelineActor: String,
profilerActor: String,
performanceActor: String,
styleSheetsActor: String,*/
traits: BrowsingContextTraits,
// Part of the official protocol, but not yet implemented.
/*storageActor: String,
memoryActor: String,
framerateActor: String,
reflowActor: String,
cssPropertiesActor: String,
animationsActor: String,
webExtensionInspectedWindowActor: String,
accessibilityActor: String,
screenshotActor: String,
changesActor: String,
webSocketActor: String,
manifestActor: String,*/
}
pub(crate) struct BrowsingContextActor {
pub name: String,
pub title: RefCell<String>,
pub url: RefCell<String>,
pub console: String,
pub _emulation: String,
pub _inspector: String,
pub _timeline: String,
pub _profiler: String,
pub _performance: String,
pub _styleSheets: String,
pub thread: String,
pub _tab: String,
pub streams: RefCell<HashMap<StreamId, TcpStream>>,
pub browsing_context_id: BrowsingContextId,
pub active_pipeline: Cell<PipelineId>,
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
}
impl Actor for BrowsingContextActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"reconfigure" => {
if let Some(options) = msg.get("options").and_then(|o| o.as_object()) {
if let Some(val) = options.get("performReload") {
if val.as_bool().unwrap_or(false) {
let _ = self
.script_chan
.send(DevtoolScriptControlMsg::Reload(self.active_pipeline.get()));
}
}
}
let _ = stream.write_json_packet(&ReconfigureReply { from: self.name() });
ActorMessageStatus::Processed
},
// https://docs.firefox-dev.tools/backend/protocol.html#listing-browser-tabs
// (see "To attach to a _targetActor_")
"attach" => {
let msg = BrowsingContextAttachedReply {
from: self.name(),
type_: "tabAttached".to_owned(),
threadActor: self.thread.clone(),
cacheDisabled: false,
javascriptEnabled: true,
traits: AttachedTraits {
reconfigure: false,
frames: true,
logInPage: false,
canRewind: false,
watchpoints: false,
},
};
if stream.write_json_packet(&msg).is_err() {
return Ok(ActorMessageStatus::Processed);
}
self.streams
.borrow_mut()
.insert(id, stream.try_clone().unwrap());
self.script_chan
.send(WantsLiveNotifications(self.active_pipeline.get(), true))
.unwrap();
ActorMessageStatus::Processed
},
"detach" => {
let msg = BrowsingContextDetachedReply {
from: self.name(),
type_: "detached".to_owned(),
};
let _ = stream.write_json_packet(&msg);
self.cleanup(id);
ActorMessageStatus::Processed
},
"listFrames" => {
let msg = ListFramesReply {
from: self.name(),
frames: vec![FrameMsg {
//FIXME: shouldn't ignore pipeline namespace field
id: self.active_pipeline.get().index.0.get(),
parentID: 0,
url: self.url.borrow().clone(),
title: self.title.borrow().clone(),
}],
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"listWorkers" => {
let msg = ListWorkersReply {
from: self.name(),
workers: vec![],
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
fn cleanup(&self, id: StreamId) {
self.streams.borrow_mut().remove(&id);
if self.streams.borrow().is_empty() {
self.script_chan
.send(WantsLiveNotifications(self.active_pipeline.get(), false))
.unwrap();
}
}
}
impl BrowsingContextActor {
pub(crate) fn new(
console: String,
id: BrowsingContextId,
page_info: DevtoolsPageInfo,
pipeline: PipelineId,
script_sender: IpcSender<DevtoolScriptControlMsg>,
actors: &mut ActorRegistry,
) -> BrowsingContextActor {
let emulation = EmulationActor::new(actors.new_name("emulation"));
let name = actors.new_name("target");
let inspector = InspectorActor {
name: actors.new_name("inspector"),
walker: RefCell::new(None),
pageStyle: RefCell::new(None),
highlighter: RefCell::new(None),
script_chan: script_sender.clone(),
browsing_context: name.clone(),
};
let timeline =
TimelineActor::new(actors.new_name("timeline"), pipeline, script_sender.clone());
let profiler = ProfilerActor::new(actors.new_name("profiler"));
let performance = PerformanceActor::new(actors.new_name("performance"));
// the strange switch between styleSheets and stylesheets is due
// to an inconsistency in devtools. See Bug #1498893 in bugzilla
let styleSheets = StyleSheetsActor::new(actors.new_name("stylesheets"));
let thread = ThreadActor::new(actors.new_name("context"));
let DevtoolsPageInfo { title, url } = page_info;
let tabdesc = TabDescriptorActor::new(actors, name.clone());
let target = BrowsingContextActor {
name: name,
script_chan: script_sender,
title: RefCell::new(String::from(title)),
url: RefCell::new(url.into_string()),
console: console,
_emulation: emulation.name(),
_inspector: inspector.name(),
_timeline: timeline.name(),
_profiler: profiler.name(),
_performance: performance.name(),
_styleSheets: styleSheets.name(),
_tab: tabdesc.name(),
thread: thread.name(),
streams: RefCell::new(HashMap::new()),
browsing_context_id: id,
active_pipeline: Cell::new(pipeline),
};
actors.register(Box::new(emulation));
actors.register(Box::new(inspector));
actors.register(Box::new(timeline));
actors.register(Box::new(profiler));
actors.register(Box::new(performance));
actors.register(Box::new(styleSheets));
actors.register(Box::new(thread));
actors.register(Box::new(tabdesc));
target
}
pub fn encodable(&self) -> BrowsingContextActorMsg {
BrowsingContextActorMsg {
actor: self.name(),
traits: BrowsingContextTraits {
isBrowsingContext: true,
},
title: self.title.borrow().clone(),
url: self.url.borrow().clone(),
//FIXME: shouldn't ignore pipeline namespace field
browsingContextId: self.browsing_context_id.index.0.get(),
//FIXME: shouldn't ignore pipeline namespace field
outerWindowID: self.active_pipeline.get().index.0.get(),
consoleActor: self.console.clone(),
/*emulationActor: self.emulation.clone(),
inspectorActor: self.inspector.clone(),
timelineActor: self.timeline.clone(),
profilerActor: self.profiler.clone(),
performanceActor: self.performance.clone(),
styleSheetsActor: self.styleSheets.clone(),*/
}
}
pub(crate) fn navigate(&self, state: NavigationState) {
let (pipeline, title, url, state) = match state {
NavigationState::Start(url) => (None, None, url, "start"),
NavigationState::Stop(pipeline, info) => {
(Some(pipeline), Some(info.title), info.url, "stop")
},
};
if let Some(p) = pipeline {
self.active_pipeline.set(p);
}
*self.url.borrow_mut() = url.as_str().to_owned();
if let Some(ref t) = title {
*self.title.borrow_mut() = t.clone();
}
let msg = TabNavigated {
from: self.name(),
type_: "tabNavigated".to_owned(),
url: url.as_str().to_owned(),
title: title,
nativeConsoleAPI: true,
state: state.to_owned(),
isFrameSwitching: false,
};
for stream in self.streams.borrow_mut().values_mut() {
let _ = stream.write_json_packet(&msg);
}
}
pub(crate) fn title_changed(&self, pipeline: PipelineId, title: String) {
if pipeline != self.active_pipeline.get() {
return;
}
*self.title.borrow_mut() = title;
}
}
#[derive(Serialize)]
struct TabNavigated {
from: String,
#[serde(rename = "type")]
type_: String,
url: String,
title: Option<String>,
nativeConsoleAPI: bool,
state: String,
isFrameSwitching: bool,
}

View file

@ -1,24 +1,33 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Liberally derived from the [Firefox JS implementation]
//! (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
//! Mediates interaction between the remote web console and equivalent functionality (object
//! inspection, JS evaluation, autocompletion) in Servo.
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use actors::object::ObjectActor;
use devtools_traits::{CONSOLE_API, CachedConsoleMessageTypes, DevtoolScriptControlMsg, PAGE_ERROR};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::object::ObjectActor;
use crate::actors::worker::WorkerActor;
use crate::protocol::JsonPacketStream;
use crate::{StreamId, UniqueId};
use devtools_traits::CachedConsoleMessage;
use devtools_traits::ConsoleMessage;
use devtools_traits::EvaluateJSReply::{ActorValue, BooleanValue, StringValue};
use devtools_traits::EvaluateJSReply::{NullValue, NumberValue, VoidValue};
use devtools_traits::{
CachedConsoleMessageTypes, ConsoleAPI, DevtoolScriptControlMsg, LogLevel, PageError,
};
use ipc_channel::ipc::{self, IpcSender};
use msg::constellation_msg::PipelineId;
use protocol::JsonPacketStream;
use msg::constellation_msg::TEST_PIPELINE_ID;
use serde_json::{self, Map, Number, Value};
use std::cell::RefCell;
use std::collections::HashMap;
use std::net::TcpStream;
use time::precise_time_ns;
use uuid::Uuid;
trait EncodableConsoleMessage {
fn encode(&self) -> serde_json::Result<String>;
@ -34,9 +43,7 @@ impl EncodableConsoleMessage for CachedConsoleMessage {
}
#[derive(Serialize)]
struct StartedListenersTraits {
customNetworkRequest: bool,
}
struct StartedListenersTraits;
#[derive(Serialize)]
struct StartedListenersReply {
@ -72,21 +79,240 @@ struct EvaluateJSReply {
result: Value,
timestamp: u64,
exception: Value,
exceptionMessage: String,
exceptionMessage: Value,
helperResult: Value,
}
#[derive(Serialize)]
struct EvaluateJSEvent {
from: String,
r#type: String,
input: String,
result: Value,
timestamp: u64,
resultID: String,
exception: Value,
exceptionMessage: Value,
helperResult: Value,
}
#[derive(Serialize)]
struct EvaluateJSAsyncReply {
from: String,
resultID: String,
}
#[derive(Serialize)]
struct SetPreferencesReply {
from: String,
updated: Vec<String>,
}
pub struct ConsoleActor {
pub(crate) enum Root {
BrowsingContext(String),
DedicatedWorker(String),
}
pub(crate) struct ConsoleActor {
pub name: String,
pub pipeline: PipelineId,
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
pub streams: RefCell<Vec<TcpStream>>,
pub root: Root,
pub cached_events: RefCell<HashMap<UniqueId, Vec<CachedConsoleMessage>>>,
}
impl ConsoleActor {
fn script_chan<'a>(
&self,
registry: &'a ActorRegistry,
) -> &'a IpcSender<DevtoolScriptControlMsg> {
match &self.root {
Root::BrowsingContext(bc) => &registry.find::<BrowsingContextActor>(bc).script_chan,
Root::DedicatedWorker(worker) => &registry.find::<WorkerActor>(worker).script_chan,
}
}
fn streams_mut<'a>(&self, registry: &'a ActorRegistry, cb: impl Fn(&mut TcpStream)) {
match &self.root {
Root::BrowsingContext(bc) => registry
.find::<BrowsingContextActor>(bc)
.streams
.borrow_mut()
.values_mut()
.for_each(cb),
Root::DedicatedWorker(worker) => registry
.find::<WorkerActor>(worker)
.streams
.borrow_mut()
.values_mut()
.for_each(cb),
}
}
fn current_unique_id(&self, registry: &ActorRegistry) -> UniqueId {
match &self.root {
Root::BrowsingContext(bc) => UniqueId::Pipeline(
registry
.find::<BrowsingContextActor>(bc)
.active_pipeline
.get(),
),
Root::DedicatedWorker(w) => UniqueId::Worker(registry.find::<WorkerActor>(w).id),
}
}
fn evaluateJS(
&self,
registry: &ActorRegistry,
msg: &Map<String, Value>,
) -> Result<EvaluateJSReply, ()> {
let input = msg.get("text").unwrap().as_str().unwrap().to_owned();
let (chan, port) = ipc::channel().unwrap();
// FIXME: redesign messages so we don't have to fake pipeline ids when
// communicating with workers.
let pipeline = match self.current_unique_id(registry) {
UniqueId::Pipeline(p) => p,
UniqueId::Worker(_) => TEST_PIPELINE_ID,
};
self.script_chan(registry)
.send(DevtoolScriptControlMsg::EvaluateJS(
pipeline,
input.clone(),
chan,
))
.unwrap();
//TODO: extract conversion into protocol module or some other useful place
let result = match port.recv().map_err(|_| ())? {
VoidValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("undefined".to_owned()));
Value::Object(m)
},
NullValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("null".to_owned()));
Value::Object(m)
},
BooleanValue(val) => Value::Bool(val),
NumberValue(val) => {
if val.is_nan() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("NaN".to_owned()));
Value::Object(m)
} else if val.is_infinite() {
let mut m = Map::new();
if val < 0. {
m.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
} else {
m.insert("type".to_owned(), Value::String("Infinity".to_owned()));
}
Value::Object(m)
} else if val == 0. && val.is_sign_negative() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("-0".to_owned()));
Value::Object(m)
} else {
Value::Number(Number::from_f64(val).unwrap())
}
},
StringValue(s) => Value::String(s),
ActorValue { class, uuid } => {
//TODO: make initial ActorValue message include these properties?
let mut m = Map::new();
let actor = ObjectActor::new(registry, uuid);
m.insert("type".to_owned(), Value::String("object".to_owned()));
m.insert("class".to_owned(), Value::String(class));
m.insert("actor".to_owned(), Value::String(actor));
m.insert("extensible".to_owned(), Value::Bool(true));
m.insert("frozen".to_owned(), Value::Bool(false));
m.insert("sealed".to_owned(), Value::Bool(false));
Value::Object(m)
},
};
//TODO: catch and return exception values from JS evaluation
let reply = EvaluateJSReply {
from: self.name(),
input: input,
result: result,
timestamp: 0,
exception: Value::Null,
exceptionMessage: Value::Null,
helperResult: Value::Null,
};
std::result::Result::Ok(reply)
}
pub(crate) fn handle_page_error(
&self,
page_error: PageError,
id: UniqueId,
registry: &ActorRegistry,
) {
self.cached_events
.borrow_mut()
.entry(id.clone())
.or_insert(vec![])
.push(CachedConsoleMessage::PageError(page_error.clone()));
if id == self.current_unique_id(registry) {
let msg = PageErrorMsg {
from: self.name(),
type_: "pageError".to_owned(),
pageError: page_error,
};
self.streams_mut(registry, |stream| {
let _ = stream.write_json_packet(&msg);
});
}
}
pub(crate) fn handle_console_api(
&self,
console_message: ConsoleMessage,
id: UniqueId,
registry: &ActorRegistry,
) {
let level = match console_message.logLevel {
LogLevel::Debug => "debug",
LogLevel::Info => "info",
LogLevel::Warn => "warn",
LogLevel::Error => "error",
LogLevel::Clear => "clear",
_ => "log",
}
.to_owned();
self.cached_events
.borrow_mut()
.entry(id.clone())
.or_insert(vec![])
.push(CachedConsoleMessage::ConsoleAPI(ConsoleAPI {
type_: "ConsoleAPI".to_owned(),
level: level.clone(),
filename: console_message.filename.clone(),
lineNumber: console_message.lineNumber as u32,
functionName: "".to_string(), //TODO
timeStamp: precise_time_ns(),
private: false,
arguments: vec![console_message.message.clone()],
}));
if id == self.current_unique_id(registry) {
let msg = ConsoleAPICall {
from: self.name(),
type_: "consoleAPICall".to_owned(),
message: ConsoleMsg {
level: level,
timeStamp: precise_time_ns(),
arguments: vec![console_message.message],
filename: console_message.filename,
lineNumber: console_message.lineNumber,
columnNumber: console_message.columnNumber,
},
};
self.streams_mut(registry, |stream| {
let _ = stream.write_json_packet(&msg);
});
}
}
}
impl Actor for ConsoleActor {
@ -94,164 +320,198 @@ impl Actor for ConsoleActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"clearMessagesCache" => {
self.cached_events
.borrow_mut()
.remove(&self.current_unique_id(registry));
ActorMessageStatus::Processed
},
"getCachedMessages" => {
let str_types = msg.get("messageTypes").unwrap().as_array().unwrap().into_iter().map(|json_type| {
json_type.as_str().unwrap()
});
let str_types = msg
.get("messageTypes")
.unwrap()
.as_array()
.unwrap()
.into_iter()
.map(|json_type| json_type.as_str().unwrap());
let mut message_types = CachedConsoleMessageTypes::empty();
for str_type in str_types {
match str_type {
"PageError" => message_types.insert(PAGE_ERROR),
"ConsoleAPI" => message_types.insert(CONSOLE_API),
"PageError" => message_types.insert(CachedConsoleMessageTypes::PAGE_ERROR),
"ConsoleAPI" => {
message_types.insert(CachedConsoleMessageTypes::CONSOLE_API)
},
s => debug!("unrecognized message type requested: \"{}\"", s),
};
};
let (chan, port) = ipc::channel().unwrap();
self.script_chan.send(DevtoolScriptControlMsg::GetCachedMessages(
self.pipeline, message_types, chan)).unwrap();
let messages = try!(port.recv().map_err(|_| ())).into_iter().map(|message| {
let json_string = message.encode().unwrap();
let json = serde_json::from_str::<Value>(&json_string).unwrap();
json.as_object().unwrap().to_owned()
}).collect();
}
let mut messages = vec![];
for event in self
.cached_events
.borrow()
.get(&self.current_unique_id(registry))
.unwrap_or(&vec![])
.iter()
{
let include = match event {
CachedConsoleMessage::PageError(_)
if message_types.contains(CachedConsoleMessageTypes::PAGE_ERROR) =>
{
true
},
CachedConsoleMessage::ConsoleAPI(_)
if message_types.contains(CachedConsoleMessageTypes::CONSOLE_API) =>
{
true
},
_ => false,
};
if include {
let json_string = event.encode().unwrap();
let json = serde_json::from_str::<Value>(&json_string).unwrap();
messages.push(json.as_object().unwrap().to_owned())
}
}
let msg = GetCachedMessagesReply {
from: self.name(),
messages: messages,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"startListeners" => {
//TODO: actually implement listener filters that support starting/stopping
let listeners = msg.get("listeners").unwrap().as_array().unwrap().to_owned();
let msg = StartedListenersReply {
from: self.name(),
nativeConsoleAPI: true,
startedListeners:
vec!("PageError".to_owned(), "ConsoleAPI".to_owned()),
traits: StartedListenersTraits {
customNetworkRequest: true,
}
startedListeners: listeners
.into_iter()
.map(|s| s.as_str().unwrap().to_owned())
.collect(),
traits: StartedListenersTraits,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"stopListeners" => {
//TODO: actually implement listener filters that support starting/stopping
let msg = StopListenersReply {
from: self.name(),
stoppedListeners: msg.get("listeners")
.unwrap()
.as_array()
.unwrap_or(&vec!())
.iter()
.map(|listener| listener.as_str().unwrap().to_owned())
.collect(),
stoppedListeners: msg
.get("listeners")
.unwrap()
.as_array()
.unwrap_or(&vec![])
.iter()
.map(|listener| listener.as_str().unwrap().to_owned())
.collect(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
//TODO: implement autocompletion like onAutocomplete in
// http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js
"autocomplete" => {
let msg = AutocompleteReply {
from: self.name(),
matches: vec!(),
matches: vec![],
matchProp: "".to_owned(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"evaluateJS" => {
let input = msg.get("text").unwrap().as_str().unwrap().to_owned();
let (chan, port) = ipc::channel().unwrap();
self.script_chan.send(DevtoolScriptControlMsg::EvaluateJS(
self.pipeline, input.clone(), chan)).unwrap();
//TODO: extract conversion into protocol module or some other useful place
let result = match try!(port.recv().map_err(|_| ())) {
VoidValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("undefined".to_owned()));
Value::Object(m)
}
NullValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("null".to_owned()));
Value::Object(m)
}
BooleanValue(val) => Value::Bool(val),
NumberValue(val) => {
if val.is_nan() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("NaN".to_owned()));
Value::Object(m)
} else if val.is_infinite() {
let mut m = Map::new();
if val < 0. {
m.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
} else {
m.insert("type".to_owned(), Value::String("Infinity".to_owned()));
}
Value::Object(m)
} else if val == 0. && val.is_sign_negative() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("-0".to_owned()));
Value::Object(m)
} else {
Value::Number(Number::from_f64(val).unwrap())
}
}
StringValue(s) => Value::String(s),
ActorValue { class, uuid } => {
//TODO: make initial ActorValue message include these properties?
let mut m = Map::new();
let actor = ObjectActor::new(registry, uuid);
m.insert("type".to_owned(), Value::String("object".to_owned()));
m.insert("class".to_owned(), Value::String(class));
m.insert("actor".to_owned(), Value::String(actor));
m.insert("extensible".to_owned(), Value::Bool(true));
m.insert("frozen".to_owned(), Value::Bool(false));
m.insert("sealed".to_owned(), Value::Bool(false));
Value::Object(m)
}
};
//TODO: catch and return exception values from JS evaluation
let msg = EvaluateJSReply {
from: self.name(),
input: input,
result: result,
timestamp: 0,
exception: Value::Object(Map::new()),
exceptionMessage: "".to_owned(),
helperResult: Value::Object(Map::new()),
};
stream.write_json_packet(&msg);
let msg = self.evaluateJS(&registry, &msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"evaluateJSAsync" => {
let resultID = Uuid::new_v4().to_string();
let early_reply = EvaluateJSAsyncReply {
from: self.name(),
resultID: resultID.clone(),
};
// Emit an eager reply so that the client starts listening
// for an async event with the resultID
if stream.write_json_packet(&early_reply).is_err() {
return Ok(ActorMessageStatus::Processed);
}
if msg.get("eager").and_then(|v| v.as_bool()).unwrap_or(false) {
// We don't support the side-effect free evaluation that eager evalaution
// really needs.
return Ok(ActorMessageStatus::Processed);
}
let reply = self.evaluateJS(&registry, &msg).unwrap();
let msg = EvaluateJSEvent {
from: self.name(),
r#type: "evaluationResult".to_owned(),
input: reply.input,
result: reply.result,
timestamp: reply.timestamp,
resultID: resultID,
exception: reply.exception,
exceptionMessage: reply.exceptionMessage,
helperResult: reply.helperResult,
};
// Send the data from evaluateJS along with a resultID
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"setPreferences" => {
let msg = SetPreferencesReply {
from: self.name(),
updated: vec![],
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored
_ => ActorMessageStatus::Ignored,
})
}
}
#[derive(Serialize)]
struct ConsoleAPICall {
from: String,
#[serde(rename = "type")]
type_: String,
message: ConsoleMsg,
}
#[derive(Serialize)]
struct ConsoleMsg {
level: String,
timeStamp: u64,
arguments: Vec<String>,
filename: String,
lineNumber: usize,
columnNumber: usize,
}
#[derive(Serialize)]
struct PageErrorMsg {
from: String,
#[serde(rename = "type")]
type_: String,
pageError: PageError,
}

View file

@ -0,0 +1,85 @@
/* 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 crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::protocol::{ActorDescription, Method};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
#[derive(Serialize)]
struct GetDescriptionReply {
from: String,
value: SystemInfo,
}
#[derive(Serialize)]
struct SystemInfo {
apptype: String,
platformVersion: String,
}
pub struct DeviceActor {
pub name: String,
}
impl Actor for DeviceActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getDescription" => {
let msg = GetDescriptionReply {
from: self.name(),
value: SystemInfo {
apptype: "servo".to_string(),
platformVersion: "71.0".to_string(),
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
}
impl DeviceActor {
pub fn new(name: String) -> DeviceActor {
DeviceActor { name: name }
}
pub fn description() -> ActorDescription {
ActorDescription {
category: "actor",
typeName: "device",
methods: vec![Method {
name: "getDescription",
request: Value::Null,
response: Value::Object(
vec![(
"value".to_owned(),
Value::Object(
vec![("_retval".to_owned(), Value::String("json".to_owned()))]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect(),
),
}],
}
}
}

View file

@ -0,0 +1,37 @@
/* 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 crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
pub struct EmulationActor {
pub name: String,
}
impl Actor for EmulationActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
_ => ActorMessageStatus::Ignored,
})
}
}
impl EmulationActor {
pub fn new(name: String) -> EmulationActor {
EmulationActor { name: name }
}
}

View file

@ -1,22 +1,22 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use actors::timeline::HighResolutionStamp;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::timeline::HighResolutionStamp;
use crate::StreamId;
use devtools_traits::DevtoolScriptControlMsg;
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use serde_json::{Map, Value};
use std::mem;
use std::net::TcpStream;
use time::precise_time_ns;
pub struct FramerateActor {
name: String,
pipeline: PipelineId,
script_sender: IpcSender<DevtoolScriptControlMsg>,
start_time: Option<u64>,
is_recording: bool,
ticks: Vec<HighResolutionStamp>,
}
@ -26,33 +26,36 @@ impl Actor for FramerateActor {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Ignored)
}
}
impl FramerateActor {
/// return name of actor
pub fn create(registry: &ActorRegistry,
pipeline_id: PipelineId,
script_sender: IpcSender<DevtoolScriptControlMsg>) -> String {
pub fn create(
registry: &ActorRegistry,
pipeline_id: PipelineId,
script_sender: IpcSender<DevtoolScriptControlMsg>,
) -> String {
let actor_name = registry.new_name("framerate");
let mut actor = FramerateActor {
name: actor_name.clone(),
pipeline: pipeline_id,
script_sender: script_sender,
start_time: None,
is_recording: false,
ticks: Vec::new(),
};
actor.start_recording();
registry.register_later(box actor);
registry.register_later(Box::new(actor));
actor_name
}
@ -60,8 +63,7 @@ impl FramerateActor {
self.ticks.push(HighResolutionStamp::wrap(tick));
if self.is_recording {
let msg = DevtoolScriptControlMsg::RequestAnimationFrame(self.pipeline,
self.name());
let msg = DevtoolScriptControlMsg::RequestAnimationFrame(self.pipeline, self.name());
self.script_sender.send(msg).unwrap();
}
}
@ -75,11 +77,9 @@ impl FramerateActor {
return;
}
self.start_time = Some(precise_time_ns());
self.is_recording = true;
let msg = DevtoolScriptControlMsg::RequestAnimationFrame(self.pipeline,
self.name());
let msg = DevtoolScriptControlMsg::RequestAnimationFrame(self.pipeline, self.name());
self.script_sender.send(msg).unwrap();
}
@ -88,9 +88,7 @@ impl FramerateActor {
return;
}
self.is_recording = false;
self.start_time = None;
}
}
impl Drop for FramerateActor {

View file

@ -1,17 +1,19 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Liberally derived from the [Firefox JS implementation]
//! (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/inspector.js).
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use devtools_traits::{ComputedNodeLayout, DevtoolScriptControlMsg, NodeInfo};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::BrowsingContextActor;
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement, GetRootNode};
use devtools_traits::DevtoolScriptControlMsg::{GetLayout, ModifyAttribute};
use devtools_traits::{ComputedNodeLayout, DevtoolScriptControlMsg, NodeInfo};
use ipc_channel::ipc::{self, IpcSender};
use msg::constellation_msg::PipelineId;
use protocol::JsonPacketStream;
use serde_json::{self, Map, Value};
use std::cell::RefCell;
use std::net::TcpStream;
@ -22,7 +24,7 @@ pub struct InspectorActor {
pub pageStyle: RefCell<Option<String>>,
pub highlighter: RefCell<Option<String>>,
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
pub pipeline: PipelineId,
pub browsing_context: String,
}
#[derive(Serialize)]
@ -61,27 +63,26 @@ impl Actor for HighlighterActor {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"showBoxModel" => {
let msg = ShowBoxModelReply {
from: self.name(),
};
stream.write_json_packet(&msg);
let msg = ShowBoxModelReply { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"hideBoxModel" => {
let msg = HideBoxModelReply {
from: self.name(),
};
stream.write_json_packet(&msg);
let msg = HideBoxModelReply { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})
@ -98,29 +99,36 @@ impl Actor for NodeActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"modifyAttributes" => {
let target = msg.get("to").unwrap().as_str().unwrap();
let mods = msg.get("modifications").unwrap().as_array().unwrap();
let modifications = mods.iter().map(|json_mod| {
serde_json::from_str(&serde_json::to_string(json_mod).unwrap()).unwrap()
}).collect();
let modifications = mods
.iter()
.map(|json_mod| {
serde_json::from_str(&serde_json::to_string(json_mod).unwrap()).unwrap()
})
.collect();
self.script_chan.send(ModifyAttribute(self.pipeline,
registry.actor_to_script(target.to_owned()),
modifications))
.unwrap();
let reply = ModifyAttributeReply {
from: self.name(),
};
stream.write_json_packet(&reply);
self.script_chan
.send(ModifyAttribute(
self.pipeline,
registry.actor_to_script(target.to_owned()),
modifications,
))
.unwrap();
let reply = ModifyAttributeReply { from: self.name() };
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})
@ -175,19 +183,23 @@ struct NodeActorMsg {
}
trait NodeInfoToProtocol {
fn encode(self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId) -> NodeActorMsg;
fn encode(
self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
) -> NodeActorMsg;
}
impl NodeInfoToProtocol for NodeInfo {
fn encode(self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId) -> NodeActorMsg {
fn encode(
self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
) -> NodeActorMsg {
let actor_name = if !actors.script_actor_registered(self.uniqueId.clone()) {
let name = actors.new_name("node");
let node_actor = NodeActor {
@ -196,7 +208,7 @@ impl NodeInfoToProtocol for NodeInfo {
pipeline: pipeline.clone(),
};
actors.register_script_actor(self.uniqueId, name.clone());
actors.register_later(box node_actor);
actors.register_later(Box::new(node_actor));
name
} else {
actors.script_to_actor(self.uniqueId)
@ -215,15 +227,17 @@ impl NodeInfoToProtocol for NodeInfo {
publicId: self.publicId,
systemId: self.systemId,
attrs: self.attrs.into_iter().map(|attr| {
AttrMsg {
attrs: self
.attrs
.into_iter()
.map(|attr| AttrMsg {
namespace: attr.namespace,
name: attr.name,
value: attr.value,
}
}).collect(),
})
.collect(),
pseudoClassLocks: vec!(), //TODO get this data from script
pseudoClassLocks: vec![], //TODO get this data from script
isDisplayed: display,
@ -272,62 +286,70 @@ impl Actor for WalkerActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"querySelector" => {
let msg = QuerySelectorReply {
from: self.name(),
};
stream.write_json_packet(&msg);
let msg = QuerySelectorReply { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"documentElement" => {
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetDocumentElement(self.pipeline, tx)).unwrap();
let doc_elem_info = try!(rx.recv().unwrap().ok_or(()));
let node = doc_elem_info.encode(registry, true, self.script_chan.clone(), self.pipeline);
self.script_chan
.send(GetDocumentElement(self.pipeline, tx))
.unwrap();
let doc_elem_info = rx.recv().unwrap().ok_or(())?;
let node =
doc_elem_info.encode(registry, true, self.script_chan.clone(), self.pipeline);
let msg = DocumentElementReply {
from: self.name(),
node: node,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"clearPseudoClassLocks" => {
let msg = ClearPseudoclassesReply {
from: self.name(),
};
stream.write_json_packet(&msg);
let msg = ClearPseudoclassesReply { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"children" => {
let target = msg.get("node").unwrap().as_str().unwrap();
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetChildren(self.pipeline,
registry.actor_to_script(target.to_owned()),
tx))
.unwrap();
let children = try!(rx.recv().unwrap().ok_or(()));
self.script_chan
.send(GetChildren(
self.pipeline,
registry.actor_to_script(target.to_owned()),
tx,
))
.unwrap();
let children = rx.recv().unwrap().ok_or(())?;
let msg = ChildrenReply {
hasFirst: true,
hasLast: true,
nodes: children.into_iter().map(|child| {
child.encode(registry, true, self.script_chan.clone(), self.pipeline)
}).collect(),
nodes: children
.into_iter()
.map(|child| {
child.encode(registry, true, self.script_chan.clone(), self.pipeline)
})
.collect(),
from: self.name(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})
@ -447,52 +469,74 @@ impl Actor for PageStyleActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getApplied" => {
//TODO: query script for relevant applied styles to node (msg.node)
let msg = GetAppliedReply {
entries: vec!(),
rules: vec!(),
sheets: vec!(),
entries: vec![],
rules: vec![],
sheets: vec![],
from: self.name(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getComputed" => {
//TODO: query script for relevant computed styles on node (msg.node)
let msg = GetComputedReply {
computed: vec!(),
computed: vec![],
from: self.name(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
//TODO: query script for box layout properties of node (msg.node)
"getLayout" => {
let target = msg.get("node").unwrap().as_str().unwrap();
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetLayout(self.pipeline,
registry.actor_to_script(target.to_owned()),
tx))
.unwrap();
self.script_chan
.send(GetLayout(
self.pipeline,
registry.actor_to_script(target.to_owned()),
tx,
))
.unwrap();
let ComputedNodeLayout {
display, position, zIndex, boxSizing,
autoMargins, marginTop, marginRight, marginBottom, marginLeft,
borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth,
paddingTop, paddingRight, paddingBottom, paddingLeft,
width, height,
} = try!(rx.recv().unwrap().ok_or(()));
display,
position,
zIndex,
boxSizing,
autoMargins,
marginTop,
marginRight,
marginBottom,
marginLeft,
borderTopWidth,
borderRightWidth,
borderBottomWidth,
borderLeftWidth,
paddingTop,
paddingRight,
paddingBottom,
paddingLeft,
width,
height,
} = rx.recv().unwrap().ok_or(())?;
let auto_margins = msg.get("autoMargins")
.and_then(&Value::as_bool).unwrap_or(false);
let auto_margins = msg
.get("autoMargins")
.and_then(&Value::as_bool)
.unwrap_or(false);
// http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/styles.js
let msg = GetLayoutReply {
@ -504,10 +548,18 @@ impl Actor for PageStyleActor {
autoMargins: if auto_margins {
let mut m = Map::new();
let auto = serde_json::value::Value::String("auto".to_owned());
if autoMargins.top { m.insert("top".to_owned(), auto.clone()); }
if autoMargins.right { m.insert("right".to_owned(), auto.clone()); }
if autoMargins.bottom { m.insert("bottom".to_owned(), auto.clone()); }
if autoMargins.left { m.insert("left".to_owned(), auto.clone()); }
if autoMargins.top {
m.insert("top".to_owned(), auto.clone());
}
if autoMargins.right {
m.insert("right".to_owned(), auto.clone());
}
if autoMargins.bottom {
m.insert("bottom".to_owned(), auto.clone());
}
if autoMargins.left {
m.insert("left".to_owned(), auto);
}
serde_json::value::Value::Object(m)
} else {
serde_json::value::Value::Null
@ -529,9 +581,9 @@ impl Actor for PageStyleActor {
};
let msg = serde_json::to_string(&msg).unwrap();
let msg = serde_json::from_str::<Value>(&msg).unwrap();
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})
@ -543,51 +595,56 @@ impl Actor for InspectorActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
let browsing_context = registry.find::<BrowsingContextActor>(&self.browsing_context);
let pipeline = browsing_context.active_pipeline.get();
Ok(match msg_type {
"getWalker" => {
if self.walker.borrow().is_none() {
let walker = WalkerActor {
name: registry.new_name("walker"),
script_chan: self.script_chan.clone(),
pipeline: self.pipeline,
pipeline: pipeline,
};
let mut walker_name = self.walker.borrow_mut();
*walker_name = Some(walker.name());
registry.register_later(box walker);
registry.register_later(Box::new(walker));
}
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetRootNode(self.pipeline, tx)).unwrap();
let root_info = try!(rx.recv().unwrap().ok_or(()));
self.script_chan.send(GetRootNode(pipeline, tx)).unwrap();
let root_info = rx.recv().unwrap().ok_or(())?;
let node = root_info.encode(registry, false, self.script_chan.clone(), self.pipeline);
let node = root_info.encode(registry, false, self.script_chan.clone(), pipeline);
let msg = GetWalkerReply {
from: self.name(),
walker: WalkerMsg {
actor: self.walker.borrow().clone().unwrap(),
root: node,
}
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getPageStyle" => {
if self.pageStyle.borrow().is_none() {
let style = PageStyleActor {
name: registry.new_name("pageStyle"),
script_chan: self.script_chan.clone(),
pipeline: self.pipeline,
pipeline: pipeline,
};
let mut pageStyle = self.pageStyle.borrow_mut();
*pageStyle = Some(style.name());
registry.register_later(box style);
registry.register_later(Box::new(style));
}
let msg = GetPageStyleReply {
@ -596,9 +653,9 @@ impl Actor for InspectorActor {
actor: self.pageStyle.borrow().clone().unwrap(),
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
//TODO: this is an old message; try adding highlightable to the root traits instead
// and support getHighlighter instead
@ -610,7 +667,7 @@ impl Actor for InspectorActor {
};
let mut highlighter = self.highlighter.borrow_mut();
*highlighter = Some(highlighter_actor.name());
registry.register_later(box highlighter_actor);
registry.register_later(Box::new(highlighter_actor));
}
let msg = GetHighlighterReply {
@ -619,9 +676,9 @@ impl Actor for InspectorActor {
actor: self.highlighter.borrow().clone().unwrap(),
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})

View file

@ -1,8 +1,9 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
@ -28,11 +29,14 @@ impl Actor for MemoryActor {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Ignored)
}
}
@ -42,10 +46,10 @@ impl MemoryActor {
pub fn create(registry: &ActorRegistry) -> String {
let actor_name = registry.new_name("memory");
let actor = MemoryActor {
name: actor_name.clone()
name: actor_name.clone(),
};
registry.register_later(box actor);
registry.register_later(Box::new(actor));
actor_name
}

View file

@ -1,31 +1,27 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Liberally derived from the [Firefox JS implementation]
//! (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
//! Handles interaction with the remote web console on network events (HTTP requests, responses) in Servo.
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use devtools_traits::HttpRequest as DevtoolsHttpRequest;
use devtools_traits::HttpResponse as DevtoolsHttpResponse;
use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding};
use hyper::header::{ContentType, Cookie};
use hyper::header::Headers;
use hyper::http::RawStatus;
use hyper::method::Method;
use protocol::JsonPacketStream;
use headers::{ContentType, Cookie, HeaderMapExt};
use http::{header, HeaderMap};
use hyper::{Method, StatusCode};
use serde_json::{Map, Value};
use std::borrow::Cow;
use std::net::TcpStream;
use time;
use time::Tm;
struct HttpRequest {
url: String,
method: Method,
headers: Headers,
headers: HeaderMap,
body: Option<Vec<u8>>,
startedDateTime: Tm,
timeStamp: i64,
@ -34,9 +30,9 @@ struct HttpRequest {
}
struct HttpResponse {
headers: Option<Headers>,
status: Option<RawStatus>,
body: Option<Vec<u8>>
headers: Option<HeaderMap>,
status: Option<(StatusCode, String)>,
body: Option<Vec<u8>>,
}
pub struct NetworkEventActor {
@ -54,7 +50,7 @@ pub struct EventActor {
pub startedDateTime: String,
pub timeStamp: i64,
pub isXHR: bool,
pub private: bool
pub private: bool,
}
#[derive(Serialize)]
@ -81,14 +77,12 @@ pub struct ResponseContentMsg {
pub discardResponseBody: bool,
}
#[derive(Serialize)]
pub struct ResponseHeadersMsg {
pub headers: usize,
pub headersSize: usize,
}
#[derive(Serialize)]
pub struct RequestCookiesMsg {
pub cookies: usize,
@ -105,7 +99,7 @@ struct GetRequestHeadersReply {
from: String,
headers: Vec<Header>,
headerSize: usize,
rawHeaders: String
rawHeaders: String,
}
#[derive(Serialize)]
@ -119,7 +113,7 @@ struct GetResponseHeadersReply {
from: String,
headers: Vec<Header>,
headerSize: usize,
rawHeaders: String
rawHeaders: String,
}
#[derive(Serialize)]
@ -133,19 +127,19 @@ struct GetResponseContentReply {
struct GetRequestPostDataReply {
from: String,
postData: Option<Vec<u8>>,
postDataDiscarded: bool
postDataDiscarded: bool,
}
#[derive(Serialize)]
struct GetRequestCookiesReply {
from: String,
cookies: Vec<u8>
cookies: Vec<u8>,
}
#[derive(Serialize)]
struct GetResponseCookiesReply {
from: String,
cookies: Vec<u8>
cookies: Vec<u8>,
}
#[derive(Serialize)]
@ -181,22 +175,27 @@ impl Actor for NetworkEventActor {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getRequestHeaders" => {
let mut headers = Vec::new();
let mut rawHeadersString = "".to_owned();
let mut headersSize = 0;
for item in self.request.headers.iter() {
let name = item.name();
let value = item.value_string();
rawHeadersString = rawHeadersString + name + ":" + &value + "\r\n";
headersSize += name.len() + value.len();
headers.push(Header { name: name.to_owned(), value: value.to_owned() });
for (name, value) in self.request.headers.iter() {
let value = &value.to_str().unwrap().to_string();
rawHeadersString = rawHeadersString + name.as_str() + ":" + &value + "\r\n";
headersSize += name.as_str().len() + value.len();
headers.push(Header {
name: name.as_str().to_owned(),
value: value.to_owned(),
});
}
let msg = GetRequestHeadersReply {
from: self.name(),
@ -204,16 +203,15 @@ impl Actor for NetworkEventActor {
headerSize: headersSize,
rawHeaders: rawHeadersString,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getRequestCookies" => {
let mut cookies = Vec::new();
if let Some(req_cookies) = self.request.headers.get_raw("Cookie") {
for cookie in &*req_cookies {
if let Ok(cookie_value) = String::from_utf8(cookie.clone()) {
cookies = cookie_value.into_bytes();
}
for cookie in self.request.headers.get_all(header::COOKIE) {
if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) {
cookies = cookie_value.into_bytes();
}
}
@ -221,34 +219,32 @@ impl Actor for NetworkEventActor {
from: self.name(),
cookies: cookies,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getRequestPostData" => {
let msg = GetRequestPostDataReply {
from: self.name(),
postData: self.request.body.clone(),
postDataDiscarded: false,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getResponseHeaders" => {
if let Some(ref response_headers) = self.response.headers {
let mut headers = vec![];
let mut rawHeadersString = "".to_owned();
let mut headersSize = 0;
for item in response_headers.iter() {
let name = item.name();
let value = item.value_string();
for (name, value) in response_headers.iter() {
headers.push(Header {
name: name.to_owned(),
value: value.clone(),
name: name.as_str().to_owned(),
value: value.to_str().unwrap().to_owned(),
});
headersSize += name.len() + value.len();
rawHeadersString.push_str(name);
headersSize += name.as_str().len() + value.len();
rawHeadersString.push_str(name.as_str());
rawHeadersString.push_str(":");
rawHeadersString.push_str(&value);
rawHeadersString.push_str(value.to_str().unwrap());
rawHeadersString.push_str("\r\n");
}
let msg = GetResponseHeadersReply {
@ -257,17 +253,16 @@ impl Actor for NetworkEventActor {
headerSize: headersSize,
rawHeaders: rawHeadersString,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
}
ActorMessageStatus::Processed
}
},
"getResponseCookies" => {
let mut cookies = Vec::new();
if let Some(res_cookies) = self.request.headers.get_raw("set-cookie") {
for cookie in &*res_cookies {
if let Ok(cookie_value) = String::from_utf8(cookie.clone()) {
cookies = cookie_value.into_bytes();
}
// TODO: This seems quite broken
for cookie in self.request.headers.get_all(header::SET_COOKIE) {
if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) {
cookies = cookie_value.into_bytes();
}
}
@ -275,18 +270,18 @@ impl Actor for NetworkEventActor {
from: self.name(),
cookies: cookies,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getResponseContent" => {
let msg = GetResponseContentReply {
from: self.name(),
content: self.response.body.clone(),
contentDiscarded: self.response.body.is_none(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getEventTimings" => {
// TODO: This is a fake timings msg
let timingsObj = Timings {
@ -304,21 +299,21 @@ impl Actor for NetworkEventActor {
timings: timingsObj,
totalTime: total,
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"getSecurityInfo" => {
// TODO: Send the correct values for securityInfo.
let msg = GetSecurityInfoReply {
from: self.name(),
securityInfo: SecurityInfo {
state: "insecure".to_owned()
state: "insecure".to_owned(),
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored
},
_ => ActorMessageStatus::Ignored,
})
}
}
@ -329,8 +324,8 @@ impl NetworkEventActor {
name: name,
request: HttpRequest {
url: String::new(),
method: Method::Get,
headers: Headers::new(),
method: Method::GET,
headers: HeaderMap::new(),
body: None,
startedDateTime: time::now(),
timeStamp: time::get_time().sec,
@ -361,10 +356,10 @@ impl NetworkEventActor {
pub fn add_response(&mut self, response: DevtoolsHttpResponse) {
self.response.headers = response.headers.clone();
self.response.status = response.status.as_ref().map(|&(s, ref st)| {
let status_text = UTF_8.decode(st, DecoderTrap::Replace).unwrap();
RawStatus(s, Cow::from(status_text))
let status_text = String::from_utf8_lossy(st).into_owned();
(StatusCode::from_u16(s).unwrap(), status_text)
});
self.response.body = response.body.clone();
self.response.body = response.body;
}
pub fn event_actor(&self) -> EventActor {
@ -384,8 +379,13 @@ impl NetworkEventActor {
// TODO: Send the correct values for all these fields.
let hSizeOption = self.response.headers.as_ref().map(|headers| headers.len());
let hSize = hSizeOption.unwrap_or(0);
let (status_code, status_message) = self.response.status.as_ref().
map_or((0, "".to_owned()), |&RawStatus(ref code, ref text)| (*code, text.clone().into_owned()));
let (status_code, status_message) = self
.response
.status
.as_ref()
.map_or((0, "".to_owned()), |(code, text)| {
(code.as_u16(), text.clone())
});
// TODO: Send the correct values for remoteAddress and remotePort and http_version.
ResponseStartMsg {
httpVersion: "HTTP/1.1".to_owned(),
@ -394,16 +394,16 @@ impl NetworkEventActor {
status: status_code.to_string(),
statusText: status_message,
headersSize: hSize,
discardResponseBody: false
discardResponseBody: false,
}
}
pub fn response_content(&self) -> ResponseContentMsg {
let mut mString = "".to_owned();
if let Some(ref headers) = self.response.headers {
mString = match headers.get() {
Some(&ContentType(ref mime)) => mime.to_string(),
None => "".to_owned()
mString = match headers.typed_get::<ContentType>() {
Some(ct) => ct.to_string(),
_ => "".to_owned(),
};
}
// TODO: Set correct values when response's body is sent to the devtools in http_loader.
@ -418,9 +418,9 @@ impl NetworkEventActor {
pub fn response_cookies(&self) -> ResponseCookiesMsg {
let mut cookies_size = 0;
if let Some(ref headers) = self.response.headers {
cookies_size = match headers.get() {
Some(&Cookie(ref cookie)) => cookie.len(),
None => 0
cookies_size = match headers.typed_get::<Cookie>() {
Some(ref cookie) => cookie.len(),
_ => 0,
};
}
ResponseCookiesMsg {
@ -433,10 +433,9 @@ impl NetworkEventActor {
let mut headers_byte_count = 0;
if let Some(ref headers) = self.response.headers {
headers_size = headers.len();
for item in headers.iter() {
headers_byte_count += item.name().len() + item.value_string().len();
for (name, value) in headers.iter() {
headers_byte_count += name.as_str().len() + value.len();
}
}
ResponseHeadersMsg {
headers: headers_size,
@ -445,10 +444,9 @@ impl NetworkEventActor {
}
pub fn request_headers(&self) -> RequestHeadersMsg {
let size = self.request
.headers
.iter()
.fold(0, |acc, h| acc + h.name().len() + h.value_string().len());
let size = self.request.headers.iter().fold(0, |acc, (name, value)| {
acc + name.as_str().len() + value.len()
});
RequestHeadersMsg {
headers: self.request.headers.len(),
headersSize: size,
@ -456,9 +454,9 @@ impl NetworkEventActor {
}
pub fn request_cookies(&self) -> RequestCookiesMsg {
let cookies_size = match self.request.headers.get() {
Some(&Cookie(ref cookie)) => cookie.len(),
None => 0
let cookies_size = match self.request.headers.typed_get::<Cookie>() {
Some(ref cookie) => cookie.len(),
_ => 0,
};
RequestCookiesMsg {
cookies: cookies_size,

View file

@ -1,8 +1,9 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
@ -15,11 +16,14 @@ impl Actor for ObjectActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_: &ActorRegistry,
_: &str,
_: &Map<String, Value>,
_: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_: &ActorRegistry,
_: &str,
_: &Map<String, Value>,
_: &mut TcpStream,
_: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Ignored)
}
}
@ -34,7 +38,7 @@ impl ObjectActor {
};
registry.register_script_actor(uuid, name.clone());
registry.register_later(box actor);
registry.register_later(Box::new(actor));
name
} else {

View file

@ -1,9 +1,10 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use protocol::{ActorDescription, JsonPacketStream, Method};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::{ActorDescription, JsonPacketStream, Method};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
@ -51,11 +52,14 @@ impl Actor for PerformanceActor {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"connect" => {
let msg = ConnectReply {
@ -70,7 +74,7 @@ impl Actor for PerformanceActor {
},
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"canCurrentlyRecord" => {
@ -79,11 +83,11 @@ impl Actor for PerformanceActor {
value: SuccessMsg {
success: true,
errors: vec![],
}
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})
}
@ -91,28 +95,36 @@ impl Actor for PerformanceActor {
impl PerformanceActor {
pub fn new(name: String) -> PerformanceActor {
PerformanceActor {
name: name,
}
PerformanceActor { name: name }
}
pub fn description() -> ActorDescription {
ActorDescription {
category: "actor",
typeName: "performance",
methods: vec![
Method {
name: "canCurrentlyRecord",
request: Value::Object(vec![
("type".to_owned(), Value::String("canCurrentlyRecord".to_owned())),
].into_iter().collect()),
response: Value::Object(vec![
("value".to_owned(), Value::Object(vec![
("_retval".to_owned(), Value::String("json".to_owned())),
].into_iter().collect())),
].into_iter().collect()),
},
],
methods: vec![Method {
name: "canCurrentlyRecord",
request: Value::Object(
vec![(
"type".to_owned(),
Value::String("canCurrentlyRecord".to_owned()),
)]
.into_iter()
.collect(),
),
response: Value::Object(
vec![(
"value".to_owned(),
Value::Object(
vec![("_retval".to_owned(), Value::String("json".to_owned()))]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect(),
),
}],
}
}
}

View file

@ -0,0 +1,136 @@
/* 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 crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use serde_json::{Map, Value};
use servo_config::pref_util::PrefValue;
use servo_config::prefs::pref_map;
use std::net::TcpStream;
pub struct PreferenceActor {
name: String,
}
impl PreferenceActor {
pub fn new(name: String) -> Self {
Self { name }
}
}
impl Actor for PreferenceActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
let pref_value = pref_map().get(msg_type);
Ok(match pref_value {
PrefValue::Float(value) => {
let reply = FloatReply {
from: self.name(),
value: value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
PrefValue::Int(value) => {
let reply = IntReply {
from: self.name(),
value: value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
PrefValue::Str(value) => {
let reply = CharReply {
from: self.name(),
value: value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
PrefValue::Bool(value) => {
let reply = BoolReply {
from: self.name(),
value: value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
PrefValue::Missing => handle_missing_preference(self.name(), msg_type, stream),
})
}
}
// if the preferences are missing from pref_map then we return a
// fake preference response based on msg_type.
fn handle_missing_preference(
name: String,
msg_type: &str,
stream: &mut TcpStream,
) -> ActorMessageStatus {
match msg_type {
"getBoolPref" => {
let reply = BoolReply {
from: name,
value: false,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"getCharPref" => {
let reply = CharReply {
from: name,
value: "".to_owned(),
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"getIntPref" => {
let reply = IntReply {
from: name,
value: 0,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
}
}
#[derive(Serialize)]
struct BoolReply {
from: String,
value: bool,
}
#[derive(Serialize)]
struct CharReply {
from: String,
value: String,
}
#[derive(Serialize)]
struct IntReply {
from: String,
value: i64,
}
#[derive(Serialize)]
struct FloatReply {
from: String,
value: f64,
}

View file

@ -0,0 +1,53 @@
/* 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 crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
#[derive(Serialize)]
struct ListWorkersReply {
from: String,
workers: Vec<u32>, // TODO: use proper JSON structure.
}
pub struct ProcessActor {
name: String,
}
impl ProcessActor {
pub fn new(name: String) -> Self {
Self { name }
}
}
impl Actor for ProcessActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"listWorkers" => {
let reply = ListWorkersReply {
from: self.name(),
workers: vec![],
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
}

View file

@ -1,8 +1,9 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
@ -15,19 +16,20 @@ impl Actor for ProfilerActor {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Ignored)
}
}
impl ProfilerActor {
pub fn new(name: String) -> ProfilerActor {
ProfilerActor {
name: name,
}
ProfilerActor { name: name }
}
}

View file

@ -1,16 +1,18 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/// Liberally derived from the [Firefox JS implementation]
/// (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/root.js).
/// Connection point for all new remote devtools interactions, providing lists of know actors
/// that perform more specific actions (tabs, addons, browser chrome, etc.)
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use actors::performance::PerformanceActor;
use actors::tab::{TabActor, TabActorMsg};
use protocol::{ActorDescription, JsonPacketStream};
/// that perform more specific actions (targets, addons, browser chrome, etc.)
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::device::DeviceActor;
use crate::actors::performance::PerformanceActor;
use crate::actors::tab::{TabDescriptorActor, TabDescriptorActorMsg};
use crate::actors::worker::{WorkerActor, WorkerMsg};
use crate::protocol::{ActorDescription, JsonPacketStream};
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
@ -31,11 +33,26 @@ struct ListAddonsReply {
#[derive(Serialize)]
enum AddonMsg {}
#[derive(Serialize)]
struct GetRootReply {
from: String,
selected: u32,
performanceActor: String,
deviceActor: String,
preferenceActor: String,
}
#[derive(Serialize)]
struct ListTabsReply {
from: String,
selected: u32,
tabs: Vec<TabActorMsg>,
tabs: Vec<TabDescriptorActorMsg>,
}
#[derive(Serialize)]
struct GetTabReply {
from: String,
tab: TabDescriptorActorMsg,
}
#[derive(Serialize)]
@ -51,13 +68,50 @@ pub struct ProtocolDescriptionReply {
types: Types,
}
#[derive(Serialize)]
struct ListWorkersReply {
from: String,
workers: Vec<WorkerMsg>,
}
#[derive(Serialize)]
struct ListServiceWorkerRegistrationsReply {
from: String,
registrations: Vec<u32>, // TODO: follow actual JSON structure.
}
#[derive(Serialize)]
pub struct Types {
performance: ActorDescription,
device: ActorDescription,
}
#[derive(Serialize)]
struct ListProcessesResponse {
from: String,
processes: Vec<ProcessForm>,
}
#[derive(Serialize)]
struct ProcessForm {
actor: String,
id: u32,
isParent: bool,
}
#[derive(Serialize)]
struct GetProcessResponse {
from: String,
form: ProcessForm,
}
pub struct RootActor {
pub tabs: Vec<String>,
pub workers: Vec<String>,
pub performance: String,
pub device: String,
pub preference: String,
pub process: String,
}
impl Actor for RootActor {
@ -65,46 +119,126 @@ impl Actor for RootActor {
"root".to_owned()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"listAddons" => {
let actor = ListAddonsReply {
from: "root".to_owned(),
addons: vec![],
};
stream.write_json_packet(&actor);
let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed
}
},
//https://wiki.mozilla.org/Remote_Debugging_Protocol#Listing_Browser_Tabs
"listProcesses" => {
let reply = ListProcessesResponse {
from: self.name(),
processes: vec![ProcessForm {
actor: self.process.clone(),
id: 0,
isParent: true,
}],
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"getProcess" => {
let reply = GetProcessResponse {
from: self.name(),
form: ProcessForm {
actor: self.process.clone(),
id: 0,
isParent: true,
},
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"getRoot" => {
let actor = GetRootReply {
from: "root".to_owned(),
selected: 0,
performanceActor: self.performance.clone(),
deviceActor: self.device.clone(),
preferenceActor: self.preference.clone(),
};
let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed
},
// https://docs.firefox-dev.tools/backend/protocol.html#listing-browser-tabs
"listTabs" => {
let actor = ListTabsReply {
from: "root".to_owned(),
selected: 0,
tabs: self.tabs.iter().map(|tab| {
registry.find::<TabActor>(tab).encodable()
}).collect()
tabs: self
.tabs
.iter()
.map(|target| {
registry
.find::<TabDescriptorActor>(target)
.encodable(&registry)
})
.collect(),
};
stream.write_json_packet(&actor);
let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed
}
},
"listServiceWorkerRegistrations" => {
let reply = ListServiceWorkerRegistrationsReply {
from: self.name(),
registrations: vec![],
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"listWorkers" => {
let reply = ListWorkersReply {
from: self.name(),
workers: self
.workers
.iter()
.map(|name| registry.find::<WorkerActor>(name).encodable())
.collect(),
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"getTab" => {
let tab = registry.find::<TabDescriptorActor>(&self.tabs[0]);
let reply = GetTabReply {
from: self.name(),
tab: tab.encodable(&registry),
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
},
"protocolDescription" => {
let msg = ProtocolDescriptionReply {
from: self.name(),
types: Types {
performance: PerformanceActor::description(),
device: DeviceActor::description(),
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored
_ => ActorMessageStatus::Ignored,
})
}
}
@ -115,10 +249,10 @@ impl RootActor {
from: "root".to_owned(),
applicationType: "browser".to_owned(),
traits: ActorTraits {
sources: true,
sources: false,
highlightable: true,
customHighlighters: true,
networkMonitor: true
networkMonitor: false,
},
}
}

View file

@ -0,0 +1,52 @@
/* 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 crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
#[derive(Serialize)]
struct GetStyleSheetsReply {
from: String,
styleSheets: Vec<u32>, // TODO: real JSON structure.
}
pub struct StyleSheetsActor {
pub name: String,
}
impl Actor for StyleSheetsActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getStyleSheets" => {
let msg = GetStyleSheetsReply {
from: self.name(),
styleSheets: vec![],
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
}
impl StyleSheetsActor {
pub fn new(name: String) -> StyleSheetsActor {
StyleSheetsActor { name: name }
}
}

View file

@ -1,169 +1,103 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Liberally derived from the [Firefox JS implementation]
//! (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webbrowser.js).
//! Connection point for remote devtools that wish to investigate a particular tab's contents.
//! Supports dynamic attaching and detaching which control notifications of navigation, etc.
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use actors::console::ConsoleActor;
use devtools_traits::DevtoolScriptControlMsg::{self, WantsLiveNotifications};
use protocol::JsonPacketStream;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::root::RootActor;
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
#[derive(Serialize)]
struct TabTraits;
#[derive(Serialize)]
struct TabAttachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
threadActor: String,
cacheDisabled: bool,
javascriptEnabled: bool,
traits: TabTraits,
pub struct TabDescriptorTraits {
getFavicon: bool,
hasTabInfo: bool,
watcher: bool,
}
#[derive(Serialize)]
struct TabDetachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
}
#[derive(Serialize)]
struct ReconfigureReply {
from: String
}
#[derive(Serialize)]
struct ListFramesReply {
from: String,
frames: Vec<FrameMsg>,
}
#[derive(Serialize)]
struct FrameMsg {
id: u32,
url: String,
title: String,
parentID: u32,
}
#[derive(Serialize)]
pub struct TabActorMsg {
pub struct TabDescriptorActorMsg {
actor: String,
title: String,
url: String,
outerWindowID: u32,
consoleActor: String,
inspectorActor: String,
timelineActor: String,
profilerActor: String,
performanceActor: String,
browsingContextId: u32,
traits: TabDescriptorTraits,
}
pub struct TabActor {
pub name: String,
pub title: String,
pub url: String,
pub console: String,
pub inspector: String,
pub timeline: String,
pub profiler: String,
pub performance: String,
pub thread: String,
#[derive(Serialize)]
struct GetTargetReply {
from: String,
frame: BrowsingContextActorMsg,
}
impl Actor for TabActor {
pub struct TabDescriptorActor {
name: String,
browsing_context_actor: String,
}
impl Actor for TabDescriptorActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"reconfigure" => {
if let Some(options) = msg.get("options").and_then(|o| o.as_object()) {
if let Some(val) = options.get("performReload") {
if val.as_bool().unwrap_or(false) {
let console_actor = registry.find::<ConsoleActor>(&self.console);
let _ = console_actor.script_chan.send(
DevtoolScriptControlMsg::Reload(console_actor.pipeline));
}
}
}
stream.write_json_packet(&ReconfigureReply { from: self.name() });
ActorMessageStatus::Processed
}
// https://wiki.mozilla.org/Remote_Debugging_Protocol#Listing_Browser_Tabs
// (see "To attach to a _tabActor_")
"attach" => {
let msg = TabAttachedReply {
"getTarget" => {
let frame = registry
.find::<BrowsingContextActor>(&self.browsing_context_actor)
.encodable();
let _ = stream.write_json_packet(&GetTargetReply {
from: self.name(),
type_: "tabAttached".to_owned(),
threadActor: self.thread.clone(),
cacheDisabled: false,
javascriptEnabled: true,
traits: TabTraits,
};
let console_actor = registry.find::<ConsoleActor>(&self.console);
console_actor.streams.borrow_mut().push(stream.try_clone().unwrap());
stream.write_json_packet(&msg);
console_actor.script_chan.send(
WantsLiveNotifications(console_actor.pipeline, true)).unwrap();
frame,
});
ActorMessageStatus::Processed
}
//FIXME: The current implementation won't work for multiple connections. Need to ensure 105
// that the correct stream is removed.
"detach" => {
let msg = TabDetachedReply {
from: self.name(),
type_: "detached".to_owned(),
};
let console_actor = registry.find::<ConsoleActor>(&self.console);
console_actor.streams.borrow_mut().pop();
stream.write_json_packet(&msg);
console_actor.script_chan.send(
WantsLiveNotifications(console_actor.pipeline, false)).unwrap();
ActorMessageStatus::Processed
}
"listFrames" => {
let msg = ListFramesReply {
from: self.name(),
frames: vec!(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored
},
_ => ActorMessageStatus::Ignored,
})
}
}
impl TabActor {
pub fn encodable(&self) -> TabActorMsg {
TabActorMsg {
impl TabDescriptorActor {
pub(crate) fn new(
actors: &mut ActorRegistry,
browsing_context_actor: String,
) -> TabDescriptorActor {
let name = actors.new_name("tabDescription");
let root = actors.find_mut::<RootActor>("root");
root.tabs.push(name.clone());
TabDescriptorActor {
name: name,
browsing_context_actor,
}
}
pub fn encodable(&self, registry: &ActorRegistry) -> TabDescriptorActorMsg {
let ctx_actor = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
let title = ctx_actor.title.borrow().clone();
let url = ctx_actor.url.borrow().clone();
TabDescriptorActorMsg {
title,
url,
actor: self.name(),
title: self.title.clone(),
url: self.url.clone(),
outerWindowID: 0, //FIXME: this should probably be the pipeline id
consoleActor: self.console.clone(),
inspectorActor: self.inspector.clone(),
timelineActor: self.timeline.clone(),
profilerActor: self.profiler.clone(),
performanceActor: self.performance.clone(),
browsingContextId: ctx_actor.browsing_context_id.index.0.get(),
outerWindowID: ctx_actor.active_pipeline.get().index.0.get(),
traits: TabDescriptorTraits {
getFavicon: false,
hasTabInfo: true,
watcher: false,
},
}
}
}

View file

@ -1,18 +1,23 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use protocol::JsonPacketStream;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use serde_json::{Map, Value};
use std::net::TcpStream;
#[derive(Serialize)]
struct ThreadAttachedReply {
struct ThreadAttached {
from: String,
#[serde(rename = "type")]
type_: String,
actor: String,
frame: u32,
error: u32,
recordingEndpoint: u32,
executionPoint: u32,
poppedFrames: Vec<PoppedFrameMsg>,
why: WhyMsg,
}
@ -33,9 +38,16 @@ struct ThreadResumedReply {
type_: String,
}
#[derive(Serialize)]
struct ThreadInterruptedReply {
from: String,
#[serde(rename = "type")]
type_: String,
}
#[derive(Serialize)]
struct ReconfigureReply {
from: String
from: String,
}
#[derive(Serialize)]
@ -47,15 +59,18 @@ struct SourcesReply {
#[derive(Serialize)]
enum Source {}
#[derive(Serialize)]
struct VoidAttachedReply {
from: String,
}
pub struct ThreadActor {
name: String,
}
impl ThreadActor {
pub fn new(name: String) -> ThreadActor {
ThreadActor {
name: name,
}
ThreadActor { name: name }
}
}
@ -64,21 +79,31 @@ impl Actor for ThreadActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"attach" => {
let msg = ThreadAttachedReply {
let msg = ThreadAttached {
from: self.name(),
type_: "paused".to_owned(),
actor: registry.new_name("pause"),
frame: 0,
error: 0,
recordingEndpoint: 0,
executionPoint: 0,
poppedFrames: vec![],
why: WhyMsg { type_: "attached".to_owned() },
why: WhyMsg {
type_: "attached".to_owned(),
},
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&VoidAttachedReply { from: self.name() });
ActorMessageStatus::Processed
},
@ -87,23 +112,33 @@ impl Actor for ThreadActor {
from: self.name(),
type_: "resumed".to_owned(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&VoidAttachedReply { from: self.name() });
ActorMessageStatus::Processed
},
"interrupt" => {
let msg = ThreadInterruptedReply {
from: self.name(),
type_: "interrupted".to_owned(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"reconfigure" => {
stream.write_json_packet(&ReconfigureReply { from: self.name() });
let _ = stream.write_json_packet(&ReconfigureReply { from: self.name() });
ActorMessageStatus::Processed
}
},
"sources" => {
let msg = SourcesReply {
from: self.name(),
sources: vec![],
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => ActorMessageStatus::Ignored,
})

View file

@ -1,19 +1,21 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use actors::framerate::FramerateActor;
use actors::memory::{MemoryActor, TimelineMemoryReply};
use devtools_traits::{PreciseTime, TimelineMarker, TimelineMarkerType};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::framerate::FramerateActor;
use crate::actors::memory::{MemoryActor, TimelineMemoryReply};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use devtools_traits::DevtoolScriptControlMsg;
use devtools_traits::DevtoolScriptControlMsg::{DropTimelineMarkers, SetTimelineMarkers};
use devtools_traits::{PreciseTime, TimelineMarker, TimelineMarkerType};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use msg::constellation_msg::PipelineId;
use protocol::JsonPacketStream;
use serde::{Serialize, Serializer};
use serde_json::{Map, Value};
use std::cell::RefCell;
use std::error::Error;
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::thread;
@ -44,7 +46,7 @@ struct Emitter {
#[derive(Serialize)]
struct IsRecordingReply {
from: String,
value: bool
value: bool,
}
#[derive(Serialize)]
@ -103,8 +105,10 @@ pub struct HighResolutionStamp(f64);
impl HighResolutionStamp {
pub fn new(start_stamp: PreciseTime, time: PreciseTime) -> HighResolutionStamp {
let duration = start_stamp.to(time).num_microseconds()
.expect("Too big duration in microseconds");
let duration = start_stamp
.to(time)
.num_microseconds()
.expect("Too big duration in microseconds");
HighResolutionStamp(duration as f64 / 1000 as f64)
}
@ -122,11 +126,12 @@ impl Serialize for HighResolutionStamp {
static DEFAULT_TIMELINE_DATA_PULL_TIMEOUT: u64 = 200; //ms
impl TimelineActor {
pub fn new(name: String,
pipeline: PipelineId,
script_sender: IpcSender<DevtoolScriptControlMsg>) -> TimelineActor {
let marker_types = vec!(TimelineMarkerType::Reflow,
TimelineMarkerType::DOMEvent);
pub fn new(
name: String,
pipeline: PipelineId,
script_sender: IpcSender<DevtoolScriptControlMsg>,
) -> TimelineActor {
let marker_types = vec![TimelineMarkerType::Reflow, TimelineMarkerType::DOMEvent];
TimelineActor {
name: name,
@ -141,15 +146,20 @@ impl TimelineActor {
}
}
fn pull_timeline_data(&self, receiver: IpcReceiver<Option<TimelineMarker>>, mut emitter: Emitter) {
fn pull_timeline_data(
&self,
receiver: IpcReceiver<Option<TimelineMarker>>,
mut emitter: Emitter,
) {
let is_recording = self.is_recording.clone();
if !*is_recording.lock().unwrap() {
return;
}
thread::Builder::new().name("PullTimelineMarkers".to_owned()).spawn(move || {
loop {
thread::Builder::new()
.name("PullTimelineMarkers".to_owned())
.spawn(move || loop {
if !*is_recording.lock().unwrap() {
break;
}
@ -158,11 +168,13 @@ impl TimelineActor {
while let Ok(Some(marker)) = receiver.try_recv() {
markers.push(emitter.marker(marker));
}
emitter.send(markers);
if emitter.send(markers).is_err() {
break;
}
thread::sleep(Duration::from_millis(DEFAULT_TIMELINE_DATA_PULL_TIMEOUT));
}
}).expect("Thread spawning failed");
})
.expect("Thread spawning failed");
}
}
@ -171,20 +183,28 @@ impl Actor for TimelineActor {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"start" => {
**self.is_recording.lock().as_mut().unwrap() = true;
let (tx, rx) = ipc::channel::<Option<TimelineMarker>>().unwrap();
self.script_sender.send(SetTimelineMarkers(self.pipeline,
self.marker_types.clone(),
tx)).unwrap();
self.script_sender
.send(SetTimelineMarkers(
self.pipeline,
self.marker_types.clone(),
tx,
))
.unwrap();
//TODO: support multiple connections by using root actor's streams instead.
*self.stream.borrow_mut() = stream.try_clone().ok();
// init memory actor
@ -198,18 +218,22 @@ impl Actor for TimelineActor {
if let Some(with_ticks) = msg.get("withTicks") {
if let Some(true) = with_ticks.as_bool() {
let framerate_actor = Some(FramerateActor::create(
registry,
self.pipeline.clone(),
self.script_sender.clone()));
registry,
self.pipeline.clone(),
self.script_sender.clone(),
));
*self.framerate_actor.borrow_mut() = framerate_actor;
}
}
let emitter = Emitter::new(self.name(), registry.shareable(),
registry.start_stamp(),
stream.try_clone().unwrap(),
self.memory_actor.borrow().clone(),
self.framerate_actor.borrow().clone());
let emitter = Emitter::new(
self.name(),
registry.shareable(),
registry.start_stamp(),
stream.try_clone().unwrap(),
self.memory_actor.borrow().clone(),
self.framerate_actor.borrow().clone(),
);
self.pull_timeline_data(rx, emitter);
@ -217,9 +241,9 @@ impl Actor for TimelineActor {
from: self.name(),
value: HighResolutionStamp::new(registry.start_stamp(), PreciseTime::now()),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
"stop" => {
let msg = StopReply {
@ -227,9 +251,15 @@ impl Actor for TimelineActor {
value: HighResolutionStamp::new(registry.start_stamp(), PreciseTime::now()),
};
stream.write_json_packet(&msg);
self.script_sender.send(DropTimelineMarkers(self.pipeline, self.marker_types.clone())).unwrap();
let _ = stream.write_json_packet(&msg);
self.script_sender
.send(DropTimelineMarkers(
self.pipeline,
self.marker_types.clone(),
))
.unwrap();
//TODO: move this to the cleanup method.
if let Some(ref actor_name) = *self.framerate_actor.borrow() {
registry.drop_actor_later(actor_name.clone());
}
@ -241,32 +271,32 @@ impl Actor for TimelineActor {
**self.is_recording.lock().as_mut().unwrap() = false;
self.stream.borrow_mut().take();
ActorMessageStatus::Processed
}
},
"isRecording" => {
let msg = IsRecordingReply {
from: self.name(),
value: self.is_recording.lock().unwrap().clone()
value: self.is_recording.lock().unwrap().clone(),
};
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
},
_ => {
ActorMessageStatus::Ignored
}
_ => ActorMessageStatus::Ignored,
})
}
}
impl Emitter {
pub fn new(name: String,
registry: Arc<Mutex<ActorRegistry>>,
start_stamp: PreciseTime,
stream: TcpStream,
memory_actor_name: Option<String>,
framerate_actor_name: Option<String>) -> Emitter {
pub fn new(
name: String,
registry: Arc<Mutex<ActorRegistry>>,
start_stamp: PreciseTime,
stream: TcpStream,
memory_actor_name: Option<String>,
framerate_actor_name: Option<String>,
) -> Emitter {
Emitter {
from: name,
stream: stream,
@ -288,7 +318,7 @@ impl Emitter {
}
}
fn send(&mut self, markers: Vec<TimelineMarkerReply>) {
fn send(&mut self, markers: Vec<TimelineMarkerReply>) -> Result<(), Box<dyn Error>> {
let end_time = PreciseTime::now();
let reply = MarkersEmitterReply {
type_: "markers".to_owned(),
@ -296,7 +326,7 @@ impl Emitter {
from: self.from.clone(),
endTime: HighResolutionStamp::new(self.start_stamp, end_time),
};
self.stream.write_json_packet(&reply);
self.stream.write_json_packet(&reply)?;
if let Some(ref actor_name) = self.framerate_actor {
let mut lock = self.registry.lock();
@ -308,7 +338,7 @@ impl Emitter {
delta: HighResolutionStamp::new(self.start_stamp, end_time),
timestamps: framerate_actor.take_pending_ticks(),
};
self.stream.write_json_packet(&framerateReply);
self.stream.write_json_packet(&framerateReply)?;
}
if let Some(ref actor_name) = self.memory_actor {
@ -320,7 +350,9 @@ impl Emitter {
delta: HighResolutionStamp::new(self.start_stamp, end_time),
measurement: memory_actor.measure(),
};
self.stream.write_json_packet(&memoryReply);
self.stream.write_json_packet(&memoryReply)?;
}
Ok(())
}
}

View file

@ -1,27 +1,159 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use devtools_traits::WorkerId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::StreamId;
use devtools_traits::DevtoolScriptControlMsg::WantsLiveNotifications;
use devtools_traits::{DevtoolScriptControlMsg, WorkerId};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::TEST_PIPELINE_ID;
use serde_json::{Map, Value};
use servo_url::ServoUrl;
use std::cell::RefCell;
use std::collections::HashMap;
use std::net::TcpStream;
pub struct WorkerActor {
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum WorkerType {
Dedicated = 0,
Shared = 1,
Service = 2,
}
pub(crate) struct WorkerActor {
pub name: String,
pub console: String,
pub thread: String,
pub id: WorkerId,
pub url: ServoUrl,
pub type_: WorkerType,
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
pub streams: RefCell<HashMap<StreamId, TcpStream>>,
}
impl WorkerActor {
pub(crate) fn encodable(&self) -> WorkerMsg {
WorkerMsg {
actor: self.name.clone(),
consoleActor: self.console.clone(),
threadActor: self.thread.clone(),
id: self.id.0.to_string(),
url: self.url.to_string(),
traits: WorkerTraits {
isParentInterceptEnabled: false,
},
type_: self.type_ as u32,
}
}
}
impl Actor for WorkerActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_: &ActorRegistry,
_: &str,
_: &Map<String, Value>,
_: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Processed)
fn handle_message(
&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"attach" => {
let msg = AttachedReply {
from: self.name(),
type_: "attached".to_owned(),
url: self.url.as_str().to_owned(),
};
if stream.write_json_packet(&msg).is_err() {
return Ok(ActorMessageStatus::Processed);
}
self.streams
.borrow_mut()
.insert(id, stream.try_clone().unwrap());
// FIXME: fix messages to not require forging a pipeline for worker messages
self.script_chan
.send(WantsLiveNotifications(TEST_PIPELINE_ID, true))
.unwrap();
ActorMessageStatus::Processed
},
"connect" => {
let msg = ConnectReply {
from: self.name(),
type_: "connected".to_owned(),
threadActor: self.thread.clone(),
consoleActor: self.console.clone(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"detach" => {
let msg = DetachedReply {
from: self.name(),
type_: "detached".to_string(),
};
let _ = stream.write_json_packet(&msg);
self.cleanup(id);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
fn cleanup(&self, id: StreamId) {
self.streams.borrow_mut().remove(&id);
if self.streams.borrow().is_empty() {
self.script_chan
.send(WantsLiveNotifications(TEST_PIPELINE_ID, false))
.unwrap();
}
}
}
#[derive(Serialize)]
struct DetachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
}
#[derive(Serialize)]
struct AttachedReply {
from: String,
#[serde(rename = "type")]
type_: String,
url: String,
}
#[derive(Serialize)]
struct ConnectReply {
from: String,
#[serde(rename = "type")]
type_: String,
threadActor: String,
consoleActor: String,
}
#[derive(Serialize)]
struct WorkerTraits {
isParentInterceptEnabled: bool,
}
#[derive(Serialize)]
pub(crate) struct WorkerMsg {
actor: String,
consoleActor: String,
threadActor: String,
id: String,
url: String,
traits: WorkerTraits,
#[serde(rename = "type")]
type_: u32,
}

View file

@ -1,6 +1,6 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! An actor-based remote devtools server implementation. Only tested with
//! nightly Firefox versions at time of writing. Largely based on
@ -9,64 +9,63 @@
#![crate_name = "devtools"]
#![crate_type = "rlib"]
#![allow(non_snake_case)]
#![deny(unsafe_code)]
#![feature(box_syntax)]
extern crate devtools_traits;
extern crate encoding;
extern crate hyper;
extern crate ipc_channel;
#[macro_use]
extern crate log;
extern crate msg;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate time;
extern crate serde;
use actor::{Actor, ActorRegistry};
use actors::console::ConsoleActor;
use actors::framerate::FramerateActor;
use actors::inspector::InspectorActor;
use actors::network_event::{EventActor, NetworkEventActor, ResponseStartMsg};
use actors::performance::PerformanceActor;
use actors::profiler::ProfilerActor;
use actors::root::RootActor;
use actors::tab::TabActor;
use actors::thread::ThreadActor;
use actors::timeline::TimelineActor;
use actors::worker::WorkerActor;
use crate::actor::{Actor, ActorRegistry};
use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::console::{ConsoleActor, Root};
use crate::actors::device::DeviceActor;
use crate::actors::framerate::FramerateActor;
use crate::actors::network_event::{EventActor, NetworkEventActor, ResponseStartMsg};
use crate::actors::performance::PerformanceActor;
use crate::actors::preference::PreferenceActor;
use crate::actors::process::ProcessActor;
use crate::actors::root::RootActor;
use crate::actors::thread::ThreadActor;
use crate::actors::worker::{WorkerActor, WorkerType};
use crate::protocol::JsonPacketStream;
use crossbeam_channel::{unbounded, Receiver, Sender};
use devtools_traits::{ChromeToDevtoolsControlMsg, ConsoleMessage, DevtoolsControlMsg};
use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo, LogLevel, NetworkEvent};
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use protocol::JsonPacketStream;
use devtools_traits::{
DevtoolScriptControlMsg, DevtoolsPageInfo, LogLevel, NavigationState, NetworkEvent,
};
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg, WorkerId};
use embedder_traits::{EmbedderMsg, EmbedderProxy, PromptDefinition, PromptOrigin, PromptResult};
use ipc_channel::ipc::{self, IpcSender};
use msg::constellation_msg::{BrowsingContextId, PipelineId};
use servo_rand::RngCore;
use std::borrow::ToOwned;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
use std::io::Read;
use std::net::{Shutdown, TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;
use time::precise_time_ns;
mod actor;
/// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/
mod actors {
pub mod browsing_context;
pub mod console;
pub mod device;
pub mod emulation;
pub mod framerate;
pub mod inspector;
pub mod memory;
pub mod network_event;
pub mod object;
pub mod performance;
pub mod preference;
pub mod process;
pub mod profiler;
pub mod root;
pub mod stylesheets;
pub mod tab;
pub mod thread;
pub mod timeline;
@ -74,22 +73,10 @@ mod actors {
}
mod protocol;
#[derive(Serialize)]
struct ConsoleAPICall {
from: String,
#[serde(rename = "type")]
type_: String,
message: ConsoleMsg,
}
#[derive(Serialize)]
struct ConsoleMsg {
level: String,
timeStamp: u64,
arguments: Vec<String>,
filename: String,
lineNumber: usize,
columnNumber: usize,
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum UniqueId {
Pipeline(PipelineId),
Worker(WorkerId),
}
#[derive(Serialize)]
@ -128,70 +115,118 @@ struct ResponseStartUpdateMsg {
}
/// Spin up a devtools server that listens for connections on the specified port.
pub fn start_server(port: u16) -> Sender<DevtoolsControlMsg> {
let (sender, receiver) = channel();
pub fn start_server(port: u16, embedder: EmbedderProxy) -> Sender<DevtoolsControlMsg> {
let (sender, receiver) = unbounded();
{
let sender = sender.clone();
thread::Builder::new().name("Devtools".to_owned()).spawn(move || {
run_server(sender, receiver, port)
}).expect("Thread spawning failed");
thread::Builder::new()
.name("Devtools".to_owned())
.spawn(move || run_server(sender, receiver, port, embedder))
.expect("Thread spawning failed");
}
sender
}
fn run_server(sender: Sender<DevtoolsControlMsg>,
receiver: Receiver<DevtoolsControlMsg>,
port: u16) {
let listener = TcpListener::bind(&("127.0.0.1", port)).unwrap();
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct StreamId(u32);
fn run_server(
sender: Sender<DevtoolsControlMsg>,
receiver: Receiver<DevtoolsControlMsg>,
port: u16,
embedder: EmbedderProxy,
) {
let bound = TcpListener::bind(&("0.0.0.0", port)).ok().and_then(|l| {
l.local_addr()
.map(|addr| addr.port())
.ok()
.map(|port| (l, port))
});
// A token shared with the embedder to bypass permission prompt.
let token = format!("{:X}", servo_rand::ServoRng::new().next_u32());
let port = bound.as_ref().map(|(_, port)| *port).ok_or(());
embedder.send((None, EmbedderMsg::OnDevtoolsStarted(port, token.clone())));
let listener = match bound {
Some((l, _)) => l,
None => return,
};
let mut registry = ActorRegistry::new();
let root = box RootActor {
tabs: vec!(),
};
let performance = PerformanceActor::new(registry.new_name("performance"));
let device = DeviceActor::new(registry.new_name("device"));
let preference = PreferenceActor::new(registry.new_name("preference"));
let process = ProcessActor::new(registry.new_name("process"));
let root = Box::new(RootActor {
tabs: vec![],
workers: vec![],
device: device.name(),
performance: performance.name(),
preference: preference.name(),
process: process.name(),
});
registry.register(root);
registry.register(Box::new(performance));
registry.register(Box::new(device));
registry.register(Box::new(preference));
registry.register(Box::new(process));
registry.find::<RootActor>("root");
let actors = registry.create_shareable();
let mut accepted_connections: Vec<TcpStream> = Vec::new();
let mut actor_pipelines: HashMap<PipelineId, String> = HashMap::new();
let mut browsing_contexts: HashMap<BrowsingContextId, String> = HashMap::new();
let mut pipelines: HashMap<PipelineId, BrowsingContextId> = HashMap::new();
let mut actor_requests: HashMap<String, String> = HashMap::new();
let mut actor_workers: HashMap<(PipelineId, WorkerId), String> = HashMap::new();
let mut actor_workers: HashMap<WorkerId, String> = HashMap::new();
/// Process the input from a single devtools client until EOF.
fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream) {
fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream, id: StreamId) {
debug!("connection established to {}", stream.peer_addr().unwrap());
{
let actors = actors.lock().unwrap();
let msg = actors.find::<RootActor>("root").encodable();
stream.write_json_packet(&msg);
if let Err(e) = stream.write_json_packet(&msg) {
warn!("Error writing response: {:?}", e);
return;
}
}
'outer: loop {
match stream.read_json_packet() {
Ok(Some(json_packet)) => {
if let Err(()) = actors.lock().unwrap().handle_message(json_packet.as_object().unwrap(),
&mut stream) {
if let Err(()) = actors.lock().unwrap().handle_message(
json_packet.as_object().unwrap(),
&mut stream,
id,
) {
debug!("error: devtools actor stopped responding");
let _ = stream.shutdown(Shutdown::Both);
break 'outer
break 'outer;
}
}
},
Ok(None) => {
debug!("error: EOF");
break 'outer
}
break 'outer;
},
Err(err_msg) => {
debug!("error: {}", err_msg);
break 'outer
}
break 'outer;
},
}
}
actors.lock().unwrap().cleanup(id);
}
fn handle_framerate_tick(actors: Arc<Mutex<ActorRegistry>>, actor_name: String, tick: f64) {
@ -200,155 +235,215 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
framerate_actor.add_tick(tick);
}
// We need separate actor representations for each script global that exists;
// clients can theoretically connect to multiple globals simultaneously.
// TODO: move this into the root or tab modules?
fn handle_new_global(actors: Arc<Mutex<ActorRegistry>>,
ids: (PipelineId, Option<WorkerId>),
script_sender: IpcSender<DevtoolScriptControlMsg>,
actor_pipelines: &mut HashMap<PipelineId, String>,
actor_workers: &mut HashMap<(PipelineId, WorkerId), String>,
page_info: DevtoolsPageInfo) {
let mut actors = actors.lock().unwrap();
let (pipeline, worker_id) = ids;
//TODO: move all this actor creation into a constructor method on TabActor
let (tab, console, inspector, timeline, profiler, performance, thread) = {
let console = ConsoleActor {
name: actors.new_name("console"),
script_chan: script_sender.clone(),
pipeline: pipeline,
streams: RefCell::new(Vec::new()),
};
let inspector = InspectorActor {
name: actors.new_name("inspector"),
walker: RefCell::new(None),
pageStyle: RefCell::new(None),
highlighter: RefCell::new(None),
script_chan: script_sender.clone(),
pipeline: pipeline,
};
let timeline = TimelineActor::new(actors.new_name("timeline"),
pipeline,
script_sender);
let profiler = ProfilerActor::new(actors.new_name("profiler"));
let performance = PerformanceActor::new(actors.new_name("performance"));
let thread = ThreadActor::new(actors.new_name("context"));
let DevtoolsPageInfo { title, url } = page_info;
let tab = TabActor {
name: actors.new_name("tab"),
title: String::from(title),
url: url.into_string(),
console: console.name(),
inspector: inspector.name(),
timeline: timeline.name(),
profiler: profiler.name(),
performance: performance.name(),
thread: thread.name(),
};
let root = actors.find_mut::<RootActor>("root");
root.tabs.push(tab.name.clone());
(tab, console, inspector, timeline, profiler, performance, thread)
};
if let Some(id) = worker_id {
let worker = WorkerActor {
name: actors.new_name("worker"),
console: console.name(),
id: id,
};
actor_workers.insert((pipeline, id), worker.name.clone());
actors.register(box worker);
}
actor_pipelines.insert(pipeline, tab.name.clone());
actors.register(box tab);
actors.register(box console);
actors.register(box inspector);
actors.register(box timeline);
actors.register(box profiler);
actors.register(box performance);
actors.register(box thread);
fn handle_navigate(
actors: Arc<Mutex<ActorRegistry>>,
browsing_contexts: &HashMap<BrowsingContextId, String>,
browsing_context: BrowsingContextId,
state: NavigationState,
) {
let actor_name = browsing_contexts.get(&browsing_context).unwrap();
actors
.lock()
.unwrap()
.find::<BrowsingContextActor>(actor_name)
.navigate(state);
}
fn handle_console_message(actors: Arc<Mutex<ActorRegistry>>,
id: PipelineId,
worker_id: Option<WorkerId>,
console_message: ConsoleMessage,
actor_pipelines: &HashMap<PipelineId, String>,
actor_workers: &HashMap<(PipelineId, WorkerId), String>) {
let console_actor_name = match find_console_actor(actors.clone(), id, worker_id, actor_workers,
actor_pipelines) {
fn handle_title_changed(
actors: Arc<Mutex<ActorRegistry>>,
pipelines: &HashMap<PipelineId, BrowsingContextId>,
browsing_contexts: &HashMap<BrowsingContextId, String>,
pipeline: PipelineId,
title: String,
) {
let bc = match pipelines.get(&pipeline) {
Some(bc) => bc,
None => return,
};
let name = match browsing_contexts.get(&bc) {
Some(name) => name,
None => return,
};
let actors = actors.lock().unwrap();
let browsing_context = actors.find::<BrowsingContextActor>(name);
browsing_context.title_changed(pipeline, title);
}
// We need separate actor representations for each script global that exists;
// clients can theoretically connect to multiple globals simultaneously.
// TODO: move this into the root or target modules?
fn handle_new_global(
actors: Arc<Mutex<ActorRegistry>>,
ids: (BrowsingContextId, PipelineId, Option<WorkerId>),
script_sender: IpcSender<DevtoolScriptControlMsg>,
browsing_contexts: &mut HashMap<BrowsingContextId, String>,
pipelines: &mut HashMap<PipelineId, BrowsingContextId>,
actor_workers: &mut HashMap<WorkerId, String>,
page_info: DevtoolsPageInfo,
) {
let mut actors = actors.lock().unwrap();
let (browsing_context, pipeline, worker_id) = ids;
let console_name = actors.new_name("console");
let parent_actor = if let Some(id) = worker_id {
assert!(pipelines.get(&pipeline).is_some());
assert!(browsing_contexts.get(&browsing_context).is_some());
let thread = ThreadActor::new(actors.new_name("context"));
let thread_name = thread.name();
actors.register(Box::new(thread));
let worker_name = actors.new_name("worker");
let worker = WorkerActor {
name: worker_name.clone(),
console: console_name.clone(),
thread: thread_name,
id: id,
url: page_info.url.clone(),
type_: WorkerType::Dedicated,
script_chan: script_sender,
streams: Default::default(),
};
let root = actors.find_mut::<RootActor>("root");
root.workers.push(worker.name.clone());
actor_workers.insert(id, worker_name.clone());
actors.register(Box::new(worker));
Root::DedicatedWorker(worker_name)
} else {
pipelines.insert(pipeline, browsing_context);
Root::BrowsingContext(
if let Some(actor) = browsing_contexts.get(&browsing_context) {
actor.to_owned()
} else {
let browsing_context_actor = BrowsingContextActor::new(
console_name.clone(),
browsing_context,
page_info,
pipeline,
script_sender,
&mut *actors,
);
let name = browsing_context_actor.name();
browsing_contexts.insert(browsing_context, name.clone());
actors.register(Box::new(browsing_context_actor));
name
},
)
};
let console = ConsoleActor {
name: console_name,
cached_events: Default::default(),
root: parent_actor,
};
actors.register(Box::new(console));
}
fn handle_page_error(
actors: Arc<Mutex<ActorRegistry>>,
id: PipelineId,
worker_id: Option<WorkerId>,
page_error: PageError,
browsing_contexts: &HashMap<BrowsingContextId, String>,
actor_workers: &HashMap<WorkerId, String>,
pipelines: &HashMap<PipelineId, BrowsingContextId>,
) {
let console_actor_name = match find_console_actor(
actors.clone(),
id,
worker_id,
actor_workers,
browsing_contexts,
pipelines,
) {
Some(name) => name,
None => return,
};
let actors = actors.lock().unwrap();
let console_actor = actors.find::<ConsoleActor>(&console_actor_name);
let msg = ConsoleAPICall {
from: console_actor.name.clone(),
type_: "consoleAPICall".to_owned(),
message: ConsoleMsg {
level: match console_message.logLevel {
LogLevel::Debug => "debug",
LogLevel::Info => "info",
LogLevel::Warn => "warn",
LogLevel::Error => "error",
_ => "log"
}.to_owned(),
timeStamp: precise_time_ns(),
arguments: vec!(console_message.message),
filename: console_message.filename,
lineNumber: console_message.lineNumber,
columnNumber: console_message.columnNumber,
},
};
for mut stream in &mut *console_actor.streams.borrow_mut() {
stream.write_json_packet(&msg);
}
let id = worker_id.map_or(UniqueId::Pipeline(id), UniqueId::Worker);
console_actor.handle_page_error(page_error, id, &*actors);
}
fn find_console_actor(actors: Arc<Mutex<ActorRegistry>>,
id: PipelineId,
worker_id: Option<WorkerId>,
actor_workers: &HashMap<(PipelineId, WorkerId), String>,
actor_pipelines: &HashMap<PipelineId, String>) -> Option<String> {
let actors = actors.lock().unwrap();
if let Some(worker_id) = worker_id {
let actor_name = match (*actor_workers).get(&(id, worker_id)) {
Some(name) => name,
None => return None,
};
Some(actors.find::<WorkerActor>(actor_name).console.clone())
} else {
let actor_name = match (*actor_pipelines).get(&id) {
Some(name) => name,
None => return None,
};
Some(actors.find::<TabActor>(actor_name).console.clone())
}
}
fn handle_network_event(actors: Arc<Mutex<ActorRegistry>>,
mut connections: Vec<TcpStream>,
actor_pipelines: &HashMap<PipelineId, String>,
actor_requests: &mut HashMap<String, String>,
actor_workers: &HashMap<(PipelineId, WorkerId), String>,
pipeline_id: PipelineId,
request_id: String,
network_event: NetworkEvent) {
let console_actor_name = match find_console_actor(actors.clone(), pipeline_id, None,
actor_workers, actor_pipelines) {
fn handle_console_message(
actors: Arc<Mutex<ActorRegistry>>,
id: PipelineId,
worker_id: Option<WorkerId>,
console_message: ConsoleMessage,
browsing_contexts: &HashMap<BrowsingContextId, String>,
actor_workers: &HashMap<WorkerId, String>,
pipelines: &HashMap<PipelineId, BrowsingContextId>,
) {
let console_actor_name = match find_console_actor(
actors.clone(),
id,
worker_id,
actor_workers,
browsing_contexts,
pipelines,
) {
Some(name) => name,
None => return,
};
let netevent_actor_name = find_network_event_actor(actors.clone(), actor_requests, request_id.clone());
let actors = actors.lock().unwrap();
let console_actor = actors.find::<ConsoleActor>(&console_actor_name);
let id = worker_id.map_or(UniqueId::Pipeline(id), UniqueId::Worker);
console_actor.handle_console_api(console_message, id, &*actors);
}
fn find_console_actor(
actors: Arc<Mutex<ActorRegistry>>,
pipeline: PipelineId,
worker_id: Option<WorkerId>,
actor_workers: &HashMap<WorkerId, String>,
browsing_contexts: &HashMap<BrowsingContextId, String>,
pipelines: &HashMap<PipelineId, BrowsingContextId>,
) -> Option<String> {
let actors = actors.lock().unwrap();
if let Some(worker_id) = worker_id {
let actor_name = actor_workers.get(&worker_id)?;
Some(actors.find::<WorkerActor>(actor_name).console.clone())
} else {
let id = pipelines.get(&pipeline)?;
let actor_name = browsing_contexts.get(id)?;
Some(
actors
.find::<BrowsingContextActor>(actor_name)
.console
.clone(),
)
}
}
fn handle_network_event(
actors: Arc<Mutex<ActorRegistry>>,
mut connections: Vec<TcpStream>,
browsing_contexts: &HashMap<BrowsingContextId, String>,
actor_requests: &mut HashMap<String, String>,
actor_workers: &HashMap<WorkerId, String>,
pipelines: &HashMap<PipelineId, BrowsingContextId>,
pipeline_id: PipelineId,
request_id: String,
network_event: NetworkEvent,
) {
let console_actor_name = match find_console_actor(
actors.clone(),
pipeline_id,
None,
actor_workers,
browsing_contexts,
pipelines,
) {
Some(name) => name,
None => return,
};
let netevent_actor_name =
find_network_event_actor(actors.clone(), actor_requests, request_id);
let mut actors = actors.lock().unwrap();
let actor = actors.find_mut::<NetworkEventActor>(&netevent_actor_name);
@ -364,10 +459,9 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
eventActor: actor.event_actor(),
};
for stream in &mut connections {
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
}
}
},
NetworkEvent::HttpResponse(httpresponse) => {
//Store the response information in the actor
actor.add_response(httpresponse);
@ -378,7 +472,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
updateType: "requestHeaders".to_owned(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &actor.request_headers());
let _ = stream.write_merged_json_packet(&msg, &actor.request_headers());
}
let msg = NetworkEventUpdateMsg {
@ -387,7 +481,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
updateType: "requestCookies".to_owned(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &actor.request_cookies());
let _ = stream.write_merged_json_packet(&msg, &actor.request_cookies());
}
//Send a networkEventUpdate (responseStart) to the client
@ -395,11 +489,11 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
from: netevent_actor_name.clone(),
type_: "networkEventUpdate".to_owned(),
updateType: "responseStart".to_owned(),
response: actor.response_start()
response: actor.response_start(),
};
for stream in &mut connections {
stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&msg);
}
let msg = NetworkEventUpdateMsg {
from: netevent_actor_name.clone(),
@ -410,7 +504,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
totalTime: actor.total_time(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &extra);
let _ = stream.write_merged_json_packet(&msg, &extra);
}
let msg = NetworkEventUpdateMsg {
@ -422,7 +516,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
state: "insecure".to_owned(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &extra);
let _ = stream.write_merged_json_packet(&msg, &extra);
}
let msg = NetworkEventUpdateMsg {
@ -431,7 +525,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
updateType: "responseContent".to_owned(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &actor.response_content());
let _ = stream.write_merged_json_packet(&msg, &actor.response_content());
}
let msg = NetworkEventUpdateMsg {
@ -440,89 +534,158 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
updateType: "responseCookies".to_owned(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &actor.response_cookies());
let _ = stream.write_merged_json_packet(&msg, &actor.response_cookies());
}
let msg = NetworkEventUpdateMsg {
from: netevent_actor_name.clone(),
from: netevent_actor_name,
type_: "networkEventUpdate".to_owned(),
updateType: "responseHeaders".to_owned(),
};
for stream in &mut connections {
stream.write_merged_json_packet(&msg, &actor.response_headers());
let _ = stream.write_merged_json_packet(&msg, &actor.response_headers());
}
}
},
}
}
// Find the name of NetworkEventActor corresponding to request_id
// Create a new one if it does not exist, add it to the actor_requests hashmap
fn find_network_event_actor(actors: Arc<Mutex<ActorRegistry>>,
actor_requests: &mut HashMap<String, String>,
request_id: String) -> String {
fn find_network_event_actor(
actors: Arc<Mutex<ActorRegistry>>,
actor_requests: &mut HashMap<String, String>,
request_id: String,
) -> String {
let mut actors = actors.lock().unwrap();
match (*actor_requests).entry(request_id) {
Occupied(name) => {
//TODO: Delete from map like Firefox does?
name.into_mut().clone()
}
},
Vacant(entry) => {
let actor_name = actors.new_name("netevent");
let actor = NetworkEventActor::new(actor_name.clone());
entry.insert(actor_name.clone());
actors.register(box actor);
actors.register(Box::new(actor));
actor_name
}
},
}
}
let sender_clone = sender.clone();
thread::Builder::new().name("DevtoolsClientAcceptor".to_owned()).spawn(move || {
// accept connections and process them, spawning a new thread for each one
for stream in listener.incoming() {
// connection succeeded
sender_clone.send(DevtoolsControlMsg::FromChrome(
ChromeToDevtoolsControlMsg::AddClient(stream.unwrap()))).unwrap();
}
}).expect("Thread spawning failed");
thread::Builder::new()
.name("DevtoolsClientAcceptor".to_owned())
.spawn(move || {
// accept connections and process them, spawning a new thread for each one
for stream in listener.incoming() {
let mut stream = stream.expect("Can't retrieve stream");
if !allow_devtools_client(&mut stream, &embedder, &token) {
continue;
};
// connection succeeded and accepted
sender
.send(DevtoolsControlMsg::FromChrome(
ChromeToDevtoolsControlMsg::AddClient(stream),
))
.unwrap();
}
})
.expect("Thread spawning failed");
let mut next_id = StreamId(0);
while let Ok(msg) = receiver.recv() {
debug!("{:?}", msg);
match msg {
DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::AddClient(stream)) => {
let actors = actors.clone();
let id = next_id;
next_id = StreamId(id.0 + 1);
accepted_connections.push(stream.try_clone().unwrap());
thread::Builder::new().name("DevtoolsClientHandler".to_owned()).spawn(move || {
handle_client(actors, stream.try_clone().unwrap())
}).expect("Thread spawning failed");
}
thread::Builder::new()
.name("DevtoolsClientHandler".to_owned())
.spawn(move || handle_client(actors, stream.try_clone().unwrap(), id))
.expect("Thread spawning failed");
},
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::FramerateTick(
actor_name, tick)) =>
handle_framerate_tick(actors.clone(), actor_name, tick),
actor_name,
tick,
)) => handle_framerate_tick(actors.clone(), actor_name, tick),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::TitleChanged(
pipeline,
title,
)) => handle_title_changed(
actors.clone(),
&pipelines,
&browsing_contexts,
pipeline,
title,
),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::NewGlobal(
ids, script_sender, pageinfo)) =>
handle_new_global(actors.clone(), ids, script_sender, &mut actor_pipelines,
&mut actor_workers, pageinfo),
ids,
script_sender,
pageinfo,
)) => handle_new_global(
actors.clone(),
ids,
script_sender,
&mut browsing_contexts,
&mut pipelines,
&mut actor_workers,
pageinfo,
),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::Navigate(
browsing_context,
state,
)) => handle_navigate(actors.clone(), &browsing_contexts, browsing_context, state),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ConsoleAPI(
id,
console_message,
worker_id)) =>
handle_console_message(actors.clone(), id, worker_id, console_message,
&actor_pipelines, &actor_workers),
id,
console_message,
worker_id,
)) => handle_console_message(
actors.clone(),
id,
worker_id,
console_message,
&browsing_contexts,
&actor_workers,
&pipelines,
),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportPageError(
id,
page_error,
)) => handle_page_error(
actors.clone(),
id,
None,
page_error,
&browsing_contexts,
&actor_workers,
&pipelines,
),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportCSSError(
id,
css_error)) => {
let console_message = ConsoleMessage {
id,
css_error,
)) => {
let console_message = ConsoleMessage {
message: css_error.msg,
logLevel: LogLevel::Warn,
filename: css_error.filename,
lineNumber: css_error.line,
columnNumber: css_error.column,
lineNumber: css_error.line as usize,
columnNumber: css_error.column as usize,
};
handle_console_message(actors.clone(), id, None, console_message,
&actor_pipelines, &actor_workers)
handle_console_message(
actors.clone(),
id,
None,
console_message,
&browsing_contexts,
&actor_workers,
&pipelines,
)
},
DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::NetworkEvent(
request_id, network_event)) => {
request_id,
network_event,
)) => {
// copy the accepted_connections vector
let mut connections = Vec::<TcpStream>::new();
for stream in &accepted_connections {
@ -533,13 +696,52 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
NetworkEvent::HttpResponse(ref response) => response.pipeline_id,
NetworkEvent::HttpRequest(ref request) => request.pipeline_id,
};
handle_network_event(actors.clone(), connections, &actor_pipelines, &mut actor_requests,
&actor_workers, pipeline_id, request_id, network_event);
handle_network_event(
actors.clone(),
connections,
&browsing_contexts,
&mut actor_requests,
&actor_workers,
&pipelines,
pipeline_id,
request_id,
network_event,
);
},
DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg) => break
DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg) => break,
}
}
for connection in &mut accepted_connections {
let _ = connection.shutdown(Shutdown::Both);
}
}
fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token: &str) -> bool {
// By-pass prompt if we receive a valid token.
let token = format!("25:{{\"auth_token\":\"{}\"}}", token);
let mut buf = [0; 28];
let timeout = std::time::Duration::from_millis(500);
// This will read but not consume the bytes from the stream.
stream.set_read_timeout(Some(timeout)).unwrap();
let peek = stream.peek(&mut buf);
stream.set_read_timeout(None).unwrap();
if let Ok(len) = peek {
if len == buf.len() {
if let Ok(s) = std::str::from_utf8(&buf) {
if s == token {
// Consume the message as it was relevant to us.
let _ = stream.read_exact(&mut buf);
return true;
}
}
}
};
// No token found. Prompt user
let (embedder_sender, receiver) = ipc::channel().expect("Failed to create IPC channel!");
let message = "Accept incoming devtools connection?".to_owned();
let prompt = PromptDefinition::YesNo(message, embedder_sender);
let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Trusted);
embedder.send((None, msg));
receiver.recv().unwrap() == PromptResult::Primary
}

View file

@ -1,6 +1,6 @@
/* 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 http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Low-level wire protocol implementation. Currently only supports
//! [JSON packets]
@ -27,42 +27,51 @@ pub struct Method {
}
pub trait JsonPacketStream {
fn write_json_packet<T: Serialize>(&mut self, obj: &T);
fn write_merged_json_packet<T: Serialize, U: Serialize>(&mut self, base: &T, extra: &U);
fn write_json_packet<T: Serialize>(&mut self, obj: &T) -> Result<(), Box<dyn Error>>;
fn write_merged_json_packet<T: Serialize, U: Serialize>(
&mut self,
base: &T,
extra: &U,
) -> Result<(), Box<dyn Error>>;
fn read_json_packet(&mut self) -> Result<Option<Value>, String>;
}
impl JsonPacketStream for TcpStream {
fn write_json_packet<T: Serialize>(&mut self, obj: &T) {
let s = serde_json::to_string(obj).unwrap();
fn write_json_packet<T: Serialize>(&mut self, obj: &T) -> Result<(), Box<dyn Error>> {
let s = serde_json::to_string(obj)?;
debug!("<- {}", s);
write!(self, "{}:{}", s.len(), s).unwrap();
write!(self, "{}:{}", s.len(), s)?;
Ok(())
}
fn write_merged_json_packet<T: Serialize, U: Serialize>(&mut self, base: &T, extra: &U) {
let mut obj = serde_json::to_value(base).unwrap();
fn write_merged_json_packet<T: Serialize, U: Serialize>(
&mut self,
base: &T,
extra: &U,
) -> Result<(), Box<dyn Error>> {
let mut obj = serde_json::to_value(base)?;
let obj = obj.as_object_mut().unwrap();
let extra = serde_json::to_value(extra).unwrap();
let extra = serde_json::to_value(extra)?;
let extra = extra.as_object().unwrap();
for (key, value) in extra {
obj.insert(key.to_owned(), value.to_owned());
}
self.write_json_packet(obj);
self.write_json_packet(obj)
}
fn read_json_packet(&mut self) -> Result<Option<Value>, String> {
// https://wiki.mozilla.org/Remote_Debugging_Protocol_Stream_Transport
// In short, each JSON packet is [ascii length]:[JSON data of given length]
let mut buffer = vec!();
let mut buffer = vec![];
loop {
let mut buf = [0];
let byte = match self.read(&mut buf) {
Ok(0) => return Ok(None), // EOF
Ok(0) => return Ok(None), // EOF
Ok(1) => buf[0],
Ok(_) => unreachable!(),
Err(e) => return Err(e.description().to_owned()),
Err(e) => return Err(e.to_string()),
};
match byte {
b':' => {
@ -75,11 +84,13 @@ impl JsonPacketStream for TcpStream {
Err(_) => return Err("packet length missing / not parsable".to_owned()),
};
let mut packet = String::new();
self.take(packet_len).read_to_string(&mut packet).unwrap();
self.take(packet_len)
.read_to_string(&mut packet)
.map_err(|e| e.to_string())?;
debug!("{}", packet);
return match serde_json::from_str(&packet) {
Ok(json) => Ok(Some(json)),
Err(err) => Err(err.description().to_owned()),
Err(err) => Err(err.to_string()),
};
},
c => buffer.push(c),

Some files were not shown because too many files have changed in this diff Show more