#![deny(unsafe_code)]
mod layout_damage;
pub mod wrapper_traits;
use std::any::Any;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::AtomicIsize;
use std::thread::JoinHandle;
use std::time::Duration;
use app_units::Au;
use background_hang_monitor_api::BackgroundHangMonitorRegister;
use bitflags::bitflags;
use embedder_traits::{Cursor, Theme, UntrustedNodeAddress, ViewportDetails};
use euclid::{Point2D, Rect};
use fonts::{FontContext, WebFontDocumentContext};
pub use layout_damage::LayoutDamage;
use libc::c_void;
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
use paint_api::CrossProcessPaintApi;
use parking_lot::RwLock;
use pixels::RasterImage;
use profile_traits::mem::Report;
use profile_traits::time;
use rustc_hash::FxHashMap;
use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
use serde::{Deserialize, Serialize};
use servo_arc::Arc as ServoArc;
use servo_base::Epoch;
use servo_base::generic_channel::GenericSender;
use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
use servo_url::{ImmutableOrigin, ServoUrl};
use style::Atom;
use style::animation::DocumentAnimationSet;
use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
use style::context::QuirksMode;
use style::data::ElementDataWrapper;
use style::device::Device;
use style::dom::OpaqueNode;
use style::invalidation::element::restyle_hints::RestyleHint;
use style::properties::style_structs::Font;
use style::properties::{ComputedValues, PropertyId};
use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
use style::str::char_is_whitespace;
use style::stylesheets::{DocumentStyleSheet, Stylesheet};
use style::stylist::Stylist;
use style::thread_state::{self, ThreadState};
use style::values::computed::Overflow;
use style_traits::CSSPixel;
use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
use webrender_api::{ExternalScrollId, ImageKey};
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
fn as_any(&self) -> &dyn Any;
}
pub type GenericLayoutData = dyn GenericLayoutDataTrait + Send + Sync;
#[derive(Default, MallocSizeOf)]
pub struct StyleData {
pub element_data: ElementDataWrapper,
pub parallel: DomParallelInfo,
}
#[derive(Default, MallocSizeOf)]
pub struct DomParallelInfo {
pub children_to_process: AtomicIsize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutNodeType {
Element(LayoutElementType),
Text,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutElementType {
Element,
HTMLBodyElement,
HTMLBRElement,
HTMLCanvasElement,
HTMLHtmlElement,
HTMLIFrameElement,
HTMLImageElement,
HTMLInputElement,
HTMLMediaElement,
HTMLObjectElement,
HTMLOptGroupElement,
HTMLOptionElement,
HTMLParagraphElement,
HTMLPreElement,
HTMLSelectElement,
HTMLTableCellElement,
HTMLTableColElement,
HTMLTableElement,
HTMLTableRowElement,
HTMLTableSectionElement,
HTMLTextAreaElement,
SVGImageElement,
SVGSVGElement,
}
pub struct HTMLCanvasData {
pub image_key: Option<ImageKey>,
pub width: u32,
pub height: u32,
}
pub struct SVGElementData<'dom> {
pub source: Option<Result<ServoUrl, ()>>,
pub width: Option<&'dom AttrValue>,
pub height: Option<&'dom AttrValue>,
pub svg_id: String,
pub view_box: Option<&'dom AttrValue>,
}
impl SVGElementData<'_> {
pub fn ratio_from_view_box(&self) -> Option<f32> {
let mut iter = self.view_box?.chars();
let _min_x = parse_integer(&mut iter).ok()?;
let _min_y = parse_integer(&mut iter).ok()?;
let width = parse_unsigned_integer(&mut iter).ok()?;
if width == 0 {
return None;
}
let height = parse_unsigned_integer(&mut iter).ok()?;
if height == 0 {
return None;
}
let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
iter.next().is_none().then(|| width as f32 / height as f32)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TrustedNodeAddress(pub *const c_void);
#[expect(unsafe_code)]
unsafe impl Send for TrustedNodeAddress {}
#[derive(Debug)]
pub enum PendingImageState {
Unrequested(ServoUrl),
PendingResponse,
}
#[derive(Debug, MallocSizeOf)]
pub enum LayoutImageDestination {
BoxTreeConstruction,
DisplayListBuilding,
}
#[derive(Debug)]
pub struct PendingImage {
pub state: PendingImageState,
pub node: UntrustedNodeAddress,
pub id: PendingImageId,
pub origin: ImmutableOrigin,
pub destination: LayoutImageDestination,
}
#[derive(Debug)]
pub struct PendingRasterizationImage {
pub node: UntrustedNodeAddress,
pub id: PendingImageId,
pub size: DeviceIntSize,
}
#[derive(Clone, Copy, Debug, MallocSizeOf)]
pub struct MediaFrame {
pub image_key: webrender_api::ImageKey,
pub width: i32,
pub height: i32,
}
pub struct MediaMetadata {
pub width: u32,
pub height: u32,
}
pub struct HTMLMediaData {
pub current_frame: Option<MediaFrame>,
pub metadata: Option<MediaMetadata>,
}
pub struct LayoutConfig {
pub id: PipelineId,
pub webview_id: WebViewId,
pub url: ServoUrl,
pub is_iframe: bool,
pub script_chan: GenericSender<ScriptThreadMessage>,
pub image_cache: Arc<dyn ImageCache>,
pub font_context: Arc<FontContext>,
pub time_profiler_chan: time::ProfilerChan,
pub paint_api: CrossProcessPaintApi,
pub viewport_details: ViewportDetails,
pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
pub theme: Theme,
}
pub trait LayoutFactory: Send + Sync {
fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
}
pub trait Layout {
fn device(&self) -> &Device;
fn set_theme(&mut self, theme: Theme) -> bool;
fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
fn load_web_fonts_from_stylesheet(
&self,
stylesheet: &ServoArc<Stylesheet>,
font_context: &WebFontDocumentContext,
);
fn add_stylesheet(
&mut self,
stylesheet: ServoArc<Stylesheet>,
before_stylsheet: Option<ServoArc<Stylesheet>>,
font_context: &WebFontDocumentContext,
);
fn exit_now(&mut self);
fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
fn remove_cached_image(&mut self, image_url: &ServoUrl);
fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
fn register_paint_worklet_modules(
&mut self,
name: Atom,
properties: Vec<Atom>,
painter: Box<dyn Painter>,
);
fn set_scroll_offsets_from_renderer(
&mut self,
scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
);
fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
fn needs_new_display_list(&self) -> bool;
fn set_needs_new_display_list(&self);
fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
fn query_box_area(
&self,
node: TrustedNodeAddress,
area: BoxAreaType,
exclude_transform_and_inline: bool,
) -> Option<Rect<Au, CSSPixel>>;
fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectIterator;
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
fn query_scroll_container(
&self,
node: Option<TrustedNodeAddress>,
flags: ScrollContainerQueryFlags,
) -> Option<ScrollContainerResponse>;
fn query_resolved_style(
&self,
node: TrustedNodeAddress,
pseudo: Option<PseudoElement>,
property_id: PropertyId,
animations: DocumentAnimationSet,
animation_timeline_value: f64,
) -> String;
fn query_resolved_font_style(
&self,
node: TrustedNodeAddress,
value: &str,
animations: DocumentAnimationSet,
animation_timeline_value: f64,
) -> Option<ServoArc<Font>>;
fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
fn query_text_index(
&self,
node: TrustedNodeAddress,
point: Point2D<Au, CSSPixel>,
) -> Option<usize>;
fn query_elements_from_point(
&self,
point: LayoutPoint,
flags: ElementsFromPointFlags,
) -> Vec<ElementsFromPointResult>;
fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
fn stylist_mut(&mut self) -> &mut Stylist;
fn set_accessibility_active(&self, active: bool);
}
pub trait ScriptThreadFactory {
fn create(
state: InitialScriptState,
layout_factory: Arc<dyn LayoutFactory>,
image_cache_factory: Arc<dyn ImageCacheFactory>,
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
) -> JoinHandle<()>;
}
#[derive(Copy, Clone)]
pub enum BoxAreaType {
Content,
Padding,
Border,
}
pub type CSSPixelRectIterator = Box<dyn Iterator<Item = Rect<Au, CSSPixel>>>;
#[derive(Default)]
pub struct PhysicalSides {
pub left: Au,
pub top: Au,
pub right: Au,
pub bottom: Au,
}
#[derive(Clone, Default)]
pub struct OffsetParentResponse {
pub node_address: Option<UntrustedNodeAddress>,
pub rect: Rect<Au, CSSPixel>,
}
bitflags! {
#[derive(PartialEq)]
pub struct ScrollContainerQueryFlags: u8 {
const ForScrollParent = 1 << 0;
const Inclusive = 1 << 1;
}
}
#[derive(Clone, Copy, Debug, MallocSizeOf)]
pub struct AxesOverflow {
pub x: Overflow,
pub y: Overflow,
}
impl Default for AxesOverflow {
fn default() -> Self {
Self {
x: Overflow::Visible,
y: Overflow::Visible,
}
}
}
impl From<&ComputedValues> for AxesOverflow {
fn from(style: &ComputedValues) -> Self {
Self {
x: style.clone_overflow_x(),
y: style.clone_overflow_y(),
}
}
}
impl AxesOverflow {
pub fn to_scrollable(&self) -> Self {
Self {
x: self.x.to_scrollable(),
y: self.y.to_scrollable(),
}
}
pub fn establishes_scroll_container(&self) -> bool {
self.x.is_scrollable()
}
}
#[derive(Clone)]
pub enum ScrollContainerResponse {
Viewport(AxesOverflow),
Element(UntrustedNodeAddress, AxesOverflow),
}
#[derive(Debug, PartialEq)]
pub enum QueryMsg {
BoxArea,
BoxAreas,
ClientRectQuery,
CurrentCSSZoomQuery,
EffectiveOverflow,
ElementInnerOuterTextQuery,
ElementsFromPoint,
InnerWindowDimensionsQuery,
NodesFromPointQuery,
OffsetParentQuery,
ScrollParentQuery,
ResolvedFontStyleQuery,
ResolvedStyleQuery,
ScrollingAreaOrOffsetQuery,
StyleQuery,
TextIndexQuery,
PaddingQuery,
}
#[derive(Debug, PartialEq)]
pub enum ReflowGoal {
UpdateTheRendering,
LayoutQuery(QueryMsg),
UpdateScrollNode(ExternalScrollId, LayoutVector2D),
}
#[derive(Clone, Debug, MallocSizeOf)]
pub struct IFrameSize {
pub browsing_context_id: BrowsingContextId,
pub pipeline_id: PipelineId,
pub viewport_details: ViewportDetails,
}
pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct RestyleReason: u16 {
const StylesheetsChanged = 1 << 0;
const DOMChanged = 1 << 1;
const PendingRestyles = 1 << 2;
const HighlightedDOMNodeChanged = 1 << 3;
const ThemeChanged = 1 << 4;
const ViewportChanged = 1 << 5;
const PaintWorkletLoaded = 1 << 6;
}
}
malloc_size_of_is_0!(RestyleReason);
impl RestyleReason {
pub fn needs_restyle(&self) -> bool {
!self.is_empty()
}
}
#[derive(Debug, Default)]
pub struct ReflowResult {
pub reflow_phases_run: ReflowPhasesRun,
pub reflow_statistics: ReflowStatistics,
pub pending_images: Vec<PendingImage>,
pub pending_rasterization_images: Vec<PendingRasterizationImage>,
pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
pub iframe_sizes: Option<IFrameSizes>,
}
bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ReflowPhasesRun: u8 {
const RanLayout = 1 << 0;
const CalculatedOverflow = 1 << 1;
const BuiltStackingContextTree = 1 << 2;
const BuiltDisplayList = 1 << 3;
const UpdatedScrollNodeOffset = 1 << 4;
const UpdatedImageData = 1 << 5;
}
}
impl ReflowPhasesRun {
pub fn needs_frame(&self) -> bool {
self.intersects(
Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
)
}
}
#[derive(Debug, Default)]
pub struct ReflowStatistics {
pub rebuilt_fragment_count: u32,
pub restyle_fragment_count: u32,
}
#[derive(Debug)]
pub struct ReflowRequestRestyle {
pub reason: RestyleReason,
pub dirty_root: Option<TrustedNodeAddress>,
pub stylesheets_changed: bool,
pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
}
#[derive(Debug)]
pub struct ReflowRequest {
pub document: TrustedNodeAddress,
pub epoch: Epoch,
pub restyle: Option<ReflowRequestRestyle>,
pub viewport_details: ViewportDetails,
pub reflow_goal: ReflowGoal,
pub dom_count: u32,
pub origin: ImmutableOrigin,
pub animation_timeline_value: f64,
pub animations: DocumentAnimationSet,
pub animating_images: Arc<RwLock<AnimatingImages>>,
pub highlighted_dom_node: Option<OpaqueNode>,
pub document_context: WebFontDocumentContext,
}
impl ReflowRequest {
pub fn stylesheets_changed(&self) -> bool {
self.restyle
.as_ref()
.is_some_and(|restyle| restyle.stylesheets_changed)
}
}
#[derive(Debug, Default, MallocSizeOf)]
pub struct PendingRestyle {
pub snapshot: Option<Snapshot>,
pub hint: RestyleHint,
pub damage: RestyleDamage,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FragmentType {
FragmentBody,
BeforePseudoContent,
AfterPseudoContent,
}
impl From<Option<PseudoElement>> for FragmentType {
fn from(value: Option<PseudoElement>) -> Self {
match value {
Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
_ => FragmentType::FragmentBody,
}
}
}
pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
debug_assert_eq!(id & (fragment_type as usize), 0);
(id as u64) | (fragment_type as u64)
}
pub fn node_id_from_scroll_id(id: usize) -> usize {
id & !3
}
#[derive(Clone, Debug, MallocSizeOf)]
pub struct ImageAnimationState {
#[ignore_malloc_size_of = "RasterImage"]
pub image: Arc<RasterImage>,
pub active_frame: usize,
frame_start_time: f64,
}
impl ImageAnimationState {
pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
Self {
image,
active_frame: 0,
frame_start_time: last_update_time,
}
}
pub fn image_key(&self) -> Option<ImageKey> {
self.image.id
}
pub fn duration_to_next_frame(&self, now: f64) -> Duration {
let frame_delay = self
.image
.frames
.get(self.active_frame)
.expect("Image frame should always be valid")
.delay
.unwrap_or_default();
let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
frame_delay - time_since_frame_start.min(frame_delay)
}
pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
if self.image.frames.len() <= 1 {
return false;
}
let image = &self.image;
let time_interval_since_last_update = now - self.frame_start_time;
let mut remain_time_interval = time_interval_since_last_update -
image
.frames
.get(self.active_frame)
.unwrap()
.delay()
.unwrap()
.as_secs_f64();
let mut next_active_frame_id = self.active_frame;
while remain_time_interval > 0.0 {
next_active_frame_id = (next_active_frame_id + 1) % image.frames.len();
remain_time_interval -= image
.frames
.get(next_active_frame_id)
.unwrap()
.delay()
.unwrap()
.as_secs_f64();
}
if self.active_frame == next_active_frame_id {
return false;
}
self.active_frame = next_active_frame_id;
self.frame_start_time = now;
true
}
}
#[derive(Debug)]
pub struct ElementsFromPointResult {
pub node: OpaqueNode,
pub point_in_target: Point2D<f32, CSSPixel>,
pub cursor: Cursor,
}
bitflags! {
pub struct ElementsFromPointFlags: u8 {
const FindAll = 0b00000001;
}
}
#[derive(Debug, Default, MallocSizeOf)]
pub struct AnimatingImages {
pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
pub dirty: bool,
}
impl AnimatingImages {
pub fn maybe_insert_or_update(
&mut self,
node: OpaqueNode,
image: Arc<RasterImage>,
current_timeline_value: f64,
) {
let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
self.dirty = true;
ImageAnimationState::new(image.clone(), current_timeline_value)
});
if entry.image.id != image.id {
self.dirty = true;
*entry = ImageAnimationState::new(image.clone(), current_timeline_value);
}
}
pub fn remove(&mut self, node: OpaqueNode) {
if self.node_to_state_map.remove(&node).is_some() {
self.dirty = true;
}
}
pub fn clear_dirty(&mut self) -> bool {
std::mem::take(&mut self.dirty)
}
pub fn is_empty(&self) -> bool {
self.node_to_state_map.is_empty()
}
}
struct ThreadStateRestorer;
impl ThreadStateRestorer {
fn new() -> Self {
#[cfg(debug_assertions)]
{
thread_state::exit(ThreadState::SCRIPT);
thread_state::enter(ThreadState::LAYOUT);
}
Self
}
}
impl Drop for ThreadStateRestorer {
fn drop(&mut self) {
#[cfg(debug_assertions)]
{
thread_state::exit(ThreadState::LAYOUT);
thread_state::enter(ThreadState::SCRIPT);
}
}
}
pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
let _guard = ThreadStateRestorer::new();
f()
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use std::time::Duration;
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
use crate::ImageAnimationState;
#[test]
fn test() {
let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
delay: Some(Duration::from_millis(100)),
byte_range: 0..1,
width: 100,
height: 100,
})
.take(10)
.collect();
let image = RasterImage {
metadata: ImageMetadata {
width: 100,
height: 100,
},
format: PixelFormat::BGRA8,
id: None,
bytes: Arc::new(vec![1]),
frames: image_frames,
cors_status: CorsStatus::Unsafe,
is_opaque: false,
};
let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
assert_eq!(image_animation_state.active_frame, 0);
assert_eq!(image_animation_state.frame_start_time, 0.0);
assert_eq!(
image_animation_state.update_frame_for_animation_timeline_value(0.101),
true
);
assert_eq!(image_animation_state.active_frame, 1);
assert_eq!(image_animation_state.frame_start_time, 0.101);
assert_eq!(
image_animation_state.update_frame_for_animation_timeline_value(0.116),
false
);
assert_eq!(image_animation_state.active_frame, 1);
assert_eq!(image_animation_state.frame_start_time, 0.101);
}
}