aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authoreri <eri@inventati.org>2024-08-25 11:30:23 +0200
committerGitHub <noreply@github.com>2024-08-25 09:30:23 +0000
commit6357998ede902de7fb75354283f4fabbc141c28c (patch)
tree75df42a51ede24d86562e00ae60d8c43a686282b /components
parent67e2bb0ee6039e98f361e33617c0401a52963daf (diff)
downloadservo-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')
-rw-r--r--components/devtools/actors/browsing_context.rs12
-rw-r--r--components/devtools/actors/inspector.rs1
-rw-r--r--components/devtools/actors/inspector/css_properties.rs30
-rw-r--r--components/devtools/actors/inspector/node.rs13
-rw-r--r--components/devtools/actors/inspector/page_style.rs378
-rw-r--r--components/devtools/actors/inspector/style_rule.rs252
-rw-r--r--components/devtools/actors/inspector/walker.rs17
-rw-r--r--components/devtools/actors/watcher.rs2
-rw-r--r--components/script/devtools.rs211
-rw-r--r--components/script/dom/cssstyledeclaration.rs2
-rw-r--r--components/script/script_thread.rs24
-rw-r--r--components/shared/devtools/lib.rs52
12 files changed, 802 insertions, 192 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),
diff --git a/components/script/devtools.rs b/components/script/devtools.rs
index 7b031da2886..dacb98683c9 100644
--- a/components/script/devtools.rs
+++ b/components/script/devtools.rs
@@ -2,34 +2,42 @@
* 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/. */
+use std::collections::HashMap;
use std::rc::Rc;
use std::str;
use base::id::PipelineId;
use devtools_traits::{
- AutoMargins, ComputedNodeLayout, EvaluateJSReply, Modification, NodeInfo, TimelineMarker,
- TimelineMarkerType,
+ AttrModification, AutoMargins, ComputedNodeLayout, CssDatabaseProperty, EvaluateJSReply,
+ NodeInfo, NodeStyle, RuleModification, TimelineMarker, TimelineMarkerType,
};
use ipc_channel::ipc::IpcSender;
use js::jsval::UndefinedValue;
use js::rust::ToString;
use uuid::Uuid;
+use crate::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods;
use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
+use crate::dom::bindings::codegen::Bindings::CSSStyleRuleBinding::CSSStyleRuleMethods;
+use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods;
use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
+use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::conversions::{jsstring_to_str, ConversionResult, FromJSValConvertible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
+use crate::dom::cssstyledeclaration::ENABLED_LONGHAND_PROPERTIES;
+use crate::dom::cssstylerule::CSSStyleRule;
use crate::dom::document::AnimationFrameCallback;
use crate::dom::element::Element;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlscriptelement::SourceCode;
-use crate::dom::node::{window_from_node, Node, ShadowIncluding};
+use crate::dom::node::{stylesheets_owner_from_node, window_from_node, Node, ShadowIncluding};
+use crate::dom::types::HTMLElement;
use crate::realms::enter_realm;
use crate::script_module::ScriptFetchOptions;
use crate::script_thread::Documents;
@@ -169,6 +177,151 @@ pub fn handle_get_children(
};
}
+pub fn handle_get_attribute_style(
+ documents: &Documents,
+ pipeline: PipelineId,
+ node_id: String,
+ reply: IpcSender<Option<Vec<NodeStyle>>>,
+) {
+ let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
+ None => return reply.send(None).unwrap(),
+ Some(found_node) => found_node,
+ };
+
+ let elem = node
+ .downcast::<HTMLElement>()
+ .expect("This should be an HTMLElement");
+ let style = elem.Style();
+
+ let msg = (0..style.Length())
+ .map(|i| {
+ let name = style.Item(i);
+ NodeStyle {
+ name: name.to_string(),
+ value: style.GetPropertyValue(name.clone()).to_string(),
+ priority: style.GetPropertyPriority(name).to_string(),
+ }
+ })
+ .collect();
+
+ reply.send(Some(msg)).unwrap();
+}
+
+#[allow(crown::unrooted_must_root)]
+pub fn handle_get_stylesheet_style(
+ documents: &Documents,
+ pipeline: PipelineId,
+ node_id: String,
+ selector: String,
+ stylesheet: usize,
+ reply: IpcSender<Option<Vec<NodeStyle>>>,
+) {
+ let msg = (|| {
+ let node = find_node_by_unique_id(documents, pipeline, &node_id)?;
+
+ let document = documents.find_document(pipeline)?;
+ let _realm = enter_realm(document.window());
+ let owner = stylesheets_owner_from_node(&*node);
+
+ let stylesheet = owner.stylesheet_at(stylesheet)?;
+ let list = stylesheet.GetCssRules().ok()?;
+
+ let styles = (0..list.Length())
+ .filter_map(move |i| {
+ let rule = list.Item(i)?;
+ let style = rule.downcast::<CSSStyleRule>()?;
+ if *selector != *style.SelectorText() {
+ return None;
+ };
+ Some(style.Style())
+ })
+ .map(|style| {
+ (0..style.Length()).map(move |i| {
+ let name = style.Item(i);
+ NodeStyle {
+ name: name.to_string(),
+ value: style.GetPropertyValue(name.clone()).to_string(),
+ priority: style.GetPropertyPriority(name).to_string(),
+ }
+ })
+ })
+ .flatten()
+ .collect();
+
+ Some(styles)
+ })();
+
+ reply.send(msg).unwrap();
+}
+
+#[allow(crown::unrooted_must_root)]
+pub fn handle_get_selectors(
+ documents: &Documents,
+ pipeline: PipelineId,
+ node_id: String,
+ reply: IpcSender<Option<Vec<(String, usize)>>>,
+) {
+ let msg = (|| {
+ let node = find_node_by_unique_id(documents, pipeline, &node_id)?;
+
+ let document = documents.find_document(pipeline)?;
+ let _realm = enter_realm(document.window());
+ let owner = stylesheets_owner_from_node(&*node);
+
+ let rules = (0..owner.stylesheet_count())
+ .filter_map(|i| {
+ let stylesheet = owner.stylesheet_at(i)?;
+ let list = stylesheet.GetCssRules().ok()?;
+ let elem = node.downcast::<Element>()?;
+
+ Some((0..list.Length()).filter_map(move |j| {
+ let rule = list.Item(j)?;
+ let style = rule.downcast::<CSSStyleRule>()?;
+ let selector = style.SelectorText();
+ let _ = elem.Matches(selector.clone()).ok()?.then_some(())?;
+ Some((selector.into(), i))
+ }))
+ })
+ .flatten()
+ .collect();
+
+ Some(rules)
+ })();
+
+ reply.send(msg).unwrap();
+}
+
+pub fn handle_get_computed_style(
+ documents: &Documents,
+ pipeline: PipelineId,
+ node_id: String,
+ reply: IpcSender<Option<Vec<NodeStyle>>>,
+) {
+ let node = match find_node_by_unique_id(documents, pipeline, &node_id) {
+ None => return reply.send(None).unwrap(),
+ Some(found_node) => found_node,
+ };
+
+ let window = window_from_node(&*node);
+ let elem = node
+ .downcast::<Element>()
+ .expect("This should be an element");
+ let computed_style = window.GetComputedStyle(elem, None);
+
+ let msg = (0..computed_style.Length())
+ .map(|i| {
+ let name = computed_style.Item(i);
+ NodeStyle {
+ name: name.to_string(),
+ value: computed_style.GetPropertyValue(name.clone()).to_string(),
+ priority: computed_style.GetPropertyPriority(name).to_string(),
+ }
+ })
+ .collect();
+
+ reply.send(Some(msg)).unwrap();
+}
+
pub fn handle_get_layout(
documents: &Documents,
pipeline: PipelineId,
@@ -233,7 +386,7 @@ pub fn handle_modify_attribute(
documents: &Documents,
pipeline: PipelineId,
node_id: String,
- modifications: Vec<Modification>,
+ modifications: Vec<AttrModification>,
) {
let Some(document) = documents.find_document(pipeline) else {
return warn!("document for pipeline id {} is not found", &pipeline);
@@ -267,6 +420,38 @@ pub fn handle_modify_attribute(
}
}
+pub fn handle_modify_rule(
+ documents: &Documents,
+ pipeline: PipelineId,
+ node_id: String,
+ modifications: Vec<RuleModification>,
+) {
+ let Some(document) = documents.find_document(pipeline) else {
+ return warn!("Document for pipeline id {} is not found", &pipeline);
+ };
+ let _realm = enter_realm(document.window());
+
+ let Some(node) = find_node_by_unique_id(documents, pipeline, &node_id) else {
+ return warn!(
+ "Node id {} for pipeline id {} is not found",
+ &node_id, &pipeline
+ );
+ };
+
+ let elem = node
+ .downcast::<HTMLElement>()
+ .expect("This should be an HTMLElement");
+ let style = elem.Style();
+
+ for modification in modifications {
+ let _ = style.SetProperty(
+ modification.name.into(),
+ modification.value.into(),
+ modification.priority.into(),
+ );
+ }
+}
+
pub fn handle_wants_live_notifications(global: &GlobalScope, send_notifications: bool) {
global.set_devtools_wants_updates(send_notifications);
}
@@ -304,3 +489,21 @@ pub fn handle_reload(documents: &Documents, id: PipelineId) {
win.Location().reload_without_origin_check();
}
}
+
+pub fn handle_get_css_database(reply: IpcSender<HashMap<String, CssDatabaseProperty>>) {
+ let database: HashMap<_, _> = ENABLED_LONGHAND_PROPERTIES
+ .iter()
+ .map(|l| {
+ (
+ l.name().into(),
+ CssDatabaseProperty {
+ is_inherited: l.inherited(),
+ values: vec![], // TODO: Get allowed values for each property
+ supports: vec![],
+ subproperties: vec![l.name().into()],
+ },
+ )
+ })
+ .collect();
+ let _ = reply.send(database);
+}
diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs
index 5777e0fe74b..b6efaa7f6c3 100644
--- a/components/script/dom/cssstyledeclaration.rs
+++ b/components/script/dom/cssstyledeclaration.rs
@@ -344,7 +344,7 @@ impl CSSStyleDeclaration {
}
}
-static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| {
+pub static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| {
// The 'all' shorthand contains all the enabled longhands with 2 exceptions:
// 'direction' and 'unicode-bidi', so these must be added afterward.
let mut enabled_longhands: Vec<LonghandId> = ShorthandId::All.longhands().collect();
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 5940ccf7abb..1f1687f422a 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -2531,12 +2531,33 @@ impl ScriptThread {
DevtoolScriptControlMsg::GetChildren(id, node_id, reply) => {
devtools::handle_get_children(&documents, id, node_id, reply)
},
+ DevtoolScriptControlMsg::GetAttributeStyle(id, node_id, reply) => {
+ devtools::handle_get_attribute_style(&documents, id, node_id, reply)
+ },
+ DevtoolScriptControlMsg::GetStylesheetStyle(
+ id,
+ node_id,
+ selector,
+ stylesheet,
+ reply,
+ ) => devtools::handle_get_stylesheet_style(
+ &documents, id, node_id, selector, stylesheet, reply,
+ ),
+ DevtoolScriptControlMsg::GetSelectors(id, node_id, reply) => {
+ devtools::handle_get_selectors(&documents, id, node_id, reply)
+ },
+ DevtoolScriptControlMsg::GetComputedStyle(id, node_id, reply) => {
+ devtools::handle_get_computed_style(&documents, id, node_id, reply)
+ },
DevtoolScriptControlMsg::GetLayout(id, node_id, reply) => {
devtools::handle_get_layout(&documents, id, node_id, reply)
},
DevtoolScriptControlMsg::ModifyAttribute(id, node_id, modifications) => {
devtools::handle_modify_attribute(&documents, id, node_id, modifications)
},
+ DevtoolScriptControlMsg::ModifyRule(id, node_id, modifications) => {
+ devtools::handle_modify_rule(&documents, id, node_id, modifications)
+ },
DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => match documents
.find_window(id)
{
@@ -2553,6 +2574,9 @@ impl ScriptThread {
devtools::handle_request_animation_frame(&documents, id, name)
},
DevtoolScriptControlMsg::Reload(id) => devtools::handle_reload(&documents, id),
+ DevtoolScriptControlMsg::GetCssDatabase(reply) => {
+ devtools::handle_get_css_database(reply)
+ },
}
}
diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs
index bf6ebed2466..a7dfc0a0262 100644
--- a/components/shared/devtools/lib.rs
+++ b/components/shared/devtools/lib.rs
@@ -10,6 +10,7 @@
#![crate_type = "rlib"]
#![deny(unsafe_code)]
+use std::collections::HashMap;
use std::net::TcpStream;
use std::time::{Duration, SystemTime};
@@ -152,6 +153,14 @@ pub enum TimelineMarkerType {
DOMEvent,
}
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct NodeStyle {
+ pub name: String,
+ pub value: String,
+ pub priority: String,
+}
+
/// The properties of a DOM node as computed by layout.
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
@@ -201,10 +210,27 @@ pub enum DevtoolScriptControlMsg {
GetDocumentElement(PipelineId, IpcSender<Option<NodeInfo>>),
/// Retrieve the details of the child nodes of the given node in the given pipeline.
GetChildren(PipelineId, String, IpcSender<Option<Vec<NodeInfo>>>),
+ /// Retrieve the CSS style properties defined in the attribute tag for the given node.
+ GetAttributeStyle(PipelineId, String, IpcSender<Option<Vec<NodeStyle>>>),
+ /// Retrieve the CSS style properties defined in an stylesheet for the given selector.
+ GetStylesheetStyle(
+ PipelineId,
+ String,
+ String,
+ usize,
+ IpcSender<Option<Vec<NodeStyle>>>,
+ ),
+ /// Retrieves the CSS selectors for the given node. A selector is comprised of the text
+ /// of the selector and the id of the stylesheet that contains it.
+ GetSelectors(PipelineId, String, IpcSender<Option<Vec<(String, usize)>>>),
+ /// Retrieve the computed CSS style properties for the given node.
+ GetComputedStyle(PipelineId, String, IpcSender<Option<Vec<NodeStyle>>>),
/// Retrieve the computed layout properties of the given node in the given pipeline.
GetLayout(PipelineId, String, IpcSender<Option<ComputedNodeLayout>>),
/// Update a given node's attributes with a list of modifications.
- ModifyAttribute(PipelineId, String, Vec<Modification>),
+ ModifyAttribute(PipelineId, String, Vec<AttrModification>),
+ /// Update a given node's style rules with a list of modifications.
+ ModifyRule(PipelineId, String, Vec<RuleModification>),
/// Request live console messages for a given pipeline (true if desired, false otherwise).
WantsLiveNotifications(PipelineId, bool),
/// Request live notifications for a given set of timeline events for a given pipeline.
@@ -220,16 +246,29 @@ pub enum DevtoolScriptControlMsg {
RequestAnimationFrame(PipelineId, String),
/// Direct the given pipeline to reload the current page.
Reload(PipelineId),
+ /// Gets the list of all allowed CSS rules and possible values.
+ GetCssDatabase(IpcSender<HashMap<String, CssDatabaseProperty>>),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
-pub struct Modification {
+pub struct AttrModification {
pub attribute_name: String,
pub new_value: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RuleModification {
+ #[serde(rename = "type")]
+ pub type_: String,
+ pub index: u32,
+ pub name: String,
+ pub value: String,
+ pub priority: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum LogLevel {
Log,
Debug,
@@ -364,3 +403,12 @@ impl PreciseTime {
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct WorkerId(pub Uuid);
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CssDatabaseProperty {
+ pub is_inherited: bool,
+ pub values: Vec<String>,
+ pub supports: Vec<String>,
+ pub subproperties: Vec<String>,
+}