/* 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/. */ //! This module contains traits in script used generically in the rest of Servo. //! The traits are here instead of in script so that these modules won't have //! to depend on script. #![deny(unsafe_code)] mod layout_damage; pub mod wrapper_traits; use std::any::Any; use std::sync::Arc; use std::sync::atomic::{AtomicIsize, AtomicU64, Ordering}; use std::thread::JoinHandle; use std::time::Duration; use app_units::Au; use atomic_refcell::AtomicRefCell; use base::Epoch; use base::generic_channel::GenericSender; use base::id::{BrowsingContextId, PipelineId, WebViewId}; use bitflags::bitflags; use compositing_traits::CrossProcessCompositorApi; use constellation_traits::LoadData; use embedder_traits::{Cursor, Theme, UntrustedNodeAddress, ViewportDetails}; use euclid::Point2D; use euclid::default::{Point2D as UntypedPoint2D, Rect}; use fonts::{FontContext, SystemFontServiceProxy}; 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, PendingImageId}; 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_url::{ImmutableOrigin, ServoUrl}; use style::Atom; use style::animation::DocumentAnimationSet; use style::context::QuirksMode; use style::data::ElementData; use style::dom::OpaqueNode; use style::invalidation::element::restyle_hints::RestyleHint; use style::media_queries::Device; use style::properties::style_structs::Font; use style::properties::{ComputedValues, PropertyId}; use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot}; use style::stylesheets::{Stylesheet, UrlExtraData}; 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(MallocSizeOf)] pub struct StyleData { /// Data that the style system associates with a node. When the /// style system is being used standalone, this is all that hangs /// off the node. This must be first to permit the various /// transmutations between ElementData and PersistentLayoutData. pub element_data: AtomicRefCell, /// Information needed during parallel traversals. pub parallel: DomParallelInfo, } impl Default for StyleData { fn default() -> Self { Self { element_data: AtomicRefCell::new(ElementData::default()), parallel: DomParallelInfo::default(), } } } /// Information that we need stored in each DOM node. #[derive(Default, MallocSizeOf)] pub struct DomParallelInfo { /// The number of children remaining to process during bottom-up traversal. 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 source: Option, pub width: u32, pub height: u32, } pub struct SVGElementData { /// The SVG's XML source represented as a base64 encoded `data:` url. pub source: Option>, pub width: Option, pub height: Option, pub ratio: Option, } /// The address of a node known to be valid. These are sent from script to layout. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TrustedNodeAddress(pub *const c_void); #[allow(unsafe_code)] unsafe impl Send for TrustedNodeAddress {} /// Whether the pending image needs to be fetched or is waiting on an existing fetch. #[derive(Debug)] pub enum PendingImageState { Unrequested(ServoUrl), PendingResponse, } /// The destination in layout where an image is needed. #[derive(Debug, MallocSizeOf)] pub enum LayoutImageDestination { BoxTreeConstruction, DisplayListBuilding, } /// The data associated with an image that is not yet present in the image cache. /// Used by the script thread to hold on to DOM elements that need to be repainted /// when an image fetch is complete. #[derive(Debug)] pub struct PendingImage { pub state: PendingImageState, pub node: UntrustedNodeAddress, pub id: PendingImageId, pub origin: ImmutableOrigin, pub destination: LayoutImageDestination, } /// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG /// tree) but not yet rasterized to the size needed by layout. The rasterization is /// happening in the image cache. #[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, pub metadata: Option, } pub struct LayoutConfig { pub id: PipelineId, pub webview_id: WebViewId, pub url: ServoUrl, pub is_iframe: bool, pub script_chan: GenericSender, pub image_cache: Arc, pub font_context: Arc, pub time_profiler_chan: time::ProfilerChan, pub compositor_api: CrossProcessCompositorApi, pub viewport_details: ViewportDetails, pub theme: Theme, } pub struct PropertyRegistration { pub name: String, pub syntax: String, pub initial_value: Option, pub inherits: bool, pub url_data: UrlExtraData, } #[derive(Debug)] pub enum RegisterPropertyError { InvalidName, AlreadyRegistered, InvalidSyntax, InvalidInitialValue, InitialValueNotComputationallyIndependent, NoInitialValue, } pub trait LayoutFactory: Send + Sync { fn create(&self, config: LayoutConfig) -> Box; } pub trait Layout { /// Get a reference to this Layout's Stylo `Device` used to handle media queries and /// resolve font metrics. fn device(&self) -> &Device; /// The currently laid out Epoch that this Layout has finished. fn current_epoch(&self) -> Epoch; /// Load all fonts from the given stylesheet, returning the number of fonts that /// need to be loaded. fn load_web_fonts_from_stylesheet(&self, stylesheet: ServoArc); /// Add a stylesheet to this Layout. This will add it to the Layout's `Stylist` as well as /// loading all web fonts defined in the stylesheet. The second stylesheet is the insertion /// point (if it exists, the sheet needs to be inserted before it). fn add_stylesheet( &mut self, stylesheet: ServoArc, before_stylsheet: Option>, ); /// Inform the layout that its ScriptThread is about to exit. fn exit_now(&mut self); /// Requests that layout measure its memory usage. The resulting reports are sent back /// via the supplied channel. fn collect_reports(&self, reports: &mut Vec, ops: &mut MallocSizeOfOps); /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used. fn set_quirks_mode(&mut self, quirks_mode: QuirksMode); /// Removes a stylesheet from the Layout. fn remove_stylesheet(&mut self, stylesheet: ServoArc); /// Requests a reflow. fn reflow(&mut self, reflow_request: ReflowRequest) -> Option; /// Do not request a reflow, but ensure that any previous reflow completes building a stacking /// context tree so that it is ready to query the final size of any elements in script. fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails); /// Tells layout that script has added some paint worklet modules. fn register_paint_worklet_modules( &mut self, name: Atom, properties: Vec, painter: Box, ); /// Set the scroll states of this layout after a compositor scroll. fn set_scroll_offsets_from_renderer( &mut self, scroll_states: &FxHashMap, ); /// Get the scroll offset of the given scroll node with id of [`ExternalScrollId`] or `None` if it does /// not exist in the tree. fn scroll_offset(&self, id: ExternalScrollId) -> Option; /// Returns true if this layout needs to produce a new display list for rendering updates. fn needs_new_display_list(&self) -> bool; /// Marks that this layout needs to produce a new display list for rendering updates. fn set_needs_new_display_list(&self); fn query_box_area(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Option>; fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec>; fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect; fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String; fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse; /// Query the scroll container for the given node. If node is `None`, the scroll container for /// the viewport is returned. fn query_scroll_container( &self, node: Option, flags: ScrollContainerQueryFlags, ) -> Option; fn query_resolved_style( &self, node: TrustedNodeAddress, pseudo: Option, 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>; fn query_scrolling_area(&self, node: Option) -> Rect; fn query_text_indext(&self, node: OpaqueNode, point: UntypedPoint2D) -> Option; fn query_elements_from_point( &self, point: LayoutPoint, flags: ElementsFromPointFlags, ) -> Vec; fn register_custom_property( &mut self, property_registration: PropertyRegistration, ) -> Result<(), RegisterPropertyError>; } /// This trait is part of `layout_api` because it depends on both `script_traits` /// and also `LayoutFactory` from this crate. If it was in `script_traits` there would be a /// circular dependency. pub trait ScriptThreadFactory { /// Create a `ScriptThread`. fn create( state: InitialScriptState, layout_factory: Arc, system_font_service: Arc, load_data: LoadData, ) -> JoinHandle<()>; } /// Type of the area of CSS box for query. /// See . #[derive(Copy, Clone)] pub enum BoxAreaType { Content, Padding, Border, } #[derive(Clone, Default)] pub struct OffsetParentResponse { pub node_address: Option, pub rect: Rect, } bitflags! { #[derive(PartialEq)] pub struct ScrollContainerQueryFlags: u8 { /// Whether or not this query is for the purposes of a `scrollParent` layout query. const ForScrollParent = 1 << 0; /// Whether or not to consider the original element's scroll box for the return value. 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(), } } } #[derive(Clone)] pub enum ScrollContainerResponse { Viewport(AxesOverflow), Element(UntrustedNodeAddress, AxesOverflow), } #[derive(Debug, PartialEq)] pub enum QueryMsg { BoxArea, BoxAreas, ClientRectQuery, ElementInnerOuterTextQuery, ElementsFromPoint, InnerWindowDimensionsQuery, NodesFromPointQuery, OffsetParentQuery, ScrollParentQuery, ResolvedFontStyleQuery, ResolvedStyleQuery, ScrollingAreaOrOffsetQuery, StyleQuery, TextIndexQuery, } /// The goal of a reflow request. /// /// Please do not add any other types of reflows. In general, all reflow should /// go through the *update the rendering* step of the HTML specification. Exceptions /// should have careful review. #[derive(Debug, PartialEq)] pub enum ReflowGoal { /// A reflow has been requesting by the *update the rendering* step of the HTML /// event loop. This nominally driven by the display's VSync. UpdateTheRendering, /// Script has done a layout query and this reflow ensurs that layout is up-to-date /// with the latest changes to the DOM. LayoutQuery(QueryMsg), /// Tells layout about a single new scrolling offset from the script. The rest will /// remain untouched. Layout will forward whether the element is scrolled through /// [ReflowResult]. 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; bitflags! { /// Conditions which cause a [`Document`] to need to be restyled during reflow, which /// might cause the rest of layout to happen as well. #[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 ViewportSizeChanged = 1 << 5; const PaintWorkletLoaded = 1 << 6; } } malloc_size_of_is_0!(RestyleReason); impl RestyleReason { pub fn needs_restyle(&self) -> bool { !self.is_empty() } } /// Information derived from a layout pass that needs to be returned to the script thread. #[derive(Debug, Default)] pub struct ReflowResult { /// The phases that were run during this reflow. pub reflow_phases_run: ReflowPhasesRun, /// The list of images that were encountered that are in progress. pub pending_images: Vec, /// The list of vector images that were encountered that still need to be rasterized. pub pending_rasterization_images: Vec, /// The list of `SVGSVGElement`s encountered in the DOM that need to be serialized. /// This is needed to support inline SVGs as the serialization needs to happen on /// the script thread. pub pending_svg_elements_for_serialization: Vec, /// The list of iframes in this layout and their sizes, used in order /// to communicate them with the Constellation and also the `Window` /// element of their content pages. Returning None if incremental reflow /// finished before reaching this stage of the layout. I.e., no update /// required. pub iframe_sizes: Option, } bitflags! { /// The phases of reflow that were run when processing a reflow in layout. #[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 UpdatedCanvasContents = 1 << 5; } } impl ReflowPhasesRun { pub fn needs_frame(&self) -> bool { self.intersects( Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedCanvasContents, ) } } /// Information needed for a script-initiated reflow that requires a restyle /// and reconstruction of box and fragment trees. #[derive(Debug)] pub struct ReflowRequestRestyle { /// Whether or not (and for what reasons) restyle needs to happen. pub reason: RestyleReason, /// The dirty root from which to restyle. pub dirty_root: Option, /// Whether the document's stylesheets have changed since the last script reflow. pub stylesheets_changed: bool, /// Restyle snapshot map. pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>, } /// Information needed for a script-initiated reflow. #[derive(Debug)] pub struct ReflowRequest { /// The document node. pub document: TrustedNodeAddress, /// If a restyle is necessary, all of the informatio needed to do that restyle. pub restyle: Option, /// The current [`ViewportDetails`] to use for this reflow. pub viewport_details: ViewportDetails, /// The goal of this reflow. pub reflow_goal: ReflowGoal, /// The number of objects in the dom #10110 pub dom_count: u32, /// The current window origin pub origin: ImmutableOrigin, /// The current animation timeline value. pub animation_timeline_value: f64, /// The set of animations for this document. pub animations: DocumentAnimationSet, /// The set of image animations. pub node_to_animating_image_map: Arc>>, /// The theme for the window pub theme: Theme, /// The node highlighted by the devtools, if any pub highlighted_dom_node: Option, } impl ReflowRequest { pub fn stylesheets_changed(&self) -> bool { self.restyle .as_ref() .is_some_and(|restyle| restyle.stylesheets_changed) } } /// A pending restyle. #[derive(Debug, Default, MallocSizeOf)] pub struct PendingRestyle { /// If this element had a state or attribute change since the last restyle, track /// the original condition of the element. pub snapshot: Option, /// Any explicit restyles hints that have been accumulated for this element. pub hint: RestyleHint, /// Any explicit restyles damage that have been accumulated for this element. pub damage: RestyleDamage, } /// The type of fragment that a scroll root is created for. /// /// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum /// into the lower 2 bits of the `ScrollRootId`, which otherwise contains a 32-bit-aligned /// heap address. #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum FragmentType { /// A StackingContext for the fragment body itself. FragmentBody, /// A StackingContext created to contain ::before pseudo-element content. BeforePseudoContent, /// A StackingContext created to contain ::after pseudo-element content. AfterPseudoContent, } impl From> for FragmentType { fn from(value: Option) -> Self { match value { Some(PseudoElement::After) => FragmentType::AfterPseudoContent, Some(PseudoElement::Before) => FragmentType::BeforePseudoContent, _ => FragmentType::FragmentBody, } } } /// The next ID that will be used for a special scroll root id. /// /// A special scroll root is a scroll root that is created for generated content. static NEXT_SPECIAL_SCROLL_ROOT_ID: AtomicU64 = AtomicU64::new(0); /// If none of the bits outside this mask are set, the scroll root is a special scroll root. /// Note that we assume that the top 16 bits of the address space are unused on the platform. const SPECIAL_SCROLL_ROOT_ID_MASK: u64 = 0xffff; /// Returns a new scroll root ID for a scroll root. fn next_special_id() -> u64 { // We shift this left by 2 to make room for the fragment type ID. ((NEXT_SPECIAL_SCROLL_ROOT_ID.fetch_add(1, Ordering::SeqCst) + 1) << 2) & SPECIAL_SCROLL_ROOT_ID_MASK } pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 { debug_assert_eq!(id & (fragment_type as usize), 0); if fragment_type == FragmentType::FragmentBody { id as u64 } else { next_special_id() | (fragment_type as u64) } } pub fn node_id_from_scroll_id(id: usize) -> Option { if (id as u64 & !SPECIAL_SCROLL_ROOT_ID_MASK) != 0 { return Some(id & !3); } None } #[derive(Clone, Debug, MallocSizeOf)] pub struct ImageAnimationState { #[ignore_malloc_size_of = "Arc is hard"] pub image: Arc, pub active_frame: usize, frame_start_time: f64, } impl ImageAnimationState { pub fn new(image: Arc, last_update_time: f64) -> Self { Self { image, active_frame: 0, frame_start_time: last_update_time, } } pub fn image_key(&self) -> Option { 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) } /// check whether image active frame need to be updated given current time, /// return true if there are image that need to be updated. /// false otherwise. 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 } } /// Describe an item that matched a hit-test query. #[derive(Debug)] pub struct ElementsFromPointResult { /// An [`OpaqueNode`] that contains a pointer to the node hit by /// this hit test result. pub node: OpaqueNode, /// The [`Point2D`] of the original query point relative to the /// node fragment rectangle. pub point_in_target: Point2D, /// The [`Cursor`] that's defined on the item that is hit by this /// hit test result. pub cursor: Cursor, } bitflags! { pub struct ElementsFromPointFlags: u8 { /// Whether or not to find all of the items for a hit test or stop at the /// first hit. const FindAll = 0b00000001; } } #[cfg(test)] mod test { use std::sync::Arc; use std::time::Duration; use ipc_channel::ipc::IpcSharedMemory; use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage}; use crate::ImageAnimationState; #[test] fn test() { let image_frames: Vec = 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: IpcSharedMemory::from_byte(1, 1), frames: image_frames, cors_status: CorsStatus::Unsafe, }; 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); } }