/* 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/. */ // Work around https://github.com/rust-lang/rust/issues/62132 #![recursion_limit = "128"] //! The layout thread. Performs layout on the DOM, builds display lists and sends them to be //! painted. #[macro_use] extern crate crossbeam_channel; #[macro_use] extern crate layout; #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; #[macro_use] extern crate profile_traits; use app_units::Au; use crossbeam_channel::{Receiver, Sender}; use embedder_traits::resources::{self, Resource}; use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D}; use fnv::FnvHashMap; use fxhash::{FxHashMap, FxHashSet}; use gfx::font; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context; use gfx_traits::{node_id_from_scroll_id, Epoch}; use histogram::Histogram; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use layout::construct::ConstructionResult; use layout::context::malloc_size_of_persistent_local_context; use layout::context::LayoutContext; use layout::context::RegisteredPainter; use layout::context::RegisteredPainters; use layout::display_list::items::WebRenderImageInfo; use layout::display_list::{IndexableText, ToLayout}; use layout::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils}; use layout::flow_ref::FlowRef; use layout::incremental::{RelayoutMode, SpecialRestyleDamage}; use layout::parallel; use layout::query::{ process_client_rect_query, process_content_box_request, process_content_boxes_request, process_element_inner_text_query, process_node_scroll_area_request, process_node_scroll_id_request, process_offset_parent_query, process_resolved_font_style_request, process_resolved_style_request, LayoutRPCImpl, LayoutThreadData, }; use layout::sequential; use layout::traversal::{ construct_flows_at_ancestors, ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows, }; use layout::wrapper::LayoutNodeLayoutData; use layout::{layout_debug, LayoutData}; use layout_traits::LayoutThreadFactory; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric}; use msg::constellation_msg::{ BackgroundHangMonitor, BackgroundHangMonitorRegister, HangAnnotation, }; use msg::constellation_msg::{BrowsingContextId, MonitoredComponentId, TopLevelBrowsingContextId}; use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId}; use net_traits::image_cache::{ImageCache, UsePlaceholder}; use parking_lot::RwLock; use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan}; use profile_traits::time::{self as profile_time, profile, TimerMetadata}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType, Reflow}; use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow}; use script_layout_interface::rpc::TextIndexResponse; use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse}; use script_layout_interface::wrapper_traits::LayoutNode; use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg}; use script_traits::{DrawAPaintImageResult, IFrameSizeMsg, PaintWorkletError, WindowSizeType}; use script_traits::{Painter, WebrenderIpcSender}; use script_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData}; use servo_arc::Arc as ServoArc; use servo_atoms::Atom; use servo_config::opts::{self, DebugOptions}; use servo_url::{ImmutableOrigin, ServoUrl}; use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::process; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; use std::time::Duration; use style::animation::{AnimationSetKey, DocumentAnimationSet, ElementAnimationSet}; use style::context::SharedStyleContext; use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; use style::driver; use style::error_reporting::RustLogReporter; use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL}; use style::invalidation::element::restyle_hints::RestyleHint; use style::logical_geometry::LogicalPoint; use style::media_queries::{Device, MediaList, MediaType}; use style::properties::PropertyId; use style::selector_parser::{PseudoElement, SnapshotMap}; use style::servo::restyle_damage::ServoRestyleDamage; use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards}; use style::stylesheets::{ DocumentStyleSheet, Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets, }; use style::stylist::Stylist; use style::thread_state::{self, ThreadState}; use style::traversal::DomTraversal; use style::traversal_flags::TraversalFlags; use style_traits::CSSPixel; use style_traits::DevicePixel; use style_traits::SpeculativePainter; use webrender_api::{units, ColorF, HitTestFlags, ScrollClamping}; /// Information needed by the layout thread. pub struct LayoutThread { /// The ID of the pipeline that we belong to. id: PipelineId, /// The ID of the top-level browsing context that we belong to. top_level_browsing_context_id: TopLevelBrowsingContextId, /// The URL of the pipeline that we belong to. url: ServoUrl, /// Performs CSS selector matching and style resolution. stylist: Stylist, /// Is the current reflow of an iframe, as opposed to a root window? is_iframe: bool, /// The port on which we receive messages from the script thread. port: Receiver, /// The port on which we receive messages from the constellation. pipeline_port: Receiver, /// The port on which we receive messages from the font cache thread. font_cache_receiver: Receiver<()>, /// The channel on which the font cache can send messages to us. font_cache_sender: IpcSender<()>, /// A means of communication with the background hang monitor. background_hang_monitor: Box, /// The channel on which messages can be sent to the constellation. constellation_chan: IpcSender, /// The channel on which messages can be sent to the script thread. script_chan: IpcSender, /// The channel on which messages can be sent to the time profiler. time_profiler_chan: profile_time::ProfilerChan, /// The channel on which messages can be sent to the memory profiler. mem_profiler_chan: profile_mem::ProfilerChan, /// Reference to the script thread image cache. image_cache: Arc, /// Public interface to the font cache thread. font_cache_thread: FontCacheThread, /// Is this the first reflow in this LayoutThread? first_reflow: Cell, /// Flag to indicate whether to use parallel operations parallel_flag: bool, /// Starts at zero, and increased by one every time a layout completes. /// This can be used to easily check for invalid stale data. generation: Cell, /// The number of Web fonts that have been requested but not yet loaded. outstanding_web_fonts: Arc, /// The root of the flow tree. root_flow: RefCell>, /// A counter for epoch messages epoch: Cell, /// The size of the viewport. This may be different from the size of the screen due to viewport /// constraints. viewport_size: UntypedSize2D, /// A mutex to allow for fast, read-only RPC of layout's internal data /// structures, while still letting the LayoutThread modify them. /// /// All the other elements of this struct are read-only. rw_data: Arc>, webrender_image_cache: Arc>>, /// The executors for paint worklets. registered_painters: RegisteredPaintersImpl, /// Webrender interface. webrender_api: WebrenderIpcSender, /// Paint time metrics. paint_time_metrics: PaintTimeMetrics, /// The time a layout query has waited before serviced by layout thread. layout_query_waiting_time: Histogram, /// The sizes of all iframes encountered during the last layout operation. last_iframe_sizes: RefCell>>, /// Flag that indicates if LayoutThread is busy handling a request. busy: Arc, /// Debug options, copied from configuration to this `LayoutThread` in order /// to avoid having to constantly access the thread-safe global options. debug: DebugOptions, /// True to turn off incremental layout. nonincremental_layout: bool, } impl LayoutThreadFactory for LayoutThread { type Message = Msg; /// Spawns a new layout thread. fn create( id: PipelineId, top_level_browsing_context_id: TopLevelBrowsingContextId, url: ServoUrl, is_iframe: bool, chan: (Sender, Receiver), pipeline_port: IpcReceiver, background_hang_monitor_register: Box, constellation_chan: IpcSender, script_chan: IpcSender, image_cache: Arc, font_cache_thread: FontCacheThread, time_profiler_chan: profile_time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, webrender_api_sender: WebrenderIpcSender, paint_time_metrics: PaintTimeMetrics, busy: Arc, window_size: WindowSizeData, ) { thread::Builder::new() .name(format!("Layout{}", id)) .spawn(move || { thread_state::initialize(ThreadState::LAYOUT); // In order to get accurate crash reports, we install the top-level bc id. TopLevelBrowsingContextId::install(top_level_browsing_context_id); { // Ensures layout thread is destroyed before we send shutdown message let sender = chan.0; let background_hang_monitor = background_hang_monitor_register .register_component( MonitoredComponentId(id, MonitoredComponentType::Layout), Duration::from_millis(1000), Duration::from_millis(5000), None, ); let layout = LayoutThread::new( id, top_level_browsing_context_id, url, is_iframe, chan.1, pipeline_port, background_hang_monitor, constellation_chan, script_chan, image_cache, font_cache_thread, time_profiler_chan, mem_profiler_chan.clone(), webrender_api_sender, paint_time_metrics, busy, window_size, ); let reporter_name = format!("layout-reporter-{}", id); mem_profiler_chan.run_with_memory_reporting( || { layout.start(); }, reporter_name, sender, Msg::CollectReports, ); } }) .expect("Thread spawning failed"); } } struct ScriptReflowResult { script_reflow: ScriptReflow, result: RefCell>, } impl Deref for ScriptReflowResult { type Target = ScriptReflow; fn deref(&self) -> &ScriptReflow { &self.script_reflow } } impl DerefMut for ScriptReflowResult { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.script_reflow } } impl ScriptReflowResult { fn new(script_reflow: ScriptReflow) -> ScriptReflowResult { ScriptReflowResult { script_reflow: script_reflow, result: RefCell::new(Some(Default::default())), } } } impl Drop for ScriptReflowResult { fn drop(&mut self) { self.script_reflow .script_join_chan .send(self.result.borrow_mut().take().unwrap()) .unwrap(); } } /// The `LayoutThread` `rw_data` lock must remain locked until the first reflow, /// as RPC calls don't make sense until then. Use this in combination with /// `LayoutThread::lock_rw_data` and `LayoutThread::return_rw_data`. pub enum RWGuard<'a> { /// If the lock was previously held, from when the thread started. Held(MutexGuard<'a, LayoutThreadData>), /// If the lock was just used, and has been returned since there has been /// a reflow already. Used(MutexGuard<'a, LayoutThreadData>), } impl<'a> Deref for RWGuard<'a> { type Target = LayoutThreadData; fn deref(&self) -> &LayoutThreadData { match *self { RWGuard::Held(ref x) => &**x, RWGuard::Used(ref x) => &**x, } } } impl<'a> DerefMut for RWGuard<'a> { fn deref_mut(&mut self) -> &mut LayoutThreadData { match *self { RWGuard::Held(ref mut x) => &mut **x, RWGuard::Used(ref mut x) => &mut **x, } } } struct RwData<'a, 'b: 'a> { rw_data: &'b Arc>, possibly_locked_rw_data: &'a mut Option>, } impl<'a, 'b: 'a> RwData<'a, 'b> { /// If no reflow has happened yet, this will just return the lock in /// `possibly_locked_rw_data`. Otherwise, it will acquire the `rw_data` lock. /// /// If you do not wish RPCs to remain blocked, just drop the `RWGuard` /// returned from this function. If you _do_ wish for them to remain blocked, /// use `block`. fn lock(&mut self) -> RWGuard<'b> { match self.possibly_locked_rw_data.take() { None => RWGuard::Used(self.rw_data.lock().unwrap()), Some(x) => RWGuard::Held(x), } } } fn add_font_face_rules( stylesheet: &Stylesheet, guard: &SharedRwLockReadGuard, device: &Device, font_cache_thread: &FontCacheThread, font_cache_sender: &IpcSender<()>, outstanding_web_fonts_counter: &Arc, load_webfonts_synchronously: bool, ) { if load_webfonts_synchronously { let (sender, receiver) = ipc::channel().unwrap(); stylesheet.effective_font_face_rules(&device, guard, |rule| { if let Some(font_face) = rule.font_face() { let effective_sources = font_face.effective_sources(); font_cache_thread.add_web_font( font_face.family().clone(), effective_sources, sender.clone(), ); receiver.recv().unwrap(); } }) } else { stylesheet.effective_font_face_rules(&device, guard, |rule| { if let Some(font_face) = rule.font_face() { let effective_sources = font_face.effective_sources(); outstanding_web_fonts_counter.fetch_add(1, Ordering::SeqCst); font_cache_thread.add_web_font( font_face.family().clone(), effective_sources, (*font_cache_sender).clone(), ); } }) } } impl LayoutThread { /// Creates a new `LayoutThread` structure. fn new( id: PipelineId, top_level_browsing_context_id: TopLevelBrowsingContextId, url: ServoUrl, is_iframe: bool, port: Receiver, pipeline_port: IpcReceiver, background_hang_monitor: Box, constellation_chan: IpcSender, script_chan: IpcSender, image_cache: Arc, font_cache_thread: FontCacheThread, time_profiler_chan: profile_time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, webrender_api: WebrenderIpcSender, paint_time_metrics: PaintTimeMetrics, busy: Arc, window_size: WindowSizeData, ) -> LayoutThread { // Let webrender know about this pipeline by sending an empty display list. webrender_api.send_initial_transaction(id.to_webrender()); let device = Device::new( MediaType::screen(), QuirksMode::NoQuirks, window_size.initial_viewport, window_size.device_pixel_ratio, ); // Proxy IPC messages from the pipeline to the layout thread. let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(pipeline_port); // Ask the router to proxy IPC messages from the font cache thread to the layout thread. let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap(); let font_cache_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_font_cache_receiver); LayoutThread { id, top_level_browsing_context_id: top_level_browsing_context_id, url, is_iframe, port, pipeline_port: pipeline_receiver, script_chan, background_hang_monitor, constellation_chan: constellation_chan.clone(), time_profiler_chan, mem_profiler_chan, registered_painters: RegisteredPaintersImpl(Default::default()), image_cache, font_cache_thread, first_reflow: Cell::new(true), font_cache_receiver, font_cache_sender: ipc_font_cache_sender, parallel_flag: true, generation: Cell::new(0), outstanding_web_fonts: Arc::new(AtomicUsize::new(0)), root_flow: RefCell::new(None), // Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR epoch: Cell::new(Epoch(1)), viewport_size: Size2D::new(Au(0), Au(0)), webrender_api, stylist: Stylist::new(device, QuirksMode::NoQuirks), rw_data: Arc::new(Mutex::new(LayoutThreadData { constellation_chan, display_list: None, indexable_text: IndexableText::default(), content_box_response: None, content_boxes_response: Vec::new(), client_rect_response: Rect::zero(), scroll_id_response: None, scroll_area_response: Rect::zero(), resolved_style_response: String::new(), resolved_font_style_response: None, offset_parent_response: OffsetParentResponse::empty(), scroll_offsets: HashMap::new(), text_index_response: TextIndexResponse(None), nodes_from_point_response: vec![], element_inner_text_response: String::new(), inner_window_dimensions_response: None, })), webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())), paint_time_metrics, layout_query_waiting_time: Histogram::new(), last_iframe_sizes: Default::default(), busy, debug: opts::get().debug.clone(), nonincremental_layout: opts::get().nonincremental_layout, } } /// Starts listening on the port. fn start(mut self) { let rw_data = self.rw_data.clone(); let mut possibly_locked_rw_data = Some(rw_data.lock().unwrap()); let mut rw_data = RwData { rw_data: &rw_data, possibly_locked_rw_data: &mut possibly_locked_rw_data, }; while self.handle_request(&mut rw_data) { // Loop indefinitely. } } // Create a layout context for use in building display lists, hit testing, &c. fn build_layout_context<'a>( &'a self, guards: StylesheetGuards<'a>, snapshot_map: &'a SnapshotMap, origin: ImmutableOrigin, animation_timeline_value: f64, animations: &DocumentAnimationSet, stylesheets_changed: bool, ) -> LayoutContext<'a> { let traversal_flags = match stylesheets_changed { true => TraversalFlags::ForCSSRuleChanges, false => TraversalFlags::empty(), }; LayoutContext { id: self.id, origin, style_context: SharedStyleContext { stylist: &self.stylist, options: GLOBAL_STYLE_DATA.options.clone(), guards, visited_styles_enabled: false, animations: animations.clone(), registered_speculative_painters: &self.registered_painters, current_time_for_animations: animation_timeline_value, traversal_flags, snapshot_map: snapshot_map, }, image_cache: self.image_cache.clone(), font_cache_thread: Mutex::new(self.font_cache_thread.clone()), webrender_image_cache: self.webrender_image_cache.clone(), pending_images: Mutex::new(vec![]), registered_painters: &self.registered_painters, } } fn notify_activity_to_hang_monitor(&self, request: &Msg) { let hang_annotation = match request { Msg::AddStylesheet(..) => LayoutHangAnnotation::AddStylesheet, Msg::RemoveStylesheet(..) => LayoutHangAnnotation::RemoveStylesheet, Msg::SetQuirksMode(..) => LayoutHangAnnotation::SetQuirksMode, Msg::Reflow(..) => LayoutHangAnnotation::Reflow, Msg::GetRPC(..) => LayoutHangAnnotation::GetRPC, Msg::CollectReports(..) => LayoutHangAnnotation::CollectReports, Msg::PrepareToExit(..) => LayoutHangAnnotation::PrepareToExit, Msg::ExitNow => LayoutHangAnnotation::ExitNow, Msg::GetCurrentEpoch(..) => LayoutHangAnnotation::GetCurrentEpoch, Msg::GetWebFontLoadState(..) => LayoutHangAnnotation::GetWebFontLoadState, Msg::CreateLayoutThread(..) => LayoutHangAnnotation::CreateLayoutThread, Msg::SetFinalUrl(..) => LayoutHangAnnotation::SetFinalUrl, Msg::SetScrollStates(..) => LayoutHangAnnotation::SetScrollStates, Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint, Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart, }; self.background_hang_monitor .notify_activity(HangAnnotation::Layout(hang_annotation)); } /// Receives and dispatches messages from the script and constellation threads fn handle_request<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool { enum Request { FromPipeline(LayoutControlMsg), FromScript(Msg), FromFontCache, } // Notify the background-hang-monitor we are waiting for an event. self.background_hang_monitor.notify_wait(); let request = select! { recv(self.pipeline_port) -> msg => Request::FromPipeline(msg.unwrap()), recv(self.port) -> msg => Request::FromScript(msg.unwrap()), recv(self.font_cache_receiver) -> msg => { msg.unwrap(); Request::FromFontCache } }; self.busy.store(true, Ordering::Relaxed); let result = match request { Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => self .handle_request_helper( Msg::SetScrollStates(new_scroll_states), possibly_locked_rw_data, ), Request::FromPipeline(LayoutControlMsg::GetCurrentEpoch(sender)) => { self.handle_request_helper(Msg::GetCurrentEpoch(sender), possibly_locked_rw_data) }, Request::FromPipeline(LayoutControlMsg::GetWebFontLoadState(sender)) => self .handle_request_helper(Msg::GetWebFontLoadState(sender), possibly_locked_rw_data), Request::FromPipeline(LayoutControlMsg::ExitNow) => { self.handle_request_helper(Msg::ExitNow, possibly_locked_rw_data) }, Request::FromPipeline(LayoutControlMsg::PaintMetric(epoch, paint_time)) => { self.paint_time_metrics.maybe_set_metric(epoch, paint_time); true }, Request::FromScript(msg) => self.handle_request_helper(msg, possibly_locked_rw_data), Request::FromFontCache => { let _rw_data = possibly_locked_rw_data.lock(); self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst); font_context::invalidate_font_caches(); self.script_chan .send(ConstellationControlMsg::WebFontLoaded(self.id)) .unwrap(); true }, }; self.busy.store(false, Ordering::Relaxed); result } /// Receives and dispatches messages from other threads. fn handle_request_helper<'a, 'b>( &mut self, request: Msg, possibly_locked_rw_data: &mut RwData<'a, 'b>, ) -> bool { self.notify_activity_to_hang_monitor(&request); match request { Msg::AddStylesheet(stylesheet, before_stylesheet) => { let guard = stylesheet.shared_lock.read(); self.handle_add_stylesheet(&stylesheet, &guard); match before_stylesheet { Some(insertion_point) => self.stylist.insert_stylesheet_before( DocumentStyleSheet(stylesheet.clone()), DocumentStyleSheet(insertion_point), &guard, ), None => self .stylist .append_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard), } }, Msg::RemoveStylesheet(stylesheet) => { let guard = stylesheet.shared_lock.read(); self.stylist .remove_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard); }, Msg::SetQuirksMode(mode) => self.handle_set_quirks_mode(mode), Msg::GetRPC(response_chan) => { response_chan .send(Box::new(LayoutRPCImpl(self.rw_data.clone())) as Box) .unwrap(); }, Msg::Reflow(data) => { let mut data = ScriptReflowResult::new(data); profile( profile_time::ProfilerCategory::LayoutPerform, self.profiler_metadata(), self.time_profiler_chan.clone(), || self.handle_reflow(&mut data, possibly_locked_rw_data), ); }, Msg::SetScrollStates(new_scroll_states) => { self.set_scroll_states(new_scroll_states, possibly_locked_rw_data); }, Msg::CollectReports(reports_chan) => { self.collect_reports(reports_chan, possibly_locked_rw_data); }, Msg::GetCurrentEpoch(sender) => { let _rw_data = possibly_locked_rw_data.lock(); sender.send(self.epoch.get()).unwrap(); }, Msg::GetWebFontLoadState(sender) => { let _rw_data = possibly_locked_rw_data.lock(); let outstanding_web_fonts = self.outstanding_web_fonts.load(Ordering::SeqCst); sender.send(outstanding_web_fonts != 0).unwrap(); }, Msg::CreateLayoutThread(info) => self.create_layout_thread(info), Msg::SetFinalUrl(final_url) => { self.url = final_url; }, Msg::RegisterPaint(name, mut properties, painter) => { debug!("Registering the painter"); let properties = properties .drain(..) .filter_map(|name| { let id = PropertyId::parse_enabled_for_all_content(&*name).ok()?; Some((name.clone(), id)) }) .filter(|&(_, ref id)| !id.is_shorthand()) .collect(); let registered_painter = RegisteredPainterImpl { name: name.clone(), properties, painter, }; self.registered_painters.0.insert(name, registered_painter); }, Msg::PrepareToExit(response_chan) => { self.prepare_to_exit(response_chan); return false; }, // Receiving the Exit message at this stage only happens when layout is undergoing a "force exit". Msg::ExitNow => { debug!("layout: ExitNow received"); self.exit_now(); return false; }, Msg::SetNavigationStart(time) => { self.paint_time_metrics.set_navigation_start(time); }, } true } fn collect_reports<'a, 'b>( &self, reports_chan: ReportsChan, possibly_locked_rw_data: &mut RwData<'a, 'b>, ) { let mut reports = vec![]; // Servo uses vanilla jemalloc, which doesn't have a // malloc_enclosing_size_of function. let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None); // FIXME(njn): Just measuring the display tree for now. let rw_data = possibly_locked_rw_data.lock(); let display_list = rw_data.display_list.as_ref(); let formatted_url = &format!("url({})", self.url); reports.push(Report { path: path![formatted_url, "layout-thread", "display-list"], kind: ReportKind::ExplicitJemallocHeapSize, size: display_list.map_or(0, |sc| sc.size_of(&mut ops)), }); reports.push(Report { path: path![formatted_url, "layout-thread", "stylist"], kind: ReportKind::ExplicitJemallocHeapSize, size: self.stylist.size_of(&mut ops), }); // The LayoutThread has data in Persistent TLS... reports.push(Report { path: path![formatted_url, "layout-thread", "local-context"], kind: ReportKind::ExplicitJemallocHeapSize, size: malloc_size_of_persistent_local_context(&mut ops), }); reports_chan.send(reports); } fn create_layout_thread(&self, info: LayoutThreadInit) { LayoutThread::create( info.id, self.top_level_browsing_context_id, info.url.clone(), info.is_parent, info.layout_pair, info.pipeline_port, info.background_hang_monitor_register, info.constellation_chan, info.script_chan, info.image_cache, self.font_cache_thread.clone(), self.time_profiler_chan.clone(), self.mem_profiler_chan.clone(), self.webrender_api.clone(), info.paint_time_metrics, info.layout_is_busy, info.window_size, ); } /// Enters a quiescent state in which no new messages will be processed until an `ExitNow` is /// received. A pong is immediately sent on the given response channel. fn prepare_to_exit(&mut self, response_chan: Sender<()>) { response_chan.send(()).unwrap(); loop { match self.port.recv().unwrap() { Msg::ExitNow => { debug!("layout thread is exiting..."); self.exit_now(); break; }, Msg::CollectReports(_) => { // Just ignore these messages at this point. }, _ => panic!("layout: unexpected message received after `PrepareToExitMsg`"), } } } /// Shuts down the layout thread now. If there are any DOM nodes left, layout will now (safely) /// crash. fn exit_now(&mut self) { // Drop the root flow explicitly to avoid holding style data, such as // rule nodes. The `Stylist` checks when it is dropped that all rule // nodes have been GCed, so we want drop anyone who holds them first. let waiting_time_min = self.layout_query_waiting_time.minimum().unwrap_or(0); let waiting_time_max = self.layout_query_waiting_time.maximum().unwrap_or(0); let waiting_time_mean = self.layout_query_waiting_time.mean().unwrap_or(0); let waiting_time_stddev = self.layout_query_waiting_time.stddev().unwrap_or(0); debug!( "layout: query waiting time: min: {}, max: {}, mean: {}, standard_deviation: {}", waiting_time_min, waiting_time_max, waiting_time_mean, waiting_time_stddev ); self.root_flow.borrow_mut().take(); self.background_hang_monitor.unregister(); } fn handle_add_stylesheet(&self, stylesheet: &Stylesheet, guard: &SharedRwLockReadGuard) { // Find all font-face rules and notify the font cache of them. // GWTODO: Need to handle unloading web fonts. if stylesheet.is_effective_for_device(self.stylist.device(), &guard) { add_font_face_rules( &*stylesheet, &guard, self.stylist.device(), &self.font_cache_thread, &self.font_cache_sender, &self.outstanding_web_fonts, self.debug.load_webfonts_synchronously, ); } } /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used. fn handle_set_quirks_mode<'a, 'b>(&mut self, quirks_mode: QuirksMode) { self.stylist.set_quirks_mode(quirks_mode); } fn try_get_layout_root<'dom>(&self, node: impl LayoutNode<'dom>) -> Option { let result = node.mutate_layout_data()?.flow_construction_result.get(); let mut flow = match result { ConstructionResult::Flow(mut flow, abs_descendants) => { // Note: Assuming that the root has display 'static' (as per // CSS Section 9.3.1). If it was absolutely positioned, // it would return a reference to itself in `abs_descendants` // and would lead to a circular reference. Otherwise, we // set Root as CB and push remaining absolute descendants. if flow .base() .flags .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { flow.set_absolute_descendants(abs_descendants); } else { flow.push_absolute_descendants(abs_descendants); } flow }, _ => return None, }; FlowRef::deref_mut(&mut flow).mark_as_root(); Some(flow) } /// Performs layout constraint solving. /// /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. #[inline(never)] fn solve_constraints(layout_root: &mut dyn Flow, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("solve_constraints"); sequential::reflow(layout_root, layout_context, RelayoutMode::Incremental); } /// Performs layout constraint solving in parallel. /// /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. #[inline(never)] fn solve_constraints_parallel( traversal: &rayon::ThreadPool, layout_root: &mut dyn Flow, profiler_metadata: Option, time_profiler_chan: profile_time::ProfilerChan, layout_context: &LayoutContext, ) { let _scope = layout_debug_scope!("solve_constraints_parallel"); // NOTE: this currently computes borders, so any pruning should separate that // operation out. parallel::reflow( layout_root, profiler_metadata, time_profiler_chan, layout_context, traversal, ); } /// Update the recorded iframe sizes of the contents of this layout thread and /// when these sizes changes, send a message to the constellation informing it /// of the new sizes. fn update_iframe_sizes( &self, new_iframe_sizes: FnvHashMap>, ) { let old_iframe_sizes = std::mem::replace(&mut *self.last_iframe_sizes.borrow_mut(), new_iframe_sizes); if self.last_iframe_sizes.borrow().is_empty() { return; } let size_messages: Vec<_> = self .last_iframe_sizes .borrow() .iter() .filter_map(|(browsing_context_id, size)| { match old_iframe_sizes.get(&browsing_context_id) { Some(old_size) if old_size != size => Some(IFrameSizeMsg { browsing_context_id: *browsing_context_id, size: *size, type_: WindowSizeType::Resize, }), None => Some(IFrameSizeMsg { browsing_context_id: *browsing_context_id, size: *size, type_: WindowSizeType::Initial, }), _ => None, } }) .collect(); if !size_messages.is_empty() { let msg = ConstellationMsg::IFrameSizes(size_messages); if let Err(e) = self.constellation_chan.send(msg) { warn!("Layout resize to constellation failed ({}).", e); } } } /// Computes the stacking-relative positions of all flows and, if the painting is dirty and the /// reflow type need it, builds the display list. fn compute_abs_pos_and_build_display_list( &self, data: &Reflow, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, layout_root: &mut dyn Flow, layout_context: &mut LayoutContext, rw_data: &mut LayoutThreadData, ) { let writing_mode = layout_root.base().writing_mode; let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone()); profile( profile_time::ProfilerCategory::LayoutDispListBuild, metadata.clone(), sender.clone(), || { layout_root.mut_base().stacking_relative_position = LogicalPoint::zero(writing_mode) .to_physical(writing_mode, self.viewport_size) .to_vector(); layout_root.mut_base().clip = data.page_clip_rect; let traversal = ComputeStackingRelativePositions { layout_context: layout_context, }; traversal.traverse(layout_root); if layout_root .base() .restyle_damage .contains(ServoRestyleDamage::REPAINT) || rw_data.display_list.is_none() { if reflow_goal.needs_display_list() { let background_color = get_root_flow_background_color(layout_root); let mut build_state = sequential::build_display_list_for_subtree( layout_root, layout_context, background_color, data.page_clip_rect.size, ); debug!("Done building display list."); let root_size = { let root_flow = layout_root.base(); root_flow.overflow.scroll.size }; let origin = Rect::new(Point2D::new(Au(0), Au(0)), root_size).to_layout(); build_state.root_stacking_context.bounds = origin; build_state.root_stacking_context.overflow = origin; // We will not use build_state.iframe_sizes again, so it's safe to move it. let iframe_sizes = std::mem::replace(&mut build_state.iframe_sizes, FnvHashMap::default()); self.update_iframe_sizes(iframe_sizes); rw_data.indexable_text = std::mem::replace( &mut build_state.indexable_text, IndexableText::default(), ); rw_data.display_list = Some(build_state.to_display_list()); } } if !reflow_goal.needs_display() { // Defer the paint step until the next ForDisplay. // // We need to tell the document about this so it doesn't // incorrectly suppress reflows. See #13131. document .expect("No document in a non-display reflow?") .needs_paint_from_layout(); return; } if let Some(document) = document { document.will_paint(); } let display_list = rw_data.display_list.as_mut().unwrap(); if self.debug.dump_display_list { display_list.print(); } if self.debug.dump_display_list_json { println!("{}", serde_json::to_string_pretty(&display_list).unwrap()); } debug!("Layout done!"); let viewport_size = units::LayoutSize::new( self.viewport_size.width.to_f32_px(), self.viewport_size.height.to_f32_px(), ); let mut epoch = self.epoch.get(); epoch.next(); self.epoch.set(epoch); // TODO: Avoid the temporary conversion and build webrender sc/dl directly! let (builder, compositor_info, is_contentful) = display_list.convert_to_webrender(self.id, viewport_size, epoch.into()); // Observe notifications about rendered frames if needed right before // sending the display list to WebRender in order to set time related // Progressive Web Metrics. self.paint_time_metrics .maybe_observe_paint_time(self, epoch, is_contentful.0); self.webrender_api .send_display_list(compositor_info, builder.finalize()); }, ); } /// The high-level routine that performs layout threads. fn handle_reflow<'a, 'b>( &mut self, data: &mut ScriptReflowResult, possibly_locked_rw_data: &mut RwData<'a, 'b>, ) { let document = unsafe { ServoLayoutNode::new(&data.document) }; let document = document.as_document().unwrap(); // Parallelize if there's more than 750 objects based on rzambre's suggestion // https://github.com/servo/servo/issues/10110 self.parallel_flag = data.dom_count > 750; debug!("layout: received layout request for: {}", self.url); debug!("Number of objects in DOM: {}", data.dom_count); debug!("layout: parallel? {}", self.parallel_flag); let mut rw_data = possibly_locked_rw_data.lock(); // Record the time that layout query has been waited. let now = time::precise_time_ns(); if let ReflowGoal::LayoutQuery(_, timestamp) = data.reflow_goal { self.layout_query_waiting_time .increment(now - timestamp) .expect("layout: wrong layout query timestamp"); }; let root_element = match document.root_element() { None => { // Since we cannot compute anything, give spec-required placeholders. debug!("layout: No root node: bailing"); match data.reflow_goal { ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg { &QueryMsg::ContentBoxQuery(_) => { rw_data.content_box_response = None; }, &QueryMsg::ContentBoxesQuery(_) => { rw_data.content_boxes_response = Vec::new(); }, &QueryMsg::NodesFromPointQuery(..) => { rw_data.nodes_from_point_response = Vec::new(); }, &QueryMsg::ClientRectQuery(_) => { rw_data.client_rect_response = Rect::zero(); }, &QueryMsg::NodeScrollGeometryQuery(_) => { rw_data.scroll_area_response = Rect::zero(); }, &QueryMsg::NodeScrollIdQuery(_) => { rw_data.scroll_id_response = None; }, &QueryMsg::ResolvedStyleQuery(_, _, _) => { rw_data.resolved_style_response = String::new(); }, &QueryMsg::OffsetParentQuery(_) => { rw_data.offset_parent_response = OffsetParentResponse::empty(); }, &QueryMsg::StyleQuery => {}, &QueryMsg::TextIndexQuery(..) => { rw_data.text_index_response = TextIndexResponse(None); }, &QueryMsg::ElementInnerTextQuery(_) => { rw_data.element_inner_text_response = String::new(); }, &QueryMsg::ResolvedFontStyleQuery(..) => { rw_data.resolved_font_style_response = None; }, &QueryMsg::InnerWindowDimensionsQuery(_) => { rw_data.inner_window_dimensions_response = None; }, }, ReflowGoal::Full | ReflowGoal::TickAnimations | ReflowGoal::UpdateScrollNode(_) => {}, } return; }, Some(x) => x, }; debug!( "layout: processing reflow request for: {:?} ({}) (query={:?})", root_element, self.url, data.reflow_goal ); trace!("{:?}", ShowSubtree(root_element.as_node())); let initial_viewport = data.window_size.initial_viewport; let device_pixel_ratio = data.window_size.device_pixel_ratio; let old_viewport_size = self.viewport_size; let current_screen_size = Size2D::new( Au::from_f32_px(initial_viewport.width), Au::from_f32_px(initial_viewport.height), ); let origin = data.origin.clone(); // Calculate the actual viewport as per DEVICE-ADAPT ยง 6 // If the entire flow tree is invalid, then it will be reflowed anyhow. let document_shared_lock = document.style_shared_lock(); let author_guard = document_shared_lock.read(); let ua_stylesheets = &*UA_STYLESHEETS; let ua_or_user_guard = ua_stylesheets.shared_lock.read(); let guards = StylesheetGuards { author: &author_guard, ua_or_user: &ua_or_user_guard, }; let had_used_viewport_units = self.stylist.device().used_viewport_units(); let device = Device::new( MediaType::screen(), self.stylist.quirks_mode(), initial_viewport, device_pixel_ratio, ); let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards); self.stylist .force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change); self.viewport_size = current_screen_size; let viewport_size_changed = self.viewport_size != old_viewport_size; if viewport_size_changed && had_used_viewport_units { if let Some(mut data) = root_element.mutate_data() { data.hint.insert(RestyleHint::recascade_subtree()); } } if self.first_reflow.get() { debug!("First reflow, rebuilding user and UA rules"); for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets { self.stylist .append_stylesheet(stylesheet.clone(), &ua_or_user_guard); self.handle_add_stylesheet(&stylesheet.0, &ua_or_user_guard); } if self.stylist.quirks_mode() != QuirksMode::NoQuirks { self.stylist.append_stylesheet( ua_stylesheets.quirks_mode_stylesheet.clone(), &ua_or_user_guard, ); self.handle_add_stylesheet( &ua_stylesheets.quirks_mode_stylesheet.0, &ua_or_user_guard, ); } } if data.stylesheets_changed { debug!("Doc sheets changed, flushing author sheets too"); self.stylist .force_stylesheet_origins_dirty(Origin::Author.into()); } if viewport_size_changed { if let Some(mut flow) = self.try_get_layout_root(root_element.as_node()) { LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow)); } } debug!( "Shadow roots in document {:?}", document.shadow_roots().len() ); // Flush shadow roots stylesheets if dirty. document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author.clone()); let restyles = std::mem::take(&mut data.pending_restyles); debug!("Draining restyles: {}", restyles.len()); let mut map = SnapshotMap::new(); let elements_with_snapshot: Vec<_> = restyles .iter() .filter(|r| r.1.snapshot.is_some()) .map(|r| unsafe { ServoLayoutNode::::new(&r.0) .as_element() .unwrap() }) .collect(); for (el, restyle) in restyles { let el: ServoLayoutElement = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() }; // If we haven't styled this node yet, we don't need to track a // restyle. let mut style_data = match el.mutate_data() { Some(d) => d, None => { unsafe { el.unset_snapshot_flags() }; continue; }, }; if let Some(s) = restyle.snapshot { unsafe { el.set_has_snapshot() }; map.insert(el.as_node().opaque(), s); } // Stash the data on the element for processing by the style system. style_data.hint.insert(restyle.hint.into()); style_data.damage = restyle.damage; debug!("Noting restyle for {:?}: {:?}", el, style_data); } self.stylist.flush(&guards, Some(root_element), Some(&map)); // Create a layout context for use throughout the following passes. let mut layout_context = self.build_layout_context( guards.clone(), &map, origin, data.animation_timeline_value, &data.animations, data.stylesheets_changed, ); let pool = STYLE_THREAD_POOL.lock().unwrap(); let thread_pool = pool.pool(); let (thread_pool, num_threads) = if self.parallel_flag { (thread_pool.as_ref(), pool.num_threads.unwrap_or(1)) } else { (None, 1) }; let dirty_root = unsafe { ServoLayoutNode::new(&data.dirty_root.unwrap()) .as_element() .unwrap() }; let traversal = RecalcStyleAndConstructFlows::new(layout_context); let token = { let shared = , >>::shared_context(&traversal); RecalcStyleAndConstructFlows::pre_traverse(dirty_root, shared) }; if token.should_traverse() { // Recalculate CSS styles and rebuild flows and fragments. profile( profile_time::ProfilerCategory::LayoutStyleRecalc, self.profiler_metadata(), self.time_profiler_chan.clone(), || { // Perform CSS selector matching and flow construction. let root = driver::traverse_dom::< ServoLayoutElement, RecalcStyleAndConstructFlows, >(&traversal, token, thread_pool); unsafe { construct_flows_at_ancestors(traversal.context(), root.as_node()); } }, ); // TODO(pcwalton): Measure energy usage of text shaping, perhaps? let text_shaping_time = font::get_and_reset_text_shaping_performance_counter() / num_threads; profile_time::send_profile_data( profile_time::ProfilerCategory::LayoutTextShaping, self.profiler_metadata(), &self.time_profiler_chan, 0, text_shaping_time as u64, ); // Retrieve the (possibly rebuilt) root flow. *self.root_flow.borrow_mut() = self.try_get_layout_root(root_element.as_node()); } for element in elements_with_snapshot { unsafe { element.unset_snapshot_flags() } } layout_context = traversal.destroy(); if self.debug.dump_style_tree { println!( "{:?}", ShowSubtreeDataAndPrimaryValues(root_element.as_node()) ); } if self.debug.dump_rule_tree { layout_context .style_context .stylist .rule_tree() .dump_stdout(&guards); } // GC the rule tree if some heuristics are met. layout_context.style_context.stylist.rule_tree().maybe_gc(); // Perform post-style recalculation layout passes. if let Some(mut root_flow) = self.root_flow.borrow().clone() { self.perform_post_style_recalc_layout_passes( &mut root_flow, &data.reflow_info, &data.reflow_goal, Some(&document), &mut rw_data, &mut layout_context, thread_pool, ); } self.first_reflow.set(false); self.respond_to_query_if_necessary( &data.reflow_goal, &mut *rw_data, &mut layout_context, data.result.borrow_mut().as_mut().unwrap(), document_shared_lock, ); } fn respond_to_query_if_necessary( &self, reflow_goal: &ReflowGoal, rw_data: &mut LayoutThreadData, context: &mut LayoutContext, reflow_result: &mut ReflowComplete, shared_lock: &SharedRwLock, ) { reflow_result.pending_images = std::mem::replace(&mut *context.pending_images.lock().unwrap(), vec![]); let mut root_flow = match self.root_flow.borrow().clone() { Some(root_flow) => root_flow, None => return, }; let root_flow = FlowRef::deref_mut(&mut root_flow); match *reflow_goal { ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg { &QueryMsg::ContentBoxQuery(node) => { rw_data.content_box_response = process_content_box_request(node, root_flow); }, &QueryMsg::ContentBoxesQuery(node) => { rw_data.content_boxes_response = process_content_boxes_request(node, root_flow); }, &QueryMsg::TextIndexQuery(node, point_in_node) => { let point_in_node = Point2D::new( Au::from_f32_px(point_in_node.x), Au::from_f32_px(point_in_node.y), ); rw_data.text_index_response = TextIndexResponse(rw_data.indexable_text.text_index(node, point_in_node)); }, &QueryMsg::ClientRectQuery(node) => { rw_data.client_rect_response = process_client_rect_query(node, root_flow); }, &QueryMsg::NodeScrollGeometryQuery(node) => { rw_data.scroll_area_response = process_node_scroll_area_request(node, root_flow); }, &QueryMsg::NodeScrollIdQuery(node) => { let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; rw_data.scroll_id_response = Some(process_node_scroll_id_request(self.id, node)); }, &QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => { let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; rw_data.resolved_style_response = process_resolved_style_request(context, node, pseudo, property, root_flow); }, &QueryMsg::ResolvedFontStyleQuery(node, ref property, ref value) => { let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; let url = self.url.clone(); rw_data.resolved_font_style_response = process_resolved_font_style_request( context, node, value, property, url, shared_lock, ); }, &QueryMsg::OffsetParentQuery(node) => { rw_data.offset_parent_response = process_offset_parent_query(node, root_flow); }, &QueryMsg::StyleQuery => {}, &QueryMsg::NodesFromPointQuery(client_point, ref reflow_goal) => { let mut flags = match reflow_goal { &NodesFromPointQueryType::Topmost => HitTestFlags::empty(), &NodesFromPointQueryType::All => HitTestFlags::FIND_ALL, }; // The point we get is not relative to the entire WebRender scene, but to this // particular pipeline, so we need to tell WebRender about that. flags.insert(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT); let client_point = units::WorldPoint::from_untyped(client_point); let results = self.webrender_api.hit_test( Some(self.id.to_webrender()), client_point, flags, ); rw_data.nodes_from_point_response = results.iter().map(|result| result.node).collect() }, &QueryMsg::ElementInnerTextQuery(node) => { let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; rw_data.element_inner_text_response = process_element_inner_text_query(node, &rw_data.indexable_text); }, &QueryMsg::InnerWindowDimensionsQuery(browsing_context_id) => { rw_data.inner_window_dimensions_response = self .last_iframe_sizes .borrow() .get(&browsing_context_id) .cloned(); }, }, ReflowGoal::UpdateScrollNode(scroll_state) => { self.update_scroll_node_state(&scroll_state, rw_data); }, ReflowGoal::Full | ReflowGoal::TickAnimations => {}, } } fn update_scroll_node_state(&self, state: &ScrollState, rw_data: &mut LayoutThreadData) { rw_data .scroll_offsets .insert(state.scroll_id, state.scroll_offset); let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y); self.webrender_api.send_scroll_node( units::LayoutPoint::from_untyped(point), state.scroll_id, ScrollClamping::ToContentBounds, ); } fn set_scroll_states<'a, 'b>( &mut self, new_scroll_states: Vec, possibly_locked_rw_data: &mut RwData<'a, 'b>, ) { let mut rw_data = possibly_locked_rw_data.lock(); let mut script_scroll_states = vec![]; let mut layout_scroll_states = HashMap::new(); for new_state in &new_scroll_states { let offset = new_state.scroll_offset; layout_scroll_states.insert(new_state.scroll_id, offset); if new_state.scroll_id.is_root() { script_scroll_states.push((UntrustedNodeAddress::from_id(0), offset)) } else if let Some(node_id) = node_id_from_scroll_id(new_state.scroll_id.0 as usize) { script_scroll_states.push((UntrustedNodeAddress::from_id(node_id), offset)) } } let _ = self .script_chan .send(ConstellationControlMsg::SetScrollState( self.id, script_scroll_states, )); rw_data.scroll_offsets = layout_scroll_states } /// Cancel animations for any nodes which have been removed from flow tree. /// TODO(mrobinson): We should look into a way of doing this during flow tree construction. /// This also doesn't yet handles nodes that have been reparented. fn cancel_animations_for_nodes_not_in_flow_tree( animations: &mut FxHashMap, root_flow: &mut dyn Flow, ) { // Assume all nodes have been removed until proven otherwise. let mut invalid_nodes = animations.keys().cloned().collect(); fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet) { flow.mutate_fragments(&mut |fragment| { // Ideally we'd only not cancel ::before and ::after animations if they // were actually in the tree. At this point layout has lost information // about whether or not they exist, but have had their fragments accumulated // together. invalid_nodes.remove(&AnimationSetKey::new_for_non_pseudo(fragment.node)); invalid_nodes.remove(&AnimationSetKey::new_for_pseudo( fragment.node, PseudoElement::Before, )); invalid_nodes.remove(&AnimationSetKey::new_for_pseudo( fragment.node, PseudoElement::After, )); }); for kid in flow.mut_base().children.iter_mut() { traverse_flow(kid, invalid_nodes) } } traverse_flow(root_flow, &mut invalid_nodes); // Cancel animations for any nodes that are no longer in the flow tree. for node in &invalid_nodes { if let Some(state) = animations.get_mut(node) { state.cancel_all_animations(); } } } fn perform_post_style_recalc_layout_passes( &self, root_flow: &mut FlowRef, data: &Reflow, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, context: &mut LayoutContext, thread_pool: Option<&rayon::ThreadPool>, ) { Self::cancel_animations_for_nodes_not_in_flow_tree( &mut *(context.style_context.animations.sets.write()), FlowRef::deref_mut(root_flow), ); profile( profile_time::ProfilerCategory::LayoutRestyleDamagePropagation, self.profiler_metadata(), self.time_profiler_chan.clone(), || { // Call `compute_layout_damage` even in non-incremental mode, because it sets flags // that are needed in both incremental and non-incremental traversals. let damage = FlowRef::deref_mut(root_flow).compute_layout_damage(); if self.nonincremental_layout || damage.contains(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT) { FlowRef::deref_mut(root_flow).reflow_entire_document() } }, ); if self.debug.trace_layout { layout_debug::begin_trace(root_flow.clone()); } // Resolve generated content. profile( profile_time::ProfilerCategory::LayoutGeneratedContent, self.profiler_metadata(), self.time_profiler_chan.clone(), || sequential::resolve_generated_content(FlowRef::deref_mut(root_flow), &context), ); // Guess float placement. profile( profile_time::ProfilerCategory::LayoutFloatPlacementSpeculation, self.profiler_metadata(), self.time_profiler_chan.clone(), || sequential::guess_float_placement(FlowRef::deref_mut(root_flow)), ); // Perform the primary layout passes over the flow tree to compute the locations of all // the boxes. if root_flow .base() .restyle_damage .intersects(ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW) { profile( profile_time::ProfilerCategory::LayoutMain, self.profiler_metadata(), self.time_profiler_chan.clone(), || { let profiler_metadata = self.profiler_metadata(); if let Some(pool) = thread_pool { // Parallel mode. LayoutThread::solve_constraints_parallel( pool, FlowRef::deref_mut(root_flow), profiler_metadata, self.time_profiler_chan.clone(), &*context, ); } else { //Sequential mode LayoutThread::solve_constraints(FlowRef::deref_mut(root_flow), &context) } }, ); } profile( profile_time::ProfilerCategory::LayoutStoreOverflow, self.profiler_metadata(), self.time_profiler_chan.clone(), || { sequential::store_overflow(context, FlowRef::deref_mut(root_flow) as &mut dyn Flow); }, ); self.perform_post_main_layout_passes( data, root_flow, reflow_goal, document, rw_data, context, ); } fn perform_post_main_layout_passes( &self, data: &Reflow, mut root_flow: &mut FlowRef, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, layout_context: &mut LayoutContext, ) { // Build the display list if necessary, and send it to the painter. self.compute_abs_pos_and_build_display_list( data, reflow_goal, document, FlowRef::deref_mut(&mut root_flow), &mut *layout_context, rw_data, ); if self.debug.trace_layout { layout_debug::end_trace(self.generation.get()); } if self.debug.dump_flow_tree { root_flow.print("Post layout flow tree".to_owned()); } self.generation.set(self.generation.get() + 1); } fn reflow_all_nodes(flow: &mut dyn Flow) { debug!("reflowing all nodes!"); flow.mut_base().restyle_damage.insert( ServoRestyleDamage::REPAINT | ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW | ServoRestyleDamage::REPOSITION, ); for child in flow.mut_base().child_iter_mut() { LayoutThread::reflow_all_nodes(child); } } /// Returns profiling information which is passed to the time profiler. fn profiler_metadata(&self) -> Option { Some(TimerMetadata { url: self.url.to_string(), iframe: if self.is_iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow }, incremental: if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental }, }) } } impl ProfilerMetadataFactory for LayoutThread { fn new_metadata(&self) -> Option { self.profiler_metadata() } } // The default computed value for background-color is transparent (see // http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we // need to propagate the background color from the root HTML/Body // element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if // it is non-transparent. The phrase in the spec "If the canvas background // is not opaque, what shows through is UA-dependent." is handled by rust-layers // clearing the frame buffer to white. This ensures that setting a background // color on an iframe element, while the iframe content itself has a default // transparent background color is handled correctly. fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF { let transparent = ColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0, }; if !flow.is_block_like() { return transparent; } let block_flow = flow.as_mut_block(); let kid = match block_flow.base.children.iter_mut().next() { None => return transparent, Some(kid) => kid, }; if !kid.is_block_like() { return transparent; } let kid_block_flow = kid.as_block(); let color = kid_block_flow.fragment.style.resolve_color( kid_block_flow .fragment .style .get_background() .background_color .clone(), ); ColorF::new( color.red_f32(), color.green_f32(), color.blue_f32(), color.alpha_f32(), ) } fn get_ua_stylesheets() -> Result { fn parse_ua_stylesheet( shared_lock: &SharedRwLock, filename: &str, content: &[u8], ) -> Result { Ok(DocumentStyleSheet(ServoArc::new(Stylesheet::from_bytes( content, ServoUrl::parse(&format!("chrome://resources/{:?}", filename)).unwrap(), None, None, Origin::UserAgent, MediaList::empty(), shared_lock.clone(), None, None, QuirksMode::NoQuirks, )))) } let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; // FIXME: presentational-hints.css should be at author origin with zero specificity. // (Does it make a difference?) let mut user_or_user_agent_stylesheets = vec![ parse_ua_stylesheet( &shared_lock, "user-agent.css", &resources::read_bytes(Resource::UserAgentCSS), )?, parse_ua_stylesheet( &shared_lock, "servo.css", &resources::read_bytes(Resource::ServoCSS), )?, parse_ua_stylesheet( &shared_lock, "presentational-hints.css", &resources::read_bytes(Resource::PresentationalHintsCSS), )?, ]; for &(ref contents, ref url) in &opts::get().user_stylesheets { user_or_user_agent_stylesheets.push(DocumentStyleSheet(ServoArc::new( Stylesheet::from_bytes( &contents, url.clone(), None, None, Origin::User, MediaList::empty(), shared_lock.clone(), None, Some(&RustLogReporter), QuirksMode::NoQuirks, ), ))); } let quirks_mode_stylesheet = parse_ua_stylesheet( &shared_lock, "quirks-mode.css", &resources::read_bytes(Resource::QuirksModeCSS), )?; Ok(UserAgentStylesheets { shared_lock: shared_lock.clone(), user_or_user_agent_stylesheets: user_or_user_agent_stylesheets, quirks_mode_stylesheet: quirks_mode_stylesheet, }) } lazy_static! { static ref UA_STYLESHEETS: UserAgentStylesheets = { match get_ua_stylesheets() { Ok(stylesheets) => stylesheets, Err(filename) => { error!("Failed to load UA stylesheet {}!", filename); process::exit(1); }, } }; } struct RegisteredPainterImpl { painter: Box, name: Atom, // FIXME: Should be a PrecomputedHashMap. properties: FxHashMap, } impl SpeculativePainter for RegisteredPainterImpl { fn speculatively_draw_a_paint_image( &self, properties: Vec<(Atom, String)>, arguments: Vec, ) { self.painter .speculatively_draw_a_paint_image(properties, arguments); } } impl RegisteredSpeculativePainter for RegisteredPainterImpl { fn properties(&self) -> &FxHashMap { &self.properties } fn name(&self) -> Atom { self.name.clone() } } impl Painter for RegisteredPainterImpl { fn draw_a_paint_image( &self, size: Size2D, device_pixel_ratio: Scale, properties: Vec<(Atom, String)>, arguments: Vec, ) -> Result { self.painter .draw_a_paint_image(size, device_pixel_ratio, properties, arguments) } } impl RegisteredPainter for RegisteredPainterImpl {} struct RegisteredPaintersImpl(FnvHashMap); impl RegisteredSpeculativePainters for RegisteredPaintersImpl { fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> { self.0 .get(&name) .map(|painter| painter as &dyn RegisteredSpeculativePainter) } } impl RegisteredPainters for RegisteredPaintersImpl { fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter> { self.0 .get(&name) .map(|painter| painter as &dyn RegisteredPainter) } }