mirror of
https://github.com/servo/servo.git
synced 2025-06-18 05:14:28 +00:00
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:
commit
01a7de50ab
222172 changed files with 4491618 additions and 9970552 deletions
16
components/allocator/Cargo.toml
Normal file
16
components/allocator/Cargo.toml
Normal 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
115
components/allocator/lib.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
32
components/background_hang_monitor/Cargo.toml
Normal file
32
components/background_hang_monitor/Cargo.toml
Normal 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"
|
593
components/background_hang_monitor/background_hang_monitor.rs
Normal file
593
components/background_hang_monitor/background_hang_monitor.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
components/background_hang_monitor/lib.rs
Normal file
24
components/background_hang_monitor/lib.rs
Normal 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::*;
|
99
components/background_hang_monitor/sampler.rs
Normal file
99
components/background_hang_monitor/sampler.rs
Normal 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
|
||||
}
|
||||
}
|
314
components/background_hang_monitor/sampler_linux.rs
Normal file
314
components/background_hang_monitor/sampler_linux.rs
Normal 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 (there’s 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);
|
||||
}
|
||||
}
|
129
components/background_hang_monitor/sampler_mac.rs
Normal file
129
components/background_hang_monitor/sampler_mac.rs
Normal 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
|
||||
}
|
40
components/background_hang_monitor/sampler_windows.rs
Normal file
40
components/background_hang_monitor/sampler_windows.rs
Normal 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(())
|
||||
}
|
||||
}
|
267
components/background_hang_monitor/tests/hang_monitor_tests.rs
Normal file
267
components/background_hang_monitor/tests/hang_monitor_tests.rs
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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"]
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())),
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
_ => {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"] }
|
||||
|
|
1464
components/canvas/canvas_data.rs
Normal file
1464
components/canvas/canvas_data.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
|
1026
components/canvas/raqote_backend.rs
Normal file
1026
components/canvas/raqote_backend.rs
Normal file
File diff suppressed because it is too large
Load diff
249
components/canvas/webgl_limits.rs
Normal file
249
components/canvas/webgl_limits.rs
Normal 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);
|
||||
}
|
186
components/canvas/webgl_mode/inprocess.rs
Normal file
186
components/canvas/webgl_mode/inprocess.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
7
components/canvas/webgl_mode/mod.rs
Normal file
7
components/canvas/webgl_mode/mod.rs
Normal 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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3463
components/canvas/webgl_thread.rs
Normal file
3463
components/canvas/webgl_thread.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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"] }
|
||||
|
|
486
components/canvas_traits/canvas.rs
Normal file
486
components/canvas_traits/canvas.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
1453
components/canvas_traits/webgl.rs
Normal file
1453
components/canvas_traits/webgl.rs
Normal file
File diff suppressed because it is too large
Load diff
14
components/canvas_traits/webgl_channel/ipc.rs
Normal file
14
components/canvas_traits/webgl_channel/ipc.rs
Normal 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()
|
||||
}
|
146
components/canvas_traits/webgl_channel/mod.rs
Normal file
146
components/canvas_traits/webgl_channel/mod.rs
Normal 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()
|
||||
}
|
||||
}
|
63
components/canvas_traits/webgl_channel/mpsc.rs
Normal file
63
components/canvas_traits/webgl_channel/mpsc.rs
Normal 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);
|
|
@ -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"
|
||||
|
|
52
components/compositing/build.rs
Normal file
52
components/compositing/build.rs
Normal 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
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
126
components/compositing/gl.rs
Normal file
126
components/compositing/gl.rs
Normal 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!")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
288
components/config/pref_util.rs
Normal file
288
components/config/pref_util.rs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
86
components/config/tests/opts.rs
Normal file
86
components/config/tests/opts.rs
Normal 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);
|
||||
}
|
334
components/config/tests/prefs.rs
Normal file
334
components/config/tests/prefs.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
components/config_plugins/Cargo.toml
Normal file
18
components/config_plugins/Cargo.toml
Normal 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"] }
|
219
components/config_plugins/lib.rs
Normal file
219
components/config_plugins/lib.rs
Normal 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)
|
||||
}
|
149
components/config_plugins/parse.rs
Normal file
149
components/config_plugins/parse.rs
Normal 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()?))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
204
components/constellation/browsingcontext.rs
Normal file
204
components/constellation/browsingcontext.rs
Normal 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
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
|
171
components/constellation/network_listener.rs
Normal file
171
components/constellation/network_listener.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
56
components/constellation/serviceworker.rs
Normal file
56
components/constellation/serviceworker.rs
Normal 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()
|
||||
}
|
||||
}
|
272
components/constellation/session_history.rs
Normal file
272
components/constellation/session_history.rs
Normal 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();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
16
components/derive_common/Cargo.toml
Normal file
16
components/derive_common/Cargo.toml
Normal 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"
|
388
components/derive_common/cg.rs
Normal file
388
components/derive_common/cg.rs
Normal 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| ¶m.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)
|
||||
}
|
13
components/derive_common/lib.rs
Normal file
13
components/derive_common/lib.rs
Normal 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;
|
|
@ -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"] }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
385
components/devtools/actors/browsing_context.rs
Normal file
385
components/devtools/actors/browsing_context.rs
Normal 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,
|
||||
}
|
|
@ -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) => ®istry.find::<BrowsingContextActor>(bc).script_chan,
|
||||
Root::DedicatedWorker(worker) => ®istry.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(®istry, &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(®istry, &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,
|
||||
}
|
||||
|
|
85
components/devtools/actors/device.rs
Normal file
85
components/devtools/actors/device.rs
Normal 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(),
|
||||
),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
37
components/devtools/actors/emulation.rs
Normal file
37
components/devtools/actors/emulation.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
136
components/devtools/actors/preference.rs
Normal file
136
components/devtools/actors/preference.rs
Normal 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,
|
||||
}
|
53
components/devtools/actors/process.rs
Normal file
53
components/devtools/actors/process.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(®istry)
|
||||
})
|
||||
.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(®istry),
|
||||
};
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
52
components/devtools/actors/stylesheets.rs
Normal file
52
components/devtools/actors/stylesheets.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue