mirror of
https://github.com/servo/servo
synced 2026-05-03 04:42:17 +02:00
The inspector view allows modifying the attributes of DOM elements. However, we lie to the devtools client: While it looks like the attributes change, the changes are never actually applied to the DOM. This change fixes that, and also makes it so attribute modifications from non-inspector sources are shown in the inspector. Testing: This change adds two tests --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
384 lines
13 KiB
Rust
384 lines
13 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](https://searchfox.org/mozilla-central/source/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 std::net::TcpStream;
|
|
|
|
use atomic_refcell::AtomicRefCell;
|
|
use base::generic_channel::{self, GenericSender, SendError};
|
|
use base::id::PipelineId;
|
|
use devtools_traits::DevtoolScriptControlMsg::{self, GetCssDatabase, SimulateColorScheme};
|
|
use devtools_traits::{DevtoolsPageInfo, NavigationState};
|
|
use embedder_traits::Theme;
|
|
use malloc_size_of_derive::MallocSizeOf;
|
|
use serde::Serialize;
|
|
use serde_json::{Map, Value};
|
|
|
|
use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
|
|
use crate::actors::inspector::InspectorActor;
|
|
use crate::actors::inspector::accessibility::AccessibilityActor;
|
|
use crate::actors::inspector::css_properties::CssPropertiesActor;
|
|
use crate::actors::reflow::ReflowActor;
|
|
use crate::actors::stylesheets::StyleSheetsActor;
|
|
use crate::actors::tab::TabDescriptorActor;
|
|
use crate::actors::thread::ThreadActor;
|
|
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
|
|
use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap};
|
|
use crate::protocol::{ClientRequest, JsonPacketStream};
|
|
use crate::resource::ResourceAvailable;
|
|
use crate::{EmptyReplyMsg, StreamId};
|
|
|
|
#[derive(Serialize)]
|
|
struct ListWorkersReply {
|
|
from: String,
|
|
workers: Vec<()>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct FrameUpdateReply {
|
|
from: String,
|
|
#[serde(rename = "type")]
|
|
type_: String,
|
|
frames: Vec<FrameUpdateMsg>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct FrameUpdateMsg {
|
|
id: u32,
|
|
is_top_level: bool,
|
|
url: String,
|
|
title: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct TabNavigated {
|
|
from: String,
|
|
#[serde(rename = "type")]
|
|
type_: String,
|
|
url: String,
|
|
title: Option<String>,
|
|
#[serde(rename = "nativeConsoleAPI")]
|
|
native_console_api: bool,
|
|
state: String,
|
|
is_frame_switching: bool,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct BrowsingContextTraits {
|
|
frames: bool,
|
|
is_browsing_context: bool,
|
|
log_in_page: bool,
|
|
navigation: bool,
|
|
supports_top_level_target_flag: bool,
|
|
watchpoints: bool,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
enum TargetType {
|
|
Frame,
|
|
// Other target types not implemented yet.
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub(crate) struct BrowsingContextActorMsg {
|
|
actor: String,
|
|
title: String,
|
|
url: String,
|
|
/// This correspond to webview_id
|
|
#[serde(rename = "browserId")]
|
|
browser_id: u32,
|
|
#[serde(rename = "outerWindowID")]
|
|
outer_window_id: u32,
|
|
#[serde(rename = "browsingContextID")]
|
|
browsing_context_id: u32,
|
|
is_top_level_target: bool,
|
|
traits: BrowsingContextTraits,
|
|
// Implemented actors
|
|
accessibility_actor: String,
|
|
console_actor: String,
|
|
css_properties_actor: String,
|
|
inspector_actor: String,
|
|
reflow_actor: String,
|
|
style_sheets_actor: String,
|
|
thread_actor: String,
|
|
target_type: TargetType,
|
|
// Part of the official protocol, but not yet implemented.
|
|
// animations_actor: String,
|
|
// changes_actor: String,
|
|
// framerate_actor: String,
|
|
// manifest_actor: String,
|
|
// memory_actor: String,
|
|
// network_content_actor: String,
|
|
// objects_manager: String,
|
|
// performance_actor: String,
|
|
// resonsive_actor: String,
|
|
// storage_actor: String,
|
|
// tracer_actor: String,
|
|
// web_extension_inspected_window_actor: String,
|
|
// web_socket_actor: String,
|
|
}
|
|
|
|
/// The browsing context actor encompasses all of the other supporting actors when debugging a web
|
|
/// view. To this extent, it contains a watcher actor that helps when communicating with the host,
|
|
/// as well as resource actors that each perform one debugging function.
|
|
#[derive(MallocSizeOf)]
|
|
pub(crate) struct BrowsingContextActor {
|
|
name: String,
|
|
pub title: AtomicRefCell<String>,
|
|
pub url: AtomicRefCell<String>,
|
|
/// This corresponds to webview_id
|
|
pub browser_id: DevtoolsBrowserId,
|
|
// TODO: Should these ids be atomic?
|
|
active_pipeline_id: AtomicRefCell<PipelineId>,
|
|
active_outer_window_id: AtomicRefCell<DevtoolsOuterWindowId>,
|
|
pub browsing_context_id: DevtoolsBrowsingContextId,
|
|
accessibility: String,
|
|
pub console: String,
|
|
css_properties: String,
|
|
pub(crate) inspector: String,
|
|
reflow: String,
|
|
style_sheets: String,
|
|
pub thread: String,
|
|
_tab: String,
|
|
pub script_chan: GenericSender<DevtoolScriptControlMsg>,
|
|
pub watcher: String,
|
|
}
|
|
|
|
impl ResourceAvailable for BrowsingContextActor {
|
|
fn actor_name(&self) -> String {
|
|
self.name.clone()
|
|
}
|
|
}
|
|
|
|
impl Actor for BrowsingContextActor {
|
|
fn name(&self) -> String {
|
|
self.name.clone()
|
|
}
|
|
|
|
fn handle_message(
|
|
&self,
|
|
request: ClientRequest,
|
|
_registry: &ActorRegistry,
|
|
msg_type: &str,
|
|
_msg: &Map<String, Value>,
|
|
_id: StreamId,
|
|
) -> Result<(), ActorError> {
|
|
match msg_type {
|
|
"listFrames" => {
|
|
// TODO: Find out what needs to be listed here
|
|
let msg = EmptyReplyMsg { from: self.name() };
|
|
request.reply_final(&msg)?
|
|
},
|
|
"listWorkers" => {
|
|
request.reply_final(&ListWorkersReply {
|
|
from: self.name(),
|
|
// TODO: Find out what needs to be listed here
|
|
workers: vec![],
|
|
})?
|
|
},
|
|
_ => return Err(ActorError::UnrecognizedPacketType),
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl BrowsingContextActor {
|
|
#[expect(clippy::too_many_arguments)]
|
|
pub(crate) fn new(
|
|
console: String,
|
|
browser_id: DevtoolsBrowserId,
|
|
browsing_context_id: DevtoolsBrowsingContextId,
|
|
page_info: DevtoolsPageInfo,
|
|
pipeline_id: PipelineId,
|
|
outer_window_id: DevtoolsOuterWindowId,
|
|
script_sender: GenericSender<DevtoolScriptControlMsg>,
|
|
actors: &ActorRegistry,
|
|
) -> BrowsingContextActor {
|
|
let name = actors.new_name::<BrowsingContextActor>();
|
|
let DevtoolsPageInfo {
|
|
title,
|
|
url,
|
|
is_top_level_global,
|
|
} = page_info;
|
|
|
|
let accessibility = AccessibilityActor::new(actors.new_name::<AccessibilityActor>());
|
|
|
|
let properties = (|| {
|
|
let (properties_sender, properties_receiver) = generic_channel::channel()?;
|
|
script_sender.send(GetCssDatabase(properties_sender)).ok()?;
|
|
properties_receiver.recv().ok()
|
|
})()
|
|
.unwrap_or_default();
|
|
let css_properties =
|
|
CssPropertiesActor::new(actors.new_name::<CssPropertiesActor>(), properties);
|
|
|
|
let inspector = InspectorActor::register(actors, pipeline_id, script_sender.clone());
|
|
|
|
let reflow = ReflowActor::new(actors.new_name::<ReflowActor>());
|
|
|
|
let style_sheets = StyleSheetsActor::new(actors.new_name::<StyleSheetsActor>());
|
|
|
|
let tabdesc = TabDescriptorActor::new(actors, name.clone(), is_top_level_global);
|
|
|
|
let thread = ThreadActor::new(actors.new_name::<ThreadActor>(), script_sender.clone());
|
|
|
|
let watcher = WatcherActor::new(
|
|
actors,
|
|
name.clone(),
|
|
SessionContext::new(SessionContextType::BrowserElement),
|
|
);
|
|
|
|
let target = BrowsingContextActor {
|
|
name,
|
|
script_chan: script_sender,
|
|
title: AtomicRefCell::new(title),
|
|
url: AtomicRefCell::new(url.into_string()),
|
|
active_pipeline_id: AtomicRefCell::new(pipeline_id),
|
|
active_outer_window_id: AtomicRefCell::new(outer_window_id),
|
|
browser_id,
|
|
browsing_context_id,
|
|
accessibility: accessibility.name(),
|
|
console,
|
|
css_properties: css_properties.name(),
|
|
inspector,
|
|
reflow: reflow.name(),
|
|
style_sheets: style_sheets.name(),
|
|
_tab: tabdesc.name(),
|
|
thread: thread.name(),
|
|
watcher: watcher.name(),
|
|
};
|
|
|
|
actors.register(accessibility);
|
|
actors.register(css_properties);
|
|
actors.register(reflow);
|
|
actors.register(style_sheets);
|
|
actors.register(tabdesc);
|
|
actors.register(thread);
|
|
actors.register(watcher);
|
|
|
|
target
|
|
}
|
|
|
|
pub(crate) fn navigate<'a>(
|
|
&self,
|
|
state: NavigationState,
|
|
id_map: &mut IdMap,
|
|
connections: impl Iterator<Item = &'a mut TcpStream>,
|
|
) {
|
|
let (pipeline_id, 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(pipeline_id) = pipeline_id {
|
|
let outer_window_id = id_map.outer_window_id(pipeline_id);
|
|
*self.active_outer_window_id.borrow_mut() = outer_window_id;
|
|
*self.active_pipeline_id.borrow_mut() = pipeline_id;
|
|
}
|
|
url.as_str().clone_into(&mut self.url.borrow_mut());
|
|
if let Some(ref t) = title {
|
|
self.title.borrow_mut().clone_from(t);
|
|
}
|
|
|
|
let msg = TabNavigated {
|
|
from: self.name(),
|
|
type_: "tabNavigated".to_owned(),
|
|
url: url.as_str().to_owned(),
|
|
title,
|
|
native_console_api: true,
|
|
state: state.to_owned(),
|
|
is_frame_switching: false,
|
|
};
|
|
|
|
for stream in connections {
|
|
let _ = stream.write_json_packet(&msg);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn title_changed(&self, pipeline_id: PipelineId, title: String) {
|
|
if pipeline_id != self.pipeline_id() {
|
|
return;
|
|
}
|
|
*self.title.borrow_mut() = title;
|
|
}
|
|
|
|
pub(crate) fn frame_update(&self, request: &mut ClientRequest) {
|
|
let _ = request.write_json_packet(&FrameUpdateReply {
|
|
from: self.name(),
|
|
type_: "frameUpdate".into(),
|
|
frames: vec![FrameUpdateMsg {
|
|
id: self.browsing_context_id.value(),
|
|
is_top_level: true,
|
|
title: self.title.borrow().clone(),
|
|
url: self.url.borrow().clone(),
|
|
}],
|
|
});
|
|
}
|
|
|
|
pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> {
|
|
self.script_chan
|
|
.send(SimulateColorScheme(self.pipeline_id(), theme))
|
|
.map_err(|_| ())
|
|
}
|
|
|
|
pub(crate) fn pipeline_id(&self) -> PipelineId {
|
|
*self.active_pipeline_id.borrow()
|
|
}
|
|
|
|
pub(crate) fn outer_window_id(&self) -> DevtoolsOuterWindowId {
|
|
*self.active_outer_window_id.borrow()
|
|
}
|
|
|
|
pub(crate) fn instruct_script_to_send_live_updates(&self, should_send_updates: bool) {
|
|
let result = self
|
|
.script_chan
|
|
.send(DevtoolScriptControlMsg::WantsLiveNotifications(
|
|
self.pipeline_id(),
|
|
should_send_updates,
|
|
));
|
|
|
|
// Notifying the script thread may fail with a "Disconnected" error if servo
|
|
// as a whole is being shut down.
|
|
debug_assert!(matches!(result, Ok(_) | Err(SendError::Disconnected)));
|
|
}
|
|
}
|
|
|
|
impl ActorEncode<BrowsingContextActorMsg> for BrowsingContextActor {
|
|
fn encode(&self, _: &ActorRegistry) -> BrowsingContextActorMsg {
|
|
BrowsingContextActorMsg {
|
|
actor: self.name(),
|
|
traits: BrowsingContextTraits {
|
|
is_browsing_context: true,
|
|
frames: true,
|
|
log_in_page: false,
|
|
navigation: true,
|
|
supports_top_level_target_flag: true,
|
|
watchpoints: true,
|
|
},
|
|
title: self.title.borrow().clone(),
|
|
url: self.url.borrow().clone(),
|
|
browser_id: self.browser_id.value(),
|
|
browsing_context_id: self.browsing_context_id.value(),
|
|
outer_window_id: self.outer_window_id().value(),
|
|
is_top_level_target: true,
|
|
accessibility_actor: self.accessibility.clone(),
|
|
console_actor: self.console.clone(),
|
|
css_properties_actor: self.css_properties.clone(),
|
|
inspector_actor: self.inspector.clone(),
|
|
reflow_actor: self.reflow.clone(),
|
|
style_sheets_actor: self.style_sheets.clone(),
|
|
thread_actor: self.thread.clone(),
|
|
target_type: TargetType::Frame,
|
|
}
|
|
}
|
|
}
|