diff options
author | eri <eri@inventati.org> | 2024-08-25 11:30:23 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-25 09:30:23 +0000 |
commit | 6357998ede902de7fb75354283f4fabbc141c28c (patch) | |
tree | 75df42a51ede24d86562e00ae60d8c43a686282b /components/devtools | |
parent | 67e2bb0ee6039e98f361e33617c0401a52963daf (diff) | |
download | servo-6357998ede902de7fb75354283f4fabbc141c28c.tar.gz servo-6357998ede902de7fb75354283f4fabbc141c28c.zip |
DevTools: Inspect node styles (#33025)
* feat: retrieve applied styles
Signed-off-by: eri <eri@inventati.org>
* feat: preliminary style showing
Signed-off-by: eri <eri@inventati.org>
* chore: some style tests
Signed-off-by: eri <eri@inventati.org>
* feat: edit style rules
Signed-off-by: eri <eri@inventati.org>
* feat: css database
Signed-off-by: eri <eri@inventati.org>
* feat: computed styles
Signed-off-by: eri <eri@inventati.org>
* feat: inherited styles
Signed-off-by: eri <eri@inventati.org>
* feat: get stylesheet styles
Signed-off-by: eri <eri@inventati.org>
* feat: all styles in inspector
Signed-off-by: eri <eri@inventati.org>
* feat: multiple stylesheets
Signed-off-by: eri <eri@inventati.org>
* refactor: clean up
Signed-off-by: eri <eri@inventati.org>
* Some minor cleanup
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
---------
Signed-off-by: eri <eri@inventati.org>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components/devtools')
-rw-r--r-- | components/devtools/actors/browsing_context.rs | 12 | ||||
-rw-r--r-- | components/devtools/actors/inspector.rs | 1 | ||||
-rw-r--r-- | components/devtools/actors/inspector/css_properties.rs | 30 | ||||
-rw-r--r-- | components/devtools/actors/inspector/node.rs | 13 | ||||
-rw-r--r-- | components/devtools/actors/inspector/page_style.rs | 378 | ||||
-rw-r--r-- | components/devtools/actors/inspector/style_rule.rs | 252 | ||||
-rw-r--r-- | components/devtools/actors/inspector/walker.rs | 17 | ||||
-rw-r--r-- | components/devtools/actors/watcher.rs | 2 |
8 files changed, 520 insertions, 185 deletions
diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs index 4a667d48556..9a9173adb7b 100644 --- a/components/devtools/actors/browsing_context.rs +++ b/components/devtools/actors/browsing_context.rs @@ -12,9 +12,9 @@ use std::net::TcpStream; use std::time::{SystemTime, UNIX_EPOCH}; use base::id::{BrowsingContextId, PipelineId}; -use devtools_traits::DevtoolScriptControlMsg::{self, WantsLiveNotifications}; +use devtools_traits::DevtoolScriptControlMsg::{self, GetCssDatabase, WantsLiveNotifications}; use devtools_traits::{ConsoleLog, DevtoolsPageInfo, NavigationState, PageError}; -use ipc_channel::ipc::IpcSender; +use ipc_channel::ipc::{self, IpcSender}; use serde::Serialize; use serde_json::{Map, Value}; @@ -229,7 +229,13 @@ impl BrowsingContextActor { let accessibility = AccessibilityActor::new(actors.new_name("accessibility")); - let css_properties = CssPropertiesActor::new(actors.new_name("css-properties")); + let properties = (|| { + let (properties_sender, properties_receiver) = ipc::channel().ok()?; + script_sender.send(GetCssDatabase(properties_sender)).ok()?; + properties_receiver.recv().ok() + })() + .unwrap_or_default(); + let css_properties = CssPropertiesActor::new(actors.new_name("css-properties"), properties); let inspector = InspectorActor { name: actors.new_name("inspector"), diff --git a/components/devtools/actors/inspector.rs b/components/devtools/actors/inspector.rs index db3cde89bec..dfc13e3433d 100644 --- a/components/devtools/actors/inspector.rs +++ b/components/devtools/actors/inspector.rs @@ -29,6 +29,7 @@ pub mod highlighter; pub mod layout; pub mod node; pub mod page_style; +pub mod style_rule; pub mod walker; #[derive(Serialize)] diff --git a/components/devtools/actors/inspector/css_properties.rs b/components/devtools/actors/inspector/css_properties.rs index 0b411292189..cb7a939f224 100644 --- a/components/devtools/actors/inspector/css_properties.rs +++ b/components/devtools/actors/inspector/css_properties.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::net::TcpStream; +use devtools_traits::CssDatabaseProperty; use serde::Serialize; use serde_json::{Map, Value}; @@ -17,21 +18,13 @@ use crate::StreamId; pub struct CssPropertiesActor { name: String, + properties: HashMap<String, CssDatabaseProperty>, } #[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct CssDatabaseProperty { - is_inherited: bool, - values: Vec<&'static str>, - supports: Vec<&'static str>, - subproperties: Vec<&'static str>, -} - -#[derive(Serialize)] -struct GetCssDatabaseReply { - properties: HashMap<&'static str, CssDatabaseProperty>, +struct GetCssDatabaseReply<'a> { from: String, + properties: &'a HashMap<String, CssDatabaseProperty>, } impl Actor for CssPropertiesActor { @@ -55,16 +48,7 @@ impl Actor for CssPropertiesActor { "getCSSDatabase" => { let _ = stream.write_json_packet(&GetCssDatabaseReply { from: self.name(), - // TODO: Fill this programatically with other properties - properties: HashMap::from([( - "color", - CssDatabaseProperty { - is_inherited: true, - values: vec!["color"], - supports: vec!["color"], - subproperties: vec!["color"], - }, - )]), + properties: &self.properties, }); ActorMessageStatus::Processed @@ -75,7 +59,7 @@ impl Actor for CssPropertiesActor { } impl CssPropertiesActor { - pub fn new(name: String) -> Self { - Self { name } + pub fn new(name: String, properties: HashMap<String, CssDatabaseProperty>) -> Self { + Self { name, properties } } } diff --git a/components/devtools/actors/inspector/node.rs b/components/devtools/actors/inspector/node.rs index 650b0c6bf54..a203f43b839 100644 --- a/components/devtools/actors/inspector/node.rs +++ b/components/devtools/actors/inspector/node.rs @@ -5,6 +5,7 @@ //! This actor represents one DOM node. It is created by the Walker actor when it is traversing the //! document tree. +use std::cell::RefCell; use std::collections::HashMap; use std::net::TcpStream; @@ -76,9 +77,10 @@ pub struct NodeActorMsg { pub struct NodeActor { name: String, - script_chan: IpcSender<DevtoolScriptControlMsg>, - pipeline: PipelineId, + pub script_chan: IpcSender<DevtoolScriptControlMsg>, + pub pipeline: PipelineId, pub walker: String, + pub style_rules: RefCell<HashMap<(String, usize), String>>, } impl Actor for NodeActor { @@ -102,7 +104,6 @@ impl Actor for NodeActor { ) -> Result<ActorMessageStatus, ()> { Ok(match msg_type { "modifyAttributes" => { - let target = msg.get("to").ok_or(())?.as_str().ok_or(())?; let mods = msg.get("modifications").ok_or(())?.as_array().ok_or(())?; let modifications: Vec<_> = mods .iter() @@ -117,7 +118,7 @@ impl Actor for NodeActor { self.script_chan .send(ModifyAttribute( self.pipeline, - registry.actor_to_script(target.to_owned()), + registry.actor_to_script(self.name()), modifications, )) .map_err(|_| ())?; @@ -176,13 +177,15 @@ impl NodeInfoToProtocol for NodeInfo { ) -> NodeActorMsg { let actor = if !actors.script_actor_registered(self.unique_id.clone()) { let name = actors.new_name("node"); + actors.register_script_actor(self.unique_id, name.clone()); + let node_actor = NodeActor { name: name.clone(), script_chan: script_chan.clone(), pipeline, walker: walker.clone(), + style_rules: RefCell::new(HashMap::new()), }; - actors.register_script_actor(self.unique_id, name.clone()); actors.register_later(Box::new(node_actor)); name } else { diff --git a/components/devtools/actors/inspector/page_style.rs b/components/devtools/actors/inspector/page_style.rs index dc69df13584..481c55c3e3e 100644 --- a/components/devtools/actors/inspector/page_style.rs +++ b/components/devtools/actors/inspector/page_style.rs @@ -5,67 +5,45 @@ //! The page style actor is responsible of informing the DevTools client of the different style //! properties applied, including the attributes and layout of each element. +use std::collections::hash_map::Entry; use std::collections::HashMap; +use std::iter::once; use std::net::TcpStream; use base::id::PipelineId; -use devtools_traits::DevtoolScriptControlMsg::GetLayout; +use devtools_traits::DevtoolScriptControlMsg::{GetLayout, GetSelectors}; use devtools_traits::{ComputedNodeLayout, DevtoolScriptControlMsg}; use ipc_channel::ipc::{self, IpcSender}; use serde::Serialize; use serde_json::{self, Map, Value}; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; +use crate::actors::inspector::node::NodeActor; +use crate::actors::inspector::style_rule::{AppliedRule, ComputedDeclaration, StyleRuleActor}; +use crate::actors::inspector::walker::{find_child, WalkerActor}; use crate::protocol::JsonPacketStream; use crate::StreamId; #[derive(Serialize)] struct GetAppliedReply { entries: Vec<AppliedEntry>, - rules: Vec<AppliedRule>, - sheets: Vec<AppliedSheet>, from: String, } #[derive(Serialize)] struct GetComputedReply { - computed: Vec<u32>, //XXX all css props + computed: HashMap<String, ComputedDeclaration>, from: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct AppliedEntry { - rule: String, - pseudo_element: Value, + rule: AppliedRule, + pseudo_element: Option<()>, is_system: bool, - matched_selectors: Vec<String>, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct AppliedRule { - actor: String, - #[serde(rename = "type")] - type_: String, - href: String, - css_text: String, - line: u32, - column: u32, - parent_style_sheet: String, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct AppliedSheet { - actor: String, - href: String, - node_href: String, - disabled: bool, - title: String, - system: bool, - style_sheet_index: isize, - rule_count: usize, + #[serde(skip_serializing_if = "Option::is_none")] + inherited: Option<String>, } #[derive(Serialize)] @@ -125,13 +103,15 @@ impl Actor for PageStyleActor { /// The page style actor can handle the following messages: /// - /// - `getApplied`: Returns the applied styles for a node, placeholder + /// - `getApplied`: Returns the applied styles for a node, they represent the explicit css + /// rules set for them, both in the style attribute and in stylesheets. /// - /// - `getComputed`: Returns the computed styles for a node, placeholder + /// - `getComputed`: Returns the computed styles for a node, these include all of the supported + /// css properties calculated values. /// - /// - `getLayout`: Returns the box layout properties for a node, placeholder + /// - `getLayout`: Returns the box layout properties for a node. /// - /// - `isPositionEditable`: Informs whether you can change a style property in the inspector + /// - `isPositionEditable`: Informs whether you can change a style property in the inspector. fn handle_message( &self, registry: &ActorRegistry, @@ -141,123 +121,231 @@ impl Actor for PageStyleActor { _id: StreamId, ) -> Result<ActorMessageStatus, ()> { Ok(match msg_type { - "getApplied" => { - // TODO: Query script for relevant applied styles to node (msg.node) - let msg = GetAppliedReply { - entries: vec![], - rules: vec![], - sheets: vec![], - from: self.name(), - }; - let _ = stream.write_json_packet(&msg); - ActorMessageStatus::Processed - }, + "getApplied" => self.get_applied(msg, registry, stream)?, + "getComputed" => self.get_computed(msg, registry, stream)?, + "getLayout" => self.get_layout(msg, registry, stream)?, + "isPositionEditable" => self.is_position_editable(stream), + _ => ActorMessageStatus::Ignored, + }) + } +} - "getComputed" => { - // TODO: Query script for relevant computed styles on node (msg.node) - let msg = GetComputedReply { - computed: vec![], - from: self.name(), - }; - let _ = stream.write_json_packet(&msg); - ActorMessageStatus::Processed - }, +impl PageStyleActor { + fn get_applied( + &self, + msg: &Map<String, Value>, + registry: &ActorRegistry, + stream: &mut TcpStream, + ) -> Result<ActorMessageStatus, ()> { + let target = msg.get("node").ok_or(())?.as_str().ok_or(())?; + let node = registry.find::<NodeActor>(&target); + let walker = registry.find::<WalkerActor>(&node.walker); + let entries: Vec<_> = find_child( + &node.script_chan, + node.pipeline, + &target, + registry, + &walker.root_node.actor, + vec![], + |msg| msg.actor == target, + ) + .unwrap_or_default() + .into_iter() + .filter_map(|node| { + let inherited = (node.actor != target).then(|| node.actor.clone()); + let node_actor = registry.find::<NodeActor>(&node.actor); - "getLayout" => { - // TODO: Query script for box layout properties of node (msg.node) - let target = msg.get("node").ok_or(())?.as_str().ok_or(())?; - let (tx, rx) = ipc::channel().map_err(|_| ())?; - self.script_chan - .send(GetLayout( - self.pipeline, - registry.actor_to_script(target.to_owned()), - tx, + // Get the css selectors that match this node present in the currently active stylesheets. + let selectors = (|| { + let (selectors_sender, selector_receiver) = ipc::channel().ok()?; + walker + .script_chan + .send(GetSelectors( + walker.pipeline, + registry.actor_to_script(node.actor.clone()), + selectors_sender, )) - .unwrap(); - let ComputedNodeLayout { - display, - position, - z_index, - box_sizing, - auto_margins, - margin_top, - margin_right, - margin_bottom, - margin_left, - border_top_width, - border_right_width, - border_bottom_width, - border_left_width, - padding_top, - padding_right, - padding_bottom, - padding_left, - width, - height, - } = rx.recv().map_err(|_| ())?.ok_or(())?; + .ok()?; + selector_receiver.recv().ok()? + })() + .unwrap_or_default(); - let msg_auto_margins = msg - .get("autoMargins") - .and_then(Value::as_bool) - .unwrap_or(false); + // For each selector (plus an empty one that represents the style attribute) + // get all of the rules associated with it. + let entries = + once(("".into(), usize::MAX)) + .chain(selectors) + .filter_map(move |selector| { + let rule = match node_actor.style_rules.borrow_mut().entry(selector) { + Entry::Vacant(e) => { + let name = registry.new_name("style-rule"); + let actor = StyleRuleActor::new( + name.clone(), + node_actor.name(), + (e.key().0 != "").then_some(e.key().clone()), + ); + let rule = actor.applied(registry)?; - // https://searchfox.org/mozilla-central/source/devtools/server/actors/page-style.js - let msg = GetLayoutReply { - from: self.name(), - display, - position, - z_index, - box_sizing, - auto_margins: if msg_auto_margins { - let mut m = Map::new(); - let auto = serde_json::value::Value::String("auto".to_owned()); - if auto_margins.top { - m.insert("top".to_owned(), auto.clone()); - } - if auto_margins.right { - m.insert("right".to_owned(), auto.clone()); - } - if auto_margins.bottom { - m.insert("bottom".to_owned(), auto.clone()); + registry.register_later(Box::new(actor)); + e.insert(name); + rule + }, + Entry::Occupied(e) => { + let actor = registry.find::<StyleRuleActor>(e.get()); + actor.applied(registry)? + }, + }; + if inherited.is_some() && rule.declarations.is_empty() { + return None; } - if auto_margins.left { - m.insert("left".to_owned(), auto); - } - serde_json::value::Value::Object(m) - } else { - serde_json::value::Value::Null - }, - margin_top, - margin_right, - margin_bottom, - margin_left, - border_top_width, - border_right_width, - border_bottom_width, - border_left_width, - padding_top, - padding_right, - padding_bottom, - padding_left, - width, - height, - }; - let msg = serde_json::to_string(&msg).map_err(|_| ())?; - let msg = serde_json::from_str::<Value>(&msg).map_err(|_| ())?; - let _ = stream.write_json_packet(&msg); - ActorMessageStatus::Processed + + Some(AppliedEntry { + rule, + // TODO: Handle pseudo elements + pseudo_element: None, + is_system: false, + inherited: inherited.clone(), + }) + }); + Some(entries) + }) + .flatten() + .collect(); + let msg = GetAppliedReply { + entries, + from: self.name(), + }; + let _ = stream.write_json_packet(&msg); + Ok(ActorMessageStatus::Processed) + } + + fn get_computed( + &self, + msg: &Map<String, Value>, + registry: &ActorRegistry, + stream: &mut TcpStream, + ) -> Result<ActorMessageStatus, ()> { + let target = msg.get("node").ok_or(())?.as_str().ok_or(())?; + let node_actor = registry.find::<NodeActor>(&target); + let computed = (|| match node_actor + .style_rules + .borrow_mut() + .entry(("".into(), usize::MAX)) + { + Entry::Vacant(e) => { + let name = registry.new_name("style-rule"); + let actor = StyleRuleActor::new(name.clone(), target.into(), None); + let computed = actor.computed(registry)?; + registry.register_later(Box::new(actor)); + e.insert(name); + Some(computed) }, + Entry::Occupied(e) => { + let actor = registry.find::<StyleRuleActor>(e.get()); + Some(actor.computed(registry)?) + }, + })() + .unwrap_or_default(); + let msg = GetComputedReply { + computed, + from: self.name(), + }; + let _ = stream.write_json_packet(&msg); + Ok(ActorMessageStatus::Processed) + } - "isPositionEditable" => { - let msg = IsPositionEditableReply { - from: self.name(), - value: false, - }; - let _ = stream.write_json_packet(&msg); - ActorMessageStatus::Processed + fn get_layout( + &self, + msg: &Map<String, Value>, + registry: &ActorRegistry, + stream: &mut TcpStream, + ) -> Result<ActorMessageStatus, ()> { + let target = msg.get("node").ok_or(())?.as_str().ok_or(())?; + let (computed_node_sender, computed_node_receiver) = ipc::channel().map_err(|_| ())?; + self.script_chan + .send(GetLayout( + self.pipeline, + registry.actor_to_script(target.to_owned()), + computed_node_sender, + )) + .unwrap(); + let ComputedNodeLayout { + display, + position, + z_index, + box_sizing, + auto_margins, + margin_top, + margin_right, + margin_bottom, + margin_left, + border_top_width, + border_right_width, + border_bottom_width, + border_left_width, + padding_top, + padding_right, + padding_bottom, + padding_left, + width, + height, + } = computed_node_receiver.recv().map_err(|_| ())?.ok_or(())?; + let msg_auto_margins = msg + .get("autoMargins") + .and_then(Value::as_bool) + .unwrap_or(false); + let msg = GetLayoutReply { + from: self.name(), + display, + position, + z_index, + box_sizing, + auto_margins: if msg_auto_margins { + let mut m = Map::new(); + let auto = serde_json::value::Value::String("auto".to_owned()); + if auto_margins.top { + m.insert("top".to_owned(), auto.clone()); + } + if auto_margins.right { + m.insert("right".to_owned(), auto.clone()); + } + if auto_margins.bottom { + m.insert("bottom".to_owned(), auto.clone()); + } + if auto_margins.left { + m.insert("left".to_owned(), auto); + } + serde_json::value::Value::Object(m) + } else { + serde_json::value::Value::Null }, + margin_top, + margin_right, + margin_bottom, + margin_left, + border_top_width, + border_right_width, + border_bottom_width, + border_left_width, + padding_top, + padding_right, + padding_bottom, + padding_left, + width, + height, + }; + let msg = serde_json::to_string(&msg).map_err(|_| ())?; + let msg = serde_json::from_str::<Value>(&msg).map_err(|_| ())?; + let _ = stream.write_json_packet(&msg); + Ok(ActorMessageStatus::Processed) + } - _ => ActorMessageStatus::Ignored, - }) + fn is_position_editable(&self, stream: &mut TcpStream) -> ActorMessageStatus { + let msg = IsPositionEditableReply { + from: self.name(), + value: false, + }; + let _ = stream.write_json_packet(&msg); + ActorMessageStatus::Processed } } diff --git a/components/devtools/actors/inspector/style_rule.rs b/components/devtools/actors/inspector/style_rule.rs new file mode 100644 index 00000000000..0be0d017fc6 --- /dev/null +++ b/components/devtools/actors/inspector/style_rule.rs @@ -0,0 +1,252 @@ +/* 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 <https://searchfox.org/mozilla-central/source/devtools/server/actors/thread-configuration.js> +//! This actor represents one css rule group from a node, allowing the inspector to view it and change it. +//! A group is either the html style attribute or one selector from one stylesheet. + +use std::collections::HashMap; +use std::net::TcpStream; + +use devtools_traits::DevtoolScriptControlMsg::{ + GetAttributeStyle, GetComputedStyle, GetDocumentElement, GetStylesheetStyle, ModifyRule, +}; +use ipc_channel::ipc; +use serde::Serialize; +use serde_json::{Map, Value}; + +use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; +use crate::actors::inspector::node::NodeActor; +use crate::actors::inspector::walker::WalkerActor; +use crate::protocol::JsonPacketStream; +use crate::StreamId; + +const ELEMENT_STYLE_TYPE: u32 = 100; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppliedRule { + actor: String, + ancestor_data: Vec<()>, + authored_text: String, + css_text: String, + pub declarations: Vec<AppliedDeclaration>, + href: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + selectors: Vec<String>, + #[serde(skip_serializing_if = "Vec::is_empty")] + selectors_specificity: Vec<u32>, + #[serde(rename = "type")] + type_: u32, + traits: StyleRuleActorTraits, +} + +#[derive(Serialize)] +pub struct IsUsed { + pub used: bool, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppliedDeclaration { + colon_offsets: Vec<i32>, + is_name_valid: bool, + is_used: IsUsed, + is_valid: bool, + name: String, + offsets: Vec<i32>, + priority: String, + terminator: String, + value: String, +} + +#[derive(Serialize)] +pub struct ComputedDeclaration { + matched: bool, + value: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StyleRuleActorTraits { + pub can_set_rule_text: bool, +} + +#[derive(Serialize)] +pub struct StyleRuleActorMsg { + from: String, + rule: Option<AppliedRule>, +} + +pub struct StyleRuleActor { + name: String, + node: String, + selector: Option<(String, usize)>, +} + +impl Actor for StyleRuleActor { + fn name(&self) -> String { + self.name.clone() + } + + /// The style rule configuration actor can handle the following messages: + /// + /// - `setRuleText`: Applies a set of modifications to the css rules that this actor manages. + /// There is also `modifyProperties`, which has a slightly different API to do the same, but + /// this is preferred. Which one the devtools client sends is decided by the `traits` defined + /// when returning the list of rules. + fn handle_message( + &self, + registry: &ActorRegistry, + msg_type: &str, + msg: &Map<String, Value>, + stream: &mut TcpStream, + _id: StreamId, + ) -> Result<ActorMessageStatus, ()> { + Ok(match msg_type { + "setRuleText" => { + // Parse the modifications sent from the client + let mods = msg.get("modifications").ok_or(())?.as_array().ok_or(())?; + let modifications: Vec<_> = mods + .iter() + .filter_map(|json_mod| { + serde_json::from_str(&serde_json::to_string(json_mod).ok()?).ok() + }) + .collect(); + + // Query the rule modification + let node = registry.find::<NodeActor>(&self.node); + let walker = registry.find::<WalkerActor>(&node.walker); + walker + .script_chan + .send(ModifyRule( + walker.pipeline, + registry.actor_to_script(self.node.clone()), + modifications, + )) + .map_err(|_| ())?; + + let _ = stream.write_json_packet(&self.encodable(registry)); + ActorMessageStatus::Processed + }, + _ => ActorMessageStatus::Ignored, + }) + } +} + +impl StyleRuleActor { + pub fn new(name: String, node: String, selector: Option<(String, usize)>) -> Self { + Self { + name, + node, + selector, + } + } + + pub fn applied(&self, registry: &ActorRegistry) -> Option<AppliedRule> { + let node = registry.find::<NodeActor>(&self.node); + let walker = registry.find::<WalkerActor>(&node.walker); + + let (document_sender, document_receiver) = ipc::channel().ok()?; + walker + .script_chan + .send(GetDocumentElement(walker.pipeline, document_sender)) + .ok()?; + let node = document_receiver.recv().ok()??; + + // Gets the style definitions. If there is a selector, query the relevant stylesheet, if + // not, this represents the style attribute. + let (style_sender, style_receiver) = ipc::channel().ok()?; + let req = match &self.selector { + Some(selector) => { + let (selector, stylesheet) = selector.clone(); + GetStylesheetStyle( + walker.pipeline, + registry.actor_to_script(self.node.clone()), + selector, + stylesheet, + style_sender, + ) + }, + None => GetAttributeStyle( + walker.pipeline, + registry.actor_to_script(self.node.clone()), + style_sender, + ), + }; + walker.script_chan.send(req).ok()?; + let style = style_receiver.recv().ok()??; + + Some(AppliedRule { + actor: self.name(), + ancestor_data: vec![], // TODO: Fill with hierarchy + authored_text: "".into(), + css_text: "".into(), // TODO: Specify the css text + declarations: style + .into_iter() + .filter_map(|decl| { + Some(AppliedDeclaration { + colon_offsets: vec![], + is_name_valid: true, + is_used: IsUsed { used: true }, + is_valid: true, + name: decl.name, + offsets: vec![], // TODO: Get the source of the declaration + priority: decl.priority, + terminator: "".into(), + value: decl.value, + }) + }) + .collect(), + href: node.base_uri.clone(), + selectors: self.selector.iter().map(|(s, _)| s).cloned().collect(), + selectors_specificity: self.selector.iter().map(|_| 1).collect(), + type_: ELEMENT_STYLE_TYPE, + traits: StyleRuleActorTraits { + can_set_rule_text: true, + }, + }) + } + + pub fn computed( + &self, + registry: &ActorRegistry, + ) -> Option<HashMap<String, ComputedDeclaration>> { + let node = registry.find::<NodeActor>(&self.node); + let walker = registry.find::<WalkerActor>(&node.walker); + + let (style_sender, style_receiver) = ipc::channel().ok()?; + walker + .script_chan + .send(GetComputedStyle( + walker.pipeline, + registry.actor_to_script(self.node.clone()), + style_sender, + )) + .ok()?; + let style = style_receiver.recv().ok()??; + + Some( + style + .into_iter() + .map(|s| { + ( + s.name, + ComputedDeclaration { + matched: true, + value: s.value, + }, + ) + }) + .collect(), + ) + } + + pub fn encodable(&self, registry: &ActorRegistry) -> StyleRuleActorMsg { + StyleRuleActorMsg { + from: self.name(), + rule: self.applied(registry), + } + } +} diff --git a/components/devtools/actors/inspector/walker.rs b/components/devtools/actors/inspector/walker.rs index 72ddd01209c..9b3447df7bf 100644 --- a/components/devtools/actors/inspector/walker.rs +++ b/components/devtools/actors/inspector/walker.rs @@ -9,7 +9,7 @@ use std::net::TcpStream; use base::id::PipelineId; use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement}; -use devtools_traits::{DevtoolScriptControlMsg, Modification}; +use devtools_traits::{AttrModification, DevtoolScriptControlMsg}; use ipc_channel::ipc::{self, IpcSender}; use serde::Serialize; use serde_json::{self, Map, Value}; @@ -31,7 +31,7 @@ pub struct WalkerActor { pub script_chan: IpcSender<DevtoolScriptControlMsg>, pub pipeline: PipelineId, pub root_node: NodeActorMsg, - pub mutations: RefCell<Vec<(Modification, String)>>, + pub mutations: RefCell<Vec<(AttrModification, String)>>, } #[derive(Serialize)] @@ -235,9 +235,9 @@ impl Actor for WalkerActor { self.pipeline, &self.name, registry, - selector, node, vec![], + |msg| msg.display_name == selector, ) .map_err(|_| ())?; hierarchy.reverse(); @@ -273,7 +273,7 @@ impl WalkerActor { &self, stream: &mut TcpStream, target: &str, - modifications: &[Modification], + modifications: &[AttrModification], ) { { let mut mutations = self.mutations.borrow_mut(); @@ -288,14 +288,15 @@ impl WalkerActor { /// Recursively searches for a child with the specified selector /// If it is found, returns a list with the child and all of its ancestors. -fn find_child( +/// TODO: Investigate how to cache this to some extent. +pub fn find_child( script_chan: &IpcSender<DevtoolScriptControlMsg>, pipeline: PipelineId, name: &str, registry: &ActorRegistry, - selector: &str, node: &str, mut hierarchy: Vec<NodeActorMsg>, + compare_fn: impl Fn(&NodeActorMsg) -> bool + Clone, ) -> Result<Vec<NodeActorMsg>, Vec<NodeActorMsg>> { let (tx, rx) = ipc::channel().unwrap(); script_chan @@ -309,7 +310,7 @@ fn find_child( for child in children { let msg = child.encode(registry, true, script_chan.clone(), pipeline, name.into()); - if msg.display_name == selector { + if compare_fn(&msg) { hierarchy.push(msg); return Ok(hierarchy); }; @@ -323,9 +324,9 @@ fn find_child( pipeline, name, registry, - selector, &msg.actor, hierarchy, + compare_fn.clone(), ) { Ok(mut hierarchy) => { hierarchy.push(msg); diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index 55358067b5b..c74a82d421e 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -59,7 +59,7 @@ impl SessionContext { // working propperly supported_resources: HashMap::from([ ("console-message", true), - ("css-change", false), + ("css-change", true), ("css-message", false), ("css-registered-properties", false), ("document-event", false), |