mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Break the association between pipelines and browsing context actors. Now there is one browsing context actor per actual browsing context, and individual actors keep track of known pipelines as necessary. There is also one console/performance/timeline/inspector/etc. actor per browsing context. This also centralizes more information in the browsing context actor. Rather than duplicating state for the active pipeline in actors that need to use it, each actor now remembers the name of its associated browsing context actor and obtains that state whenever it's necessary.
356 lines
12 KiB
Rust
356 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: String,
|
|
pub url: 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: false,
|
|
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 {
|
|
id: 0, //FIXME should match outerwindow id
|
|
parentID: 0,
|
|
url: self.url.clone(),
|
|
title: self.title.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: String::from(title),
|
|
url: 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.clone(),
|
|
url: self.url.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);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct TabNavigated {
|
|
from: String,
|
|
#[serde(rename = "type")]
|
|
type_: String,
|
|
url: String,
|
|
title: Option<String>,
|
|
nativeConsoleAPI: bool,
|
|
state: String,
|
|
isFrameSwitching: bool,
|
|
}
|