mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
devtools: Allow highlighting elements from the inspector (#35822)
This change connects the `HighlighterActor` from the devtools with the document, which will draw a blue rectangle over any highlighted dom node. https://github.com/user-attachments/assets/571b2dab-497f-4102-9e55-517cdcc040ba --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes do not require tests because we don't have devtools tests Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
parent
20f20a07f2
commit
8608e328a1
11 changed files with 305 additions and 4 deletions
|
@ -166,6 +166,8 @@ impl Actor for InspectorActor {
|
||||||
if self.highlighter.borrow().is_none() {
|
if self.highlighter.borrow().is_none() {
|
||||||
let highlighter_actor = HighlighterActor {
|
let highlighter_actor = HighlighterActor {
|
||||||
name: registry.new_name("highlighter"),
|
name: registry.new_name("highlighter"),
|
||||||
|
pipeline,
|
||||||
|
script_sender: self.script_chan.clone(),
|
||||||
};
|
};
|
||||||
let mut highlighter = self.highlighter.borrow_mut();
|
let mut highlighter = self.highlighter.borrow_mut();
|
||||||
*highlighter = Some(highlighter_actor.name());
|
*highlighter = Some(highlighter_actor.name());
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
use base::id::PipelineId;
|
||||||
|
use devtools_traits::DevtoolScriptControlMsg;
|
||||||
|
use ipc_channel::ipc::IpcSender;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{self, Map, Value};
|
use serde_json::{self, Map, Value};
|
||||||
|
|
||||||
|
@ -21,6 +24,8 @@ pub struct HighlighterMsg {
|
||||||
|
|
||||||
pub struct HighlighterActor {
|
pub struct HighlighterActor {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
|
pub pipeline: PipelineId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -41,14 +46,39 @@ impl Actor for HighlighterActor {
|
||||||
/// - `hide`: Disables highlighting for the selected node
|
/// - `hide`: Disables highlighting for the selected node
|
||||||
fn handle_message(
|
fn handle_message(
|
||||||
&self,
|
&self,
|
||||||
_registry: &ActorRegistry,
|
registry: &ActorRegistry,
|
||||||
msg_type: &str,
|
msg_type: &str,
|
||||||
_msg: &Map<String, Value>,
|
msg: &Map<String, Value>,
|
||||||
stream: &mut TcpStream,
|
stream: &mut TcpStream,
|
||||||
_id: StreamId,
|
_id: StreamId,
|
||||||
) -> Result<ActorMessageStatus, ()> {
|
) -> Result<ActorMessageStatus, ()> {
|
||||||
Ok(match msg_type {
|
Ok(match msg_type {
|
||||||
"show" => {
|
"show" => {
|
||||||
|
let Some(node_actor) = msg.get("node") else {
|
||||||
|
// TODO: send missing parameter error
|
||||||
|
return Ok(ActorMessageStatus::Ignored);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(node_actor_name) = node_actor.as_str() else {
|
||||||
|
// TODO: send invalid parameter error
|
||||||
|
return Ok(ActorMessageStatus::Ignored);
|
||||||
|
};
|
||||||
|
|
||||||
|
if node_actor_name.starts_with("inspector") {
|
||||||
|
// TODO: For some reason, the client initially asks us to highlight
|
||||||
|
// the inspector? Investigate what this is supposed to mean.
|
||||||
|
let msg = ShowReply {
|
||||||
|
from: self.name(),
|
||||||
|
value: false,
|
||||||
|
};
|
||||||
|
let _ = stream.write_json_packet(&msg);
|
||||||
|
return Ok(ActorMessageStatus::Processed);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.instruct_script_thread_to_highlight_node(
|
||||||
|
Some(node_actor_name.to_owned()),
|
||||||
|
registry,
|
||||||
|
);
|
||||||
let msg = ShowReply {
|
let msg = ShowReply {
|
||||||
from: self.name(),
|
from: self.name(),
|
||||||
value: true,
|
value: true,
|
||||||
|
@ -58,6 +88,8 @@ impl Actor for HighlighterActor {
|
||||||
},
|
},
|
||||||
|
|
||||||
"hide" => {
|
"hide" => {
|
||||||
|
self.instruct_script_thread_to_highlight_node(None, registry);
|
||||||
|
|
||||||
let msg = EmptyReplyMsg { from: self.name() };
|
let msg = EmptyReplyMsg { from: self.name() };
|
||||||
let _ = stream.write_json_packet(&msg);
|
let _ = stream.write_json_packet(&msg);
|
||||||
ActorMessageStatus::Processed
|
ActorMessageStatus::Processed
|
||||||
|
@ -67,3 +99,19 @@ impl Actor for HighlighterActor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HighlighterActor {
|
||||||
|
fn instruct_script_thread_to_highlight_node(
|
||||||
|
&self,
|
||||||
|
node_actor: Option<String>,
|
||||||
|
registry: &ActorRegistry,
|
||||||
|
) {
|
||||||
|
let node_id = node_actor.map(|node_actor| registry.actor_to_script(node_actor));
|
||||||
|
self.script_sender
|
||||||
|
.send(DevtoolScriptControlMsg::HighlightDomNode(
|
||||||
|
self.pipeline,
|
||||||
|
node_id,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,9 @@ pub struct LayoutContext<'a> {
|
||||||
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
|
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
|
||||||
|
|
||||||
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
|
pub node_image_animation_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
|
||||||
|
|
||||||
|
/// The DOM node that is highlighted by the devtools inspector, if any
|
||||||
|
pub highlighted_dom_node: Option<OpaqueNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ResolvedImage<'a> {
|
pub enum ResolvedImage<'a> {
|
||||||
|
|
|
@ -31,13 +31,15 @@ use style::values::generics::NonNegative;
|
||||||
use style::values::generics::rect::Rect;
|
use style::values::generics::rect::Rect;
|
||||||
use style::values::specified::text::TextDecorationLine;
|
use style::values::specified::text::TextDecorationLine;
|
||||||
use style::values::specified::ui::CursorKind;
|
use style::values::specified::ui::CursorKind;
|
||||||
|
use style_traits::CSSPixel;
|
||||||
use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
|
use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
|
||||||
use webrender_api::{
|
use webrender_api::{
|
||||||
self as wr, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties,
|
self as wr, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties,
|
||||||
ImageRendering, NinePatchBorder, NinePatchBorderSource, units,
|
ImageRendering, NinePatchBorder, NinePatchBorderSource, SpatialId, units,
|
||||||
};
|
};
|
||||||
use wr::units::LayoutVector2D;
|
use wr::units::LayoutVector2D;
|
||||||
|
|
||||||
|
use crate::cell::ArcRefCell;
|
||||||
use crate::context::{LayoutContext, ResolvedImage};
|
use crate::context::{LayoutContext, ResolvedImage};
|
||||||
pub use crate::display_list::conversions::ToWebRender;
|
pub use crate::display_list::conversions::ToWebRender;
|
||||||
use crate::display_list::stacking_context::StackingContextSection;
|
use crate::display_list::stacking_context::StackingContextSection;
|
||||||
|
@ -161,10 +163,49 @@ pub(crate) struct DisplayListBuilder<'a> {
|
||||||
|
|
||||||
/// The [DisplayList] used to collect display list items and metadata.
|
/// The [DisplayList] used to collect display list items and metadata.
|
||||||
pub display_list: &'a mut DisplayList,
|
pub display_list: &'a mut DisplayList,
|
||||||
|
|
||||||
|
/// Data about the fragments that are highlighted by the inspector, if any.
|
||||||
|
///
|
||||||
|
/// This data is collected during the traversal of the fragment tree and used
|
||||||
|
/// to paint the highlight at the very end.
|
||||||
|
inspector_highlight: Option<InspectorHighlight>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InspectorHighlight {
|
||||||
|
/// The node that should be highlighted
|
||||||
|
tag: Tag,
|
||||||
|
|
||||||
|
/// Accumulates information about the fragments that belong to the highlighted node.
|
||||||
|
///
|
||||||
|
/// This information is collected as the fragment tree is traversed to build the
|
||||||
|
/// display list.
|
||||||
|
state: Option<HighlightTraversalState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightTraversalState {
|
||||||
|
/// The smallest rectangle that fully encloses all fragments created by the highlighted
|
||||||
|
/// dom node, if any.
|
||||||
|
content_box: euclid::Rect<Au, CSSPixel>,
|
||||||
|
|
||||||
|
spatial_id: SpatialId,
|
||||||
|
|
||||||
|
clip_chain_id: ClipChainId,
|
||||||
|
|
||||||
|
/// When the highlighted fragment is a box fragment we remember the information
|
||||||
|
/// needed to paint padding, border and margin areas.
|
||||||
|
maybe_box_fragment: Option<ArcRefCell<BoxFragment>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InspectorHighlight {
|
||||||
|
fn for_node(node: OpaqueNode) -> Self {
|
||||||
|
Self {
|
||||||
|
tag: Tag::new(node),
|
||||||
|
state: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayList {
|
impl DisplayList {
|
||||||
/// Build the display list, returning true if it was contentful.
|
|
||||||
pub fn build(
|
pub fn build(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &LayoutContext,
|
context: &LayoutContext,
|
||||||
|
@ -180,8 +221,19 @@ impl DisplayList {
|
||||||
element_for_canvas_background: fragment_tree.canvas_background.from_element,
|
element_for_canvas_background: fragment_tree.canvas_background.from_element,
|
||||||
context,
|
context,
|
||||||
display_list: self,
|
display_list: self,
|
||||||
|
inspector_highlight: context
|
||||||
|
.highlighted_dom_node
|
||||||
|
.map(InspectorHighlight::for_node),
|
||||||
};
|
};
|
||||||
fragment_tree.build_display_list(&mut builder, root_stacking_context);
|
fragment_tree.build_display_list(&mut builder, root_stacking_context);
|
||||||
|
|
||||||
|
if let Some(highlight) = builder
|
||||||
|
.inspector_highlight
|
||||||
|
.take()
|
||||||
|
.and_then(|highlight| highlight.state)
|
||||||
|
{
|
||||||
|
builder.paint_dom_inspector_highlight(highlight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +285,150 @@ impl DisplayListBuilder<'_> {
|
||||||
self.display_list.compositor_info.epoch.as_u16(),
|
self.display_list.compositor_info.epoch.as_u16(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw highlights around the node that is currently hovered in the devtools.
|
||||||
|
fn paint_dom_inspector_highlight(&mut self, highlight: HighlightTraversalState) {
|
||||||
|
const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
|
||||||
|
r: 0.23,
|
||||||
|
g: 0.7,
|
||||||
|
b: 0.87,
|
||||||
|
a: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
|
||||||
|
r: 0.49,
|
||||||
|
g: 0.3,
|
||||||
|
b: 0.7,
|
||||||
|
a: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
|
||||||
|
r: 0.2,
|
||||||
|
g: 0.2,
|
||||||
|
b: 0.2,
|
||||||
|
a: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
|
||||||
|
r: 1.,
|
||||||
|
g: 0.93,
|
||||||
|
b: 0.,
|
||||||
|
a: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highlight content box
|
||||||
|
let content_box = highlight.content_box.to_webrender();
|
||||||
|
let properties = wr::CommonItemProperties {
|
||||||
|
clip_rect: content_box,
|
||||||
|
spatial_id: highlight.spatial_id,
|
||||||
|
clip_chain_id: highlight.clip_chain_id,
|
||||||
|
flags: wr::PrimitiveFlags::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.display_list
|
||||||
|
.wr
|
||||||
|
.push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
|
||||||
|
|
||||||
|
// Highlight margin, border and padding
|
||||||
|
if let Some(box_fragment) = highlight.maybe_box_fragment {
|
||||||
|
let mut paint_highlight =
|
||||||
|
|color: webrender_api::ColorF,
|
||||||
|
fragment_relative_bounds: PhysicalRect<Au>,
|
||||||
|
widths: webrender_api::units::LayoutSideOffsets| {
|
||||||
|
if widths.is_zero() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = box_fragment
|
||||||
|
.borrow()
|
||||||
|
.offset_by_containing_block(&fragment_relative_bounds)
|
||||||
|
.to_webrender();
|
||||||
|
|
||||||
|
// We paint each highlighted area as if it was a border for simplicity
|
||||||
|
let border_style = wr::BorderSide {
|
||||||
|
color,
|
||||||
|
style: webrender_api::BorderStyle::Solid,
|
||||||
|
};
|
||||||
|
|
||||||
|
let details = wr::BorderDetails::Normal(wr::NormalBorder {
|
||||||
|
top: border_style,
|
||||||
|
right: border_style,
|
||||||
|
bottom: border_style,
|
||||||
|
left: border_style,
|
||||||
|
radius: webrender_api::BorderRadius::default(),
|
||||||
|
do_aa: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let common = wr::CommonItemProperties {
|
||||||
|
clip_rect: bounds,
|
||||||
|
spatial_id: highlight.spatial_id,
|
||||||
|
clip_chain_id: highlight.clip_chain_id,
|
||||||
|
flags: wr::PrimitiveFlags::default(),
|
||||||
|
};
|
||||||
|
self.wr().push_border(&common, bounds, widths, details)
|
||||||
|
};
|
||||||
|
|
||||||
|
let box_fragment = box_fragment.borrow();
|
||||||
|
paint_highlight(
|
||||||
|
PADDING_BOX_HIGHLIGHT_COLOR,
|
||||||
|
box_fragment.padding_rect(),
|
||||||
|
box_fragment.padding.to_webrender(),
|
||||||
|
);
|
||||||
|
paint_highlight(
|
||||||
|
BORDER_BOX_HIGHLIGHT_COLOR,
|
||||||
|
box_fragment.border_rect(),
|
||||||
|
box_fragment.border.to_webrender(),
|
||||||
|
);
|
||||||
|
paint_highlight(
|
||||||
|
MARGIN_BOX_HIGHLIGHT_COLOR,
|
||||||
|
box_fragment.margin_rect(),
|
||||||
|
box_fragment.margin.to_webrender(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InspectorHighlight {
|
||||||
|
fn register_fragment_of_highlighted_dom_node(
|
||||||
|
&mut self,
|
||||||
|
fragment: &Fragment,
|
||||||
|
spatial_id: SpatialId,
|
||||||
|
clip_chain_id: ClipChainId,
|
||||||
|
containing_block: &PhysicalRect<Au>,
|
||||||
|
) {
|
||||||
|
let state = self.state.get_or_insert(HighlightTraversalState {
|
||||||
|
content_box: euclid::Rect::zero(),
|
||||||
|
spatial_id,
|
||||||
|
clip_chain_id,
|
||||||
|
maybe_box_fragment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// We expect all fragments generated by one node to be in the same scroll tree node and clip node
|
||||||
|
debug_assert_eq!(spatial_id, state.spatial_id);
|
||||||
|
if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
|
||||||
|
debug_assert_eq!(
|
||||||
|
clip_chain_id, state.clip_chain_id,
|
||||||
|
"Fragments of the same node must either have no clip chain or the same one"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fragment_relative_rect = match fragment {
|
||||||
|
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
||||||
|
state.maybe_box_fragment = Some(fragment.clone());
|
||||||
|
|
||||||
|
fragment.borrow().content_rect
|
||||||
|
},
|
||||||
|
Fragment::Positioning(fragment) => fragment.borrow().rect,
|
||||||
|
Fragment::Text(fragment) => fragment.borrow().rect,
|
||||||
|
Fragment::Image(image_fragment) => image_fragment.borrow().rect,
|
||||||
|
Fragment::AbsoluteOrFixedPositioned(_) => return,
|
||||||
|
Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow().rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.content_box = state
|
||||||
|
.content_box
|
||||||
|
.union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fragment {
|
impl Fragment {
|
||||||
|
@ -244,6 +440,17 @@ impl Fragment {
|
||||||
is_hit_test_for_scrollable_overflow: bool,
|
is_hit_test_for_scrollable_overflow: bool,
|
||||||
is_collapsed_table_borders: bool,
|
is_collapsed_table_borders: bool,
|
||||||
) {
|
) {
|
||||||
|
if let Some(inspector_highlight) = &mut builder.inspector_highlight {
|
||||||
|
if self.tag() == Some(inspector_highlight.tag) {
|
||||||
|
inspector_highlight.register_fragment_of_highlighted_dom_node(
|
||||||
|
self,
|
||||||
|
builder.current_scroll_node_id.spatial_id,
|
||||||
|
builder.current_clip_chain_id,
|
||||||
|
containing_block,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
|
||||||
let box_fragment = &*box_fragment.borrow();
|
let box_fragment = &*box_fragment.borrow();
|
||||||
|
@ -739,6 +946,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.build_background(builder);
|
self.build_background(builder);
|
||||||
self.build_box_shadow(builder);
|
self.build_box_shadow(builder);
|
||||||
self.build_border(builder);
|
self.build_border(builder);
|
||||||
|
|
|
@ -651,6 +651,7 @@ impl LayoutThread {
|
||||||
))),
|
))),
|
||||||
iframe_sizes: Mutex::default(),
|
iframe_sizes: Mutex::default(),
|
||||||
use_rayon: rayon_pool.is_some(),
|
use_rayon: rayon_pool.is_some(),
|
||||||
|
highlighted_dom_node: reflow_request.highlighted_dom_node,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.restyle_and_build_trees(
|
self.restyle_and_build_trees(
|
||||||
|
|
|
@ -538,3 +538,21 @@ pub(crate) fn handle_get_css_database(reply: IpcSender<HashMap<String, CssDataba
|
||||||
.collect();
|
.collect();
|
||||||
let _ = reply.send(database);
|
let _ = reply.send(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_highlight_dom_node(
|
||||||
|
documents: &DocumentCollection,
|
||||||
|
id: PipelineId,
|
||||||
|
node_id: Option<String>,
|
||||||
|
) {
|
||||||
|
let node = node_id.and_then(|node_id| {
|
||||||
|
let node = find_node_by_unique_id(documents, id, &node_id);
|
||||||
|
if node.is_none() {
|
||||||
|
log::warn!("Node id {node_id} for pipeline id {id} is not found",);
|
||||||
|
}
|
||||||
|
node
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(window) = documents.find_window(id) {
|
||||||
|
window.Document().highlight_dom_node(node.as_deref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -565,6 +565,8 @@ pub(crate) struct Document {
|
||||||
/// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
|
/// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
active_keyboard_modifiers: Cell<Modifiers>,
|
active_keyboard_modifiers: Cell<Modifiers>,
|
||||||
|
/// The node that is currently highlighted by the devtools
|
||||||
|
highlighted_dom_node: MutNullableDom<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -4222,6 +4224,7 @@ impl Document {
|
||||||
intersection_observer_task_queued: Cell::new(false),
|
intersection_observer_task_queued: Cell::new(false),
|
||||||
intersection_observers: Default::default(),
|
intersection_observers: Default::default(),
|
||||||
active_keyboard_modifiers: Cell::new(Modifiers::empty()),
|
active_keyboard_modifiers: Cell::new(Modifiers::empty()),
|
||||||
|
highlighted_dom_node: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5213,6 +5216,14 @@ impl Document {
|
||||||
self.has_trustworthy_ancestor_origin.get() ||
|
self.has_trustworthy_ancestor_origin.get() ||
|
||||||
self.origin().immutable().is_potentially_trustworthy()
|
self.origin().immutable().is_potentially_trustworthy()
|
||||||
}
|
}
|
||||||
|
pub(crate) fn highlight_dom_node(&self, node: Option<&Node>) {
|
||||||
|
self.highlighted_dom_node.set(node);
|
||||||
|
self.set_needs_paint(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn highlighted_dom_node(&self) -> Option<DomRoot<Node>> {
|
||||||
|
self.highlighted_dom_node.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|
|
@ -2142,6 +2142,8 @@ impl Window {
|
||||||
.or_else(|| document.GetDocumentElement())
|
.or_else(|| document.GetDocumentElement())
|
||||||
.map(|root| root.upcast::<Node>().to_trusted_node_address());
|
.map(|root| root.upcast::<Node>().to_trusted_node_address());
|
||||||
|
|
||||||
|
let highlighted_dom_node = document.highlighted_dom_node().map(|node| node.to_opaque());
|
||||||
|
|
||||||
// Send new document and relevant styles to layout.
|
// Send new document and relevant styles to layout.
|
||||||
let reflow = ReflowRequest {
|
let reflow = ReflowRequest {
|
||||||
reflow_info: Reflow {
|
reflow_info: Reflow {
|
||||||
|
@ -2161,6 +2163,7 @@ impl Window {
|
||||||
.image_animation_manager_mut()
|
.image_animation_manager_mut()
|
||||||
.take_image_animate_set(),
|
.take_image_animate_set(),
|
||||||
theme: self.theme.get(),
|
theme: self.theme.get(),
|
||||||
|
highlighted_dom_node,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(results) = self.layout.borrow_mut().reflow(reflow) else {
|
let Some(results) = self.layout.borrow_mut().reflow(reflow) else {
|
||||||
|
|
|
@ -2069,6 +2069,9 @@ impl ScriptThread {
|
||||||
None => warn!("Message sent to closed pipeline {}.", id),
|
None => warn!("Message sent to closed pipeline {}.", id),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
DevtoolScriptControlMsg::HighlightDomNode(id, node_id) => {
|
||||||
|
devtools::handle_highlight_dom_node(&documents, id, node_id)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -273,6 +273,8 @@ pub enum DevtoolScriptControlMsg {
|
||||||
GetCssDatabase(IpcSender<HashMap<String, CssDatabaseProperty>>),
|
GetCssDatabase(IpcSender<HashMap<String, CssDatabaseProperty>>),
|
||||||
/// Simulates a light or dark color scheme for the given pipeline
|
/// Simulates a light or dark color scheme for the given pipeline
|
||||||
SimulateColorScheme(PipelineId, Theme),
|
SimulateColorScheme(PipelineId, Theme),
|
||||||
|
/// Highlight the given DOM node
|
||||||
|
HighlightDomNode(PipelineId, Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
|
|
@ -428,6 +428,8 @@ pub struct ReflowRequest {
|
||||||
pub node_to_image_animation_map: FxHashMap<OpaqueNode, ImageAnimationState>,
|
pub node_to_image_animation_map: FxHashMap<OpaqueNode, ImageAnimationState>,
|
||||||
/// The theme for the window
|
/// The theme for the window
|
||||||
pub theme: PrefersColorScheme,
|
pub theme: PrefersColorScheme,
|
||||||
|
/// The node highlighted by the devtools, if any
|
||||||
|
pub highlighted_dom_node: Option<OpaqueNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pending restyle.
|
/// A pending restyle.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue