Firefox timeline integration #4957

This commit is contained in:
Guro Bokum 2015-03-28 23:27:54 +07:00
parent 1e45d025b3
commit 97714ec5ed
15 changed files with 761 additions and 14 deletions

View file

@ -10,6 +10,7 @@ use std::cell::{Cell, RefCell};
use std::mem::{replace, transmute}; use std::mem::{replace, transmute};
use std::net::TcpStream; use std::net::TcpStream;
use std::raw::TraitObject; use std::raw::TraitObject;
use std::sync::{Arc, Mutex};
use rustc_serialize::json; use rustc_serialize::json;
/// A common trait for all devtools actors that encompasses an immutable name /// A common trait for all devtools actors that encompasses an immutable name
@ -77,7 +78,9 @@ impl Actor {
pub struct ActorRegistry { pub struct ActorRegistry {
actors: HashMap<String, Box<Actor+Send>>, actors: HashMap<String, Box<Actor+Send>>,
new_actors: RefCell<Vec<Box<Actor+Send>>>, new_actors: RefCell<Vec<Box<Actor+Send>>>,
old_actors: RefCell<Vec<String>>,
script_actors: RefCell<HashMap<String, String>>, script_actors: RefCell<HashMap<String, String>>,
shareable: Option<Arc<Mutex<ActorRegistry>>>,
next: Cell<u32>, next: Cell<u32>,
} }
@ -87,11 +90,31 @@ impl ActorRegistry {
ActorRegistry { ActorRegistry {
actors: HashMap::new(), actors: HashMap::new(),
new_actors: RefCell::new(vec!()), new_actors: RefCell::new(vec!()),
old_actors: RefCell::new(vec!()),
script_actors: RefCell::new(HashMap::new()), script_actors: RefCell::new(HashMap::new()),
shareable: None,
next: Cell::new(0), next: Cell::new(0),
} }
} }
/// Creating shareable registry
pub fn create_shareable(self) -> Arc<Mutex<ActorRegistry>>{
if self.shareable.is_some() {
return self.shareable.unwrap();
}
let shareable = Arc::new(Mutex::new(self));
let mut lock = shareable.lock();
let registry = lock.as_mut().unwrap();
registry.shareable = Some(shareable.clone());
shareable.clone()
}
/// Get shareable registry through threads
pub fn get_shareable(&self) -> Arc<Mutex<ActorRegistry>> {
self.shareable.as_ref().unwrap().clone()
}
pub fn register_script_actor(&self, script_id: String, actor: String) { pub fn register_script_actor(&self, script_id: String, actor: String) {
println!("registering {} ({})", actor, script_id); println!("registering {} ({})", actor, script_id);
let mut script_actors = self.script_actors.borrow_mut(); let mut script_actors = self.script_actors.borrow_mut();
@ -155,6 +178,7 @@ impl ActorRegistry {
stream: &mut TcpStream) stream: &mut TcpStream)
-> Result<(), ()> { -> Result<(), ()> {
let to = msg.get("to").unwrap().as_string().unwrap(); let to = msg.get("to").unwrap().as_string().unwrap();
match self.actors.get(&to.to_string()) { match self.actors.get(&to.to_string()) {
None => println!("message received for unknown actor \"{}\"", to), None => println!("message received for unknown actor \"{}\"", to),
Some(actor) => { Some(actor) => {
@ -169,6 +193,20 @@ impl ActorRegistry {
for actor in new_actors.into_iter() { for actor in new_actors.into_iter() {
self.actors.insert(actor.name().to_string(), actor); self.actors.insert(actor.name().to_string(), actor);
} }
let old_actors = replace(&mut *self.old_actors.borrow_mut(), vec!());
for name in old_actors.into_iter() {
self.drop_actor(name);
}
Ok(()) Ok(())
} }
pub fn drop_actor(&mut self, name: String) {
self.actors.remove(&name);
}
pub fn drop_actor_later(&self, name: String) {
let mut actors = self.old_actors.borrow_mut();
actors.push(name);
}
} }

View file

@ -0,0 +1,88 @@
/* 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 rustc_serialize::json;
use std::mem;
use std::net::TcpStream;
use time::precise_time_ns;
use actor::{Actor, ActorRegistry};
pub struct FramerateActor {
name: String,
start_time: Option<u64>,
is_recording: bool,
ticks: Vec<u64>,
}
impl Actor for FramerateActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &json::Object,
_stream: &mut TcpStream) -> Result<bool, ()> {
Ok(false)
}
}
impl FramerateActor {
/// return name of actor
pub fn create(registry: &ActorRegistry) -> String {
let actor_name = registry.new_name("framerate");
let mut actor = FramerateActor {
name: actor_name.clone(),
start_time: None,
is_recording: false,
ticks: Vec::new(),
};
actor.start_recording();
registry.register_later(box actor);
actor_name
}
// callback on request animation frame
pub fn on_refresh_driver_tick(&mut self) {
if !self.is_recording {
return;
}
// TODO: Need implement requesting animation frame
// http://hg.mozilla.org/mozilla-central/file/0a46652bd992/dom/base/nsGlobalWindow.cpp#l5314
let start_time = self.start_time.as_ref().unwrap();
self.ticks.push(*start_time - precise_time_ns());
}
pub fn take_pending_ticks(&mut self) -> Vec<u64> {
mem::replace(&mut self.ticks, Vec::new())
}
fn start_recording(&mut self) {
self.is_recording = true;
self.start_time = Some(precise_time_ns());
// TODO: Need implement requesting animation frame
// http://hg.mozilla.org/mozilla-central/file/0a46652bd992/dom/base/nsGlobalWindow.cpp#l5314
}
fn stop_recording(&mut self) {
if !self.is_recording {
return;
}
self.is_recording = false;
self.start_time = None;
}
}
impl Drop for FramerateActor {
fn drop(&mut self) {
self.stop_recording();
}
}

View file

@ -15,9 +15,9 @@ use collections::BTreeMap;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use rustc_serialize::json::{self, Json, ToJson}; use rustc_serialize::json::{self, Json, ToJson};
use std::cell::RefCell; use std::cell::RefCell;
use std::sync::mpsc::{channel, Sender};
use std::net::TcpStream; use std::net::TcpStream;
use std::num::Float; use std::num::Float;
use std::sync::mpsc::{channel, Sender};
pub struct InspectorActor { pub struct InspectorActor {
pub name: String, pub name: String,

View file

@ -0,0 +1,67 @@
/* 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 rustc_serialize::json;
use std::net::TcpStream;
use actor::{Actor, ActorRegistry};
#[derive(RustcEncodable)]
pub struct TimelineMemoryReply {
jsObjectSize: u64,
jsStringSize: u64,
jsOtherSize: u64,
domSize: u64,
styleSize: u64,
otherSize: u64,
totalSize: u64,
jsMilliseconds: f64,
nonJSMilliseconds: f64,
}
pub struct MemoryActor {
pub name: String,
}
impl Actor for MemoryActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &json::Object,
_stream: &mut TcpStream) -> Result<bool, ()> {
Ok(false)
}
}
impl MemoryActor {
/// return name of actor
pub fn create(registry: &ActorRegistry) -> String {
let actor_name = registry.new_name("memory");
let actor = MemoryActor {
name: actor_name.clone()
};
registry.register_later(box actor);
actor_name
}
pub fn measure(&self) -> TimelineMemoryReply {
//TODO:
TimelineMemoryReply {
jsObjectSize: 1,
jsStringSize: 1,
jsOtherSize: 1,
domSize: 1,
styleSize: 1,
otherSize: 1,
totalSize: 1,
jsMilliseconds: 1.1,
nonJSMilliseconds: 1.1,
}
}
}

View file

@ -60,6 +60,7 @@ pub struct TabActorMsg {
outerWindowID: u32, outerWindowID: u32,
consoleActor: String, consoleActor: String,
inspectorActor: String, inspectorActor: String,
timelineActor: String,
} }
pub struct TabActor { pub struct TabActor {
@ -68,6 +69,7 @@ pub struct TabActor {
pub url: String, pub url: String,
pub console: String, pub console: String,
pub inspector: String, pub inspector: String,
pub timeline: String,
} }
impl Actor for TabActor { impl Actor for TabActor {
@ -143,6 +145,7 @@ impl TabActor {
outerWindowID: 0, //FIXME: this should probably be the pipeline id outerWindowID: 0, //FIXME: this should probably be the pipeline id
consoleActor: self.console.clone(), consoleActor: self.console.clone(),
inspectorActor: self.inspector.clone(), inspectorActor: self.inspector.clone(),
timelineActor: self.timeline.clone(),
} }
} }
} }

View file

@ -0,0 +1,352 @@
/* 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 core::iter::FromIterator;
use msg::constellation_msg::PipelineId;
use rustc_serialize::json;
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::net::TcpStream;
use std::old_io::timer::sleep;
use std::sync::{Arc, Mutex};
use std::time::duration::Duration;
use std::sync::mpsc::{channel, Sender, Receiver, TryRecvError};
use time::precise_time_ns;
use actor::{Actor, ActorRegistry};
use actors::memory::{MemoryActor, TimelineMemoryReply};
use actors::framerate::FramerateActor;
use devtools_traits::DevtoolScriptControlMsg;
use devtools_traits::DevtoolScriptControlMsg::{SetTimelineMarkers, DropTimelineMarkers};
use devtools_traits::{TimelineMarker, TracingMetadata, TimelineMarkerType};
use protocol::JsonPacketStream;
use util::task;
pub struct TimelineActor {
name: String,
script_sender: Sender<DevtoolScriptControlMsg>,
marker_types: Vec<TimelineMarkerType>,
pipeline: PipelineId,
is_recording: Arc<Mutex<bool>>,
stream: RefCell<Option<TcpStream>>,
framerate_actor: RefCell<Option<String>>,
memory_actor: RefCell<Option<String>>,
}
struct Emitter {
from: String,
stream: TcpStream,
markers: Vec<TimelineMarkerReply>,
registry: Arc<Mutex<ActorRegistry>>,
framerate_actor: Option<String>,
memory_actor: Option<String>,
}
#[derive(RustcEncodable)]
struct IsRecordingReply {
from: String,
value: bool
}
#[derive(RustcEncodable)]
struct StartReply {
from: String,
value: u64
}
#[derive(RustcEncodable)]
struct StopReply {
from: String,
value: u64
}
#[derive(RustcEncodable)]
struct TimelineMarkerReply {
name: String,
start: u64,
end: u64,
stack: Option<Vec<()>>,
endStack: Option<Vec<()>>,
}
#[derive(RustcEncodable)]
struct MarkersEmitterReply {
__type__: String,
markers: Vec<TimelineMarkerReply>,
from: String,
endTime: u64,
}
#[derive(RustcEncodable)]
struct MemoryEmitterReply {
__type__: String,
from: String,
delta: u64,
measurement: TimelineMemoryReply,
}
#[derive(RustcEncodable)]
struct FramerateEmitterReply {
__type__: String,
from: String,
delta: u64,
timestamps: Vec<u64>,
}
static DEFAULT_TIMELINE_DATA_PULL_TIMEOUT: usize = 200; //ms
impl TimelineActor {
pub fn new(name: String,
pipeline: PipelineId,
script_sender: Sender<DevtoolScriptControlMsg>) -> TimelineActor {
let marker_types = vec!(TimelineMarkerType::Reflow,
TimelineMarkerType::DOMEvent);
TimelineActor {
name: name,
pipeline: pipeline,
marker_types: marker_types,
script_sender: script_sender,
is_recording: Arc::new(Mutex::new(false)),
stream: RefCell::new(None),
framerate_actor: RefCell::new(None),
memory_actor: RefCell::new(None),
}
}
fn pull_timeline_data(&self, receiver: Receiver<TimelineMarker>, mut emitter: Emitter) {
let is_recording = self.is_recording.clone();
if !*is_recording.lock().unwrap() {
return;
}
/// Select root(with depth 0) TimelineMarker pair (IntervalStart + IntervalEnd)
/// from queue and add marker to emitter
/// Return true if closed (IntervalStart + IntervalEnd) pair was founded
fn group(queue: &mut VecDeque<TimelineMarker>, depth: usize,
start_payload: Option<TimelineMarker>, emitter: &mut Emitter) -> bool {
if let Some(start_payload) = start_payload {
if start_payload.metadata != TracingMetadata::IntervalStart {
panic!("Start payload doesn't have metadata IntervalStart");
}
if let Some(end_payload) = queue.pop_front() {
match end_payload.metadata {
TracingMetadata::IntervalEnd => {
if depth == 0 {
// Emit TimelineMarkerReply, pair was found
emitter.add_marker(start_payload, end_payload);
}
return true;
}
TracingMetadata::IntervalStart => {
if group(queue, depth + 1, Some(end_payload), emitter) {
return group(queue, depth, Some(start_payload), emitter);
} else {
queue.push_front(start_payload);
}
}
_ => panic!("Unknown tracingMetadata")
}
} else {
queue.push_front(start_payload);
}
}
false
}
task::spawn_named("PullTimelineMarkers".to_string(), move || {
let mut queues = HashMap::new();
queues.insert("Reflow".to_string(), VecDeque::new());
queues.insert("DOMEvent".to_string(), VecDeque::new());
loop {
if !*is_recording.lock().unwrap() {
break;
}
// Creating queues by marker.name
loop {
match receiver.try_recv() {
Ok(marker) => {
if let Some(list) = queues.get_mut(&marker.name) {
list.push_back(marker);
}
}
Err(TryRecvError) => break
}
}
// Emit all markers
for (_, queue) in queues.iter_mut() {
let start_payload = queue.pop_front();
group(queue, 0, start_payload, &mut emitter);
}
emitter.send();
sleep(Duration::milliseconds(DEFAULT_TIMELINE_DATA_PULL_TIMEOUT as i64));
}
});
}
}
impl Actor for TimelineActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &json::Object,
stream: &mut TcpStream) -> Result<bool, ()> {
Ok(match msg_type {
"start" => {
**self.is_recording.lock().as_mut().unwrap() = true;
let (tx, rx) = channel::<TimelineMarker>();
self.script_sender.send(SetTimelineMarkers(self.pipeline, self.marker_types.clone(), tx));
*self.stream.borrow_mut() = stream.try_clone().ok();
// init memory actor
if let Some(with_memory) = msg.get("withMemory") {
if let Some(true) = with_memory.as_boolean() {
*self.memory_actor.borrow_mut() = Some(MemoryActor::create(registry));
}
}
// init framerate actor
if let Some(with_ticks) = msg.get("withTicks") {
if let Some(true) = with_ticks.as_boolean() {
*self.framerate_actor.borrow_mut() = Some(FramerateActor::create(registry));
}
}
let emitter = Emitter::new(self.name(), registry.get_shareable(),
stream.try_clone().unwrap(),
self.memory_actor.borrow().clone(),
self.framerate_actor.borrow().clone());
self.pull_timeline_data(rx, emitter);
let msg = StartReply {
from: self.name(),
value: precise_time_ns(),
};
stream.write_json_packet(&msg);
true
}
"stop" => {
let msg = StopReply {
from: self.name(),
value: precise_time_ns()
};
stream.write_json_packet(&msg);
self.script_sender.send(DropTimelineMarkers(self.pipeline, self.marker_types.clone()));
if let Some(ref actor_name) = *self.framerate_actor.borrow() {
registry.drop_actor_later(actor_name.clone());
}
if let Some(ref actor_name) = *self.memory_actor.borrow() {
registry.drop_actor_later(actor_name.clone());
}
**self.is_recording.lock().as_mut().unwrap() = false;
self.stream.borrow_mut().take();
true
}
"isRecording" => {
let msg = IsRecordingReply {
from: self.name(),
value: self.is_recording.lock().unwrap().clone()
};
stream.write_json_packet(&msg);
true
}
_ => {
false
}
})
}
}
impl Emitter {
pub fn new(name: String,
registry: Arc<Mutex<ActorRegistry>>,
stream: TcpStream,
memory_actor_name: Option<String>,
framerate_actor_name: Option<String>) -> Emitter {
Emitter {
from: name,
stream: stream,
markers: Vec::new(),
registry: registry,
framerate_actor: framerate_actor_name,
memory_actor: memory_actor_name,
}
}
fn add_marker(&mut self, start_payload: TimelineMarker, end_payload: TimelineMarker) -> () {
self.markers.push(TimelineMarkerReply {
name: start_payload.name,
start: start_payload.time,
end: end_payload.time,
stack: start_payload.stack,
endStack: end_payload.stack,
});
}
fn send(&mut self) -> () {
let end_time = precise_time_ns();
let reply = MarkersEmitterReply {
__type__: "markers".to_string(),
markers: Vec::from_iter(self.markers.drain()),
from: self.from.clone(),
endTime: end_time,
};
self.stream.write_json_packet(&reply);
if let Some(ref actor_name) = self.framerate_actor {
let mut lock = self.registry.lock();
let registry = lock.as_mut().unwrap();
let mut framerate_actor = registry.find_mut::<FramerateActor>(actor_name);
let framerateReply = FramerateEmitterReply {
__type__: "framerate".to_string(),
from: framerate_actor.name(),
delta: end_time,
timestamps: framerate_actor.take_pending_ticks(),
};
self.stream.write_json_packet(&framerateReply);
}
if let Some(ref actor_name) = self.memory_actor {
let registry = self.registry.lock().unwrap();
let memory_actor = registry.find::<MemoryActor>(actor_name);
let memoryReply = MemoryEmitterReply {
__type__: "memory".to_string(),
from: memory_actor.name(),
delta: end_time,
measurement: memory_actor.measure(),
};
self.stream.write_json_packet(&memoryReply);
}
}
}

View file

@ -3,9 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use actor::{Actor, ActorRegistry}; use actor::{Actor, ActorRegistry};
use msg::constellation_msg::WorkerId;
use rustc_serialize::json; use rustc_serialize::json;
use std::net::TcpStream; use std::net::TcpStream;
use msg::constellation_msg::WorkerId;
pub struct WorkerActor { pub struct WorkerActor {
pub name: String, pub name: String,

View file

@ -34,6 +34,7 @@ use actors::worker::WorkerActor;
use actors::inspector::InspectorActor; use actors::inspector::InspectorActor;
use actors::root::RootActor; use actors::root::RootActor;
use actors::tab::TabActor; use actors::tab::TabActor;
use actors::timeline::TimelineActor;
use protocol::JsonPacketStream; use protocol::JsonPacketStream;
use devtools_traits::{ConsoleMessage, DevtoolsControlMsg}; use devtools_traits::{ConsoleMessage, DevtoolsControlMsg};
@ -53,9 +54,12 @@ mod actor;
/// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/ /// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/
mod actors { mod actors {
pub mod console; pub mod console;
pub mod framerate;
pub mod memory;
pub mod inspector; pub mod inspector;
pub mod root; pub mod root;
pub mod tab; pub mod tab;
pub mod timeline;
pub mod worker; pub mod worker;
} }
mod protocol; mod protocol;
@ -103,7 +107,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
registry.register(root); registry.register(root);
registry.find::<RootActor>("root"); registry.find::<RootActor>("root");
let actors = Arc::new(Mutex::new(registry)); let actors = registry.create_shareable();
let mut accepted_connections: Vec<TcpStream> = Vec::new(); let mut accepted_connections: Vec<TcpStream> = Vec::new();
@ -122,8 +126,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
'outer: loop { 'outer: loop {
match stream.read_json_packet() { match stream.read_json_packet() {
Ok(json_packet) => { Ok(json_packet) => {
let mut actors = actors.lock().unwrap(); match actors.lock().unwrap().handle_message(json_packet.as_object().unwrap(),
match actors.handle_message(json_packet.as_object().unwrap(),
&mut stream) { &mut stream) {
Ok(()) => {}, Ok(()) => {},
Err(()) => { Err(()) => {
@ -156,7 +159,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
let mut actor_workers: HashMap<(PipelineId, WorkerId), String> = HashMap::new(); let mut actor_workers: HashMap<(PipelineId, WorkerId), String> = HashMap::new();
//TODO: move all this actor creation into a constructor method on TabActor //TODO: move all this actor creation into a constructor method on TabActor
let (tab, console, inspector) = { let (tab, console, inspector, timeline) = {
let console = ConsoleActor { let console = ConsoleActor {
name: actors.new_name("console"), name: actors.new_name("console"),
script_chan: scriptSender.clone(), script_chan: scriptSender.clone(),
@ -168,10 +171,14 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
walker: RefCell::new(None), walker: RefCell::new(None),
pageStyle: RefCell::new(None), pageStyle: RefCell::new(None),
highlighter: RefCell::new(None), highlighter: RefCell::new(None),
script_chan: scriptSender, script_chan: scriptSender.clone(),
pipeline: pipeline, pipeline: pipeline,
}; };
let timeline = TimelineActor::new(actors.new_name("timeline"),
pipeline,
scriptSender);
let DevtoolsPageInfo { title, url } = page_info; let DevtoolsPageInfo { title, url } = page_info;
let tab = TabActor { let tab = TabActor {
name: actors.new_name("tab"), name: actors.new_name("tab"),
@ -179,11 +186,12 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
url: url.serialize(), url: url.serialize(),
console: console.name(), console: console.name(),
inspector: inspector.name(), inspector: inspector.name(),
timeline: timeline.name(),
}; };
let root = actors.find_mut::<RootActor>("root"); let root = actors.find_mut::<RootActor>("root");
root.tabs.push(tab.name.clone()); root.tabs.push(tab.name.clone());
(tab, console, inspector) (tab, console, inspector, timeline)
}; };
if let Some(id) = worker_id { if let Some(id) = worker_id {
@ -199,6 +207,7 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
actors.register(box tab); actors.register(box tab);
actors.register(box console); actors.register(box console);
actors.register(box inspector); actors.register(box inspector);
actors.register(box timeline);
} }
fn handle_console_message(actors: Arc<Mutex<ActorRegistry>>, fn handle_console_message(actors: Arc<Mutex<ActorRegistry>>,

View file

@ -15,4 +15,7 @@ path = "../util"
[dependencies] [dependencies]
url = "0.2.16" url = "0.2.16"
[dependencies]
time = "*"
rustc-serialize = "0.3" rustc-serialize = "0.3"

View file

@ -17,6 +17,7 @@ extern crate msg;
extern crate "rustc-serialize" as rustc_serialize; extern crate "rustc-serialize" as rustc_serialize;
extern crate url; extern crate url;
extern crate util; extern crate util;
extern crate time;
use rustc_serialize::{Decodable, Decoder}; use rustc_serialize::{Decodable, Decoder};
use msg::constellation_msg::{PipelineId, WorkerId}; use msg::constellation_msg::{PipelineId, WorkerId};
@ -83,6 +84,28 @@ pub struct NodeInfo {
pub incompleteValue: bool, pub incompleteValue: bool,
} }
#[derive(PartialEq, Eq)]
pub enum TracingMetadata {
Default,
IntervalStart,
IntervalEnd,
Event,
EventBacktrace,
}
pub struct TimelineMarker {
pub name: String,
pub metadata: TracingMetadata,
pub time: u64,
pub stack: Option<Vec<()>>,
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub enum TimelineMarkerType {
Reflow,
DOMEvent,
}
/// Messages to process in a particular script task, as instructed by a devtools client. /// Messages to process in a particular script task, as instructed by a devtools client.
pub enum DevtoolScriptControlMsg { pub enum DevtoolScriptControlMsg {
EvaluateJS(PipelineId, String, Sender<EvaluateJSReply>), EvaluateJS(PipelineId, String, Sender<EvaluateJSReply>),
@ -92,6 +115,8 @@ pub enum DevtoolScriptControlMsg {
GetLayout(PipelineId, String, Sender<(f32, f32)>), GetLayout(PipelineId, String, Sender<(f32, f32)>),
ModifyAttribute(PipelineId, String, Vec<Modification>), ModifyAttribute(PipelineId, String, Vec<Modification>),
WantsLiveNotifications(PipelineId, bool), WantsLiveNotifications(PipelineId, bool),
SetTimelineMarkers(PipelineId, Vec<TimelineMarkerType>, Sender<TimelineMarker>),
DropTimelineMarkers(PipelineId, Vec<TimelineMarkerType>),
} }
/// Messages to instruct devtools server to update its state relating to a particular /// Messages to instruct devtools server to update its state relating to a particular
@ -128,3 +153,14 @@ pub enum ConsoleMessage {
LogMessage(String, String, u32, u32), LogMessage(String, String, u32, u32),
//WarnMessage(String), //WarnMessage(String),
} }
impl TimelineMarker {
pub fn new(name: String, metadata: TracingMetadata) -> TimelineMarker {
TimelineMarker {
name: name,
metadata: metadata,
time: time::precise_time_ns(),
stack: None,
}
}
}

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use devtools_traits::{EvaluateJSReply, NodeInfo, Modification}; use devtools_traits::{EvaluateJSReply, NodeInfo, Modification, TimelineMarker, TimelineMarkerType};
use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::FromJSValConvertible;
use dom::bindings::conversions::StringificationBehavior; use dom::bindings::conversions::StringificationBehavior;
use dom::bindings::js::{JSRef, Temporary, OptionalRootable}; use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
@ -16,7 +16,7 @@ use dom::element::Element;
use dom::document::DocumentHelpers; use dom::document::DocumentHelpers;
use page::Page; use page::Page;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use script_task::get_page; use script_task::{get_page, ScriptTask};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::rc::Rc; use std::rc::Rc;
@ -114,3 +114,36 @@ pub fn handle_wants_live_notifications(page: &Rc<Page>, pipeline_id: PipelineId,
let window = page.window().root(); let window = page.window().root();
window.r().set_devtools_wants_updates(send_notifications); window.r().set_devtools_wants_updates(send_notifications);
} }
pub fn handle_set_timeline_markers(page: &Rc<Page>,
script_task: &ScriptTask,
marker_types: Vec<TimelineMarkerType>,
reply: Sender<TimelineMarker>) {
for marker_type in &marker_types {
match *marker_type {
TimelineMarkerType::Reflow => {
let window = page.window().root();
window.r().set_devtools_timeline_marker(TimelineMarkerType::Reflow, reply.clone());
}
TimelineMarkerType::DOMEvent => {
script_task.set_devtools_timeline_marker(TimelineMarkerType::DOMEvent, reply.clone());
}
}
}
}
pub fn handle_drop_timeline_markers(page: &Rc<Page>,
script_task: &ScriptTask,
marker_types: Vec<TimelineMarkerType>) {
let window = page.window().root();
for marker_type in &marker_types {
match *marker_type {
TimelineMarkerType::Reflow => {
window.r().drop_devtools_timeline_markers();
}
TimelineMarkerType::DOMEvent => {
script_task.drop_devtools_timeline_markers();
}
}
}
}

View file

@ -241,6 +241,7 @@ no_jsmanaged_fields!(ImageCacheTask, ScriptControlChan);
no_jsmanaged_fields!(Atom, Namespace, Timer); no_jsmanaged_fields!(Atom, Namespace, Timer);
no_jsmanaged_fields!(Trusted<T>); no_jsmanaged_fields!(Trusted<T>);
no_jsmanaged_fields!(PropertyDeclarationBlock); no_jsmanaged_fields!(PropertyDeclarationBlock);
no_jsmanaged_fields!(HashSet<T>);
// These three are interdependent, if you plan to put jsmanaged data // These three are interdependent, if you plan to put jsmanaged data
// in one of these make sure it is propagated properly to containing structs // in one of these make sure it is propagated properly to containing structs
no_jsmanaged_fields!(SubpageId, WindowSizeData, PipelineId); no_jsmanaged_fields!(SubpageId, WindowSizeData, PipelineId);

View file

@ -34,7 +34,7 @@ use script_task::ScriptMsg;
use script_traits::ScriptControlChan; use script_traits::ScriptControlChan;
use timers::{IsInterval, TimerId, TimerManager, TimerCallback}; use timers::{IsInterval, TimerId, TimerManager, TimerCallback};
use devtools_traits::DevtoolsControlChan; use devtools_traits::{DevtoolsControlChan, TimelineMarker, TimelineMarkerType, TracingMetadata};
use msg::compositor_msg::ScriptListener; use msg::compositor_msg::ScriptListener;
use msg::constellation_msg::{LoadData, PipelineId, SubpageId, ConstellationChan, WindowSizeData, WorkerId}; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, ConstellationChan, WindowSizeData, WorkerId};
use net_traits::ResourceTask; use net_traits::ResourceTask;
@ -54,13 +54,15 @@ use url::{Url, UrlParser};
use libc; use libc;
use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD}; use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD};
use std::cell::{Cell, Ref, RefMut}; use std::borrow::ToOwned;
use std::cell::{Cell, Ref, RefMut, RefCell};
use std::collections::HashSet;
use std::default::Default; use std::default::Default;
use std::ffi::CString; use std::ffi::CString;
use std::mem; use std::mem;
use std::num::Float; use std::num::Float;
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::mpsc::TryRecvError::{Empty, Disconnected}; use std::sync::mpsc::TryRecvError::{Empty, Disconnected};
use time; use time;
@ -102,6 +104,10 @@ pub struct Window {
/// For providing instructions to an optional devtools server. /// For providing instructions to an optional devtools server.
devtools_chan: Option<DevtoolsControlChan>, devtools_chan: Option<DevtoolsControlChan>,
/// For sending timeline markers. Will be ignored if
/// no devtools server
devtools_markers: RefCell<HashSet<TimelineMarkerType>>,
devtools_marker_sender: RefCell<Option<Sender<TimelineMarker>>>,
/// A flag to indicate whether the developer tools have requested live updates of /// A flag to indicate whether the developer tools have requested live updates of
/// page changes. /// page changes.
@ -477,6 +483,10 @@ pub trait WindowHelpers {
fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option<Temporary<Window>>; fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option<Temporary<Window>>;
fn thaw(self); fn thaw(self);
fn freeze(self); fn freeze(self);
fn need_emit_timeline_marker(self, timeline_type: TimelineMarkerType) -> bool;
fn emit_timeline_marker(self, marker: TimelineMarker);
fn set_devtools_timeline_marker(self, marker: TimelineMarkerType, reply: Sender<TimelineMarker>);
fn drop_devtools_timeline_markers(self);
} }
pub trait ScriptHelpers { pub trait ScriptHelpers {
@ -547,6 +557,11 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
None => return, None => return,
}; };
if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) {
let marker = TimelineMarker::new("Reflow".to_owned(), TracingMetadata::IntervalStart);
self.emit_timeline_marker(marker);
}
// Layout will let us know when it's done. // Layout will let us know when it's done.
let (join_chan, join_port) = channel(); let (join_chan, join_port) = channel();
@ -583,6 +598,11 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
debug!("script: layout forked"); debug!("script: layout forked");
self.join_layout(); self.join_layout();
if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) {
let marker = TimelineMarker::new("Reflow".to_owned(), TracingMetadata::IntervalEnd);
self.emit_timeline_marker(marker);
}
} }
// FIXME(cgaebel): join_layout is racey. What if the compositor triggers a // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a
@ -776,6 +796,27 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
fn freeze(self) { fn freeze(self) {
self.timers.suspend(); self.timers.suspend();
} }
fn need_emit_timeline_marker(self, timeline_type: TimelineMarkerType) -> bool {
let markers = self.devtools_markers.borrow();
markers.contains(&timeline_type)
}
fn emit_timeline_marker(self, marker: TimelineMarker) {
let sender = self.devtools_marker_sender.borrow();
let sender = sender.as_ref().expect("There is no marker sender");
sender.send(marker);
}
fn set_devtools_timeline_marker(self, marker: TimelineMarkerType, reply: Sender<TimelineMarker>) {
*self.devtools_marker_sender.borrow_mut() = Some(reply);
self.devtools_markers.borrow_mut().insert(marker);
}
fn drop_devtools_timeline_markers(self) {
self.devtools_markers.borrow_mut().clear();
*self.devtools_marker_sender.borrow_mut() = None;
}
} }
impl Window { impl Window {
@ -836,6 +877,9 @@ impl Window {
layout_rpc: layout_rpc, layout_rpc: layout_rpc,
layout_join_port: DOMRefCell::new(None), layout_join_port: DOMRefCell::new(None),
window_size: Cell::new(window_size), window_size: Cell::new(window_size),
devtools_marker_sender: RefCell::new(None),
devtools_markers: RefCell::new(HashSet::new()),
devtools_wants_updates: Cell::new(false), devtools_wants_updates: Cell::new(false),
}; };

View file

@ -47,6 +47,7 @@ use devtools;
use devtools_traits::{DevtoolsControlChan, DevtoolsControlPort, DevtoolsPageInfo}; use devtools_traits::{DevtoolsControlChan, DevtoolsControlPort, DevtoolsPageInfo};
use devtools_traits::{DevtoolsControlMsg, DevtoolScriptControlMsg}; use devtools_traits::{DevtoolsControlMsg, DevtoolScriptControlMsg};
use devtools_traits::{TimelineMarker, TimelineMarkerType, TracingMetadata};
use script_traits::CompositorEvent; use script_traits::CompositorEvent;
use script_traits::CompositorEvent::{ResizeEvent, ReflowEvent, ClickEvent}; use script_traits::CompositorEvent::{ResizeEvent, ReflowEvent, ClickEvent};
use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent}; use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent};
@ -86,6 +87,7 @@ use std::ascii::AsciiExt;
use std::any::Any; use std::any::Any;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashSet;
use std::num::ToPrimitive; use std::num::ToPrimitive;
use std::option::Option; use std::option::Option;
use std::ptr; use std::ptr;
@ -279,6 +281,10 @@ pub struct ScriptTask {
/// no such server exists. /// no such server exists.
devtools_port: DevtoolsControlPort, devtools_port: DevtoolsControlPort,
devtools_sender: Sender<DevtoolScriptControlMsg>, devtools_sender: Sender<DevtoolScriptControlMsg>,
/// For sending timeline markers. Will be ignored if
/// no devtools server
devtools_markers: RefCell<HashSet<TimelineMarkerType>>,
devtools_marker_sender: RefCell<Option<Sender<TimelineMarker>>>,
/// The JavaScript runtime. /// The JavaScript runtime.
js_runtime: js::rust::rt, js_runtime: js::rust::rt,
@ -447,6 +453,8 @@ impl ScriptTask {
devtools_chan: devtools_chan, devtools_chan: devtools_chan,
devtools_port: devtools_receiver, devtools_port: devtools_receiver,
devtools_sender: devtools_sender, devtools_sender: devtools_sender,
devtools_markers: RefCell::new(HashSet::new()),
devtools_marker_sender: RefCell::new(None),
js_runtime: js_runtime, js_runtime: js_runtime,
js_context: DOMRefCell::new(Some(js_context)), js_context: DOMRefCell::new(Some(js_context)),
@ -700,6 +708,10 @@ impl ScriptTask {
devtools::handle_modify_attribute(&page, id, node_id, modifications), devtools::handle_modify_attribute(&page, id, node_id, modifications),
DevtoolScriptControlMsg::WantsLiveNotifications(pipeline_id, to_send) => DevtoolScriptControlMsg::WantsLiveNotifications(pipeline_id, to_send) =>
devtools::handle_wants_live_notifications(&page, pipeline_id, to_send), devtools::handle_wants_live_notifications(&page, pipeline_id, to_send),
DevtoolScriptControlMsg::SetTimelineMarkers(_pipeline_id, marker_types, reply) =>
devtools::handle_set_timeline_markers(&page, self, marker_types, reply),
DevtoolScriptControlMsg::DropTimelineMarkers(_pipeline_id, marker_types) =>
devtools::handle_drop_timeline_markers(&page, self, marker_types),
} }
} }
@ -1150,8 +1162,14 @@ impl ScriptTask {
/// ///
/// TODO: Actually perform DOM event dispatch. /// TODO: Actually perform DOM event dispatch.
fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) { fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) {
match event { match event {
ResizeEvent(new_size) => { ResizeEvent(new_size) => {
let _marker;
if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) {
_marker = AutoDOMEventMarker::new(self);
}
self.handle_resize_event(pipeline_id, new_size); self.handle_resize_event(pipeline_id, new_size);
} }
@ -1179,6 +1197,10 @@ impl ScriptTask {
} }
ClickEvent(button, point) => { ClickEvent(button, point) => {
let _marker;
if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) {
_marker = AutoDOMEventMarker::new(self);
}
let page = get_page(&self.root_page(), pipeline_id); let page = get_page(&self.root_page(), pipeline_id);
let document = page.document().root(); let document = page.document().root();
document.r().handle_click_event(self.js_runtime.ptr, button, point); document.r().handle_click_event(self.js_runtime.ptr, button, point);
@ -1187,6 +1209,10 @@ impl ScriptTask {
MouseDownEvent(..) => {} MouseDownEvent(..) => {}
MouseUpEvent(..) => {} MouseUpEvent(..) => {}
MouseMoveEvent(point) => { MouseMoveEvent(point) => {
let _marker;
if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) {
_marker = AutoDOMEventMarker::new(self);
}
let page = get_page(&self.root_page(), pipeline_id); let page = get_page(&self.root_page(), pipeline_id);
let document = page.document().root(); let document = page.document().root();
let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut();
@ -1195,6 +1221,10 @@ impl ScriptTask {
} }
KeyEvent(key, state, modifiers) => { KeyEvent(key, state, modifiers) => {
let _marker;
if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) {
_marker = AutoDOMEventMarker::new(self);
}
let page = get_page(&self.root_page(), pipeline_id); let page = get_page(&self.root_page(), pipeline_id);
let document = page.document().root(); let document = page.document().root();
document.r().dispatch_key_event( document.r().dispatch_key_event(
@ -1311,6 +1341,26 @@ impl ScriptTask {
self.incomplete_loads.borrow_mut().push(incomplete); self.incomplete_loads.borrow_mut().push(incomplete);
} }
fn need_emit_timeline_marker(&self, timeline_type: TimelineMarkerType) -> bool {
self.devtools_markers.borrow().contains(&timeline_type)
}
fn emit_timeline_marker(&self, marker: TimelineMarker) {
let sender = self.devtools_marker_sender.borrow();
let sender = sender.as_ref().expect("There is no marker sender");
sender.send(marker);
}
pub fn set_devtools_timeline_marker(&self, marker: TimelineMarkerType, reply: Sender<TimelineMarker>) {
*self.devtools_marker_sender.borrow_mut() = Some(reply);
self.devtools_markers.borrow_mut().insert(marker);
}
pub fn drop_devtools_timeline_markers(&self) {
self.devtools_markers.borrow_mut().clear();
*self.devtools_marker_sender.borrow_mut() = None;
}
} }
impl Drop for ScriptTask { impl Drop for ScriptTask {
@ -1321,6 +1371,28 @@ impl Drop for ScriptTask {
} }
} }
struct AutoDOMEventMarker<'a> {
script_task: &'a ScriptTask
}
impl<'a> AutoDOMEventMarker<'a> {
fn new(script_task: &'a ScriptTask) -> AutoDOMEventMarker<'a> {
let marker = TimelineMarker::new("DOMEvent".to_owned(), TracingMetadata::IntervalStart);
script_task.emit_timeline_marker(marker);
AutoDOMEventMarker {
script_task: script_task
}
}
}
#[unsafe_destructor]
impl<'a> Drop for AutoDOMEventMarker<'a> {
fn drop(&mut self) {
let marker = TimelineMarker::new("DOMEvent".to_owned(), TracingMetadata::IntervalEnd);
self.script_task.emit_timeline_marker(marker);
}
}
/// Shuts down layout for the given page tree. /// Shuts down layout for the given page tree.
fn shut_down_layout(page_tree: &Rc<Page>, exit_type: PipelineExitType) { fn shut_down_layout(page_tree: &Rc<Page>, exit_type: PipelineExitType) {
let mut channels = vec!(); let mut channels = vec!();

View file

@ -173,6 +173,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"msg 0.0.1", "msg 0.0.1",
"rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1", "util 0.0.1",
] ]