mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
369 lines
12 KiB
Rust
369 lines
12 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
//! 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::root::RootActor;
|
|
use crate::actors::stylesheets::StyleSheetsActor;
|
|
use crate::actors::thread::ThreadActor;
|
|
use crate::actors::timeline::TimelineActor;
|
|
use crate::protocol::JsonPacketStream;
|
|
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::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 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 streams: RefCell<Vec<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,
|
|
) -> 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()));
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
},
|
|
};
|
|
self.streams.borrow_mut().push(stream.try_clone().unwrap());
|
|
stream.write_json_packet(&msg);
|
|
self.script_chan
|
|
.send(WantsLiveNotifications(self.active_pipeline.get(), true))
|
|
.unwrap();
|
|
ActorMessageStatus::Processed
|
|
},
|
|
|
|
//FIXME: The current implementation won't work for multiple connections. Need to ensure
|
|
// that the correct stream is removed.
|
|
"detach" => {
|
|
let msg = BrowsingContextDetachedReply {
|
|
from: self.name(),
|
|
type_: "detached".to_owned(),
|
|
};
|
|
self.streams.borrow_mut().pop();
|
|
stream.write_json_packet(&msg);
|
|
self.script_chan
|
|
.send(WantsLiveNotifications(self.active_pipeline.get(), false))
|
|
.unwrap();
|
|
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(),
|
|
}],
|
|
};
|
|
stream.write_json_packet(&msg);
|
|
ActorMessageStatus::Processed
|
|
},
|
|
|
|
"listWorkers" => {
|
|
let msg = ListWorkersReply {
|
|
from: self.name(),
|
|
workers: vec![],
|
|
};
|
|
stream.write_json_packet(&msg);
|
|
ActorMessageStatus::Processed
|
|
},
|
|
|
|
_ => ActorMessageStatus::Ignored,
|
|
})
|
|
}
|
|
}
|
|
|
|
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 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(),
|
|
thread: thread.name(),
|
|
streams: RefCell::new(Vec::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));
|
|
|
|
let root = actors.find_mut::<RootActor>("root");
|
|
root.tabs.push(target.name.clone());
|
|
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 &mut *self.streams.borrow_mut() {
|
|
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,
|
|
}
|