mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
DevTools was collecting CSS rules by walking stylesheets and matching selector text. This ignored cascade order and did not correctly handle rules inside layer blocks. This change uses computed values (rule tree) to get the actual applied rules in cascade order. It then maps those rules back to CSSStyleRule using the declaration block identity, and walks the CSSOM to get selector text and layer ancestry. This fills ancestor_data with layer names and lets the inspector show layered rules correctly. Testing: - Verified using the minimized testcase from the issue - Verified on https://www.sharyap.com/ - Confirmed that rules inside layer blocks are now shown with correct order and hierarchy. Fixes: #43541 Signed-off-by: arabson99 <arabiusman99@gmail.com>
274 lines
9.1 KiB
Rust
274 lines
9.1 KiB
Rust
/* 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 devtools_traits::DevtoolScriptControlMsg::{
|
|
GetAttributeStyle, GetComputedStyle, GetDocumentElement, GetStylesheetStyle, ModifyRule,
|
|
};
|
|
use devtools_traits::{AncestorData, MatchedRule};
|
|
use malloc_size_of_derive::MallocSizeOf;
|
|
use serde::Serialize;
|
|
use serde_json::{Map, Value};
|
|
use servo_base::generic_channel;
|
|
|
|
use crate::StreamId;
|
|
use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
|
|
use crate::actors::inspector::node::NodeActor;
|
|
use crate::actors::inspector::walker::WalkerActor;
|
|
use crate::protocol::ClientRequest;
|
|
|
|
const ELEMENT_STYLE_TYPE: u32 = 100;
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub(crate) struct AppliedRule {
|
|
actor: String,
|
|
ancestor_data: Vec<AncestorData>,
|
|
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(crate) struct IsUsed {
|
|
used: bool,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub(crate) 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(crate) struct ComputedDeclaration {
|
|
matched: bool,
|
|
value: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub(crate) struct StyleRuleActorTraits {
|
|
pub can_set_rule_text: bool,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub(crate) struct StyleRuleActorMsg {
|
|
from: String,
|
|
rule: Option<AppliedRule>,
|
|
}
|
|
|
|
#[derive(MallocSizeOf)]
|
|
pub(crate) struct StyleRuleActor {
|
|
name: String,
|
|
node_name: String,
|
|
selector: Option<MatchedRule>,
|
|
}
|
|
|
|
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,
|
|
request: ClientRequest,
|
|
registry: &ActorRegistry,
|
|
msg_type: &str,
|
|
msg: &Map<String, Value>,
|
|
_id: StreamId,
|
|
) -> Result<(), ActorError> {
|
|
match msg_type {
|
|
"setRuleText" => {
|
|
// Parse the modifications sent from the client
|
|
let mods = msg
|
|
.get("modifications")
|
|
.ok_or(ActorError::MissingParameter)?
|
|
.as_array()
|
|
.ok_or(ActorError::BadParameterType)?;
|
|
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_actor = registry.find::<NodeActor>(&self.node_name);
|
|
let walker = registry.find::<WalkerActor>(&node_actor.walker);
|
|
let browsing_context_actor = walker.browsing_context_actor(registry);
|
|
browsing_context_actor
|
|
.script_chan()
|
|
.send(ModifyRule(
|
|
browsing_context_actor.pipeline_id(),
|
|
registry.actor_to_script(self.node_name.clone()),
|
|
modifications,
|
|
))
|
|
.map_err(|_| ActorError::Internal)?;
|
|
|
|
request.reply_final(&self.encode(registry))?
|
|
},
|
|
_ => return Err(ActorError::UnrecognizedPacketType),
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl StyleRuleActor {
|
|
pub fn register(
|
|
registry: &ActorRegistry,
|
|
node: String,
|
|
selector: Option<MatchedRule>,
|
|
) -> String {
|
|
let name = registry.new_name::<Self>();
|
|
let actor = Self {
|
|
name: name.clone(),
|
|
node_name: node,
|
|
selector,
|
|
};
|
|
registry.register::<Self>(actor);
|
|
name
|
|
}
|
|
|
|
pub fn applied(&self, registry: &ActorRegistry) -> Option<AppliedRule> {
|
|
let node_actor = registry.find::<NodeActor>(&self.node_name);
|
|
let walker = registry.find::<WalkerActor>(&node_actor.walker);
|
|
let browsing_context_actor = walker.browsing_context_actor(registry);
|
|
|
|
let (document_sender, document_receiver) = generic_channel::channel()?;
|
|
browsing_context_actor
|
|
.script_chan()
|
|
.send(GetDocumentElement(
|
|
browsing_context_actor.pipeline_id(),
|
|
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) = generic_channel::channel()?;
|
|
let req = match &self.selector {
|
|
Some(matched_rule) => GetStylesheetStyle(
|
|
browsing_context_actor.pipeline_id(),
|
|
registry.actor_to_script(self.node_name.clone()),
|
|
matched_rule.clone(),
|
|
style_sender,
|
|
),
|
|
None => GetAttributeStyle(
|
|
browsing_context_actor.pipeline_id(),
|
|
registry.actor_to_script(self.node_name.clone()),
|
|
style_sender,
|
|
),
|
|
};
|
|
browsing_context_actor.script_chan().send(req).ok()?;
|
|
let style = style_receiver.recv().ok()??;
|
|
|
|
Some(AppliedRule {
|
|
actor: self.name(),
|
|
ancestor_data: self
|
|
.selector
|
|
.as_ref()
|
|
.map(|r| r.ancestor_data.clone())
|
|
.unwrap_or_default(),
|
|
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,
|
|
selectors: self.selector.iter().map(|r| r.selector.clone()).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_actor = registry.find::<NodeActor>(&self.node_name);
|
|
let walker = registry.find::<WalkerActor>(&node_actor.walker);
|
|
let browsing_context_actor = walker.browsing_context_actor(registry);
|
|
|
|
let (style_sender, style_receiver) = generic_channel::channel()?;
|
|
browsing_context_actor
|
|
.script_chan()
|
|
.send(GetComputedStyle(
|
|
browsing_context_actor.pipeline_id(),
|
|
registry.actor_to_script(self.node_name.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(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl ActorEncode<StyleRuleActorMsg> for StyleRuleActor {
|
|
fn encode(&self, registry: &ActorRegistry) -> StyleRuleActorMsg {
|
|
StyleRuleActorMsg {
|
|
from: self.name(),
|
|
rule: self.applied(registry),
|
|
}
|
|
}
|
|
}
|