Inform the devtools about shadow roots on a node (#35294)

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-02-05 14:16:36 +01:00 committed by GitHub
parent 2bd96633d4
commit 09bfaf51b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 108 additions and 35 deletions

View file

@ -11,7 +11,7 @@ use std::net::TcpStream;
use base::id::PipelineId; use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement, ModifyAttribute}; use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement, ModifyAttribute};
use devtools_traits::{DevtoolScriptControlMsg, NodeInfo}; use devtools_traits::{DevtoolScriptControlMsg, NodeInfo, ShadowRootMode};
use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::ipc::{self, IpcSender};
use serde::Serialize; use serde::Serialize;
use serde_json::{self, Map, Value}; use serde_json::{self, Map, Value};
@ -44,6 +44,10 @@ struct AttrMsg {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct NodeActorMsg { pub struct NodeActorMsg {
pub actor: String, pub actor: String,
/// The ID of the shadow host of this node, if it is
/// a shadow root
host: Option<String>,
#[serde(rename = "baseURI")] #[serde(rename = "baseURI")]
base_uri: String, base_uri: String,
causes_overflow: bool, causes_overflow: bool,
@ -62,6 +66,7 @@ pub struct NodeActorMsg {
is_marker_pseudo_element: bool, is_marker_pseudo_element: bool,
is_native_anonymous: bool, is_native_anonymous: bool,
is_scrollable: bool, is_scrollable: bool,
is_shadow_host: bool,
is_shadow_root: bool, is_shadow_root: bool,
is_top_level_document: bool, is_top_level_document: bool,
node_name: String, node_name: String,
@ -70,7 +75,7 @@ pub struct NodeActorMsg {
pub num_children: usize, pub num_children: usize,
#[serde(skip_serializing_if = "String::is_empty")] #[serde(skip_serializing_if = "String::is_empty")]
parent: String, parent: String,
shadow_root_mode: Option<()>, shadow_root_mode: Option<String>,
traits: HashMap<String, ()>, traits: HashMap<String, ()>,
attrs: Vec<AttrMsg>, attrs: Vec<AttrMsg>,
} }
@ -175,23 +180,31 @@ impl NodeInfoToProtocol for NodeInfo {
pipeline: PipelineId, pipeline: PipelineId,
walker: String, walker: String,
) -> NodeActorMsg { ) -> NodeActorMsg {
let actor = if !actors.script_actor_registered(self.unique_id.clone()) { let get_or_register_node_actor = |id: &str| {
let name = actors.new_name("node"); if !actors.script_actor_registered(id.to_string()) {
actors.register_script_actor(self.unique_id, name.clone()); let name = actors.new_name("node");
actors.register_script_actor(id.to_string(), name.clone());
let node_actor = NodeActor { let node_actor = NodeActor {
name: name.clone(), name: name.clone(),
script_chan: script_chan.clone(), script_chan: script_chan.clone(),
pipeline, pipeline,
walker: walker.clone(), walker: walker.clone(),
style_rules: RefCell::new(HashMap::new()), style_rules: RefCell::new(HashMap::new()),
}; };
actors.register_later(Box::new(node_actor)); actors.register_later(Box::new(node_actor));
name name
} else { } else {
actors.script_to_actor(self.unique_id) actors.script_to_actor(id.to_string())
}
}; };
let actor = get_or_register_node_actor(&self.unique_id);
let host = self
.host
.as_ref()
.map(|host_id| get_or_register_node_actor(host_id));
let name = actors.actor_to_script(actor.clone()); let name = actors.actor_to_script(actor.clone());
// If a node only has a single text node as a child whith a small enough text, // If a node only has a single text node as a child whith a small enough text,
@ -226,6 +239,7 @@ impl NodeInfoToProtocol for NodeInfo {
NodeActorMsg { NodeActorMsg {
actor, actor,
host,
base_uri: self.base_uri, base_uri: self.base_uri,
causes_overflow: false, causes_overflow: false,
container_type: None, container_type: None,
@ -241,14 +255,18 @@ impl NodeInfoToProtocol for NodeInfo {
is_marker_pseudo_element: false, is_marker_pseudo_element: false,
is_native_anonymous: false, is_native_anonymous: false,
is_scrollable: false, is_scrollable: false,
is_shadow_root: false, is_shadow_host: self.is_shadow_host,
is_shadow_root: self.shadow_root_mode.is_some(),
is_top_level_document: self.is_top_level_document, is_top_level_document: self.is_top_level_document,
node_name: self.node_name, node_name: self.node_name,
node_type: self.node_type, node_type: self.node_type,
node_value: self.node_value, node_value: self.node_value,
num_children: self.num_children, num_children: self.num_children,
parent: actors.script_to_actor(self.parent.clone()), parent: actors.script_to_actor(self.parent.clone()),
shadow_root_mode: None, shadow_root_mode: self
.shadow_root_mode
.as_ref()
.map(ShadowRootMode::to_string),
traits: HashMap::new(), traits: HashMap::new(),
attrs: self attrs: self
.attrs .attrs

View file

@ -162,23 +162,24 @@ pub(crate) fn handle_get_children(
}) })
.collect(); .collect();
let children: Vec<_> = parent let mut children = vec![];
.children() if let Some(shadow_root) = parent.downcast::<Element>().and_then(Element::shadow_root) {
.enumerate() children.push(shadow_root.upcast::<Node>().summarize());
.filter_map(|(i, child)| { }
// Filter whitespace only text nodes that are not inline level let children_iter = parent.children().enumerate().filter_map(|(i, child)| {
// https://firefox-source-docs.mozilla.org/devtools-user/page_inspector/how_to/examine_and_edit_html/index.html#whitespace-only-text-nodes // Filter whitespace only text nodes that are not inline level
let prev_inline = i > 0 && inline[i - 1]; // https://firefox-source-docs.mozilla.org/devtools-user/page_inspector/how_to/examine_and_edit_html/index.html#whitespace-only-text-nodes
let next_inline = i < inline.len() - 1 && inline[i + 1]; let prev_inline = i > 0 && inline[i - 1];
let next_inline = i < inline.len() - 1 && inline[i + 1];
let info = child.summarize(); let info = child.summarize();
if !is_whitespace(&info) { if !is_whitespace(&info) {
return Some(info); return Some(info);
} }
(prev_inline && next_inline).then_some(info) (prev_inline && next_inline).then_some(info)
}) });
.collect(); children.extend(children_iter);
reply.send(Some(children)).unwrap(); reply.send(Some(children)).unwrap();
}, },

View file

@ -46,6 +46,7 @@ use uuid::Uuid;
use xml5ever::serialize as xml_serialize; use xml5ever::serialize as xml_serialize;
use super::globalscope::GlobalScope; use super::globalscope::GlobalScope;
use crate::conversions::Convert;
use crate::document_loader::DocumentLoader; use crate::document_loader::DocumentLoader;
use crate::dom::attr::Attr; use crate::dom::attr::Attr;
use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut}; use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut};
@ -60,7 +61,9 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::{
use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::SlotAssignmentMode; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode,
};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::InheritTypes::DocumentFragmentTypeId; use crate::dom::bindings::codegen::InheritTypes::DocumentFragmentTypeId;
use crate::dom::bindings::codegen::UnionTypes::NodeOrString; use crate::dom::bindings::codegen::UnionTypes::NodeOrString;
@ -1180,8 +1183,28 @@ impl Node {
pub(crate) fn summarize(&self) -> NodeInfo { pub(crate) fn summarize(&self) -> NodeInfo {
let USVString(base_uri) = self.BaseURI(); let USVString(base_uri) = self.BaseURI();
let node_type = self.NodeType(); let node_type = self.NodeType();
let maybe_shadow_root = self.downcast::<ShadowRoot>();
let shadow_root_mode = maybe_shadow_root
.map(ShadowRoot::Mode)
.map(ShadowRootMode::convert);
let host = maybe_shadow_root
.map(ShadowRoot::Host)
.map(|host| host.upcast::<Node>().unique_id());
let is_shadow_host = self
.downcast::<Element>()
.is_some_and(Element::is_shadow_host);
let num_children = if is_shadow_host {
// Shadow roots count as children
self.ChildNodes().Length() as usize + 1
} else {
self.ChildNodes().Length() as usize
};
NodeInfo { NodeInfo {
unique_id: self.unique_id(), unique_id: self.unique_id(),
host,
base_uri, base_uri,
parent: self parent: self
.GetParentNode() .GetParentNode()
@ -1190,8 +1213,10 @@ impl Node {
is_top_level_document: node_type == NodeConstants::DOCUMENT_NODE, is_top_level_document: node_type == NodeConstants::DOCUMENT_NODE,
node_name: String::from(self.NodeName()), node_name: String::from(self.NodeName()),
node_value: self.GetNodeValue().map(|v| v.into()), node_value: self.GetNodeValue().map(|v| v.into()),
num_children: self.ChildNodes().Length() as usize, num_children,
attrs: self.downcast().map(Element::summarize).unwrap_or(vec![]), attrs: self.downcast().map(Element::summarize).unwrap_or(vec![]),
is_shadow_host,
shadow_root_mode,
} }
} }

View file

@ -11,6 +11,7 @@ use style::shared_lock::SharedRwLockReadGuard;
use style::stylesheets::Stylesheet; use style::stylesheets::Stylesheet;
use style::stylist::{CascadeData, Stylist}; use style::stylist::{CascadeData, Stylist};
use crate::conversions::Convert;
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
@ -421,3 +422,12 @@ impl<'dom> LayoutShadowRootHelpers<'dom> for LayoutDom<'dom, ShadowRoot> {
} }
} }
} }
impl Convert<devtools_traits::ShadowRootMode> for ShadowRootMode {
fn convert(self) -> devtools_traits::ShadowRootMode {
match self {
ShadowRootMode::Open => devtools_traits::ShadowRootMode::Open,
ShadowRootMode::Closed => devtools_traits::ShadowRootMode::Closed,
}
}
}

View file

@ -10,6 +10,7 @@
#![crate_type = "rlib"] #![crate_type = "rlib"]
#![deny(unsafe_code)] #![deny(unsafe_code)]
use core::fmt;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::TcpStream; use std::net::TcpStream;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
@ -125,6 +126,7 @@ pub struct AttrInfo {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct NodeInfo { pub struct NodeInfo {
pub unique_id: String, pub unique_id: String,
pub host: Option<String>,
#[serde(rename = "baseURI")] #[serde(rename = "baseURI")]
pub base_uri: String, pub base_uri: String,
pub parent: String, pub parent: String,
@ -134,6 +136,8 @@ pub struct NodeInfo {
pub num_children: usize, pub num_children: usize,
pub attrs: Vec<AttrInfo>, pub attrs: Vec<AttrInfo>,
pub is_top_level_document: bool, pub is_top_level_document: bool,
pub shadow_root_mode: Option<ShadowRootMode>,
pub is_shadow_host: bool,
} }
pub struct StartedTimelineMarker { pub struct StartedTimelineMarker {
@ -519,3 +523,18 @@ impl ConsoleMessageBuilder {
} }
} }
} }
#[derive(Debug, Deserialize, Serialize)]
pub enum ShadowRootMode {
Open,
Closed,
}
impl fmt::Display for ShadowRootMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open => write!(f, "open"),
Self::Closed => write!(f, "close"),
}
}
}