mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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>
This commit is contained in:
parent
67e2bb0ee6
commit
6357998ede
12 changed files with 810 additions and 200 deletions
|
@ -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"),
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
||||
"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
|
||||
},
|
||||
|
||||
"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,
|
||||
))
|
||||
.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(())?;
|
||||
|
||||
let msg_auto_margins = msg
|
||||
.get("autoMargins")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
|
||||
// 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());
|
||||
}
|
||||
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
|
||||
},
|
||||
|
||||
"isPositionEditable" => {
|
||||
let msg = IsPositionEditableReply {
|
||||
from: self.name(),
|
||||
value: false,
|
||||
};
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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,
|
||||
))
|
||||
.ok()?;
|
||||
selector_receiver.recv().ok()?
|
||||
})()
|
||||
.unwrap_or_default();
|
||||
|
||||
// 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)?;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
252
components/devtools/actors/inspector/style_rule.rs
Normal file
252
components/devtools/actors/inspector/style_rule.rs
Normal file
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,15 +246,28 @@ 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,
|
||||
|
@ -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>,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue