/* 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 //! 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::StreamId; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::actors::inspector::node::NodeActor; use crate::actors::inspector::walker::WalkerActor; use crate::protocol::JsonPacketStream; 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, href: String, #[serde(skip_serializing_if = "Vec::is_empty")] selectors: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] selectors_specificity: Vec, #[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, is_name_valid: bool, is_used: IsUsed, is_valid: bool, name: String, offsets: Vec, 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, } 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, stream: &mut TcpStream, _id: StreamId, ) -> Result { 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::(&self.node); let walker = registry.find::(&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 { let node = registry.find::(&self.node); let walker = registry.find::(&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() .map(|decl| { 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> { let node = registry.find::(&self.node); let walker = registry.find::(&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), } } }