use std::sync::Arc;
use app_units::Au;
use euclid::default::{Point2D, Rect};
use euclid::{SideOffsets2D, Size2D, Vector2D};
use log::warn;
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::OffsetParentResponse;
use servo_arc::Arc as ServoArc;
use servo_url::ServoUrl;
use style::computed_values::position::T as Position;
use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
use style::dom::{OpaqueNode, TElement};
use style::properties::style_structs::Font;
use style::properties::{
parse_one_declaration_into, ComputedValues, Importance, LonghandId, PropertyDeclarationBlock,
PropertyDeclarationId, PropertyId, ShorthandId, SourcePropertyDeclaration,
};
use style::selector_parser::PseudoElement;
use style::shared_lock::SharedRwLock;
use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
use style::stylist::RuleInclusion;
use style::traversal::resolve_style;
use style::values::generics::font::LineHeight;
use style_traits::{ParsingMode, ToCss};
use crate::fragment_tree::{BoxFragment, Fragment, FragmentFlags, FragmentTree, Tag};
pub fn process_content_box_request(
requested_node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Option<Rect<Au>> {
let rects = fragment_tree?.get_content_boxes_for_node(requested_node);
if rects.is_empty() {
return None;
}
Some(
rects
.iter()
.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)),
)
}
pub fn process_content_boxes_request(
requested_node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Vec<Rect<Au>> {
fragment_tree
.map(|tree| tree.get_content_boxes_for_node(requested_node))
.unwrap_or_default()
}
pub fn process_node_geometry_request(
requested_node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Rect<i32> {
if let Some(fragment_tree) = fragment_tree {
fragment_tree.get_border_dimensions_for_node(requested_node)
} else {
Rect::zero()
}
}
pub fn process_node_scroll_area_request(
requested_node: Option<OpaqueNode>,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Rect<i32> {
let rect = match (fragment_tree, requested_node) {
(Some(tree), Some(node)) => tree.get_scrolling_area_for_node(node),
(Some(tree), None) => tree.get_scrolling_area_for_viewport(),
_ => return Rect::zero(),
};
Rect::new(
Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
)
.round()
.to_i32()
.to_untyped()
}
pub fn process_resolved_style_request<'dom>(
context: &SharedStyleContext,
node: impl LayoutNode<'dom>,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
fragment_tree: Option<Arc<FragmentTree>>,
) -> String {
if !node.as_element().unwrap().has_data() {
return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
}
let layout_element = node.to_threadsafe().as_element().unwrap();
let layout_element = match *pseudo {
None => Some(layout_element),
Some(PseudoElement::Before) => layout_element.get_before_pseudo(),
Some(PseudoElement::After) => layout_element.get_after_pseudo(),
Some(_) => {
warn!("Got unexpected pseudo element type!");
None
},
};
let layout_element = match layout_element {
None => {
return String::new();
},
Some(layout_element) => layout_element,
};
let style = &*layout_element.resolved_style();
let longhand_id = match *property {
PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
Ok(longhand_id) => longhand_id,
Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
},
PropertyId::Custom(ref name) => {
return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
},
}
.to_physical(style.writing_mode);
let computed_style =
|| style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id));
let tag_to_find = Tag::new_pseudo(node.opaque(), *pseudo);
if longhand_id == LonghandId::LineHeight {
let font = style.get_font();
let font_size = font.font_size.computed_size();
return match font.line_height {
LineHeight::Normal => computed_style(),
LineHeight::Number(value) => (font_size * value.0).to_css_string(),
LineHeight::Length(value) => value.0.to_css_string(),
};
}
let display = style.get_box().display;
if display.is_none() || display.is_contents() {
return computed_style();
}
let fragment_tree = match fragment_tree {
Some(fragment_tree) => fragment_tree,
None => return computed_style(),
};
fragment_tree
.find(|fragment, _, containing_block| {
if Some(tag_to_find) != fragment.tag() {
return None;
}
let (content_rect, margins, padding) = match fragment {
Fragment::Box(ref box_fragment) | Fragment::Float(ref box_fragment) => {
if style.get_box().position != Position::Static {
let resolved_insets = || {
box_fragment.calculate_resolved_insets_if_positioned(containing_block)
};
match longhand_id {
LonghandId::Top => return Some(resolved_insets().top.to_css_string()),
LonghandId::Right => {
return Some(resolved_insets().right.to_css_string())
},
LonghandId::Bottom => {
return Some(resolved_insets().bottom.to_css_string())
},
LonghandId::Left => {
return Some(resolved_insets().left.to_css_string())
},
_ => {},
}
}
let content_rect = box_fragment.content_rect;
let margins = box_fragment.margin;
let padding = box_fragment.padding;
(content_rect, margins, padding)
},
Fragment::Positioning(positioning_fragment) => {
let content_rect = positioning_fragment.rect;
(content_rect, SideOffsets2D::zero(), SideOffsets2D::zero())
},
_ => return None,
};
match longhand_id {
LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
Some(content_rect.size.width)
},
LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
Some(content_rect.size.height)
},
LonghandId::MarginBottom => Some(margins.bottom),
LonghandId::MarginTop => Some(margins.top),
LonghandId::MarginLeft => Some(margins.left),
LonghandId::MarginRight => Some(margins.right),
LonghandId::PaddingBottom => Some(padding.bottom),
LonghandId::PaddingTop => Some(padding.top),
LonghandId::PaddingLeft => Some(padding.left),
LonghandId::PaddingRight => Some(padding.right),
_ => None,
}
.map(|value| value.to_css_string())
})
.unwrap_or_else(computed_style)
}
fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
match fragment {
Fragment::Box(box_fragment) => {
!box_fragment.style.get_box().display.is_inline_flow() ||
fragment
.base()
.is_some_and(|base| base.flags.contains(FragmentFlags::IS_REPLACED))
},
Fragment::Float(_) |
Fragment::Positioning(_) |
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) => true,
Fragment::Text(_) => false,
}
}
pub fn process_resolved_style_request_for_unstyled_node<'dom>(
context: &SharedStyleContext,
node: impl LayoutNode<'dom>,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
) -> String {
if pseudo.is_some() {
return String::new();
}
let mut tlc = ThreadLocalStyleContext::new();
let mut context = StyleContext {
shared: context,
thread_local: &mut tlc,
};
let element = node.as_element().unwrap();
let styles = resolve_style(
&mut context,
element,
RuleInclusion::All,
pseudo.as_ref(),
None,
);
let style = styles.primary();
let longhand_id = match *property {
PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
Ok(longhand_id) => longhand_id,
Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
},
PropertyId::Custom(ref name) => {
return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
},
};
style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id))
}
fn shorthand_to_css_string(
id: style::properties::ShorthandId,
style: &style::properties::ComputedValues,
) -> String {
use style::values::resolved::Context;
let mut block = PropertyDeclarationBlock::new();
let mut dest = String::new();
for longhand in id.longhands() {
block.push(
style.computed_or_resolved_declaration(longhand, Some(&Context { style })),
Importance::Normal,
);
}
match block.shorthand_to_css(id, &mut dest) {
Ok(_) => dest.to_owned(),
Err(_) => String::new(),
}
}
pub fn process_offset_parent_query(
node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> OffsetParentResponse {
process_offset_parent_query_inner(node, fragment_tree).unwrap_or_default()
}
#[inline]
fn process_offset_parent_query_inner(
node: OpaqueNode,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Option<OffsetParentResponse> {
let fragment_tree = fragment_tree?;
struct NodeOffsetBoxInfo {
border_box: Rect<Au>,
offset_parent_node_address: Option<OpaqueNode>,
}
let mut parent_node_addresses = Vec::new();
let tag_to_find = Tag::new(node);
let node_offset_box = fragment_tree.find(|fragment, level, containing_block| {
let base = fragment.base()?;
let is_body_element = base
.flags
.contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
if fragment.tag() == Some(tag_to_find) {
let fragment_relative_rect = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.border_rect(),
Fragment::Text(fragment) => fragment.rect,
Fragment::Positioning(fragment) => fragment.rect,
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) => unreachable!(),
};
let mut border_box = fragment_relative_rect.translate(containing_block.origin.to_vector()).to_untyped();
let is_fixed = matches!(
fragment, Fragment::Box(fragment) if fragment.style.get_box().position == Position::Fixed
);
if is_body_element {
border_box.origin = Point2D::zero();
}
let offset_parent_node_address = if is_fixed {
None
} else {
parent_node_addresses[..level]
.iter()
.rev()
.cloned()
.find_map(std::convert::identity)
};
Some(NodeOffsetBoxInfo {
border_box,
offset_parent_node_address,
})
} else {
let parent_node_address = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let is_eligible_parent = is_eligible_parent(fragment);
match base.tag {
Some(tag) if is_eligible_parent && !tag.is_pseudo() => Some(tag.node),
_ => None,
}
},
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::IFrame(_) |
Fragment::Image(_) |
Fragment::Positioning(_) |
Fragment::Text(_) => None,
};
while parent_node_addresses.len() <= level {
parent_node_addresses.push(None);
}
parent_node_addresses[level] = parent_node_address;
None
}
});
let node_offset_box = node_offset_box?;
let offset_parent_padding_box_corner = node_offset_box
.offset_parent_node_address
.map(|offset_parent_node_address| {
let offset_parent_node_tag = Tag::new(offset_parent_node_address);
fragment_tree
.find(|fragment, _, containing_block| {
match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
if fragment.base.tag == Some(offset_parent_node_tag) {
let padding_box_corner =
fragment.padding_rect().origin.to_vector() +
containing_block.origin.to_vector();
let padding_box_corner = padding_box_corner.to_untyped();
Some(padding_box_corner)
} else {
None
}
},
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Text(_) |
Fragment::Image(_) |
Fragment::IFrame(_) |
Fragment::Positioning(_) => None,
}
})
.unwrap()
})
.unwrap_or(Vector2D::zero());
Some(OffsetParentResponse {
node_address: node_offset_box.offset_parent_node_address.map(Into::into),
rect: node_offset_box
.border_box
.translate(-offset_parent_padding_box_corner),
})
}
fn is_eligible_parent(fragment: &BoxFragment) -> bool {
fragment
.base
.flags
.contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) ||
fragment.style.get_box().position != Position::Static ||
fragment
.base
.flags
.contains(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT)
}
pub fn process_element_inner_text_query<'dom>(_node: impl LayoutNode<'dom>) -> String {
"".to_owned()
}
pub fn process_text_index_request(_node: OpaqueNode, _point: Point2D<Au>) -> Option<usize> {
None
}
pub fn process_resolved_font_style_query<'dom, E>(
context: &SharedStyleContext,
node: E,
value: &str,
url_data: ServoUrl,
shared_lock: &SharedRwLock,
) -> Option<ServoArc<Font>>
where
E: LayoutNode<'dom>,
{
fn create_font_declaration(
value: &str,
url_data: &ServoUrl,
quirks_mode: QuirksMode,
) -> Option<PropertyDeclarationBlock> {
let mut declarations = SourcePropertyDeclaration::default();
let result = parse_one_declaration_into(
&mut declarations,
PropertyId::NonCustom(ShorthandId::Font.into()),
value,
Origin::Author,
&UrlExtraData(url_data.get_arc()),
None,
ParsingMode::DEFAULT,
quirks_mode,
CssRuleType::Style,
);
let declarations = match result {
Ok(()) => {
let mut block = PropertyDeclarationBlock::new();
block.extend(declarations.drain(), Importance::Normal);
block
},
Err(_) => return None,
};
Some(declarations)
}
fn resolve_for_declarations<'dom, E>(
context: &SharedStyleContext,
parent_style: Option<&ComputedValues>,
declarations: PropertyDeclarationBlock,
shared_lock: &SharedRwLock,
) -> ServoArc<ComputedValues>
where
E: LayoutNode<'dom>,
{
let parent_style = match parent_style {
Some(parent) => parent,
None => context.stylist.device().default_computed_values(),
};
context
.stylist
.compute_for_declarations::<E::ConcreteElement>(
&context.guards,
parent_style,
ServoArc::new(shared_lock.wrap(declarations)),
)
}
let quirks_mode = context.quirks_mode();
let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
let element = node.as_element().unwrap();
let parent_style = if node.is_connected() {
if element.has_data() {
node.to_threadsafe().as_element().unwrap().resolved_style()
} else {
let mut tlc = ThreadLocalStyleContext::new();
let mut context = StyleContext {
shared: context,
thread_local: &mut tlc,
};
let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
styles.primary().clone()
}
} else {
let default_declarations =
create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
};
let computed_values =
resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
Some(computed_values.clone_font())
}