diff options
44 files changed, 638 insertions, 1255 deletions
diff --git a/Cargo.lock b/Cargo.lock index 7b257dccde8..bf76bcf88ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,7 +886,6 @@ dependencies = [ "http", "ipc-channel", "keyboard-types", - "layout_traits", "log", "media", "metrics", @@ -894,6 +893,7 @@ dependencies = [ "net", "net_traits", "profile_traits", + "script_layout_interface", "script_traits", "serde", "servo_config", @@ -3150,7 +3150,6 @@ dependencies = [ "histogram", "ipc-channel", "layout_2013", - "layout_traits", "lazy_static", "log", "malloc_size_of", @@ -3190,7 +3189,6 @@ dependencies = [ "gfx_traits", "ipc-channel", "layout_2020", - "layout_traits", "lazy_static", "log", "malloc_size_of", @@ -3214,21 +3212,6 @@ dependencies = [ ] [[package]] -name = "layout_traits" -version = "0.0.1" -dependencies = [ - "crossbeam-channel", - "gfx", - "ipc-channel", - "metrics", - "msg", - "net_traits", - "profile_traits", - "script_traits", - "servo_url", -] - -[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4919,6 +4902,7 @@ dependencies = [ "euclid", "fnv", "fxhash", + "gfx", "gfx_traits", "headers", "html5ever", @@ -4998,6 +4982,7 @@ dependencies = [ "canvas_traits", "crossbeam-channel", "euclid", + "gfx", "gfx_traits", "html5ever", "ipc-channel", diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 37ac7feb35a..612774a7675 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -37,9 +37,9 @@ use profile_traits::time::{self as profile_time, profile, ProfilerCategory}; use script_traits::compositor::{HitTestInfo, ScrollTree}; use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent, TouchEvent, WheelEvent}; use script_traits::{ - AnimationState, AnimationTickType, CompositorHitTestResult, LayoutControlMsg, MouseButton, - MouseEventType, ScrollState, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta, - WindowSizeData, WindowSizeType, + AnimationState, AnimationTickType, CompositorHitTestResult, ConstellationControlMsg, + LayoutControlMsg, MouseButton, MouseEventType, ScrollState, TouchEventType, TouchId, + UntrustedNodeAddress, WheelDelta, WindowSizeData, WindowSizeType, }; use servo_geometry::{DeviceIndependentPixel, FramebufferUintLength}; use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor}; @@ -207,11 +207,11 @@ pub struct IOCompositor<Window: WindowMethods + ?Sized> { /// Some XR devices want to run on the main thread. pub webxr_main_thread: webxr::MainThreadRegistry, - /// Map of the pending paint metrics per layout thread. - /// The layout thread for each specific pipeline expects the compositor to + /// Map of the pending paint metrics per Layout. + /// The Layout for each specific pipeline expects the compositor to /// paint frames with specific given IDs (epoch). Once the compositor paints /// these frames, it records the paint time for each of them and sends the - /// metric to the corresponding layout thread. + /// metric to the corresponding Layout. pending_paint_metrics: HashMap<PipelineId, Epoch>, /// The coordinates of the native window, its view and the screen. @@ -1606,9 +1606,11 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { }); if let Some(pipeline) = details.pipeline.as_ref() { - let _ = pipeline - .layout_chan - .send(LayoutControlMsg::SetScrollStates(scroll_states)); + let message = ConstellationControlMsg::ForLayoutFromConstellation( + LayoutControlMsg::SetScrollStates(scroll_states), + *pipeline_id, + ); + let _ = pipeline.script_chan.send(message); } } @@ -1792,10 +1794,9 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { }, ); - // If there are pending paint metrics, we check if any of the painted epochs is - // one of the ones that the paint metrics recorder is expecting . In that case, - // we get the current time, inform the layout thread about it and remove the - // pending metric from the list. + // If there are pending paint metrics, we check if any of the painted epochs is one of the + // ones that the paint metrics recorder is expecting. In that case, we get the current + // time, inform layout about it and remove the pending metric from the list. if !self.pending_paint_metrics.is_empty() { let paint_time = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -1809,7 +1810,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { .webrender .current_epoch(self.webrender_document, id.to_webrender()) { - // and check if it is the one the layout thread is expecting, + // and check if it is the one layout is expecting, let epoch = Epoch(epoch); if *pending_epoch != epoch { continue; @@ -1817,9 +1818,11 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { // in which case, we remove it from the list of pending metrics, to_remove.push(id.clone()); if let Some(pipeline) = self.pipeline(*id) { - // and inform the layout thread with the measured paint time. - let msg = LayoutControlMsg::PaintMetric(epoch, paint_time); - if let Err(e) = pipeline.layout_chan.send(msg) { + // and inform layout with the measured paint time. + let message = LayoutControlMsg::PaintMetric(epoch, paint_time); + let message = + ConstellationControlMsg::ForLayoutFromConstellation(message, *id); + if let Err(e) = pipeline.script_chan.send(message) { warn!("Sending PaintMetric message to layout failed ({:?}).", e); } } diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index 43a4c7d982d..2f2120070d3 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -29,7 +29,6 @@ gfx_traits = { workspace = true } http = { workspace = true } ipc-channel = { workspace = true } keyboard-types = { workspace = true } -layout_traits = { workspace = true } log = { workspace = true } media = { path = "../media" } metrics = { path = "../metrics" } @@ -37,6 +36,7 @@ msg = { workspace = true } net = { path = "../net" } net_traits = { workspace = true } profile_traits = { workspace = true } +script_layout_interface = { workspace = true } script_traits = { workspace = true } serde = { workspace = true } servo_config = { path = "../config" } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index e7ddb5a95f5..1acd379d7bf 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -13,8 +13,7 @@ //! //! * The set of all `Pipeline` objects. Each pipeline gives the //! constellation's view of a `Window`, with its script thread and -//! layout threads. Pipelines may share script threads, but not -//! layout threads. +//! layout. Pipelines may share script threads. //! //! * The set of all `BrowsingContext` objects. Each browsing context //! gives the constellation's view of a `WindowProxy`. @@ -45,7 +44,7 @@ // //! The constellation also maintains channels to threads, including: //! -//! * The script and layout threads. +//! * The script thread. //! * The graphics compositor. //! * The font cache, image cache, and resource manager, which load //! and cache shared fonts, images, or other resources. @@ -67,12 +66,8 @@ //! sender to send some data. Servo tries to achieve deadlock-freedom by using the following //! can-block-on relation: //! -//! * Layout can block on canvas -//! * Layout can block on font cache -//! * Layout can block on image cache //! * Constellation can block on compositor //! * Constellation can block on embedder -//! * Constellation can block on layout //! * Script can block on anything (other than script) //! * Blocking is transitive (if T1 can block on T2 and T2 can block on T3 then T1 can block on T3) //! * Nothing can block on itself! @@ -124,7 +119,6 @@ use ipc_channel::router::ROUTER; use ipc_channel::Error as IpcError; use keyboard_types::webdriver::Event as WebDriverInputEvent; use keyboard_types::KeyboardEvent; -use layout_traits::LayoutThreadFactory; use log::{debug, error, info, trace, warn}; use media::{GLPlayerThreads, WindowGLContext}; use msg::constellation_msg::{ @@ -138,17 +132,18 @@ use net_traits::request::{Referrer, RequestBuilder}; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; use net_traits::{self, FetchResponseMsg, IpcSend, ResourceThreads}; use profile_traits::{mem, time}; +use script_layout_interface::{LayoutFactory, ScriptThreadFactory}; use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent}; use script_traits::{ webdriver_msg, AnimationState, AnimationTickType, AuxiliaryBrowsingContextLoadInfo, BroadcastMsg, CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity, DocumentState, GamepadEvent, HistoryEntryReplacement, IFrameLoadInfo, - IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LayoutControlMsg, - LayoutMsg as FromLayoutMsg, LoadData, LoadOrigin, LogEntry, MediaSessionActionType, - MessagePortMsg, MouseEventType, PortMessageTask, SWManagerMsg, SWManagerSenders, - ScriptMsg as FromScriptMsg, ScriptThreadFactory, ScriptToConstellationChan, - ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TimerSchedulerMsg, - UpdatePipelineIdReason, WebDriverCommandMsg, WindowSizeData, WindowSizeType, + IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LayoutMsg as FromLayoutMsg, + LoadData, LoadOrigin, LogEntry, MediaSessionActionType, MessagePortMsg, MouseEventType, + PortMessageTask, SWManagerMsg, SWManagerSenders, ScriptMsg as FromScriptMsg, + ScriptToConstellationChan, ServiceWorkerManagerFactory, ServiceWorkerMsg, + StructuredSerializedData, TimerSchedulerMsg, UpdatePipelineIdReason, WebDriverCommandMsg, + WindowSizeData, WindowSizeType, }; use serde::{Deserialize, Serialize}; use servo_config::{opts, pref}; @@ -270,7 +265,7 @@ struct BrowsingContextGroup { /// `LayoutThread` in the `layout` crate, and `ScriptThread` in /// the `script` crate). Script and layout communicate using a `Message` /// type. -pub struct Constellation<Message, LTF, STF, SWF> { +pub struct Constellation<STF, SWF> { /// An ipc-sender/threaded-receiver pair /// to facilitate installing pipeline namespaces in threads /// via a per-process installer. @@ -302,11 +297,16 @@ pub struct Constellation<Message, LTF, STF, SWF> { /// from the background hang monitor. background_hang_monitor_receiver: Receiver<Result<HangMonitorAlert, IpcError>>, - /// An IPC channel for layout threads to send messages to the constellation. - /// This is the layout threads' view of `layout_receiver`. + /// A factory for creating layouts. This allows customizing the kind + /// of layout created for a [`Constellation`] and prevents a circular crate + /// dependency between script and layout. + layout_factory: Arc<dyn LayoutFactory>, + + /// An IPC channel for layout to send messages to the constellation. + /// This is the layout's view of `layout_receiver`. layout_sender: IpcSender<FromLayoutMsg>, - /// A channel for the constellation to receive messages from layout threads. + /// A channel for the constellation to receive messages from layout. /// This is the constellation's view of `layout_sender`. layout_receiver: Receiver<Result<FromLayoutMsg, IpcError>>, @@ -455,7 +455,7 @@ pub struct Constellation<Message, LTF, STF, SWF> { random_pipeline_closure: Option<(ServoRng, f32)>, /// Phantom data that keeps the Rust type system happy. - phantom: PhantomData<(Message, LTF, STF, SWF)>, + phantom: PhantomData<(STF, SWF)>, /// Entry point to create and get channels to a WebGLThread. webgl_threads: Option<WebGLThreads>, @@ -575,7 +575,6 @@ impl WebDriverData { enum ReadyToSave { NoTopLevelBrowsingContext, PendingChanges, - WebFontNotLoaded, DocumentLoading, EpochMismatch, PipelineUnknown, @@ -610,15 +609,15 @@ where crossbeam_receiver } -impl<Message, LTF, STF, SWF> Constellation<Message, LTF, STF, SWF> +impl<STF, SWF> Constellation<STF, SWF> where - LTF: LayoutThreadFactory<Message = Message>, - STF: ScriptThreadFactory<Message = Message>, + STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory, { /// Create a new constellation thread. pub fn start( state: InitialConstellationState, + layout_factory: Arc<dyn LayoutFactory>, initial_window_size: WindowSizeData, random_pipeline_closure_probability: Option<f32>, random_pipeline_closure_seed: Option<usize>, @@ -738,7 +737,7 @@ where wgpu_image_map: state.wgpu_image_map, }; - let mut constellation: Constellation<Message, LTF, STF, SWF> = Constellation { + let mut constellation: Constellation<STF, SWF> = Constellation { namespace_receiver, namespace_ipc_sender, script_sender: script_ipc_sender, @@ -749,6 +748,7 @@ where layout_sender: layout_ipc_sender, script_receiver: script_receiver, compositor_receiver: compositor_receiver, + layout_factory, layout_receiver: layout_receiver, network_listener_sender: network_listener_sender, network_listener_receiver: network_listener_receiver, @@ -1020,7 +1020,7 @@ where self.public_resource_threads.clone() }; - let result = Pipeline::spawn::<Message, LTF, STF>(InitialPipelineState { + let result = Pipeline::spawn::<STF>(InitialPipelineState { id: pipeline_id, browsing_context_id, top_level_browsing_context_id, @@ -1037,6 +1037,7 @@ where .background_hang_monitor_sender .clone(), layout_to_constellation_chan: self.layout_sender.clone(), + layout_factory: self.layout_factory.clone(), scheduler_chan: self.scheduler_ipc_sender.clone(), compositor_proxy: self.compositor_proxy.clone(), devtools_sender: self.devtools_sender.clone(), @@ -1652,11 +1653,11 @@ where FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => { self.handle_script_loaded_url_in_iframe_msg(load_info); }, - FromScriptMsg::ScriptNewIFrame(load_info, response_sender) => { - self.handle_script_new_iframe(load_info, response_sender); + FromScriptMsg::ScriptNewIFrame(load_info) => { + self.handle_script_new_iframe(load_info); }, - FromScriptMsg::ScriptNewAuxiliary(load_info, response_sender) => { - self.handle_script_new_auxiliary(load_info, response_sender); + FromScriptMsg::ScriptNewAuxiliary(load_info) => { + self.handle_script_new_auxiliary(load_info); }, FromScriptMsg::ChangeRunningAnimationsState(animation_state) => { self.handle_change_running_animations_state(source_pipeline_id, animation_state) @@ -1737,6 +1738,13 @@ where FromScriptMsg::SetDocumentState(state) => { self.document_states.insert(source_pipeline_id, state); }, + FromScriptMsg::SetLayoutEpoch(epoch, response_sender) => { + if let Some(pipeline) = self.pipelines.get_mut(&source_pipeline_id) { + pipeline.layout_epoch = epoch; + } + + response_sender.send(true).unwrap_or_default(); + }, FromScriptMsg::GetClientWindow(response_sender) => { self.compositor_proxy .send(CompositorMsg::GetClientWindow(response_sender)); @@ -2560,6 +2568,8 @@ where } fn handle_exit(&mut self) { + debug!("Handling exit."); + // TODO: add a timer, which forces shutdown if threads aren't responsive. if self.shutting_down { return; @@ -2638,6 +2648,8 @@ where } fn handle_shutdown(&mut self) { + debug!("Handling shutdown."); + // At this point, there are no active pipelines, // so we can safely block on other threads, without worrying about deadlock. // Channels to receive signals when threads are done exiting. @@ -3233,11 +3245,7 @@ where }); } - fn handle_script_new_iframe( - &mut self, - load_info: IFrameLoadInfoWithData, - layout_sender: IpcSender<LayoutControlMsg>, - ) { + fn handle_script_new_iframe(&mut self, load_info: IFrameLoadInfoWithData) { let IFrameLoadInfo { parent_pipeline_id, new_pipeline_id, @@ -3274,7 +3282,6 @@ where top_level_browsing_context_id, None, script_sender, - layout_sender, self.compositor_proxy.clone(), is_parent_visible, load_info.load_data, @@ -3298,11 +3305,7 @@ where }); } - fn handle_script_new_auxiliary( - &mut self, - load_info: AuxiliaryBrowsingContextLoadInfo, - layout_sender: IpcSender<LayoutControlMsg>, - ) { + fn handle_script_new_auxiliary(&mut self, load_info: AuxiliaryBrowsingContextLoadInfo) { let AuxiliaryBrowsingContextLoadInfo { load_data, opener_pipeline_id, @@ -3337,7 +3340,6 @@ where new_top_level_browsing_context_id, Some(opener_browsing_context_id), script_sender, - layout_sender, self.compositor_proxy.clone(), is_opener_visible, load_data, @@ -4956,14 +4958,9 @@ where return ReadyToSave::PendingChanges; } - let (state_sender, state_receiver) = ipc::channel().expect("Failed to create IPC channel!"); - let (epoch_ipc_sender, epoch_ipc_receiver) = - ipc::channel().expect("Failed to create IPC channel!"); - - // Step through the fully active browsing contexts, checking that the script - // thread is idle, and that the current epoch of the layout thread - // matches what the compositor has painted. If all these conditions - // are met, then the output image should not change and a reftest + // Step through the fully active browsing contexts, checking that the script thread is idle, + // and that the current epoch of the layout matches what the compositor has painted. If all + // these conditions are met, then the output image should not change and a reftest // screenshot can safely be written. for browsing_context in self.fully_active_browsing_contexts_iter(top_level_browsing_context_id) @@ -4983,22 +4980,6 @@ where Some(pipeline) => pipeline, }; - // Check to see if there are any webfonts still loading. - // - // If GetWebFontLoadState returns false, either there are no - // webfonts loading, or there's a WebFontLoaded message waiting in - // script_chan's message queue. Therefore, we need to check this - // before we check whether the document is ready; otherwise, - // there's a race condition where a webfont has finished loading, - // but hasn't yet notified the document. - let msg = LayoutControlMsg::GetWebFontLoadState(state_sender.clone()); - if let Err(e) = pipeline.layout_chan.send(msg) { - warn!("Get web font failed ({})", e); - } - if state_receiver.recv().unwrap_or(true) { - return ReadyToSave::WebFontNotLoaded; - } - // See if this pipeline has reached idle script state yet. match self.document_states.get(&browsing_context.pipeline_id) { Some(&DocumentState::Idle) => {}, @@ -5017,25 +4998,14 @@ where continue; } - // Get the epoch that the compositor has drawn for this pipeline. + // Get the epoch that the compositor has drawn for this pipeline and then check if the + // last laid out epoch matches what the compositor has drawn. If they match (and script + // is idle) then this pipeline won't change again and can be considered stable. let compositor_epoch = pipeline_states.get(&browsing_context.pipeline_id); match compositor_epoch { Some(compositor_epoch) => { - // Synchronously query the layout thread to see if the current - // epoch matches what the compositor has drawn. If they match - // (and script is idle) then this pipeline won't change again - // and can be considered stable. - let message = LayoutControlMsg::GetCurrentEpoch(epoch_ipc_sender.clone()); - if let Err(e) = pipeline.layout_chan.send(message) { - warn!("Failed to send GetCurrentEpoch ({}).", e); - } - match epoch_ipc_receiver.recv() { - Err(e) => warn!("Failed to receive current epoch ({:?}).", e), - Ok(layout_thread_epoch) => { - if layout_thread_epoch != *compositor_epoch { - return ReadyToSave::EpochMismatch; - } - }, + if pipeline.layout_epoch != *compositor_epoch { + return ReadyToSave::EpochMismatch; } }, None => { diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 061cf218789..525fca57f79 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -5,7 +5,6 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::rc::Rc; -use std::sync::atomic::AtomicBool; use std::sync::Arc; use background_hang_monitor::HangMonitorRegister; @@ -16,13 +15,12 @@ use crossbeam_channel::{unbounded, Sender}; use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg}; use embedder_traits::EventLoopWaker; use gfx::font_cache_thread::FontCacheThread; +use gfx_traits::Epoch; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use ipc_channel::Error; -use layout_traits::LayoutThreadFactory; use log::{debug, error, warn}; use media::WindowGLContext; -use metrics::PaintTimeMetrics; use msg::constellation_msg::{ BackgroundHangMonitorControlMsg, BackgroundHangMonitorRegister, BrowsingContextId, HangMonitorAlert, HistoryStateId, PipelineId, PipelineNamespace, PipelineNamespaceId, @@ -32,10 +30,11 @@ use net::image_cache::ImageCacheImpl; use net_traits::image_cache::ImageCache; use net_traits::ResourceThreads; use profile_traits::{mem as profile_mem, time}; +use script_layout_interface::{LayoutFactory, ScriptThreadFactory}; use script_traits::{ AnimationState, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity, - InitialScriptState, LayoutControlMsg, LayoutMsg, LoadData, NewLayoutInfo, SWManagerMsg, - ScriptThreadFactory, ScriptToConstellationChan, TimerSchedulerMsg, WindowSizeData, + InitialScriptState, LayoutMsg, LoadData, NewLayoutInfo, SWManagerMsg, + ScriptToConstellationChan, TimerSchedulerMsg, WindowSizeData, }; use serde::{Deserialize, Serialize}; use servo_config::opts::{self, Opts}; @@ -47,10 +46,8 @@ use webrender_api::DocumentId; use crate::event_loop::EventLoop; use crate::sandboxing::{spawn_multiprocess, UnprivilegedContent}; -/// A `Pipeline` is the constellation's view of a `Document`. Each pipeline has an -/// event loop (executed by a script thread) and a layout thread. A script thread -/// may be responsible for many pipelines, but a layout thread is only responsible -/// for one. +/// A `Pipeline` is the constellation's view of a `Window`. Each pipeline has an event loop +/// (executed by a script thread). A script thread may be responsible for many pipelines. pub struct Pipeline { /// The ID of the pipeline. pub id: PipelineId, @@ -66,9 +63,6 @@ pub struct Pipeline { /// The event loop handling this pipeline. pub event_loop: Rc<EventLoop>, - /// A channel to layout, for performing reflows and shutdown. - pub layout_chan: IpcSender<LayoutControlMsg>, - /// A channel to the compositor. pub compositor_proxy: CompositorProxy, @@ -98,6 +92,10 @@ pub struct Pipeline { /// The title of this pipeline's document. pub title: String, + + /// The last compositor [`Epoch`] that was laid out in this pipeline if "exit after load" is + /// enabled. + pub layout_epoch: Epoch, } /// Initial setup data needed to construct a pipeline. @@ -133,9 +131,12 @@ pub struct InitialPipelineState { /// A channel for the background hang monitor to send messages to the constellation. pub background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>, - /// A channel for the layout thread to send messages to the constellation. + /// A channel for the layout to send messages to the constellation. pub layout_to_constellation_chan: IpcSender<LayoutMsg>, + /// A fatory for creating layouts to be used by the ScriptThread. + pub layout_factory: Arc<dyn LayoutFactory>, + /// A channel to schedule timer events. pub scheduler_chan: IpcSender<TimerSchedulerMsg>, @@ -212,17 +213,12 @@ pub struct NewPipeline { } impl Pipeline { - /// Starts a layout thread, and possibly a script thread, in - /// a new process if requested. - pub fn spawn<Message, LTF, STF>(state: InitialPipelineState) -> Result<NewPipeline, Error> - where - LTF: LayoutThreadFactory<Message = Message>, - STF: ScriptThreadFactory<Message = Message>, - { + /// Possibly starts a script thread, in a new process if requested. + pub fn spawn<STF: ScriptThreadFactory>( + state: InitialPipelineState, + ) -> Result<NewPipeline, Error> { // Note: we allow channel creation to panic, since recovering from this // probably requires a general low-memory strategy. - let (pipeline_chan, pipeline_port) = ipc::channel().expect("Pipeline main chan"); - let (script_chan, bhm_control_chan) = match state.event_loop { Some(script_chan) => { let new_layout_info = NewLayoutInfo { @@ -233,7 +229,6 @@ impl Pipeline { opener: state.opener, load_data: state.load_data.clone(), window_size: state.window_size, - pipeline_port: pipeline_port, }; if let Err(e) = @@ -299,7 +294,6 @@ impl Pipeline { script_port: script_port, opts: (*opts::get()).clone(), prefs: prefs::pref_map().iter().collect(), - pipeline_port: pipeline_port, pipeline_namespace_id: state.pipeline_namespace_id, webrender_api_sender: state.webrender_api_sender, webrender_image_api_sender: state.webrender_image_api_sender, @@ -324,7 +318,11 @@ impl Pipeline { let register = state .background_monitor_register .expect("Couldn't start content, no background monitor has been initiated"); - unprivileged_pipeline_content.start_all::<Message, LTF, STF>(false, register); + unprivileged_pipeline_content.start_all::<STF>( + false, + state.layout_factory, + register, + ); None }; @@ -338,7 +336,6 @@ impl Pipeline { state.top_level_browsing_context_id, state.opener, script_chan, - pipeline_chan, state.compositor_proxy, state.prev_visibility, state.load_data, @@ -349,15 +346,13 @@ impl Pipeline { }) } - /// Creates a new `Pipeline`, after the script and layout threads have been - /// spawned. + /// Creates a new `Pipeline`, after the script has been spawned. pub fn new( id: PipelineId, browsing_context_id: BrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId, opener: Option<BrowsingContextId>, event_loop: Rc<EventLoop>, - layout_chan: IpcSender<LayoutControlMsg>, compositor_proxy: CompositorProxy, is_visible: bool, load_data: LoadData, @@ -368,7 +363,6 @@ impl Pipeline { top_level_browsing_context_id: top_level_browsing_context_id, opener: opener, event_loop: event_loop, - layout_chan: layout_chan, compositor_proxy: compositor_proxy, url: load_data.url.clone(), children: vec![], @@ -378,6 +372,7 @@ impl Pipeline { history_states: HashSet::new(), completely_loaded: false, title: String::new(), + layout_epoch: Epoch(0), }; pipeline.notify_visibility(is_visible); @@ -418,9 +413,6 @@ impl Pipeline { if let Err(e) = self.event_loop.send(msg) { warn!("Sending script exit message failed ({}).", e); } - if let Err(e) = self.layout_chan.send(LayoutControlMsg::ExitNow) { - warn!("Sending layout exit message failed ({}).", e); - } } /// Notify this pipeline of its activity. @@ -437,7 +429,6 @@ impl Pipeline { id: self.id.clone(), top_level_browsing_context_id: self.top_level_browsing_context_id.clone(), script_chan: self.event_loop.sender(), - layout_chan: self.layout_chan.clone(), } } @@ -504,7 +495,6 @@ pub struct UnprivilegedPipelineContent { script_port: IpcReceiver<ConstellationControlMsg>, opts: Opts, prefs: HashMap<String, PrefValue>, - pipeline_port: IpcReceiver<LayoutControlMsg>, pipeline_namespace_id: PipelineNamespaceId, webrender_api_sender: script_traits::WebrenderIpcSender, webrender_image_api_sender: net_traits::WebrenderIpcSender, @@ -516,29 +506,19 @@ pub struct UnprivilegedPipelineContent { } impl UnprivilegedPipelineContent { - pub fn start_all<Message, LTF, STF>( + pub fn start_all<STF: ScriptThreadFactory>( self, wait_for_completion: bool, + layout_factory: Arc<dyn LayoutFactory>, background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, - ) where - LTF: LayoutThreadFactory<Message = Message>, - STF: ScriptThreadFactory<Message = Message>, - { + ) { // Setup pipeline-namespace-installing for all threads in this process. // Idempotent in single-process mode. PipelineNamespace::set_installer_sender(self.namespace_request_sender); let image_cache = Arc::new(ImageCacheImpl::new(self.webrender_image_api_sender.clone())); - let paint_time_metrics = PaintTimeMetrics::new( - self.id, - self.time_profiler_chan.clone(), - self.layout_to_constellation_chan.clone(), - self.script_chan.clone(), - self.load_data.url.clone(), - ); let (content_process_shutdown_chan, content_process_shutdown_port) = unbounded(); - let layout_thread_busy_flag = Arc::new(AtomicBool::new(false)); - let layout_pair = STF::create( + STF::create( InitialScriptState { id: self.id, browsing_context_id: self.browsing_context_id, @@ -564,34 +544,15 @@ impl UnprivilegedPipelineContent { webxr_registry: self.webxr_registry, webrender_document: self.webrender_document, webrender_api_sender: self.webrender_api_sender.clone(), - layout_is_busy: layout_thread_busy_flag.clone(), player_context: self.player_context.clone(), inherited_secure_context: self.load_data.inherited_secure_context.clone(), }, + layout_factory, + self.font_cache_thread.clone(), self.load_data.clone(), self.user_agent, ); - LTF::create( - self.id, - self.top_level_browsing_context_id, - self.load_data.url, - self.parent_pipeline_id.is_some(), - layout_pair, - self.pipeline_port, - background_hang_monitor_register, - self.layout_to_constellation_chan, - self.script_chan, - image_cache, - self.font_cache_thread, - self.time_profiler_chan, - self.mem_profiler_chan, - self.webrender_api_sender, - paint_time_metrics, - layout_thread_busy_flag.clone(), - self.window_size, - ); - if wait_for_completion { match content_process_shutdown_port.recv() { Ok(()) => {}, diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs index 83290031644..a0de645a544 100644 --- a/components/gfx/platform/freetype/font.rs +++ b/components/gfx/platform/freetype/font.rs @@ -111,7 +111,7 @@ fn create_face( &mut face, ) } else { - // This will trigger a synchronous file read in the layout thread, which we may want to + // This will trigger a synchronous file read during layout, which we may want to // revisit at some point. See discussion here: // // https://github.com/servo/servo/pull/20506#issuecomment-378838800 diff --git a/components/layout/context.rs b/components/layout/context.rs index e0a4450b41f..eb25b5bd51d 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -2,7 +2,7 @@ * 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/. */ -//! Data needed by the layout thread. +//! Data needed by layout. use std::cell::{RefCell, RefMut}; use std::collections::HashMap; diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index f22a0edb9a0..6884d4cbe6b 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -488,7 +488,7 @@ impl ImageFragmentInfo { } /// A fragment that represents an inline frame (iframe). This stores the frame ID so that the -/// size of this iframe can be communicated via the constellation to the iframe's own layout thread. +/// size of this iframe can be communicated via the constellation to the iframe's own layout. #[derive(Clone)] pub struct IframeFragmentInfo { /// The frame ID of this iframe. None if there is no nested browsing context. diff --git a/components/layout/query.rs b/components/layout/query.rs index d55ef89a6fe..2bb03e2a5ca 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -2,7 +2,7 @@ * 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/. */ -//! Utilities for querying the layout, as needed by the layout thread. +//! Utilities for querying the layout, as needed by layout. use std::cmp::{max, min}; use std::ops::Deref; diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 61c2a46b4dd..30ae7ce1725 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -112,7 +112,7 @@ pub(crate) struct DisplayListBuilder<'a> { pub display_list: &'a mut DisplayList, /// A recording of the sizes of iframes encountered when building this - /// display list. This information is forwarded to the layout thread for the + /// display list. This information is forwarded to layout for the /// iframe so that its layout knows how large the initial containing block / /// viewport is. iframe_sizes: FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>, diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index 8ec2f0b4570..9c39abda413 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -2,7 +2,7 @@ * 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/. */ -//! Utilities for querying the layout, as needed by the layout thread. +//! Utilities for querying the layout, as needed by layout. use std::collections::HashMap; use std::sync::{Arc, Mutex}; diff --git a/components/layout_thread/Cargo.toml b/components/layout_thread/Cargo.toml index dbe3db3e248..4da4f2d785a 100644 --- a/components/layout_thread/Cargo.toml +++ b/components/layout_thread/Cargo.toml @@ -22,7 +22,6 @@ gfx_traits = { workspace = true } histogram = "0.6.8" ipc-channel = { workspace = true } layout = { path = "../layout", package = "layout_2013" } -layout_traits = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } malloc_size_of = { path = "../malloc_size_of" } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 01b66e39274..74be9d0cfd9 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -5,20 +5,18 @@ // 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 +//! Layout. Performs layout on the DOM, builds display lists and sends them to be //! painted. use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::process; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; -use std::time::Duration; -use std::{process, thread}; use app_units::Au; -use crossbeam_channel::{select, Receiver, Sender}; use embedder_traits::resources::{self, Resource}; use euclid::default::Size2D as UntypedSize2D; use euclid::{Point2D, Rect, Scale, Size2D}; @@ -28,7 +26,7 @@ use gfx::font_cache_thread::FontCacheThread; use gfx::{font, font_context}; use gfx_traits::{node_id_from_scroll_id, Epoch}; use histogram::Histogram; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use layout::construct::ConstructionResult; use layout::context::{ @@ -51,30 +49,25 @@ use layout::traversal::{ }; use layout::wrapper::LayoutNodeLayoutData; use layout::{layout_debug, layout_debug_scope, parallel, sequential, LayoutData}; -use layout_traits::LayoutThreadFactory; use lazy_static::lazy_static; use log::{debug, error, trace, warn}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric}; -use msg::constellation_msg::{ - BackgroundHangMonitor, BackgroundHangMonitorRegister, BrowsingContextId, HangAnnotation, - LayoutHangAnnotation, MonitoredComponentId, MonitoredComponentType, PipelineId, - TopLevelBrowsingContextId, -}; +use metrics::{PaintTimeMetrics, ProfilerMetadataFactory}; +use msg::constellation_msg::{BrowsingContextId, 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::mem::{Report, ReportKind, ReportsChan}; use profile_traits::path; use profile_traits::time::{ self as profile_time, profile, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, }; use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::message::{ - LayoutThreadInit, Msg, NodesFromPointQueryType, QueryMsg, Reflow, ReflowComplete, ReflowGoal, - ScriptReflow, + Msg, NodesFromPointQueryType, QueryMsg, Reflow, ReflowComplete, ReflowGoal, ScriptReflow, }; use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse, TextIndexResponse}; use script_layout_interface::wrapper_traits::LayoutNode; +use script_layout_interface::{Layout, LayoutConfig, LayoutFactory}; use script_traits::{ ConstellationControlMsg, DrawAPaintImageResult, IFrameSizeMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, PaintWorkletError, Painter, ScrollState, UntrustedNodeAddress, @@ -104,21 +97,17 @@ use style::stylesheets::{ UserAgentStylesheets, }; use style::stylist::Stylist; -use style::thread_state::{self, ThreadState}; use style::traversal::DomTraversal; use style::traversal_flags::TraversalFlags; use style_traits::{CSSPixel, DevicePixel, SpeculativePainter}; use url::Url; use webrender_api::{units, ColorF, HitTestFlags}; -/// Information needed by the layout thread. +/// Information needed by layout. 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, @@ -128,21 +117,9 @@ pub struct LayoutThread { /// 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<Msg>, - - /// The port on which we receive messages from the constellation. - pipeline_port: Receiver<LayoutControlMsg>, - - /// 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<dyn BackgroundHangMonitor>, - /// The channel on which messages can be sent to the constellation. constellation_chan: IpcSender<ConstellationMsg>, @@ -152,9 +129,6 @@ pub struct LayoutThread { /// 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<dyn ImageCache>, @@ -201,15 +175,12 @@ pub struct LayoutThread { /// Paint time metrics. paint_time_metrics: PaintTimeMetrics, - /// The time a layout query has waited before serviced by layout thread. + /// The time a layout query has waited before serviced by layout. layout_query_waiting_time: Histogram, /// The sizes of all iframes encountered during the last layout operation. last_iframe_sizes: RefCell<FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>>, - /// Flag that indicates if LayoutThread is busy handling a request. - busy: Arc<AtomicBool>, - /// Debug options, copied from configuration to this `LayoutThread` in order /// to avoid having to constantly access the thread-safe global options. debug: DebugOptions, @@ -218,81 +189,23 @@ pub struct LayoutThread { 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<Msg>, Receiver<Msg>), - pipeline_port: IpcReceiver<LayoutControlMsg>, - background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, - constellation_chan: IpcSender<ConstellationMsg>, - script_chan: IpcSender<ConstellationControlMsg>, - image_cache: Arc<dyn ImageCache>, - 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<AtomicBool>, - 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"); +pub struct LayoutFactoryImpl(); + +impl LayoutFactory for LayoutFactoryImpl { + fn create(&self, config: LayoutConfig) -> Box<dyn Layout> { + Box::new(LayoutThread::new( + config.id, + config.url, + config.is_iframe, + config.constellation_chan, + config.script_chan, + config.image_cache, + config.font_cache_thread, + config.time_profiler_chan, + config.webrender_api_sender, + config.paint_time_metrics, + config.window_size, + )) } } @@ -419,25 +332,49 @@ fn add_font_face_rules( } } +impl Layout for LayoutThread { + fn process(&mut self, msg: script_layout_interface::message::Msg) { + self.handle_request(Request::FromScript(msg)); + } + + fn handle_constellation_msg(&mut self, msg: script_traits::LayoutControlMsg) { + self.handle_request(Request::FromPipeline(msg)); + } + + fn handle_font_cache_msg(&mut self) { + self.handle_request(Request::FromFontCache); + } + + fn rpc(&self) -> Box<dyn script_layout_interface::rpc::LayoutRPC> { + Box::new(LayoutRPCImpl(self.rw_data.clone())) as Box<dyn LayoutRPC> + } + + fn waiting_for_web_fonts_to_load(&self) -> bool { + self.outstanding_web_fonts.load(Ordering::SeqCst) != 0 + } + + fn current_epoch(&self) -> Epoch { + self.epoch.get() + } +} +enum Request { + FromPipeline(LayoutControlMsg), + FromScript(Msg), + FromFontCache, +} + impl LayoutThread { - /// Creates a new `LayoutThread` structure. fn new( id: PipelineId, - top_level_browsing_context_id: TopLevelBrowsingContextId, url: ServoUrl, is_iframe: bool, - port: Receiver<Msg>, - pipeline_port: IpcReceiver<LayoutControlMsg>, - background_hang_monitor: Box<dyn BackgroundHangMonitor>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, image_cache: Arc<dyn ImageCache>, 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<AtomicBool>, window_size: WindowSizeData, ) -> LayoutThread { // Let webrender know about this pipeline by sending an empty display list. @@ -450,31 +387,28 @@ impl LayoutThread { 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. + // Ask the router to proxy IPC messages from the font cache thread to layout. 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); + let cloned_script_chan = script_chan.clone(); + ROUTER.add_route( + ipc_font_cache_receiver.to_opaque(), + Box::new(move |_message| { + let _ = + cloned_script_chan.send(ConstellationControlMsg::ForLayoutFromFontCache(id)); + }), + ); 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), @@ -507,25 +441,11 @@ impl LayoutThread { 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, @@ -563,77 +483,35 @@ impl LayoutThread { } } - 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 } + fn handle_request<'a, 'b>(&mut self, request: Request) { + 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, }; - 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) + match request { + Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => { + self.handle_request_helper(Msg::SetScrollStates(new_scroll_states), &mut 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) + self.handle_request_helper(Msg::ExitNow, &mut 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::FromScript(msg) => self.handle_request_helper(msg, &mut rw_data), Request::FromFontCache => { - let _rw_data = possibly_locked_rw_data.lock(); + let _rw_data = 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. @@ -641,9 +519,7 @@ impl LayoutThread { &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(); @@ -686,19 +562,6 @@ impl LayoutThread { 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 @@ -716,22 +579,12 @@ impl LayoutThread { }; 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>( @@ -770,49 +623,7 @@ impl LayoutThread { 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. + /// Shuts down layout now. 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 @@ -827,7 +638,6 @@ impl LayoutThread { ); self.root_flow.borrow_mut().take(); - self.background_hang_monitor.unregister(); } fn handle_add_stylesheet(&self, stylesheet: &Stylesheet, guard: &SharedRwLockReadGuard) { @@ -916,9 +726,8 @@ impl LayoutThread { ); } - /// 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. + /// Update the recorded iframe sizes of the contents of layout 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<BrowsingContextId, Size2D<f32, CSSPixel>>, @@ -1078,7 +887,7 @@ impl LayoutThread { ); } - /// The high-level routine that performs layout threads. + /// The high-level routine that performs layout. fn handle_reflow<'a, 'b>( &mut self, data: &mut ScriptReflowResult, diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml index df18d6e0322..2a1e510630c 100644 --- a/components/layout_thread_2020/Cargo.toml +++ b/components/layout_thread_2020/Cargo.toml @@ -21,7 +21,6 @@ gfx = { path = "../gfx" } gfx_traits = { workspace = true } ipc-channel = { workspace = true } layout = { path = "../layout_2020", package = "layout_2020" } -layout_traits = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } malloc_size_of = { path = "../malloc_size_of" } diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index cddb4ac04ed..9e7bb76f31d 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -5,19 +5,17 @@ // 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 +//! Layout. Performs layout on the DOM, builds display lists and sends them to be //! painted. use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::process; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; -use std::time::Duration; -use std::{process, thread}; use app_units::Au; -use crossbeam_channel::{select, Receiver, Sender}; use embedder_traits::resources::{self, Resource}; use euclid::default::Size2D as UntypedSize2D; use euclid::{Point2D, Rect, Scale, Size2D}; @@ -26,7 +24,7 @@ use fxhash::FxHashMap; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context; use gfx_traits::{node_id_from_scroll_id, Epoch}; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use layout::context::LayoutContext; use layout::display_list::{DisplayList, WebRenderImageInfo}; @@ -39,29 +37,24 @@ use layout::query::{ }; use layout::traversal::RecalcStyle; use layout::{layout_debug, BoxTree, FragmentTree}; -use layout_traits::LayoutThreadFactory; use lazy_static::lazy_static; use log::{debug, error, warn}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric}; -use msg::constellation_msg::{ - BackgroundHangMonitor, BackgroundHangMonitorRegister, BrowsingContextId, HangAnnotation, - LayoutHangAnnotation, MonitoredComponentId, MonitoredComponentType, PipelineId, - TopLevelBrowsingContextId, -}; +use metrics::{PaintTimeMetrics, ProfilerMetadataFactory}; +use msg::constellation_msg::{BrowsingContextId, 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::mem::{Report, ReportKind, ReportsChan}; use profile_traits::path; use profile_traits::time::{ self as profile_time, profile, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, }; use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::message::{ - LayoutThreadInit, Msg, NodesFromPointQueryType, QueryMsg, ReflowComplete, ReflowGoal, - ScriptReflow, + Msg, NodesFromPointQueryType, QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow, }; use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse, TextIndexResponse}; +use script_layout_interface::{Layout, LayoutConfig, LayoutFactory}; use script_traits::{ ConstellationControlMsg, DrawAPaintImageResult, IFrameSizeMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, PaintWorkletError, Painter, ScrollState, UntrustedNodeAddress, @@ -88,21 +81,17 @@ use style::stylesheets::{ UserAgentStylesheets, }; use style::stylist::Stylist; -use style::thread_state::{self, ThreadState}; use style::traversal::DomTraversal; use style::traversal_flags::TraversalFlags; use style_traits::{CSSPixel, DevicePixel, SpeculativePainter}; use url::Url; use webrender_api::{units, HitTestFlags}; -/// Information needed by the layout thread. +/// Information needed by layout. 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, @@ -112,21 +101,9 @@ pub struct LayoutThread { /// 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<Msg>, - - /// The port on which we receive messages from the constellation. - pipeline_port: Receiver<LayoutControlMsg>, - - /// 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<dyn BackgroundHangMonitor>, - /// The channel on which messages can be sent to the constellation. constellation_chan: IpcSender<ConstellationMsg>, @@ -136,9 +113,6 @@ pub struct LayoutThread { /// 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<dyn ImageCache>, @@ -188,89 +162,28 @@ pub struct LayoutThread { /// The sizes of all iframes encountered during the last layout operation. last_iframe_sizes: RefCell<FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>>, - /// Flag that indicates if LayoutThread is busy handling a request. - busy: Arc<AtomicBool>, - /// Debug options, copied from configuration to this `LayoutThread` in order /// to avoid having to constantly access the thread-safe global options. debug: DebugOptions, } -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<Msg>, Receiver<Msg>), - pipeline_port: IpcReceiver<LayoutControlMsg>, - background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, - constellation_chan: IpcSender<ConstellationMsg>, - script_chan: IpcSender<ConstellationControlMsg>, - image_cache: Arc<dyn ImageCache>, - 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<AtomicBool>, - 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"); +pub struct LayoutFactoryImpl(); + +impl LayoutFactory for LayoutFactoryImpl { + fn create(&self, config: LayoutConfig) -> Box<dyn Layout> { + Box::new(LayoutThread::new( + config.id, + config.url, + config.is_iframe, + config.constellation_chan, + config.script_chan, + config.image_cache, + config.font_cache_thread, + config.time_profiler_chan, + config.webrender_api_sender, + config.paint_time_metrics, + config.window_size, + )) } } @@ -396,26 +309,50 @@ fn add_font_face_rules( }) } } +impl Layout for LayoutThread { + fn process(&mut self, msg: script_layout_interface::message::Msg) { + self.handle_request(Request::FromScript(msg)); + } + + fn handle_constellation_msg(&mut self, msg: script_traits::LayoutControlMsg) { + self.handle_request(Request::FromPipeline(msg)); + } + + fn handle_font_cache_msg(&mut self) { + self.handle_request(Request::FromFontCache); + } + + fn rpc(&self) -> Box<dyn script_layout_interface::rpc::LayoutRPC> { + Box::new(LayoutRPCImpl(self.rw_data.clone())) as Box<dyn LayoutRPC> + } + + fn waiting_for_web_fonts_to_load(&self) -> bool { + self.outstanding_web_fonts.load(Ordering::SeqCst) != 0 + } + + fn current_epoch(&self) -> Epoch { + self.epoch.get() + } +} + +enum Request { + FromPipeline(LayoutControlMsg), + FromScript(Msg), + FromFontCache, +} impl LayoutThread { - /// Creates a new `LayoutThread` structure. fn new( id: PipelineId, - top_level_browsing_context_id: TopLevelBrowsingContextId, url: ServoUrl, is_iframe: bool, - port: Receiver<Msg>, - pipeline_port: IpcReceiver<LayoutControlMsg>, - background_hang_monitor: Box<dyn BackgroundHangMonitor>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, image_cache: Arc<dyn ImageCache>, 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<AtomicBool>, window_size: WindowSizeData, ) -> LayoutThread { // Let webrender know about this pipeline by sending an empty display list. @@ -430,31 +367,28 @@ impl LayoutThread { 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. + // Ask the router to proxy IPC messages from the font cache thread to layout. 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); + let cloned_script_chan = script_chan.clone(); + ROUTER.add_route( + ipc_font_cache_receiver.to_opaque(), + Box::new(move |_message| { + let _ = + cloned_script_chan.send(ConstellationControlMsg::ForLayoutFromFontCache(id)); + }), + ); LayoutThread { id, - top_level_browsing_context_id: top_level_browsing_context_id, url, is_iframe, - port, - pipeline_port: pipeline_receiver, constellation_chan, script_chan: script_chan.clone(), - background_hang_monitor, 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, generation: Cell::new(0), outstanding_web_fonts: Arc::new(AtomicUsize::new(0)), @@ -484,24 +418,10 @@ impl LayoutThread { webrender_image_cache: Default::default(), paint_time_metrics: paint_time_metrics, last_iframe_sizes: Default::default(), - busy, debug: opts::get().debug.clone(), } } - /// 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, @@ -540,77 +460,35 @@ impl LayoutThread { } } - 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 } + fn handle_request<'a, 'b>(&mut self, request: Request) { + 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, }; - 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) + match request { + Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => { + self.handle_request_helper(Msg::SetScrollStates(new_scroll_states), &mut 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) + self.handle_request_helper(Msg::ExitNow, &mut 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::FromScript(msg) => self.handle_request_helper(msg, &mut rw_data), Request::FromFontCache => { - let _rw_data = possibly_locked_rw_data.lock(); + let _rw_data = 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. @@ -618,9 +496,7 @@ impl LayoutThread { &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(); @@ -663,36 +539,10 @@ impl LayoutThread { 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, _properties, _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); - }, + Msg::ExitNow => {}, } - - true } fn collect_reports<'a, 'b>( @@ -724,51 +574,6 @@ impl LayoutThread { 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.clone(), - info.image_cache.clone(), - 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`"), - } - } - } - - fn exit_now(&mut self) { - 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. @@ -790,7 +595,7 @@ impl LayoutThread { self.stylist.set_quirks_mode(quirks_mode); } - /// The high-level routine that performs layout threads. + /// The high-level routine that performs layout. fn handle_reflow<'a, 'b>( &mut self, data: &mut ScriptReflowResult, @@ -1333,9 +1138,8 @@ impl LayoutThread { } } - /// 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. + /// Update the recorded iframe sizes of the contents of layout 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<BrowsingContextId, Size2D<f32, CSSPixel>>, diff --git a/components/metrics/lib.rs b/components/metrics/lib.rs index f3ea4b26c51..971ad729441 100644 --- a/components/metrics/lib.rs +++ b/components/metrics/lib.rs @@ -261,7 +261,7 @@ impl ProgressiveWebMetric for InteractiveMetrics { // https://w3c.github.io/paint-timing/ pub struct PaintTimeMetrics { pending_metrics: RefCell<HashMap<Epoch, (Option<TimerMetadata>, bool)>>, - navigation_start: Option<u64>, + navigation_start: u64, first_paint: Cell<Option<u64>>, first_contentful_paint: Cell<Option<u64>>, pipeline_id: PipelineId, @@ -278,10 +278,11 @@ impl PaintTimeMetrics { constellation_chan: IpcSender<LayoutMsg>, script_chan: IpcSender<ConstellationControlMsg>, url: ServoUrl, + navigation_start: u64, ) -> PaintTimeMetrics { PaintTimeMetrics { pending_metrics: RefCell::new(HashMap::new()), - navigation_start: None, + navigation_start, first_paint: Cell::new(None), first_contentful_paint: Cell::new(None), pipeline_id, @@ -342,11 +343,8 @@ impl PaintTimeMetrics { } pub fn maybe_set_metric(&self, epoch: Epoch, paint_time: u64) { - if self.first_paint.get().is_some() && self.first_contentful_paint.get().is_some() || - self.navigation_start.is_none() - { - // If we already set all paint metrics or we have not set navigation start yet, - // we just bail out. + if self.first_paint.get().is_some() && self.first_contentful_paint.get().is_some() { + // If we already set all paint metrics we just bail out. return; } @@ -387,11 +385,11 @@ impl PaintTimeMetrics { impl ProgressiveWebMetric for PaintTimeMetrics { fn get_navigation_start(&self) -> Option<u64> { - self.navigation_start + Some(self.navigation_start) } fn set_navigation_start(&mut self, time: u64) { - self.navigation_start = Some(time); + self.navigation_start = time; } fn send_queued_constellation_msg(&self, name: ProgressiveWebMetricType, time: u64) { diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index 684b57c2e20..6a25aad9818 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -29,7 +29,7 @@ use webrender_api::{ImageData, ImageDescriptor, ImageDescriptorFlags, ImageForma /// TODO(gw): Remaining work on image cache: /// * Make use of the prefetch support in various parts of the code. /// * Profile time in GetImageIfAvailable - might be worth caching these -/// results per paint / layout thread. +/// results per paint / layout. /// /// MAYBE(Yoric): /// * For faster lookups, it might be useful to store the LoadKey in the diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 31e6c6b466a..cc29865bd69 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -53,6 +53,7 @@ euclid = { workspace = true } fnv = { workspace = true } fxhash = { workspace = true } gfx_traits = { workspace = true } +gfx = { path = "../gfx" } headers = { workspace = true } html5ever = { workspace = true } http = { workspace = true } diff --git a/components/script/dom/bindings/cell.rs b/components/script/dom/bindings/cell.rs index 124240e311e..a9fb9b9b9d3 100644 --- a/components/script/dom/bindings/cell.rs +++ b/components/script/dom/bindings/cell.rs @@ -30,7 +30,7 @@ pub struct DomRefCell<T> { impl<T> DomRefCell<T> { /// Return a reference to the contents. /// - /// For use in the layout thread only. + /// For use in layout only. #[allow(unsafe_code)] pub unsafe fn borrow_for_layout(&self) -> &T { assert_in_layout(); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 7de5d650bdd..512c3870b1f 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -320,7 +320,7 @@ pub struct Document { /// This field is set to the document itself for inert documents. /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document> appropriate_template_contents_owner_document: MutNullableDom<Document>, - /// Information on elements needing restyle to ship over to the layout thread when the + /// Information on elements needing restyle to ship over to layout when the /// time comes. pending_restyles: DomRefCell<HashMap<Dom<Element>, NoTrace<PendingRestyle>>>, /// This flag will be true if layout suppressed a reflow attempt that was @@ -367,7 +367,7 @@ pub struct Document { spurious_animation_frames: Cell<u8>, /// Track the total number of elements in this DOM's tree. - /// This is sent to the layout thread every time a reflow is done; + /// This is sent to layout every time a reflow is done; /// layout uses this to determine if the gains from parallel layout will be worth the overhead. /// /// See also: <https://github.com/servo/servo/issues/10110> @@ -827,10 +827,9 @@ impl Document { let old_mode = self.quirks_mode.replace(new_mode); if old_mode != new_mode { - match self.window.layout_chan() { - Some(chan) => chan.send(Msg::SetQuirksMode(new_mode)).unwrap(), - None => warn!("Layout channel unavailable"), - } + let _ = self + .window + .with_layout(move |layout| layout.process(Msg::SetQuirksMode(new_mode))); } } @@ -2092,7 +2091,7 @@ impl Document { }, LoadType::PageSource(_) => { if self.has_browsing_context && self.is_fully_active() { - // Note: if the document is not fully active, the layout thread will have exited already. + // Note: if the document is not fully active, layout will have exited already. // The underlying problem might actually be that layout exits while it should be kept alive. // See https://github.com/servo/servo/issues/22507 @@ -2264,7 +2263,7 @@ impl Document { None => false, }; - // Note: if the document is not fully active, the layout thread will have exited already, + // Note: if the document is not fully active, layout will have exited already, // and this method will panic. // The underlying problem might actually be that layout exits while it should be kept alive. // See https://github.com/servo/servo/issues/22507 @@ -3441,12 +3440,11 @@ impl Document { /// Flushes the stylesheet list, and returns whether any stylesheet changed. pub fn flush_stylesheets_for_reflow(&self) -> bool { // NOTE(emilio): The invalidation machinery is used on the replicated - // list on the layout thread. + // list in layout. // // FIXME(emilio): This really should differentiate between CSSOM changes // and normal stylesheets additions / removals, because in the last case - // the layout thread already has that information and we could avoid - // dirtying the whole thing. + // layout already has that information and we could avoid dirtying the whole thing. let mut stylesheets = self.stylesheets.borrow_mut(); let have_changed = stylesheets.has_changed(); stylesheets.flush_without_invalidation(); @@ -3830,15 +3828,14 @@ impl Document { }) .cloned(); - match self.window.layout_chan() { - Some(chan) => chan - .send(Msg::AddStylesheet( - sheet.clone(), - insertion_point.as_ref().map(|s| s.sheet.clone()), - )) - .unwrap(), - None => return warn!("Layout channel unavailable"), - } + let cloned_stylesheet = sheet.clone(); + let insertion_point2 = insertion_point.clone(); + let _ = self.window.with_layout(move |layout| { + layout.process(Msg::AddStylesheet( + cloned_stylesheet, + insertion_point2.as_ref().map(|s| s.sheet.clone()), + )); + }); DocumentOrShadowRoot::add_stylesheet( owner, @@ -3851,15 +3848,15 @@ impl Document { /// Remove a stylesheet owned by `owner` from the list of document sheets. #[allow(crown::unrooted_must_root)] // Owner needs to be rooted already necessarily. - pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { - match self.window.layout_chan() { - Some(chan) => chan.send(Msg::RemoveStylesheet(s.clone())).unwrap(), - None => return warn!("Layout channel unavailable"), - } + pub fn remove_stylesheet(&self, owner: &Element, stylesheet: &Arc<Stylesheet>) { + let cloned_stylesheet = stylesheet.clone(); + let _ = self + .window + .with_layout(|layout| layout.process(Msg::RemoveStylesheet(cloned_stylesheet))); DocumentOrShadowRoot::remove_stylesheet( owner, - s, + stylesheet, StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()), ) } diff --git a/components/script/dom/documentorshadowroot.rs b/components/script/dom/documentorshadowroot.rs index 9bbe435359b..27b6734d5d7 100644 --- a/components/script/dom/documentorshadowroot.rs +++ b/components/script/dom/documentorshadowroot.rs @@ -93,7 +93,7 @@ impl DocumentOrShadowRoot { return vec![]; }; - self.window.layout().nodes_from_point_response() + self.window.layout_rpc().nodes_from_point_response() } #[allow(unsafe_code)] diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 7d546f154fb..42f3a575d4d 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -457,7 +457,7 @@ impl HTMLElementMethods for HTMLElement { window.layout_reflow(QueryMsg::ElementInnerTextQuery( node.to_trusted_node_address(), )); - DOMString::from(window.layout().element_inner_text()) + DOMString::from(window.layout_rpc().element_inner_text()) } // https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index f973ddc73f8..d082e09d287 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -7,7 +7,6 @@ use std::cell::Cell; use bitflags::bitflags; use dom_struct::dom_struct; use html5ever::{local_name, namespace_url, ns, LocalName, Prefix}; -use ipc_channel::ipc; use js::rust::HandleObject; use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; use profile_traits::ipc as ProfiledIpc; @@ -200,8 +199,6 @@ impl HTMLIFrameElement { match pipeline_type { PipelineType::InitialAboutBlank => { - let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); - self.about_blank_pipeline_id.set(Some(new_pipeline_id)); let load_info = IFrameLoadInfoWithData { @@ -213,7 +210,7 @@ impl HTMLIFrameElement { }; global_scope .script_to_constellation_chan() - .send(ScriptMsg::ScriptNewIFrame(load_info, pipeline_sender)) + .send(ScriptMsg::ScriptNewIFrame(load_info)) .unwrap(); let new_layout_info = NewLayoutInfo { @@ -223,7 +220,6 @@ impl HTMLIFrameElement { top_level_browsing_context_id: top_level_browsing_context_id, opener: None, load_data: load_data, - pipeline_port: pipeline_receiver, window_size, }; diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 681f8e0ef4b..8d7561114ff 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -197,7 +197,7 @@ //! Layout code can access the DOM through the //! [`LayoutDom`](bindings/root/struct.LayoutDom.html) smart pointer. This does not //! keep the DOM object alive; we ensure that no DOM code (Garbage Collection -//! in particular) runs while the layout thread is accessing the DOM. +//! in particular) runs while layout is accessing the DOM. //! //! Methods accessible to layout are implemented on `LayoutDom<Foo>` using //! `LayoutFooHelpers` traits. diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index cbdb3412cc4..e44d62f7778 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -10,7 +10,7 @@ use std::default::Default; use std::io::{stderr, stdout, Write}; use std::ptr::NonNull; use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use std::{cmp, env, mem}; @@ -26,7 +26,7 @@ use dom_struct::dom_struct; use embedder_traits::{EmbedderMsg, PromptDefinition, PromptOrigin, PromptResult}; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; -use ipc_channel::ipc::IpcSender; +use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use js::conversions::ToJSValConvertible; use js::jsapi::{GCReason, Heap, JSAutoRealm, JSObject, StackFormat, JSPROP_ENUMERATE, JS_GC}; @@ -47,13 +47,13 @@ use num_traits::ToPrimitive; use parking_lot::Mutex as ParkMutex; use profile_traits::ipc as ProfiledIpc; use profile_traits::mem::ProfilerChan as MemProfilerChan; -use profile_traits::time::{ProfilerChan as TimeProfilerChan, ProfilerMsg}; +use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_layout_interface::message::{Msg, QueryMsg, Reflow, ReflowGoal, ScriptReflow}; use script_layout_interface::rpc::{ ContentBoxResponse, ContentBoxesResponse, LayoutRPC, NodeScrollIdResponse, ResolvedStyleResponse, TextIndexResponse, }; -use script_layout_interface::{PendingImageState, TrustedNodeAddress}; +use script_layout_interface::{Layout, PendingImageState, TrustedNodeAddress}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; use script_traits::{ ConstellationControlMsg, DocumentState, HistoryEntryReplacement, LoadData, ScriptMsg, @@ -237,19 +237,6 @@ pub struct Window { #[ignore_malloc_size_of = "Rc<T> is hard"] js_runtime: DomRefCell<Option<Rc<Runtime>>>, - /// A handle for communicating messages to the layout thread. - /// - /// This channel shouldn't be accessed directly, but through `Window::layout_chan()`, - /// which returns `None` if there's no layout thread anymore. - #[ignore_malloc_size_of = "channels are hard"] - #[no_trace] - layout_chan: Sender<Msg>, - - /// A handle to perform RPC calls into the layout, quickly. - #[ignore_malloc_size_of = "trait objects are hard"] - #[no_trace] - layout_rpc: Box<dyn LayoutRPC + Send + 'static>, - /// The current size of the window, in pixels. #[no_trace] window_size: Cell<WindowSizeData>, @@ -339,10 +326,6 @@ pub struct Window { /// It is used to avoid sending idle message more than once, which is unneccessary. has_sent_idle_message: Cell<bool>, - /// Flag that indicates if the layout thread is busy handling a request. - #[ignore_malloc_size_of = "Arc<T> is hard"] - layout_is_busy: Arc<AtomicBool>, - /// Emits notifications when there is a relayout. relayout_event: bool, @@ -519,8 +502,8 @@ impl Window { } pub fn pending_image_notification(&self, response: PendingImageResponse) { - //XXXjdm could be more efficient to send the responses to the layout thread, - // rather than making the layout thread talk to the image cache to + //XXXjdm could be more efficient to send the responses to layout, + // rather than making layout talk to the image cache to // obtain the same data. let mut images = self.pending_layout_images.borrow_mut(); let nodes = images.entry(response.id); @@ -1676,7 +1659,7 @@ impl Window { // objects removed from the tree that haven't been collected // yet). There should not be any such DOM nodes with layout // data, but if there are, then when they are dropped, they - // will attempt to send a message to the closed layout thread. + // will attempt to send a message to layout. // This causes memory safety issues, because the DOM node uses // the layout channel from its window, and the window has // already been GC'd. For nodes which do not have a live @@ -1803,18 +1786,14 @@ impl Window { ScriptThread::handle_tick_all_animations_for_testing(pipeline_id); } - /// Reflows the page unconditionally if possible and not suppressed. This - /// method will wait for the layout thread to complete (but see the `TODO` - /// below). If there is no window size yet, the page is presumed invisible - /// and no reflow is performed. If reflow is suppressed, no reflow will be - /// performed for ForDisplay goals. - /// - /// TODO(pcwalton): Only wait for style recalc, since we have - /// off-main-thread layout. + /// Reflows the page unconditionally if possible and not suppressed. This method will wait for + /// the layout to complete. If there is no window size yet, the page is presumed invisible and + /// no reflow is performed. If reflow is suppressed, no reflow will be performed for ForDisplay + /// goals. /// /// Returns true if layout actually happened, false otherwise. #[allow(unsafe_code)] - pub fn force_reflow( + pub(crate) fn force_reflow( &self, reflow_goal: ReflowGoal, reason: ReflowReason, @@ -1905,14 +1884,7 @@ impl Window { animations: document.animations().sets.clone(), }; - match self.layout_chan() { - Some(layout_chan) => layout_chan - .send(Msg::Reflow(reflow)) - .expect("Layout thread disconnected"), - None => return false, - }; - - debug!("script: layout forked"); + let _ = self.with_layout(move |layout| layout.process(Msg::Reflow(reflow))); let complete = match join_port.try_recv() { Err(TryRecvError::Empty) => { @@ -1921,11 +1893,11 @@ impl Window { }, Ok(reflow_complete) => reflow_complete, Err(TryRecvError::Disconnected) => { - panic!("Layout thread failed while script was waiting for a result."); + panic!("Layout failed while script was waiting for a result."); }, }; - debug!("script: layout joined"); + debug!("script: layout complete"); // Pending reflows require display, so only reset the pending reflow count if this reflow // was to be displayed. @@ -1968,17 +1940,12 @@ impl Window { } document.update_animations_post_reflow(); + self.update_constellation_epoch(); true } - /// Reflows the page if it's possible to do so and the page is dirty. This - /// method will wait for the layout thread to complete (but see the `TODO` - /// below). If there is no window size yet, the page is presumed invisible - /// and no reflow is performed. - /// - /// TODO(pcwalton): Only wait for style recalc, since we have - /// off-main-thread layout. + /// Reflows the page if it's possible to do so and the page is dirty. /// /// Returns true if layout actually happened, false otherwise. /// This return value is useful for script queries, that wait for a lock @@ -2032,12 +1999,24 @@ impl Window { elem.has_class(&atom!("reftest-wait"), CaseSensitivity::CaseSensitive) }); + let pending_web_fonts = self + .with_layout(move |layout| layout.waiting_for_web_fonts_to_load()) + .unwrap(); + let has_sent_idle_message = self.has_sent_idle_message.get(); let is_ready_state_complete = document.ReadyState() == DocumentReadyState::Complete; - let pending_images = self.pending_layout_images.borrow().is_empty(); + let pending_images = !self.pending_layout_images.borrow().is_empty(); - if !has_sent_idle_message && is_ready_state_complete && !reftest_wait && pending_images + if !has_sent_idle_message && + is_ready_state_complete && + !reftest_wait && + !pending_images && + !pending_web_fonts { + debug!( + "{:?}: Sending DocumentState::Idle to Constellation", + self.pipeline_id() + ); let event = ScriptMsg::SetDocumentState(DocumentState::Idle); self.send_to_constellation(event); self.has_sent_idle_message.set(true); @@ -2047,13 +2026,28 @@ impl Window { issued_reflow } - pub fn layout_reflow(&self, query_msg: QueryMsg) -> bool { - if self.layout_is_busy.load(Ordering::Relaxed) { - let url = self.get_url().into_string(); - self.time_profiler_chan() - .send(ProfilerMsg::BlockedLayoutQuery(url)); + /// If writing a screenshot, synchronously update the layout epoch that it set + /// in the constellation. + pub(crate) fn update_constellation_epoch(&self) { + if !self.prepare_for_screenshot { + return; } + let epoch = self + .with_layout(move |layout| layout.current_epoch()) + .unwrap(); + + debug!( + "{:?}: Updating constellation epoch: {epoch:?}", + self.pipeline_id() + ); + let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); + let event = ScriptMsg::SetLayoutEpoch(epoch, sender); + self.send_to_constellation(event); + receiver.recv().unwrap(); + } + + pub fn layout_reflow(&self, query_msg: QueryMsg) -> bool { self.reflow( ReflowGoal::LayoutQuery(query_msg, time::precise_time_ns()), ReflowReason::Query, @@ -2069,18 +2063,18 @@ impl Window { )) { return None; } - self.layout_rpc.resolved_font_style() + self.layout_rpc().resolved_font_style() } - pub fn layout(&self) -> &dyn LayoutRPC { - &*self.layout_rpc + pub fn layout_rpc(&self) -> Box<dyn LayoutRPC> { + self.with_layout(|layout| layout.rpc()).unwrap() } pub fn content_box_query(&self, node: &Node) -> Option<UntypedRect<Au>> { if !self.layout_reflow(QueryMsg::ContentBoxQuery(node.to_opaque())) { return None; } - let ContentBoxResponse(rect) = self.layout_rpc.content_box(); + let ContentBoxResponse(rect) = self.layout_rpc().content_box(); rect } @@ -2088,7 +2082,7 @@ impl Window { if !self.layout_reflow(QueryMsg::ContentBoxesQuery(node.to_opaque())) { return vec![]; } - let ContentBoxesResponse(rects) = self.layout_rpc.content_boxes(); + let ContentBoxesResponse(rects) = self.layout_rpc().content_boxes(); rects } @@ -2096,7 +2090,7 @@ impl Window { if !self.layout_reflow(QueryMsg::ClientRectQuery(node.to_opaque())) { return Rect::zero(); } - self.layout_rpc.node_geometry().client_rect + self.layout_rpc().node_geometry().client_rect } /// Find the scroll area of the given node, if it is not None. If the node @@ -2106,7 +2100,7 @@ impl Window { if !self.layout_reflow(QueryMsg::ScrollingAreaQuery(opaque)) { return Rect::zero(); } - self.layout_rpc.scrolling_area().client_rect + self.layout_rpc().scrolling_area().client_rect } pub fn scroll_offset_query(&self, node: &Node) -> Vector2D<f32, LayoutPixel> { @@ -2129,7 +2123,7 @@ impl Window { .borrow_mut() .insert(node.to_opaque(), Vector2D::new(x_ as f32, y_ as f32)); - let NodeScrollIdResponse(scroll_id) = self.layout_rpc.node_scroll_id(); + let NodeScrollIdResponse(scroll_id) = self.layout_rpc().node_scroll_id(); // Step 12 self.perform_a_scroll( @@ -2150,7 +2144,7 @@ impl Window { if !self.layout_reflow(QueryMsg::ResolvedStyleQuery(element, pseudo, property)) { return DOMString::new(); } - let ResolvedStyleResponse(resolved) = self.layout_rpc.resolved_style(); + let ResolvedStyleResponse(resolved) = self.layout_rpc().resolved_style(); DOMString::from(resolved) } @@ -2161,7 +2155,7 @@ impl Window { if !self.layout_reflow(QueryMsg::InnerWindowDimensionsQuery(browsing_context)) { return None; } - self.layout_rpc.inner_window_dimensions() + self.layout_rpc().inner_window_dimensions() } #[allow(unsafe_code)] @@ -2172,7 +2166,7 @@ impl Window { // FIXME(nox): Layout can reply with a garbage value which doesn't // actually correspond to an element, that's unsound. - let response = self.layout_rpc.offset_parent(); + let response = self.layout_rpc().offset_parent(); let element = response.node_address.and_then(|parent_node_address| { let node = unsafe { from_untrusted_node_address(parent_node_address) }; DomRoot::downcast(node) @@ -2188,7 +2182,7 @@ impl Window { if !self.layout_reflow(QueryMsg::TextIndexQuery(node.to_opaque(), point_in_node)) { return TextIndexResponse(None); } - self.layout_rpc.text_index() + self.layout_rpc().text_index() } #[allow(unsafe_code)] @@ -2313,12 +2307,8 @@ impl Window { self.Document().url() } - pub fn layout_chan(&self) -> Option<&Sender<Msg>> { - if self.is_alive() { - Some(&self.layout_chan) - } else { - None - } + pub fn with_layout<'a, T>(&self, call: impl FnOnce(&mut dyn Layout) -> T) -> Result<T, ()> { + ScriptThread::with_layout(self.pipeline_id(), call) } pub fn windowproxy_handler(&self) -> WindowProxyHandler { @@ -2539,7 +2529,6 @@ impl Window { constellation_chan: ScriptToConstellationChan, control_chan: IpcSender<ConstellationControlMsg>, scheduler_chan: IpcSender<TimerSchedulerMsg>, - layout_chan: Sender<Msg>, pipelineid: PipelineId, parent_info: Option<PipelineId>, window_size: WindowSizeData, @@ -2552,7 +2541,6 @@ impl Window { microtask_queue: Rc<MicrotaskQueue>, webrender_document: DocumentId, webrender_api_sender: WebrenderIpcSender, - layout_is_busy: Arc<AtomicBool>, relayout_event: bool, prepare_for_screenshot: bool, unminify_js: bool, @@ -2565,11 +2553,6 @@ impl Window { gpu_id_hub: Arc<ParkMutex<Identities>>, inherited_secure_context: Option<bool>, ) -> DomRoot<Self> { - let layout_rpc: Box<dyn LayoutRPC + Send> = { - let (rpc_send, rpc_recv) = unbounded(); - layout_chan.send(Msg::GetRPC(rpc_send)).unwrap(); - rpc_recv.recv().unwrap() - }; let error_reporter = CSSErrorReporter { pipelineid, script_chan: Arc::new(Mutex::new(control_chan)), @@ -2615,8 +2598,6 @@ impl Window { bluetooth_extra_permission_data: BluetoothExtraPermissionData::new(), page_clip_rect: Cell::new(MaxRect::max_rect()), resize_event: Default::default(), - layout_chan, - layout_rpc, window_size: Cell::new(window_size), current_viewport: Cell::new(Rect::zero()), suppress_reflow: Cell::new(true), @@ -2640,7 +2621,6 @@ impl Window { exists_mut_observer: Cell::new(false), webrender_api_sender, has_sent_idle_message: Cell::new(false), - layout_is_busy, relayout_event, prepare_for_screenshot, unminify_js, diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 886d63dc17c..b0a40118e1f 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -323,7 +323,6 @@ impl WindowProxy { new_pipeline_id: new_pipeline_id, }; - let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); let new_layout_info = NewLayoutInfo { parent_info: None, new_pipeline_id: new_pipeline_id, @@ -331,10 +330,9 @@ impl WindowProxy { top_level_browsing_context_id: new_top_level_browsing_context_id, opener: Some(self.browsing_context_id), load_data: load_data, - pipeline_port: pipeline_receiver, window_size: window.window_size(), }; - let constellation_msg = ScriptMsg::ScriptNewAuxiliary(load_info, pipeline_sender); + let constellation_msg = ScriptMsg::ScriptNewAuxiliary(load_info); window.send_to_constellation(constellation_msg); ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); let msg = EmbedderMsg::WebViewOpened(new_top_level_browsing_context_id); diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs index b039fc12cd0..f96b41e17cc 100644 --- a/components/script/dom/worklet.rs +++ b/components/script/dom/worklet.rs @@ -234,7 +234,7 @@ impl PendingTasksStruct { /// when a worklet adds a module. It is dropped when the script thread /// is dropped, and asks each of the worklet threads to quit. /// -/// The layout thread can end up blocking on the primary worklet thread +/// Layout can end up blocking on the primary worklet thread /// (e.g. when invoking a paint callback), so it is important to avoid /// deadlock by making sure the primary worklet thread doesn't end up /// blocking waiting on layout. In particular, since the constellation diff --git a/components/script/layout_image.rs b/components/script/layout_image.rs index a7e1d6fa06c..c9fed246773 100644 --- a/components/script/layout_image.rs +++ b/components/script/layout_image.rs @@ -2,10 +2,9 @@ * 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/. */ -//! Infrastructure to initiate network requests for images needed by the layout -//! thread. The script thread needs to be responsible for them because there's -//! no guarantee that the responsible nodes will still exist in the future if the -//! layout thread holds on to them during asynchronous operations. +//! Infrastructure to initiate network requests for images needed by layout. The script thread needs +//! to be responsible for them because there's no guarantee that the responsible nodes will still +//! exist in the future if layout holds on to them during asynchronous operations. use std::sync::{Arc, Mutex}; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 2ac9b1f05f5..06dd6613ab2 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -2,8 +2,8 @@ * 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/. */ -//! The script thread is the thread that owns the DOM in memory, runs JavaScript, and spawns parsing -//! and layout threads. It's in charge of processing events for all same-origin pages in a frame +//! The script thread is the thread that owns the DOM in memory, runs JavaScript, and triggers +//! layout. It's in charge of processing events for all same-origin pages in a frame //! tree, and manages the entire lifetime of pages in the frame tree from initial request to //! teardown. //! @@ -41,6 +41,7 @@ use devtools_traits::{ use embedder_traits::EmbedderMsg; use euclid::default::{Point2D, Rect}; use euclid::Vector2D; +use gfx::font_cache_thread::FontCacheThread; use headers::{HeaderMapExt, LastModified, ReferrerPolicy as ReferrerPolicyHeader}; use html5ever::{local_name, namespace_url, ns}; use hyper_serde::Serde; @@ -56,10 +57,9 @@ use media::WindowGLContext; use metrics::{PaintTimeMetrics, MAX_TASK_NS}; use mime::{self, Mime}; use msg::constellation_msg::{ - BackgroundHangMonitor, BackgroundHangMonitorExitSignal, BackgroundHangMonitorRegister, - BrowsingContextId, HangAnnotation, HistoryStateId, MonitoredComponentId, - MonitoredComponentType, PipelineId, PipelineNamespace, ScriptHangAnnotation, - TopLevelBrowsingContextId, + BackgroundHangMonitor, BackgroundHangMonitorExitSignal, BrowsingContextId, HangAnnotation, + HistoryStateId, MonitoredComponentId, MonitoredComponentType, PipelineId, PipelineNamespace, + ScriptHangAnnotation, TopLevelBrowsingContextId, }; use net_traits::image_cache::{ImageCache, PendingImageResponse}; use net_traits::request::{CredentialsMode, Destination, RedirectMode, RequestBuilder}; @@ -72,7 +72,8 @@ use parking_lot::Mutex; use percent_encoding::percent_decode; use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan}; use profile_traits::time::{self as profile_time, profile, ProfilerCategory}; -use script_layout_interface::message::{self, LayoutThreadInit, Msg, ReflowGoal}; +use script_layout_interface::message::{Msg, ReflowGoal}; +use script_layout_interface::{Layout, LayoutConfig, LayoutFactory, ScriptThreadFactory}; use script_traits::webdriver_msg::WebDriverScriptCommand; use script_traits::CompositorEvent::{ CompositionEvent, GamepadEvent, IMEDismissedEvent, KeyboardEvent, MouseButtonEvent, @@ -81,8 +82,8 @@ use script_traits::CompositorEvent::{ use script_traits::{ AnimationTickType, CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity, EventResult, HistoryEntryReplacement, InitialScriptState, JsEvalResult, - LayoutMsg, LoadData, LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType, - NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory, + LayoutControlMsg, LayoutMsg, LoadData, LoadOrigin, MediaSessionActionType, MouseButton, + MouseEventType, NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptToConstellationChan, StructuredSerializedData, TimerSchedulerMsg, TouchEventType, TouchId, UntrustedNodeAddress, UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, WindowSizeData, WindowSizeType, @@ -202,9 +203,6 @@ struct InProgressLoad { /// The current window size associated with this pipeline. #[no_trace] window_size: WindowSizeData, - /// Channel to the layout thread associated with this pipeline. - #[no_trace] - layout_chan: Sender<message::Msg>, /// The activity level of the document (inactive, active or fully active). #[no_trace] activity: DocumentActivity, @@ -222,8 +220,6 @@ struct InProgressLoad { navigation_start_precise: u64, /// For cancelling the fetch canceller: FetchCanceller, - /// Flag for sharing with the layout thread that is not yet created. - layout_is_busy: Arc<AtomicBool>, /// If inheriting the security context inherited_secure_context: Option<bool>, } @@ -236,11 +232,9 @@ impl InProgressLoad { top_level_browsing_context_id: TopLevelBrowsingContextId, parent_info: Option<PipelineId>, opener: Option<BrowsingContextId>, - layout_chan: Sender<message::Msg>, window_size: WindowSizeData, url: ServoUrl, origin: MutableOrigin, - layout_is_busy: Arc<AtomicBool>, inherited_secure_context: Option<bool>, ) -> InProgressLoad { let duration = SystemTime::now() @@ -248,18 +242,12 @@ impl InProgressLoad { .unwrap_or_default(); let navigation_start = duration.as_millis(); let navigation_start_precise = precise_time_ns(); - layout_chan - .send(message::Msg::SetNavigationStart( - navigation_start_precise as u64, - )) - .unwrap(); InProgressLoad { pipeline_id: id, browsing_context_id: browsing_context_id, top_level_browsing_context_id: top_level_browsing_context_id, parent_info: parent_info, opener: opener, - layout_chan: layout_chan, window_size: window_size, activity: DocumentActivity::FullyActive, is_visible: true, @@ -268,7 +256,6 @@ impl InProgressLoad { navigation_start: navigation_start as u64, navigation_start_precise: navigation_start_precise, canceller: Default::default(), - layout_is_busy: layout_is_busy, inherited_secure_context: inherited_secure_context, } } @@ -554,9 +541,6 @@ pub struct ScriptThread { /// A queue of tasks to be executed in this script-thread. task_queue: TaskQueue<MainThreadScriptMsg>, - /// A handle to register associated layout threads for hang-monitoring. - #[no_trace] - background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, /// The dedicated means of communication with the background-hang-monitor for this script-thread. #[no_trace] background_hang_monitor: Box<dyn BackgroundHangMonitor>, @@ -596,7 +580,7 @@ pub struct ScriptThread { #[no_trace] control_chan: IpcSender<ConstellationControlMsg>, - /// The port on which the constellation and layout threads can communicate with the + /// The port on which the constellation and layout can communicate with the /// script thread. #[no_trace] control_port: Receiver<ConstellationControlMsg>, @@ -605,10 +589,14 @@ pub struct ScriptThread { #[no_trace] script_sender: IpcSender<(PipelineId, ScriptMsg)>, - /// A sender for new layout threads to communicate to the constellation. + /// A sender for layout to communicate to the constellation. #[no_trace] layout_to_constellation_chan: IpcSender<LayoutMsg>, + /// The font cache thread to use for layout that happens in this [`ScriptThread`]. + #[no_trace] + font_cache_thread: FontCacheThread, + /// The port on which we receive messages from the image cache #[no_trace] image_cache_port: Receiver<ImageCacheMsg>, @@ -738,6 +726,14 @@ pub struct ScriptThread { // Secure context inherited_secure_context: Option<bool>, + + /// The layouts that we control. + #[no_trace] + layouts: RefCell<HashMap<PipelineId, Box<dyn Layout>>>, + + /// A factory for making new layouts. This allows layout to depend on script. + #[no_trace] + layout_factory: Arc<dyn LayoutFactory>, } struct BHMExitSignal { @@ -791,21 +787,18 @@ impl<'a> Drop for ScriptMemoryFailsafe<'a> { } impl ScriptThreadFactory for ScriptThread { - type Message = message::Msg; - fn create( state: InitialScriptState, + layout_factory: Arc<dyn LayoutFactory>, + font_cache_thread: FontCacheThread, load_data: LoadData, user_agent: Cow<'static, str>, - ) -> (Sender<message::Msg>, Receiver<message::Msg>) { + ) { let (script_chan, script_port) = unbounded(); - - let (sender, receiver) = unbounded(); - let layout_chan = sender.clone(); thread::Builder::new() .name(format!("Script{:?}", state.id)) .spawn(move || { - thread_state::initialize(ThreadState::SCRIPT); + thread_state::initialize(ThreadState::SCRIPT | ThreadState::LAYOUT); PipelineNamespace::install(state.pipeline_namespace_id); TopLevelBrowsingContextId::install(state.top_level_browsing_context_id); let roots = RootCollection::new(); @@ -818,10 +811,15 @@ impl ScriptThreadFactory for ScriptThread { let secure = load_data.inherited_secure_context.clone(); let mem_profiler_chan = state.mem_profiler_chan.clone(); let window_size = state.window_size; - let layout_is_busy = state.layout_is_busy.clone(); - let script_thread = - ScriptThread::new(state, script_port, script_chan.clone(), user_agent); + let script_thread = ScriptThread::new( + state, + script_port, + script_chan.clone(), + layout_factory, + font_cache_thread, + user_agent, + ); SCRIPT_THREAD_ROOT.with(|root| { root.set(Some(&script_thread as *const _)); @@ -836,11 +834,9 @@ impl ScriptThreadFactory for ScriptThread { top_level_browsing_context_id, parent_info, opener, - layout_chan, window_size, load_data.url.clone(), origin, - layout_is_busy, secure, ); script_thread.pre_page_load(new_load, load_data); @@ -860,12 +856,26 @@ impl ScriptThreadFactory for ScriptThread { failsafe.neuter(); }) .expect("Thread spawning failed"); - - (sender, receiver) } } impl ScriptThread { + pub fn with_layout<'a, T>( + pipeline_id: PipelineId, + call: impl FnOnce(&mut dyn Layout) -> T, + ) -> Result<T, ()> { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = unsafe { &*root.get().unwrap() }; + let mut layouts = script_thread.layouts.borrow_mut(); + if let Some(ref mut layout) = layouts.get_mut(&pipeline_id) { + Ok(call(&mut ***layout)) + } else { + warn!("No layout found for {}", pipeline_id); + Err(()) + } + }) + } + pub fn runtime_handle() -> ParentRuntime { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; @@ -1201,12 +1211,8 @@ impl ScriptThread { }, }; - match window.layout_chan() { - Some(chan) => chan - .send(Msg::RegisterPaint(name, properties, painter)) - .unwrap(), - None => warn!("Layout channel unavailable"), - } + let _ = window + .with_layout(|layout| layout.process(Msg::RegisterPaint(name, properties, painter))); } pub fn push_new_element_queue() { @@ -1292,6 +1298,8 @@ impl ScriptThread { state: InitialScriptState, port: Receiver<MainThreadScriptMsg>, chan: Sender<MainThreadScriptMsg>, + layout_factory: Arc<dyn LayoutFactory>, + font_cache_thread: FontCacheThread, user_agent: Cow<'static, str>, ) -> ScriptThread { let opts = opts::get(); @@ -1351,7 +1359,6 @@ impl ScriptThread { task_queue, - background_hang_monitor_register: state.background_hang_monitor_register, background_hang_monitor, closing, @@ -1394,6 +1401,7 @@ impl ScriptThread { mutation_observers: Default::default(), layout_to_constellation_chan: state.layout_to_constellation_chan, + font_cache_thread, webgl_chan: state.webgl_chan, webxr_registry: state.webxr_registry, @@ -1426,6 +1434,8 @@ impl ScriptThread { gpu_id_hub: Arc::new(Mutex::new(Identities::new())), webgpu_port: RefCell::new(None), inherited_secure_context: state.inherited_secure_context, + layouts: Default::default(), + layout_factory, } } @@ -1847,6 +1857,8 @@ impl ScriptThread { ExitFullScreen(id, ..) => Some(id), MediaSessionAction(..) => None, SetWebGPUPort(..) => None, + ForLayoutFromConstellation(_, id) => Some(id), + ForLayoutFromFontCache(id) => Some(id), }, MixedMessage::FromDevtools(_) => None, MixedMessage::FromScript(ref inner_msg) => match *inner_msg { @@ -2081,9 +2093,23 @@ impl ScriptThread { msg @ ConstellationControlMsg::ExitScriptThread => { panic!("should have handled {:?} already", msg) }, + ConstellationControlMsg::ForLayoutFromConstellation(msg, pipeline_id) => { + self.handle_layout_message(msg, pipeline_id) + }, + ConstellationControlMsg::ForLayoutFromFontCache(pipeline_id) => { + self.handle_font_cache(pipeline_id) + }, } } + fn handle_layout_message(&self, msg: LayoutControlMsg, pipeline_id: PipelineId) { + let _ = Self::with_layout(pipeline_id, |layout| layout.handle_constellation_msg(msg)); + } + + fn handle_font_cache(&self, pipeline_id: PipelineId) { + let _ = Self::with_layout(pipeline_id, |layout| layout.handle_font_cache_msg()); + } + fn handle_msg_from_webgpu_server(&self, msg: WebGPUMsg) { match msg { WebGPUMsg::FreeAdapter(id) => self.gpu_id_hub.lock().kill_adapter_id(id), @@ -2509,55 +2535,8 @@ impl ScriptThread { opener, load_data, window_size, - pipeline_port, } = new_layout_info; - let layout_pair = unbounded(); - let layout_chan = layout_pair.0.clone(); - - let layout_is_busy = Arc::new(AtomicBool::new(false)); - - let msg = message::Msg::CreateLayoutThread(LayoutThreadInit { - id: new_pipeline_id, - url: load_data.url.clone(), - is_parent: false, - layout_pair: layout_pair, - pipeline_port: pipeline_port, - background_hang_monitor_register: self.background_hang_monitor_register.clone(), - constellation_chan: self.layout_to_constellation_chan.clone(), - script_chan: self.control_chan.clone(), - image_cache: self.image_cache.clone(), - paint_time_metrics: PaintTimeMetrics::new( - new_pipeline_id, - self.time_profiler_chan.clone(), - self.layout_to_constellation_chan.clone(), - self.control_chan.clone(), - load_data.url.clone(), - ), - layout_is_busy: layout_is_busy.clone(), - window_size, - }); - - // Pick a layout thread, any layout thread - let current_layout_chan: Option<Sender<Msg>> = self - .documents - .borrow() - .iter() - .next() - .and_then(|(_, document)| document.window().layout_chan().cloned()) - .or_else(|| { - self.incomplete_loads - .borrow() - .first() - .map(|load| load.layout_chan.clone()) - }); - - match current_layout_chan { - None => panic!("Layout attached to empty script thread."), - // Tell the layout thread factory to actually spawn the thread. - Some(layout_chan) => layout_chan.send(msg).unwrap(), - }; - // Kick off the fetch for the new resource. let new_load = InProgressLoad::new( new_pipeline_id, @@ -2565,11 +2544,9 @@ impl ScriptThread { top_level_browsing_context_id, parent_info, opener, - layout_chan, window_size, load_data.url.clone(), origin, - layout_is_busy.clone(), load_data.inherited_secure_context.clone(), ); if load_data.url.as_str() == "about:blank" { @@ -2926,60 +2903,37 @@ impl ScriptThread { /// Handles a request to exit a pipeline and shut down layout. fn handle_exit_pipeline_msg(&self, id: PipelineId, discard_bc: DiscardBrowsingContext) { - debug!("Exiting pipeline {}.", id); + debug!("{id}: Starting pipeline exit."); self.closed_pipelines.borrow_mut().insert(id); - // Check if the exit message is for an in progress load. - let idx = self - .incomplete_loads - .borrow() - .iter() - .position(|load| load.pipeline_id == id); - - let document = self.documents.borrow_mut().remove(id); - // Abort the parser, if any, // to prevent any further incoming networking messages from being handled. - if let Some(document) = document.as_ref() { + let document = self.documents.borrow_mut().remove(id); + if let Some(document) = document { + // We should never have a pipeline that's still an incomplete load, but also has a Document. + debug_assert!(!self + .incomplete_loads + .borrow() + .iter() + .any(|load| load.pipeline_id == id)); + if let Some(parser) = document.get_current_parser() { parser.abort(); } - } - - // We should never have a pipeline that's still an incomplete load, - // but also has a Document. - debug_assert!(idx.is_none() || document.is_none()); - - // Remove any incomplete load. - let chan = if let Some(idx) = idx { - let load = self.incomplete_loads.borrow_mut().remove(idx); - load.layout_chan.clone() - } else if let Some(ref document) = document { - match document.window().layout_chan() { - Some(chan) => chan.clone(), - None => return warn!("Layout channel unavailable"), - } - } else { - return warn!("Exiting nonexistant pipeline {}.", id); - }; - // We shut down layout before removing the document, - // since layout might still be in the middle of laying it out. - debug!("preparing to shut down layout for page {}", id); - let (response_chan, response_port) = unbounded(); - chan.send(message::Msg::PrepareToExit(response_chan)).ok(); - let _ = response_port.recv(); + debug!("{id}: Shutting down layout"); + let _ = document.window().with_layout(|layout| { + layout.process(Msg::ExitNow); + }); - debug!("shutting down layout for page {}", id); - chan.send(message::Msg::ExitNow).ok(); - self.script_sender - .send((id, ScriptMsg::PipelineExited)) - .ok(); + debug!("{id}: Sending PipelineExited message to constellation"); + self.script_sender + .send((id, ScriptMsg::PipelineExited)) + .ok(); - // Now that layout is shut down, it's OK to remove the document. - if let Some(document) = document { // Clear any active animations and unroot all of the associated DOM objects. + debug!("{id}: Clearing animations"); document.animations().clear(); // We don't want to dispatch `mouseout` event pointing to non-existing element @@ -2995,10 +2949,12 @@ impl ScriptThread { if discard_bc == DiscardBrowsingContext::Yes { window.discard_browsing_context(); } + + debug!("{id}: Clearing JavaScript runtime"); window.clear_js_runtime(); } - debug!("Exited pipeline {}.", id); + debug!("{id}: Finished pipeline exit"); } /// Handles a request to exit the script thread and shut down layout. @@ -3045,7 +3001,7 @@ impl ScriptThread { }); } - /// Handles when layout thread finishes all animation in one tick + /// Handles when layout finishes all animation in one tick fn handle_tick_all_animations(&self, id: PipelineId, tick_type: AnimationTickType) { let document = match self.documents.borrow().find_document(id) { Some(document) => document, @@ -3255,13 +3211,6 @@ impl ScriptThread { fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> DomRoot<ServoParser> { let final_url = metadata.final_url.clone(); { - // send the final url to the layout thread. - incomplete - .layout_chan - .send(message::Msg::SetFinalUrl(final_url.clone())) - .unwrap(); - - // update the pipeline url self.script_sender .send(( incomplete.pipeline_id, @@ -3304,6 +3253,33 @@ impl ScriptThread { self.websocket_task_source(incomplete.pipeline_id), ); + let paint_time_metrics = PaintTimeMetrics::new( + incomplete.pipeline_id, + self.time_profiler_chan.clone(), + self.layout_to_constellation_chan.clone(), + self.control_chan.clone(), + final_url.clone(), + incomplete.navigation_start_precise, + ); + + let layout_config = LayoutConfig { + id: incomplete.pipeline_id, + url: final_url.clone(), + is_iframe: incomplete.parent_info.is_some(), + constellation_chan: self.layout_to_constellation_chan.clone(), + script_chan: self.control_chan.clone(), + image_cache: self.image_cache.clone(), + font_cache_thread: self.font_cache_thread.clone(), + time_profiler_chan: self.time_profiler_chan.clone(), + webrender_api_sender: self.webrender_api_sender.clone(), + paint_time_metrics, + window_size: incomplete.window_size.clone(), + }; + self.layouts.borrow_mut().insert( + incomplete.pipeline_id, + self.layout_factory.create(layout_config), + ); + // Create the window and document objects. let window = Window::new( self.js_runtime.clone(), @@ -3319,7 +3295,6 @@ impl ScriptThread { script_to_constellation_chan, self.control_chan.clone(), self.scheduler_chan.clone(), - incomplete.layout_chan, incomplete.pipeline_id, incomplete.parent_info, incomplete.window_size, @@ -3332,7 +3307,6 @@ impl ScriptThread { self.microtask_queue.clone(), self.webrender_document, self.webrender_api_sender.clone(), - incomplete.layout_is_busy, self.relayout_event, self.prepare_for_screenshot, self.unminify_js, diff --git a/components/servo/lib.rs b/components/servo/lib.rs index eb423ea4685..c21c6af4ede 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -75,6 +75,7 @@ use profile::{mem as profile_mem, time as profile_time}; use profile_traits::{mem, time}; use script::serviceworker_manager::ServiceWorkerManager; use script::JSEngineSetup; +use script_layout_interface::LayoutFactory; use script_traits::{ScriptToConstellationChan, WindowSizeData}; use servo_config::{opts, pref, prefs}; use servo_media::player::context::GlContext; @@ -417,9 +418,8 @@ where device_pixel_ratio: Scale::new(device_pixel_ratio), }; - // Create the constellation, which maintains the engine - // pipelines, including the script and layout threads, as well - // as the navigation context. + // Create the constellation, which maintains the engine pipelines, including script and + // layout, as well as the navigation context. let constellation_chan = create_constellation( user_agent, opts.config_dir.clone(), @@ -986,24 +986,18 @@ fn create_constellation( wgpu_image_map, }; - let start_constellation_chan = if opts::get().legacy_layout { - Constellation::< - script_layout_interface::message::Msg, - layout_thread_2013::LayoutThread, - script::script_thread::ScriptThread, - script::serviceworker_manager::ServiceWorkerManager, - >::start + let layout_factory: Arc<dyn LayoutFactory> = if opts::get().legacy_layout { + Arc::new(layout_thread_2013::LayoutFactoryImpl()) } else { - Constellation::< - script_layout_interface::message::Msg, - layout_thread_2020::LayoutThread, - script::script_thread::ScriptThread, - script::serviceworker_manager::ServiceWorkerManager, - >::start + Arc::new(layout_thread_2020::LayoutFactoryImpl()) }; - start_constellation_chan( + Constellation::< + script::script_thread::ScriptThread, + script::serviceworker_manager::ServiceWorkerManager, + >::start( initial_state, + layout_factory, initial_window_size, opts.random_pipeline_closure_probability, opts.random_pipeline_closure_seed, @@ -1129,21 +1123,17 @@ pub fn run_content_process(token: String) { set_logger(content.script_to_constellation_chan().clone()); let background_hang_monitor_register = content.register_with_background_hang_monitor(); - if opts::get().legacy_layout { - content.start_all::<script_layout_interface::message::Msg, - layout_thread_2013::LayoutThread, - script::script_thread::ScriptThread>( - true, - background_hang_monitor_register, - ); + let layout_factory: Arc<dyn LayoutFactory> = if opts::get().legacy_layout { + Arc::new(layout_thread_2013::LayoutFactoryImpl()) } else { - content.start_all::<script_layout_interface::message::Msg, - layout_thread_2020::LayoutThread, - script::script_thread::ScriptThread>( - true, - background_hang_monitor_register, - ); - } + Arc::new(layout_thread_2020::LayoutFactoryImpl()) + }; + + content.start_all::<script::script_thread::ScriptThread>( + true, + layout_factory, + background_hang_monitor_register, + ); }, UnprivilegedContent::ServiceWorker(content) => { content.start::<ServiceWorkerManager>(); diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 81ef4ace3b9..b3810344439 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -20,8 +20,8 @@ use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId}; use net_traits::image::base::Image; use net_traits::NetToCompositorMsg; use script_traits::{ - AnimationState, ConstellationControlMsg, EventResult, LayoutControlMsg, MouseButton, - MouseEventType, ScriptToCompositorMsg, + AnimationState, ConstellationControlMsg, EventResult, MouseButton, MouseEventType, + ScriptToCompositorMsg, }; use style_traits::CSSPixel; use webrender_api::units::{DeviceIntPoint, DeviceIntSize}; @@ -132,7 +132,7 @@ pub enum CompositorMsg { /// Required to allow WGL GLContext sharing in Windows. Dispatch(Box<dyn Fn() + Send>), /// Indicates to the compositor that it needs to record the time when the frame with - /// the given ID (epoch) is painted and report it to the layout thread of the given + /// the given ID (epoch) is painted and report it to the layout of the given /// pipeline ID. PendingPaintMetric(PipelineId, Epoch), /// The load of a page has completed @@ -165,7 +165,6 @@ pub struct CompositionPipeline { pub id: PipelineId, pub top_level_browsing_context_id: TopLevelBrowsingContextId, pub script_chan: IpcSender<ConstellationControlMsg>, - pub layout_chan: IpcSender<LayoutControlMsg>, } pub enum FontToCompositorMsg { diff --git a/components/shared/layout/Cargo.toml b/components/shared/layout/Cargo.toml deleted file mode 100644 index 88b04842f6c..00000000000 --- a/components/shared/layout/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "layout_traits" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -publish = false - -[lib] -name = "layout_traits" -path = "lib.rs" - -[dependencies] -crossbeam-channel = { workspace = true } -gfx = { path = "../../gfx" } -ipc-channel = { workspace = true } -metrics = { path = "../../metrics" } -msg = { workspace = true } -net_traits = { workspace = true } -profile_traits = { workspace = true } -script_traits = { workspace = true } -servo_url = { path = "../../url" } diff --git a/components/shared/layout/lib.rs b/components/shared/layout/lib.rs deleted file mode 100644 index 1e1783e489f..00000000000 --- a/components/shared/layout/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -/* 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/. */ - -#![deny(unsafe_code)] - -// This module contains traits in layout used generically -// in the rest of Servo. -// The traits are here instead of in layout so -// that these modules won't have to depend on layout. - -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -use crossbeam_channel::{Receiver, Sender}; -use gfx::font_cache_thread::FontCacheThread; -use ipc_channel::ipc::{IpcReceiver, IpcSender}; -use metrics::PaintTimeMetrics; -use msg::constellation_msg::{ - BackgroundHangMonitorRegister, PipelineId, TopLevelBrowsingContextId, -}; -use net_traits::image_cache::ImageCache; -use profile_traits::{mem, time}; -use script_traits::{ - ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, WebrenderIpcSender, - WindowSizeData, -}; -use servo_url::ServoUrl; - -// A static method creating a layout thread -// Here to remove the compositor -> layout dependency -pub trait LayoutThreadFactory { - type Message; - fn create( - id: PipelineId, - top_level_browsing_context_id: TopLevelBrowsingContextId, - url: ServoUrl, - is_iframe: bool, - chan: (Sender<Self::Message>, Receiver<Self::Message>), - pipeline_port: IpcReceiver<LayoutControlMsg>, - background_hang_monitor: Box<dyn BackgroundHangMonitorRegister>, - constellation_chan: IpcSender<ConstellationMsg>, - script_chan: IpcSender<ConstellationControlMsg>, - image_cache: Arc<dyn ImageCache>, - font_cache_thread: FontCacheThread, - time_profiler_chan: time::ProfilerChan, - mem_profiler_chan: mem::ProfilerChan, - webrender_api_sender: WebrenderIpcSender, - paint_time_metrics: PaintTimeMetrics, - busy: Arc<AtomicBool>, - window_size: WindowSizeData, - ); -} diff --git a/components/shared/msg/constellation_msg.rs b/components/shared/msg/constellation_msg.rs index 46c947de67b..45f43b612b7 100644 --- a/components/shared/msg/constellation_msg.rs +++ b/components/shared/msg/constellation_msg.rs @@ -459,7 +459,6 @@ pub enum LayoutHangAnnotation { Reflow, GetRPC, CollectReports, - PrepareToExit, ExitNow, GetCurrentEpoch, GetWebFontLoadState, @@ -468,7 +467,6 @@ pub enum LayoutHangAnnotation { SetScrollStates, UpdateScrollStateFromScript, RegisterPaint, - SetNavigationStart, } #[derive(Clone, Copy, Debug, Deserialize, Serialize)] @@ -611,7 +609,6 @@ impl fmt::Debug for HangProfile { #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub enum MonitoredComponentType { - Layout, Script, } diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 7ade170fe77..f39089cf3a0 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -18,14 +18,13 @@ pub mod webdriver_msg; use std::borrow::Cow; use std::collections::{HashMap, VecDeque}; use std::fmt; -use std::sync::atomic::AtomicBool; use std::sync::Arc; use bitflags::bitflags; use bluetooth_traits::BluetoothRequest; use canvas_traits::webgl::WebGLPipeline; use compositor::ScrollTreeNodeId; -use crossbeam_channel::{Receiver, RecvTimeoutError, Sender}; +use crossbeam_channel::{RecvTimeoutError, Sender}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::{CompositorEventVariant, Cursor}; use euclid::default::Point2D; @@ -110,19 +109,14 @@ impl UntrustedNodeAddress { } } -/// Messages sent to the layout thread from the constellation and/or compositor. +/// Messages sent to layout from the constellation and/or compositor. #[derive(Debug, Deserialize, Serialize)] pub enum LayoutControlMsg { - /// Requests that this layout thread exit. + /// Requests that this layout clean up before exit. ExitNow, - /// Requests the current epoch (layout counter) from this layout. - GetCurrentEpoch(IpcSender<Epoch>), /// Tells layout about the new scrolling offsets of each scrollable stacking context. SetScrollStates(Vec<ScrollState>), - /// Requests the current load state of Web fonts. `true` is returned if fonts are still loading - /// and `false` is returned if all fonts have loaded. - GetWebFontLoadState(IpcSender<bool>), - /// Send the paint time for a specific epoch to the layout thread. + /// Send the paint time for a specific epoch to layout. PaintMetric(Epoch, u64), } @@ -233,8 +227,6 @@ pub struct NewLayoutInfo { pub load_data: LoadData, /// Information about the initial window size. pub window_size: WindowSizeData, - /// A port on which layout can receive messages from the pipeline. - pub pipeline_port: IpcReceiver<LayoutControlMsg>, } /// When a pipeline is closed, should its browsing context be discarded too? @@ -293,7 +285,7 @@ pub enum ConstellationControlMsg { /// Sends the final response to script thread for fetching after all redirections /// have been resolved NavigationResponse(PipelineId, FetchResponseMsg), - /// Gives a channel and ID to a layout thread, as well as the ID of that layout's parent + /// Gives a channel and ID to a layout, as well as the ID of that layout's parent AttachLayout(NewLayoutInfo), /// Window resized. Sends a DOM event eventually, but first we combine events. Resize(PipelineId, WindowSizeData, WindowSizeType), @@ -401,6 +393,10 @@ pub enum ConstellationControlMsg { MediaSessionAction(PipelineId, MediaSessionActionType), /// Notifies script thread that WebGPU server has started SetWebGPUPort(IpcReceiver<WebGPUMsg>), + /// A mesage for a layout from the constellation. + ForLayoutFromConstellation(LayoutControlMsg, PipelineId), + /// A message for a layout from the font cache. + ForLayoutFromFontCache(PipelineId), } impl fmt::Debug for ConstellationControlMsg { @@ -439,6 +435,8 @@ impl fmt::Debug for ConstellationControlMsg { ExitFullScreen(..) => "ExitFullScreen", MediaSessionAction(..) => "MediaSessionAction", SetWebGPUPort(..) => "SetWebGPUPort", + ForLayoutFromConstellation(..) => "ForLayoutFromConstellation", + ForLayoutFromFontCache(..) => "ForLayoutFromFontCache", }; write!(formatter, "ConstellationControlMsg::{}", variant) } @@ -664,7 +662,7 @@ pub struct InitialScriptState { pub script_to_constellation_chan: ScriptToConstellationChan, /// A handle to register script-(and associated layout-)threads for hang monitoring. pub background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, - /// A sender for the layout thread to communicate to the constellation. + /// A sender layout to communicate to the constellation. pub layout_to_constellation_chan: IpcSender<LayoutMsg>, /// A channel to schedule timer events. pub scheduler_chan: IpcSender<TimerSchedulerMsg>, @@ -694,25 +692,10 @@ pub struct InitialScriptState { pub webrender_document: DocumentId, /// FIXME(victor): The Webrender API sender in this constellation's pipeline pub webrender_api_sender: WebrenderIpcSender, - /// Flag to indicate if the layout thread is busy handling a request. - pub layout_is_busy: Arc<AtomicBool>, /// Application window's GL Context for Media player pub player_context: WindowGLContext, } -/// This trait allows creating a `ScriptThread` without depending on the `script` -/// crate. -pub trait ScriptThreadFactory { - /// Type of message sent from script to layout. - type Message; - /// Create a `ScriptThread`. - fn create( - state: InitialScriptState, - load_data: LoadData, - user_agent: Cow<'static, str>, - ) -> (Sender<Self::Message>, Receiver<Self::Message>); -} - /// This trait allows creating a `ServiceWorkerManager` without depending on the `script` /// crate. pub trait ServiceWorkerManagerFactory { diff --git a/components/shared/script/script_msg.rs b/components/shared/script/script_msg.rs index 5abef781743..8f55db70a12 100644 --- a/components/shared/script/script_msg.rs +++ b/components/shared/script/script_msg.rs @@ -29,8 +29,8 @@ use webrender_api::units::{DeviceIntPoint, DeviceIntSize}; use crate::{ AnimationState, AuxiliaryBrowsingContextLoadInfo, BroadcastMsg, DocumentState, - IFrameLoadInfoWithData, LayoutControlMsg, LoadData, MessagePortMsg, PortMessageTask, - StructuredSerializedData, WindowSizeType, WorkerGlobalScopeInit, WorkerScriptLoadOrigin, + IFrameLoadInfoWithData, LoadData, MessagePortMsg, PortMessageTask, StructuredSerializedData, + WindowSizeType, WorkerGlobalScopeInit, WorkerScriptLoadOrigin, }; /// An iframe sizing operation. @@ -220,16 +220,15 @@ pub enum ScriptMsg { /// A load has been requested in an IFrame. ScriptLoadedURLInIFrame(IFrameLoadInfoWithData), /// A load of the initial `about:blank` has been completed in an IFrame. - ScriptNewIFrame(IFrameLoadInfoWithData, IpcSender<LayoutControlMsg>), + ScriptNewIFrame(IFrameLoadInfoWithData), /// Script has opened a new auxiliary browsing context. - ScriptNewAuxiliary( - AuxiliaryBrowsingContextLoadInfo, - IpcSender<LayoutControlMsg>, - ), + ScriptNewAuxiliary(AuxiliaryBrowsingContextLoadInfo), /// Mark a new document as active ActivateDocument, /// Set the document state for a pipeline (used by screenshot / reftests) SetDocumentState(DocumentState), + /// Update the layout epoch in the constellation (used by screenshot / reftests). + SetLayoutEpoch(Epoch, IpcSender<bool>), /// Update the pipeline Url, which can change after redirections. SetFinalUrl(ServoUrl), /// Script has handled a touch event, and either prevented or allowed default actions. @@ -311,6 +310,7 @@ impl fmt::Debug for ScriptMsg { ScriptNewAuxiliary(..) => "ScriptNewAuxiliary", ActivateDocument => "ActivateDocument", SetDocumentState(..) => "SetDocumentState", + SetLayoutEpoch(..) => "SetLayoutEpoch", SetFinalUrl(..) => "SetFinalUrl", TouchEventProcessed(..) => "TouchEventProcessed", LogEntry(..) => "LogEntry", diff --git a/components/shared/script_layout/Cargo.toml b/components/shared/script_layout/Cargo.toml index 4055e337e42..40c92e96c8e 100644 --- a/components/shared/script_layout/Cargo.toml +++ b/components/shared/script_layout/Cargo.toml @@ -16,6 +16,7 @@ atomic_refcell = { workspace = true } canvas_traits = { workspace = true } crossbeam-channel = { workspace = true } euclid = { workspace = true } +gfx = { path = "../../gfx" } gfx_traits = { workspace = true } html5ever = { workspace = true } ipc-channel = { workspace = true } diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 0db42229678..933484fc1fa 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -13,15 +13,25 @@ pub mod rpc; pub mod wrapper_traits; use std::any::Any; +use std::borrow::Cow; use std::sync::atomic::AtomicIsize; +use std::sync::Arc; use atomic_refcell::AtomicRefCell; use canvas_traits::canvas::{CanvasId, CanvasMsg}; +use gfx::font_cache_thread::FontCacheThread; +use gfx_traits::Epoch; use ipc_channel::ipc::IpcSender; use libc::c_void; use malloc_size_of_derive::MallocSizeOf; -use net_traits::image_cache::PendingImageId; -use script_traits::UntrustedNodeAddress; +use metrics::PaintTimeMetrics; +use msg::constellation_msg::PipelineId; +use net_traits::image_cache::{ImageCache, PendingImageId}; +use profile_traits::time; +use script_traits::{ + ConstellationControlMsg, InitialScriptState, LayoutControlMsg, LayoutMsg, LoadData, + UntrustedNodeAddress, WebrenderIpcSender, WindowSizeData, +}; use servo_url::{ImmutableOrigin, ServoUrl}; use style::data::ElementData; use webrender_api::ImageKey; @@ -162,3 +172,57 @@ pub struct PendingImage { pub struct HTMLMediaData { pub current_frame: Option<(ImageKey, i32, i32)>, } + +pub struct LayoutConfig { + pub id: PipelineId, + pub url: ServoUrl, + pub is_iframe: bool, + pub constellation_chan: IpcSender<LayoutMsg>, + pub script_chan: IpcSender<ConstellationControlMsg>, + pub image_cache: Arc<dyn ImageCache>, + pub font_cache_thread: FontCacheThread, + pub time_profiler_chan: time::ProfilerChan, + pub webrender_api_sender: WebrenderIpcSender, + pub paint_time_metrics: PaintTimeMetrics, + pub window_size: WindowSizeData, +} + +pub trait LayoutFactory: Send + Sync { + fn create(&self, config: LayoutConfig) -> Box<dyn Layout>; +} + +pub trait Layout { + /// Process a single message from script. + fn process(&mut self, msg: message::Msg); + + /// Handle a single message from the Constellation. + fn handle_constellation_msg(&mut self, msg: LayoutControlMsg); + + /// Handle a a single mesasge from the FontCacheThread. + fn handle_font_cache_msg(&mut self); + + /// Return the interface used for scipt queries. + /// TODO: Make this part of the the Layout interface itself now that the + /// layout thread has been removed. + fn rpc(&self) -> Box<dyn rpc::LayoutRPC>; + + /// Whether or not this layout is waiting for fonts from loaded stylesheets to finish loading. + fn waiting_for_web_fonts_to_load(&self) -> bool; + + /// The currently laid out Epoch that this Layout has finished. + fn current_epoch(&self) -> Epoch; +} + +/// This trait is part of `layout_traits` 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<dyn LayoutFactory>, + font_cache_thread: FontCacheThread, + load_data: LoadData, + user_agent: Cow<'static, str>, + ); +} diff --git a/components/shared/script_layout/message.rs b/components/shared/script_layout/message.rs index a83557757a4..d07272821b0 100644 --- a/components/shared/script_layout/message.rs +++ b/components/shared/script_layout/message.rs @@ -2,26 +2,16 @@ * 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/. */ -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - use app_units::Au; -use crossbeam_channel::{Receiver, Sender}; +use crossbeam_channel::Sender; use euclid::default::{Point2D, Rect}; -use gfx_traits::Epoch; -use ipc_channel::ipc::{IpcReceiver, IpcSender}; use malloc_size_of_derive::MallocSizeOf; -use metrics::PaintTimeMetrics; -use msg::constellation_msg::{BackgroundHangMonitorRegister, BrowsingContextId, PipelineId}; -use net_traits::image_cache::ImageCache; +use msg::constellation_msg::BrowsingContextId; use profile_traits::mem::ReportsChan; -use script_traits::{ - ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, Painter, ScrollState, - WindowSizeData, -}; +use script_traits::{Painter, ScrollState, WindowSizeData}; use servo_arc::Arc as ServoArc; use servo_atoms::Atom; -use servo_url::{ImmutableOrigin, ServoUrl}; +use servo_url::ImmutableOrigin; use style::animation::DocumentAnimationSet; use style::context::QuirksMode; use style::dom::OpaqueNode; @@ -52,42 +42,19 @@ pub enum Msg { /// Get an RPC interface. GetRPC(Sender<Box<dyn LayoutRPC + Send>>), - /// Requests that the layout thread measure its memory usage. The resulting reports are sent back + /// Requests that layout measure its memory usage. The resulting reports are sent back /// via the supplied channel. CollectReports(ReportsChan), - /// Requests that the layout thread enter a quiescent state in which no more messages are - /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when - /// this happens. - PrepareToExit(Sender<()>), - - /// Requests that the layout thread immediately shut down. There must be no more nodes left after + /// Requests that layout immediately shut down. There must be no more nodes left after /// this, or layout will crash. ExitNow, - /// Get the last epoch counter for this layout thread. - GetCurrentEpoch(IpcSender<Epoch>), - - /// Asks the layout thread whether any Web fonts have yet to load (if true, loads are pending; - /// false otherwise). - GetWebFontLoadState(IpcSender<bool>), - - /// Creates a new layout thread. - /// - /// This basically exists to keep the script-layout dependency one-way. - CreateLayoutThread(LayoutThreadInit), - - /// Set the final Url. - SetFinalUrl(ServoUrl), - /// Tells layout about the new scrolling offsets of each scrollable stacking context. SetScrollStates(Vec<ScrollState>), /// Tells layout that script has added some paint worklet modules. RegisterPaint(Atom, Vec<Atom>, Box<dyn Painter>), - - /// Send to layout the precise time when the navigation started. - SetNavigationStart(u64), } #[derive(Debug, PartialEq)] @@ -218,21 +185,6 @@ pub struct ScriptReflow { pub animations: DocumentAnimationSet, } -pub struct LayoutThreadInit { - pub id: PipelineId, - pub url: ServoUrl, - pub is_parent: bool, - pub layout_pair: (Sender<Msg>, Receiver<Msg>), - pub pipeline_port: IpcReceiver<LayoutControlMsg>, - pub background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, - pub constellation_chan: IpcSender<ConstellationMsg>, - pub script_chan: IpcSender<ConstellationControlMsg>, - pub image_cache: Arc<dyn ImageCache>, - pub paint_time_metrics: PaintTimeMetrics, - pub layout_is_busy: Arc<AtomicBool>, - pub window_size: WindowSizeData, -} - /// A pending restyle. #[derive(Debug, MallocSizeOf)] pub struct PendingRestyle { diff --git a/components/style/context.rs b/components/style/context.rs index f38963539b9..8f717eca61f 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -479,7 +479,7 @@ impl<E: TElement> SequentialTask<E> { /// Executes this task. pub fn execute(self) { use self::SequentialTask::*; - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); + debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); match self { Unused(_) => unreachable!(), #[cfg(feature = "gecko")] @@ -555,7 +555,7 @@ where E: TElement, { fn drop(&mut self) { - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); + debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); for task in self.0.drain(..) { task.execute() } diff --git a/tests/unit/metrics/paint_time.rs b/tests/unit/metrics/paint_time.rs index ac8ade3842c..65316e09d16 100644 --- a/tests/unit/metrics/paint_time.rs +++ b/tests/unit/metrics/paint_time.rs @@ -28,11 +28,12 @@ fn test_paint_metrics_construction() { layout_sender, script_sender, ServoUrl::parse("about:blank").unwrap(), + 0, ); assert_eq!( (&paint_time_metrics).get_navigation_start(), - None, - "navigation start is None" + Some(0), + "navigation start is set properly" ); assert_eq!( paint_time_metrics.get_first_paint(), @@ -57,6 +58,7 @@ fn test_common(display_list_is_contentful: bool, epoch: Epoch) -> PaintTimeMetri layout_sender, script_sender, ServoUrl::parse("about:blank").unwrap(), + 0, ); let dummy_profiler_metadata_factory = DummyProfilerMetadataFactory {}; @@ -66,8 +68,6 @@ fn test_common(display_list_is_contentful: bool, epoch: Epoch) -> PaintTimeMetri display_list_is_contentful, ); - // Should not set any metric until navigation start is set. - paint_time_metrics.maybe_set_metric(epoch, 0); assert_eq!( paint_time_metrics.get_first_paint(), None, diff --git a/tests/wpt/meta-legacy-layout/html/the-xhtml-syntax/parsing-xhtml-documents/adopt-while-parsing-001.html.ini b/tests/wpt/meta-legacy-layout/html/the-xhtml-syntax/parsing-xhtml-documents/adopt-while-parsing-001.html.ini deleted file mode 100644 index 7c64c626527..00000000000 --- a/tests/wpt/meta-legacy-layout/html/the-xhtml-syntax/parsing-xhtml-documents/adopt-while-parsing-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[adopt-while-parsing-001.html] - expected: TIMEOUT diff --git a/tests/wpt/meta/html/the-xhtml-syntax/parsing-xhtml-documents/adopt-while-parsing-001.html.ini b/tests/wpt/meta/html/the-xhtml-syntax/parsing-xhtml-documents/adopt-while-parsing-001.html.ini deleted file mode 100644 index 7c64c626527..00000000000 --- a/tests/wpt/meta/html/the-xhtml-syntax/parsing-xhtml-documents/adopt-while-parsing-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[adopt-while-parsing-001.html] - expected: TIMEOUT diff --git a/tests/wpt/mozilla/meta-legacy-layout/mozilla/detached_layout.html.ini b/tests/wpt/mozilla/meta-legacy-layout/mozilla/detached_layout.html.ini new file mode 100644 index 00000000000..bc10807d4c2 --- /dev/null +++ b/tests/wpt/mozilla/meta-legacy-layout/mozilla/detached_layout.html.ini @@ -0,0 +1,3 @@ +[detached_layout.html] + [Detached layout doesn't panic] + expected: FAIL |