mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01: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
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue