From 1fbb23de9ee000f11e8e8020ffba16f542601e91 Mon Sep 17 00:00:00 2001 From: Alan Jeffrey Date: Mon, 19 Dec 2016 14:53:03 -0600 Subject: [PATCH] Updated documentation for the constellation. --- components/constellation/constellation.rs | 401 ++++++++++++++++------ 1 file changed, 300 insertions(+), 101 deletions(-) diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 4b180073d77..fa500a40bfd 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -4,10 +4,63 @@ //! The `Constellation`, Servo's Grand Central Station //! -//! The primary duty of a `Constellation` is to mediate between the -//! graphics compositor and the many `Pipeline`s in the browser's -//! navigation context, each `Pipeline` encompassing a `ScriptThread`, -//! `LayoutThread`, and `PaintThread`. +//! The constellation tracks all information kept globally by the +//! browser engine, which includes: +//! +//! * The set of all `EventLoop` objects. Each event loop is +//! the constellation's view of a script thread. The constellation +//! interacts with a script thread by message-passing. +//! +//! * 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. +//! +//! * The set of all `Frame` objects. Each frame gives the constellation's +//! view of a browsing context. Each browsing context stores an independent +//! session history, created by navigation of that frame. The session +//! history can be traversed, for example by the back and forwards UI, +//! so each session history maintains a list of past and future pipelines, +//! as well as the current active pipeline. +//! +//! There are two kinds of frames: top-level frames (for example tabs +//! in a browser UI), and nested frames (typically caused by `iframe` +//! elements). Frames have a hierarchy (typically caused by `iframe`s +//! containing `iframe`s), giving rise to a frame tree with a root frame. +//! The logical relationship between these types is: +//! +//! ``` +//! +---------+ +------------+ +-------------+ +//! | Frame | --parent?--> | Pipeline | --event_loop--> | EventLoop | +//! | | --current--> | | | | +//! | | --prev*----> | | <---pipeline*-- | | +//! | | --next*----> | | +-------------+ +//! | | | | +//! | | <----frame-- | | +//! +---------+ +------------+ +//! ``` +// +//! Complicating matters, there are also mozbrowser iframes, which are top-level +//! frames with a parent. +//! +//! The constellation also maintains channels to threads, including: +//! +//! * The script and layout threads. +//! * The graphics compositor. +//! * The font cache, image cache, and resource manager, which load +//! and cache shared fonts, images, or other resources. +//! * The service worker manager. +//! * The devtools, debugger and webdriver servers. +//! +//! The constellation passes messages between the threads, and updates its state +//! to track the evolving state of the frame tree. +//! +//! The constellation acts as a logger, tracking any `warn!` messages from threads, +//! and converting any `error!` or `panic!` into a crash report, which is filed +//! using an appropriate `mozbrowsererror` event. +//! +//! Since there is only one constellation, and its responsibilities include crash reporting, +//! it is very important that it does not panic. use backtrace::Backtrace; use bluetooth_traits::BluetoothRequest; @@ -70,117 +123,150 @@ use style_traits::viewport::ViewportConstraints; use timer_scheduler::TimerScheduler; use webrender_traits; -#[derive(Debug, PartialEq)] -enum ReadyToSave { - NoRootFrame, - PendingFrames, - WebFontNotLoaded, - DocumentLoading, - EpochMismatch, - PipelineUnknown, - Ready, -} - -/// Maintains the pipelines and navigation context and grants permission to composite. +/// The `Constellation` itself. In the servo browser, there is one +/// constellation, which maintains all of the browser global data. +/// In embedded applications, there may be more than one constellation, +/// which are independent of each other. +/// +/// The constellation may be in a different process from the pipelines, +/// and communicates using IPC. /// /// It is parameterized over a `LayoutThreadFactory` and a /// `ScriptThreadFactory` (which in practice are implemented by /// `LayoutThread` in the `layout` crate, and `ScriptThread` in -/// the `script` crate). +/// the `script` crate). Script and layout communicate using a `Message` +/// type. pub struct Constellation { - /// A channel through which script messages can be sent to this object. + /// An IPC channel for script threads to send messages to the constellation. + /// This is the script threads' view of `script_receiver`. script_sender: IpcSender, - /// A channel through which layout thread messages can be sent to this object. - layout_sender: IpcSender, - - /// Receives messages from scripts. + /// A channel for the constellation to receive messages from script threads. + /// This is the constellation's view of `script_sender`. script_receiver: Receiver, - /// Receives messages from the compositor - compositor_receiver: Receiver, + /// An IPC channel for layout threads to send messages to the constellation. + /// This is the layout threads' view of `layout_receiver`. + layout_sender: IpcSender, - /// Receives messages from the layout thread + /// A channel for the constellation to receive messages from layout threads. + /// This is the constellation's view of `layout_sender`. layout_receiver: Receiver, - /// A channel (the implementation of which is port-specific) through which messages can be sent - /// to the compositor. + /// A channel for the constellation to receive messages from the compositor thread. + compositor_receiver: Receiver, + + /// A channel (the implementation of which is port-specific) for the + /// constellation to send messages to the compositor thread. compositor_proxy: Box, - /// Channels through which messages can be sent to the resource-related threads. + /// Channels for the constellation to send messages to the public + /// resource-related threads. There are two groups of resource + /// threads: one for public browsing, and one for private + /// browsing. public_resource_threads: ResourceThreads, - /// Channels through which messages can be sent to the resource-related threads. + /// Channels for the constellation to send messages to the private + /// resource-related threads. There are two groups of resource + /// threads: one for public browsing, and one for private + /// browsing. private_resource_threads: ResourceThreads, - /// A channel through which messages can be sent to the image cache thread. + /// A channel for the constellation to send messages to the image + /// cache thread. image_cache_thread: ImageCacheThread, - /// A channel through which messages can be sent to the debugger. - debugger_chan: Option, - - /// A channel through which messages can be sent to the developer tools. - devtools_chan: Option>, - - /// A channel through which messages can be sent to the bluetooth thread. - bluetooth_thread: IpcSender, - - /// Sender to Service Worker Manager thread - swmanager_chan: Option>, - - /// to send messages to this object - swmanager_sender: IpcSender, - - /// to receive sw manager message - swmanager_receiver: Receiver, - - /// A map from top-level frame id and registered domain name to event loops. - /// This double indirection ensures that separate tabs do not share event loops, - /// even if the same domain is loaded in each. - event_loops: HashMap>>, - - /// A list of all the pipelines. (See the `pipeline` module for more details.) - pipelines: HashMap, - - /// A list of all the frames - frames: HashMap, - - /// A channel through which messages can be sent to the font cache. + /// A channel for the constellation to send messages to the font + /// cache thread. font_cache_thread: FontCacheThread, - /// ID of the root frame. - root_frame_id: FrameId, + /// A channel for the constellation to send messages to the + /// debugger thread. + debugger_chan: Option, - /// The next free ID to assign to a pipeline ID namespace. - next_pipeline_namespace_id: PipelineNamespaceId, + /// A channel for the constellation to send messages to the + /// devtools thread. + devtools_chan: Option>, - /// Pipeline ID that has currently focused element for key events. - focus_pipeline_id: Option, + /// An IPC channel for the constellation to send messages to the + /// bluetooth thread. + bluetooth_thread: IpcSender, - /// Navigation operations that are in progress. - pending_frames: Vec, + /// An IPC channel for the constellation to send messages to the + /// Service Worker Manager thread. + swmanager_chan: Option>, - /// A channel through which messages can be sent to the time profiler. + /// An IPC channel for Service Worker Manager threads to send + /// messages to the constellation. This is the SW Manager thread's + /// view of `swmanager_receiver`. + swmanager_sender: IpcSender, + + /// A channel for the constellation to receive messages from the + /// Service Worker Manager thread. This is the constellation's view of + /// `swmanager_sender`. + swmanager_receiver: Receiver, + + /// A channel for the constellation to send messages to the + /// time profiler thread. time_profiler_chan: time::ProfilerChan, - /// A channel through which messages can be sent to the memory profiler. + /// A channel for the constellation to send messages to the + /// memory profiler thread. mem_profiler_chan: mem::ProfilerChan, - phantom: PhantomData<(Message, LTF, STF)>, + /// A channel for the constellation to send messages to the + /// timer thread. + scheduler_chan: IpcSender, + /// A channel for the constellation to send messages to the + /// Webrender thread. + webrender_api_sender: webrender_traits::RenderApiSender, + + /// The set of all event loops in the browser. We generate a new + /// event loop for each registered domain name (aka eTLD+1) in + /// each top-level frame. We store the event loops in a map + /// indexed by top-level frame id (as a `FrameId`) and registered + /// domain name (as a `String`) to event loops. This double + /// indirection ensures that separate tabs do not share event + /// loops, even if the same domain is loaded in each. + /// It is important that scripts with the same eTLD+1 + /// share an event loop, since they can use `document.domain` + /// to become same-origin, at which point they can share DOM objects. + event_loops: HashMap>>, + + /// The set of all the pipelines in the browser. + /// (See the `pipeline` module for more details.) + pipelines: HashMap, + + /// The set of all the frames in the browser. + frames: HashMap, + + /// When a navigation is performed, we do not immediately update + /// the frame tree, instead we ask the event loop to begin loading + /// the new document, and do not update the frame tree until the + /// document is active. Between starting the load and it activating, + /// we store a `FrameChange` object for the navigation in progress. + pending_frames: Vec, + + /// The root frame. + root_frame_id: FrameId, + + /// The currently focused pipeline for key events. + focus_pipeline_id: Option, + + /// Pipeline IDs are namespaced in order to avoid name collisions, + /// and the namespaces are allocated by the constellation. + next_pipeline_namespace_id: PipelineNamespaceId, + + /// The size of the top-level window. window_size: WindowSizeData, /// Bits of state used to interact with the webdriver implementation webdriver: WebDriverData, - scheduler_chan: IpcSender, - /// Document states for loaded pipelines (used only when writing screenshots). document_states: HashMap, - // Webrender interface. - webrender_api_sender: webrender_traits::RenderApiSender, - /// Are we shutting down? shutting_down: bool, @@ -191,62 +277,77 @@ pub struct Constellation { /// The random number generator and probability for closing pipelines. /// This is for testing the hardening of the constellation. random_pipeline_closure: Option<(StdRng, f32)>, + + /// Phantom data that keeps the Rust type system happy. + phantom: PhantomData<(Message, LTF, STF)>, } /// State needed to construct a constellation. pub struct InitialConstellationState { /// A channel through which messages can be sent to the compositor. pub compositor_proxy: Box, + /// A channel to the debugger, if applicable. pub debugger_chan: Option, + /// A channel to the developer tools, if applicable. pub devtools_chan: Option>, + /// A channel to the bluetooth thread. pub bluetooth_thread: IpcSender, + /// A channel to the image cache thread. pub image_cache_thread: ImageCacheThread, + /// A channel to the font cache thread. pub font_cache_thread: FontCacheThread, + /// A channel to the resource thread. pub public_resource_threads: ResourceThreads, + /// A channel to the resource thread. pub private_resource_threads: ResourceThreads, + /// A channel to the time profiler thread. pub time_profiler_chan: time::ProfilerChan, + /// A channel to the memory profiler thread. pub mem_profiler_chan: mem::ProfilerChan, - /// Whether the constellation supports the clipboard. - pub supports_clipboard: bool, + /// Webrender API. pub webrender_api_sender: webrender_traits::RenderApiSender, + + /// Whether the constellation supports the clipboard. + /// TODO: this field is not used, remove it? + pub supports_clipboard: bool, } +/// A frame in the frame tree. +/// Each frame is the constrellation's view of a browsing context. +/// Each browsing context has a session history, caused by +/// navigation and traversing the history. Each frame has its +/// current entry, plus past and future entries. The past is sorted +/// chronologically, the future is sorted reverse chronoogically: +/// in partiucular prev.pop() is the latest past entry, and +/// next.pop() is the earliest future entry. #[derive(Debug, Clone)] -struct FrameState { - instant: Instant, - pipeline_id: PipelineId, - frame_id: FrameId, -} - -impl FrameState { - fn new(pipeline_id: PipelineId, frame_id: FrameId) -> FrameState { - FrameState { - instant: Instant::now(), - pipeline_id: pipeline_id, - frame_id: frame_id, - } - } -} - -/// Stores the navigation context for a single frame in the frame tree. struct Frame { + /// The frame id. id: FrameId, + + /// The past session history, ordered chronologically. prev: Vec, + + /// The currently active session history entry. current: FrameState, + + /// The future session history, ordered reverse chronologically. next: Vec, } impl Frame { + /// Create a new frame. + /// Note this just creates the frame, it doesn't add it to the frame tree. fn new(id: FrameId, pipeline_id: PipelineId) -> Frame { Frame { id: id, @@ -256,36 +357,84 @@ impl Frame { } } + /// Set the current frame entry, and push the current frame entry into the past. fn load(&mut self, pipeline_id: PipelineId) { self.prev.push(self.current.clone()); self.current = FrameState::new(pipeline_id, self.id); } + /// Set the future to be empty. fn remove_forward_entries(&mut self) -> Vec { replace(&mut self.next, vec!()) } + /// Set the current frame entry, and drop the current frame entry. fn replace_current(&mut self, pipeline_id: PipelineId) -> FrameState { replace(&mut self.current, FrameState::new(pipeline_id, self.id)) } } +/// An entry in a frame's session history. +/// Each entry stores the pipeline id for a document in the session history. +/// When we operate on the joint session history, entries are sorted chronologically, +/// so we timestamp the entries by when the entry was added to the session history. +#[derive(Debug, Clone)] +struct FrameState { + /// The timestamp for when the session history entry was created + instant: Instant, + /// The pipeline for the document in the session history + pipeline_id: PipelineId, + /// The frame that this session history entry is part of + frame_id: FrameId, +} + +impl FrameState { + /// Create a new session history entry. + fn new(pipeline_id: PipelineId, frame_id: FrameId) -> FrameState { + FrameState { + instant: Instant::now(), + pipeline_id: pipeline_id, + frame_id: frame_id, + } + } +} + /// Represents a pending change in the frame tree, that will be applied /// once the new pipeline has loaded and completed initial layout / paint. struct FrameChange { + /// The frame to change. frame_id: FrameId, + + /// The pipeline that was currently active at the time the change started. + /// TODO: can this field be removed? old_pipeline_id: Option, + + /// The pipeline for the document being loaded. new_pipeline_id: PipelineId, + + /// Is this document ready to be activated? + /// TODO: this flag is never set, it can be removed. document_ready: bool, + + /// Is the new document replacing the current document (e.g. a reload) + /// or pushing it into the session history (e.g. a navigation)? replace: bool, } -/// An iterator over a frame tree, returning nodes in depth-first order. -/// Note that this iterator should _not_ be used to mutate nodes _during_ -/// iteration. Mutating nodes once the iterator is out of scope is OK. +/// An iterator over a frame tree, returning the fully active frames in +/// depth-first order. Note that this iterator only returns the fully +/// active frames, that is ones where every ancestor frame is +/// in the currently active pipeline of its parent frame. struct FrameTreeIterator<'a> { + /// The frames still to iterate over. stack: Vec, + + /// The set of all frames. frames: &'a HashMap, + + /// The set of all pipelines. We use this to find the active + /// children of a frame, which are the iframes in the currently + /// active document. pipelines: &'a HashMap, } @@ -317,9 +466,19 @@ impl<'a> Iterator for FrameTreeIterator<'a> { } } +/// An iterator over a frame tree, returning all frames in depth-first +/// order. Note that this iterator returns all frames, not just the +/// fully active ones. struct FullFrameTreeIterator<'a> { + /// The frames still to iterate over. stack: Vec, + + /// The set of all frames. frames: &'a HashMap, + + /// The set of all pipelines. We use this to find the + /// children of a frame, which are the iframes in all documents + /// in the session history. pipelines: &'a HashMap, } @@ -348,6 +507,7 @@ impl<'a> Iterator for FullFrameTreeIterator<'a> { } } +/// Data needed for webdriver struct WebDriverData { load_channel: Option<(PipelineId, IpcSender)>, resize_channel: Option>, @@ -362,12 +522,33 @@ impl WebDriverData { } } +/// When we are running reftests, we save an image to compare against a reference. +/// This enum gives the possible states of preparing such an image. +#[derive(Debug, PartialEq)] +enum ReadyToSave { + NoRootFrame, + PendingFrames, + WebFontNotLoaded, + DocumentLoading, + EpochMismatch, + PipelineUnknown, + Ready, +} + +/// When we are exiting a pipeline, we can either force exiting or not. +/// A normal exit waits for the compositor to update its state before +/// exiting, and delegates layout exit to script. A forced exit does +/// not notify the compositor, and exits layout without involving script. #[derive(Clone, Copy)] enum ExitPipelineMode { Normal, Force, } +/// The constellation uses logging to perform crash reporting. +/// The constellation receives all `warn!`, `error!` and `panic!` messages, +/// and generates a crash report when it receives a panic. + /// A logger directed at the constellation from content processes #[derive(Clone)] pub struct FromScriptLogger { @@ -444,6 +625,10 @@ impl Log for FromCompositorLogger { } } +/// Rust uses `LogRecord` for storing logging, but servo converts that to +/// a `LogEntry`. We do this so that we can record panics as well as log +/// messages, and because `LogRecord` does not implement serde (de)serialization, +/// so cannot be used over an IPC channel. fn log_entry(record: &LogRecord) -> Option { match record.level() { LogLevel::Error if thread::panicking() => Some(LogEntry::Panic( @@ -460,6 +645,7 @@ fn log_entry(record: &LogRecord) -> Option { } } +/// The number of warnings to include in each crash report. const WARNINGS_BUFFER_SIZE: usize = 32; /// The registered domain name (aka eTLD+1) for a URL. @@ -474,6 +660,7 @@ impl Constellation where LTF: LayoutThreadFactory, STF: ScriptThreadFactory { + /// Create a new constellation thread. pub fn start(state: InitialConstellationState) -> (Sender, IpcSender) { let (compositor_sender, compositor_receiver) = channel(); @@ -549,6 +736,7 @@ impl Constellation (compositor_sender, swmanager_sender) } + /// The main event loop for the constellation. fn run(&mut self) { while !self.shutting_down || !self.pipelines.is_empty() { // Randomly close a pipeline if --random-pipeline-closure-probability is set @@ -559,6 +747,7 @@ impl Constellation self.handle_shutdown(); } + /// Generate a new pipeline id namespace. fn next_pipeline_namespace_id(&mut self) -> PipelineNamespaceId { let namespace_id = self.next_pipeline_namespace_id; let PipelineNamespaceId(ref mut i) = self.next_pipeline_namespace_id; @@ -666,8 +855,9 @@ impl Constellation self.pipelines.insert(pipeline_id, pipeline); } - // Get an iterator for the current frame tree. Specify self.root_frame_id to - // iterate the entire tree, or a specific frame id to iterate only that sub-tree. + /// Get an iterator for the current frame tree. Specify self.root_frame_id to + /// iterate the entire tree, or a specific frame id to iterate only that sub-tree. + /// Iterates over the fully active frames in the tree. fn current_frame_tree_iter(&self, frame_id_root: FrameId) -> FrameTreeIterator { FrameTreeIterator { stack: vec!(frame_id_root), @@ -676,6 +866,9 @@ impl Constellation } } + /// Get an iterator for the current frame tree. Specify self.root_frame_id to + /// iterate the entire tree, or a specific frame id to iterate only that sub-tree. + /// Iterates over all frames in the tree. fn full_frame_tree_iter(&self, frame_id_root: FrameId) -> FullFrameTreeIterator { FullFrameTreeIterator { stack: vec!(frame_id_root), @@ -684,6 +877,8 @@ impl Constellation } } + /// The joint session future is the merge of the session future of every + /// frame in the frame tree, sorted reverse chronologically. fn joint_session_future(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> { let mut future = vec!(); for frame in self.full_frame_tree_iter(frame_id_root) { @@ -695,11 +890,14 @@ impl Constellation future } + /// Is the joint session future empty? fn joint_session_future_is_empty(&self, frame_id_root: FrameId) -> bool { self.full_frame_tree_iter(frame_id_root) .all(|frame| frame.next.is_empty()) } + /// The joint session past is the merge of the session past of every + /// frame in the frame tree, sorted chronologically. fn joint_session_past(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> { let mut past = vec!(); for frame in self.full_frame_tree_iter(frame_id_root) { @@ -714,12 +912,13 @@ impl Constellation past } + /// Is the joint session past empty? fn joint_session_past_is_empty(&self, frame_id_root: FrameId) -> bool { self.full_frame_tree_iter(frame_id_root) .all(|frame| frame.prev.is_empty()) } - // Create a new frame and update the internal bookkeeping. + /// Create a new frame and update the internal bookkeeping. fn new_frame(&mut self, frame_id: FrameId, pipeline_id: PipelineId) { let frame = Frame::new(frame_id, pipeline_id); self.frames.insert(frame_id, frame);