diff options
Diffstat (limited to 'components/compositing')
-rw-r--r-- | components/compositing/Cargo.toml | 5 | ||||
-rw-r--r-- | components/compositing/compositor.rs | 2 | ||||
-rw-r--r-- | components/compositing/compositor_thread.rs | 2 | ||||
-rw-r--r-- | components/compositing/constellation.rs | 2093 | ||||
-rw-r--r-- | components/compositing/lib.rs | 19 | ||||
-rw-r--r-- | components/compositing/timer_scheduler.rs | 221 |
6 files changed, 11 insertions, 2331 deletions
diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml index 3f15019100b..501285d04aa 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -19,12 +19,9 @@ profile_traits = {path = "../profile_traits"} net_traits = {path = "../net_traits"} util = {path = "../util"} devtools_traits = {path = "../devtools_traits"} -canvas_traits = {path = "../canvas_traits"} -canvas = {path = "../canvas"} plugins = {path = "../plugins"} azure = {git = "https://github.com/servo/rust-azure", features = ["plugins"]} layers = {git = "https://github.com/servo/rust-layers", features = ["plugins"]} -clipboard = {git = "https://github.com/aweinstock314/rust-clipboard"} ipc-channel = {git = "https://github.com/servo/ipc-channel"} webrender_traits = {git = "https://github.com/servo/webrender_traits"} webrender = {git = "https://github.com/servo/webrender"} @@ -33,8 +30,6 @@ euclid = {version = "0.6.4", features = ["plugins"]} gleam = "0.2.8" image = "0.10" log = "0.3.5" -offscreen_gl_context = "0.1.2" -rand = "0.3" serde = "0.7" serde_macros = "0.7" time = "0.1.17" diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index a20cccb6346..b296fff68db 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -4,11 +4,11 @@ use AnimationTickType; use CompositorMsg as ConstellationMsg; +use SendableFrameTree; use app_units::Au; use compositor_layer::{CompositorData, CompositorLayer, RcCompositorLayer, WantsScrollEventsFlag}; use compositor_thread::{CompositorEventListener, CompositorProxy}; use compositor_thread::{CompositorReceiver, InitialCompositorState, Msg, RenderListener}; -use constellation::SendableFrameTree; use delayed_composition::DelayedCompositionTimerProxy; use euclid::point::TypedPoint2D; use euclid::rect::TypedRect; diff --git a/components/compositing/compositor_thread.rs b/components/compositing/compositor_thread.rs index 1fa17c6d70e..811dd782684 100644 --- a/components/compositing/compositor_thread.rs +++ b/components/compositing/compositor_thread.rs @@ -23,7 +23,7 @@ use style_traits::cursor::Cursor; use style_traits::viewport::ViewportConstraints; use url::Url; use windowing::{WindowEvent, WindowMethods}; -pub use constellation::SendableFrameTree; +pub use SendableFrameTree; pub use windowing; use webrender; use webrender_traits; diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs deleted file mode 100644 index 69a02ad05e6..00000000000 --- a/components/compositing/constellation.rs +++ /dev/null @@ -1,2093 +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 http://mozilla.org/MPL/2.0/. */ - -//! 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`. - -use AnimationTickType; -use CompositorMsg as FromCompositorMsg; -use canvas::canvas_paint_thread::CanvasPaintThread; -use canvas::webgl_paint_thread::WebGLPaintThread; -use canvas_traits::CanvasMsg; -use clipboard::ClipboardContext; -use compositor_thread::CompositorProxy; -use compositor_thread::Msg as ToCompositorMsg; -use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg}; -use euclid::scale_factor::ScaleFactor; -use euclid::size::{Size2D, TypedSize2D}; -#[cfg(not(target_os = "windows"))] -use gaol; -#[cfg(not(target_os = "windows"))] -use gaol::sandbox::{self, Sandbox, SandboxMethods}; -use gfx::font_cache_thread::FontCacheThread; -use gfx_traits::Epoch; -#[cfg(not(target_os = "windows"))] -use ipc_channel::ipc::IpcOneShotServer; -use ipc_channel::ipc::{self, IpcSender}; -use ipc_channel::router::ROUTER; -use layout_traits::{LayoutControlChan, LayoutThreadFactory}; -use msg::constellation_msg::WebDriverCommandMsg; -use msg::constellation_msg::{FrameId, PipelineId}; -use msg::constellation_msg::{Key, KeyModifiers, KeyState, LoadData}; -use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, NavigationDirection}; -use msg::constellation_msg::{SubpageId, WindowSizeData, WindowSizeType}; -use msg::constellation_msg::{self, ConstellationChan, PanicMsg}; -use msg::webdriver_msg; -use net_traits::bluetooth_thread::BluetoothMethodMsg; -use net_traits::image_cache_thread::ImageCacheThread; -use net_traits::storage_thread::{StorageThread, StorageThreadMsg}; -use net_traits::{self, ResourceThread}; -use offscreen_gl_context::{GLContextAttributes, GLLimits}; -use pipeline::{CompositionPipeline, InitialPipelineState, Pipeline, UnprivilegedPipelineContent}; -use profile_traits::mem; -use profile_traits::time; -use rand::{random, Rng, SeedableRng, StdRng}; -#[cfg(not(target_os = "windows"))] -use sandboxing; -use script_traits::{AnimationState, CompositorEvent, ConstellationControlMsg}; -use script_traits::{DocumentState, LayoutControlMsg}; -use script_traits::{IFrameLoadInfo, IFrameSandboxState, TimerEventRequest}; -use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; -use script_traits::{MozBrowserEvent, MozBrowserErrorType}; -use std::borrow::ToOwned; -use std::collections::HashMap; -#[cfg(not(target_os = "windows"))] -use std::env; -use std::io::Error as IOError; -use std::marker::PhantomData; -use std::mem::replace; -use std::process; -use std::sync::mpsc::{Sender, channel, Receiver}; -use style_traits::cursor::Cursor; -use style_traits::viewport::ViewportConstraints; -use timer_scheduler::TimerScheduler; -use url::Url; -use util::geometry::PagePx; -use util::thread::spawn_named; -use util::{opts, prefs}; -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. -/// -/// 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). -pub struct Constellation<LTF, STF> { - /// A channel through which script messages can be sent to this object. - pub script_sender: ConstellationChan<FromScriptMsg>, - - /// A channel through which compositor messages can be sent to this object. - pub compositor_sender: Sender<FromCompositorMsg>, - - /// A channel through which layout thread messages can be sent to this object. - pub layout_sender: ConstellationChan<FromLayoutMsg>, - - /// A channel through which panic messages can be sent to this object. - pub panic_sender: ConstellationChan<PanicMsg>, - - /// Receives messages from scripts. - pub script_receiver: Receiver<FromScriptMsg>, - - /// Receives messages from the compositor - pub compositor_receiver: Receiver<FromCompositorMsg>, - - /// Receives messages from the layout thread - pub layout_receiver: Receiver<FromLayoutMsg>, - - /// Receives panic messages. - pub panic_receiver: Receiver<PanicMsg>, - - /// A channel (the implementation of which is port-specific) through which messages can be sent - /// to the compositor. - pub compositor_proxy: Box<CompositorProxy>, - - /// A channel through which messages can be sent to the resource thread. - pub resource_thread: ResourceThread, - - /// A channel through which messages can be sent to the image cache thread. - pub image_cache_thread: ImageCacheThread, - - /// A channel through which messages can be sent to the developer tools. - devtools_chan: Option<Sender<DevtoolsControlMsg>>, - - /// A channel through which messages can be sent to the bluetooth thread. - bluetooth_thread: IpcSender<BluetoothMethodMsg>, - - /// A channel through which messages can be sent to the storage thread. - storage_thread: StorageThread, - - /// A list of all the pipelines. (See the `pipeline` module for more details.) - pipelines: HashMap<PipelineId, Pipeline>, - - /// A list of all the frames - frames: HashMap<FrameId, Frame>, - - /// Maps from pipeline ID to the frame that contains it. - pipeline_to_frame_map: HashMap<PipelineId, FrameId>, - - /// Maps from a (parent pipeline, subpage) to the actual child pipeline ID. - subpage_map: HashMap<(PipelineId, SubpageId), PipelineId>, - - /// A channel through which messages can be sent to the font cache. - font_cache_thread: FontCacheThread, - - /// ID of the root frame. - root_frame_id: Option<FrameId>, - - /// The next free ID to assign to a pipeline ID namespace. - next_pipeline_namespace_id: PipelineNamespaceId, - - /// The next free ID to assign to a frame. - next_frame_id: FrameId, - - /// Pipeline ID that has currently focused element for key events. - focus_pipeline_id: Option<PipelineId>, - - /// Navigation operations that are in progress. - pending_frames: Vec<FrameChange>, - - /// A channel through which messages can be sent to the time profiler. - pub time_profiler_chan: time::ProfilerChan, - - /// A channel through which messages can be sent to the memory profiler. - pub mem_profiler_chan: mem::ProfilerChan, - - phantom: PhantomData<(LTF, STF)>, - - pub window_size: WindowSizeData, - - /// Means of accessing the clipboard - clipboard_ctx: Option<ClipboardContext>, - - /// Bits of state used to interact with the webdriver implementation - webdriver: WebDriverData, - - scheduler_chan: IpcSender<TimerEventRequest>, - - /// A list of child content processes. - #[cfg_attr(target_os = "windows", allow(dead_code))] - child_processes: Vec<ChildProcess>, - - /// Document states for loaded pipelines (used only when writing screenshots). - document_states: HashMap<PipelineId, DocumentState>, - - // Webrender interface, if enabled. - webrender_api_sender: Option<webrender_traits::RenderApiSender>, - - /// Have we seen any panics? Hopefully always false! - handled_panic: bool, - - /// The random number generator and probability for closing pipelines. - /// This is for testing the hardening of the constellation. - random_pipeline_closure: Option<(StdRng, f32)>, -} - -/// State needed to construct a constellation. -pub struct InitialConstellationState { - /// A channel through which messages can be sent to the compositor. - pub compositor_proxy: Box<CompositorProxy + Send>, - /// A channel to the developer tools, if applicable. - pub devtools_chan: Option<Sender<DevtoolsControlMsg>>, - /// A channel to the bluetooth thread. - pub bluetooth_thread: IpcSender<BluetoothMethodMsg>, - /// 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 resource_thread: ResourceThread, - /// A channel to the storage thread. - pub storage_thread: StorageThread, - /// 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, - /// Optional webrender API reference (if enabled). - pub webrender_api_sender: Option<webrender_traits::RenderApiSender>, -} - -/// Stores the navigation context for a single frame in the frame tree. -pub struct Frame { - prev: Vec<PipelineId>, - current: PipelineId, - next: Vec<PipelineId>, -} - -impl Frame { - fn new(pipeline_id: PipelineId) -> Frame { - Frame { - prev: vec!(), - current: pipeline_id, - next: vec!(), - } - } - - fn load(&mut self, pipeline_id: PipelineId) -> Vec<PipelineId> { - // TODO(gw): To also allow navigations within subframes - // to affect the parent navigation history, this should bubble - // up the navigation change to each parent. - self.prev.push(self.current); - self.current = pipeline_id; - replace(&mut self.next, vec!()) - } -} - -/// 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 { - old_pipeline_id: Option<PipelineId>, - new_pipeline_id: PipelineId, - document_ready: 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. -struct FrameTreeIterator<'a> { - stack: Vec<FrameId>, - frames: &'a HashMap<FrameId, Frame>, - pipelines: &'a HashMap<PipelineId, Pipeline>, -} - -impl<'a> Iterator for FrameTreeIterator<'a> { - type Item = &'a Frame; - fn next(&mut self) -> Option<&'a Frame> { - loop { - let frame_id = match self.stack.pop() { - Some(frame_id) => frame_id, - None => return None, - }; - let frame = match self.frames.get(&frame_id) { - Some(frame) => frame, - None => { - warn!("Frame {:?} iterated after closure.", frame_id); - continue; - }, - }; - let pipeline = match self.pipelines.get(&frame.current) { - Some(pipeline) => pipeline, - None => { - warn!("Pipeline {:?} iterated after closure.", frame.current); - continue; - }, - }; - self.stack.extend(pipeline.children.iter().map(|&c| c)); - return Some(frame) - } - } -} - -pub struct SendableFrameTree { - pub pipeline: CompositionPipeline, - pub size: Option<TypedSize2D<PagePx, f32>>, - pub children: Vec<SendableFrameTree>, -} - -struct WebDriverData { - load_channel: Option<(PipelineId, IpcSender<webdriver_msg::LoadStatus>)> -} - -impl WebDriverData { - pub fn new() -> WebDriverData { - WebDriverData { - load_channel: None - } - } -} - -#[derive(Clone, Copy)] -enum ExitPipelineMode { - Normal, - Force, -} - -enum ChildProcess { - #[cfg(not(target_os = "windows"))] - Sandboxed(gaol::platform::process::Process), - #[cfg(not(target_os = "windows"))] - Unsandboxed(process::Child), -} - -impl<LTF: LayoutThreadFactory, STF: ScriptThreadFactory> Constellation<LTF, STF> { - pub fn start(state: InitialConstellationState) -> Sender<FromCompositorMsg> { - let (ipc_script_receiver, ipc_script_sender) = ConstellationChan::<FromScriptMsg>::new(); - let script_receiver = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_script_receiver); - let (compositor_sender, compositor_receiver) = channel(); - let (ipc_layout_receiver, ipc_layout_sender) = ConstellationChan::<FromLayoutMsg>::new(); - let layout_receiver = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_layout_receiver); - let (ipc_panic_receiver, ipc_panic_sender) = ConstellationChan::<PanicMsg>::new(); - let panic_receiver = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_panic_receiver); - let compositor_sender_clone = compositor_sender.clone(); - spawn_named("Constellation".to_owned(), move || { - let mut constellation: Constellation<LTF, STF> = Constellation { - script_sender: ipc_script_sender, - compositor_sender: compositor_sender_clone, - layout_sender: ipc_layout_sender, - script_receiver: script_receiver, - panic_sender: ipc_panic_sender, - compositor_receiver: compositor_receiver, - layout_receiver: layout_receiver, - panic_receiver: panic_receiver, - compositor_proxy: state.compositor_proxy, - devtools_chan: state.devtools_chan, - bluetooth_thread: state.bluetooth_thread, - resource_thread: state.resource_thread, - image_cache_thread: state.image_cache_thread, - font_cache_thread: state.font_cache_thread, - storage_thread: state.storage_thread, - pipelines: HashMap::new(), - frames: HashMap::new(), - pipeline_to_frame_map: HashMap::new(), - subpage_map: HashMap::new(), - pending_frames: vec!(), - next_pipeline_namespace_id: PipelineNamespaceId(0), - root_frame_id: None, - next_frame_id: FrameId(0), - focus_pipeline_id: None, - time_profiler_chan: state.time_profiler_chan, - mem_profiler_chan: state.mem_profiler_chan, - window_size: WindowSizeData { - visible_viewport: opts::get().initial_window_size.as_f32() * - ScaleFactor::new(1.0), - initial_viewport: opts::get().initial_window_size.as_f32() * - ScaleFactor::new(1.0), - device_pixel_ratio: - ScaleFactor::new(opts::get().device_pixels_per_px.unwrap_or(1.0)), - }, - phantom: PhantomData, - clipboard_ctx: if state.supports_clipboard { - ClipboardContext::new().ok() - } else { - None - }, - webdriver: WebDriverData::new(), - scheduler_chan: TimerScheduler::start(), - child_processes: Vec::new(), - document_states: HashMap::new(), - webrender_api_sender: state.webrender_api_sender, - handled_panic: false, - random_pipeline_closure: opts::get().random_pipeline_closure_probability.map(|prob| { - let seed = opts::get().random_pipeline_closure_seed.unwrap_or_else(random); - let rng = StdRng::from_seed(&[seed]); - warn!("Randomly closing pipelines."); - info!("Using seed {} for random pipeline closure.", seed); - (rng, prob) - }), - }; - let namespace_id = constellation.next_pipeline_namespace_id(); - PipelineNamespace::install(namespace_id); - constellation.run(); - }); - compositor_sender - } - - fn run(&mut self) { - loop { - // Randomly close a pipeline if --random-pipeline-closure-probability is set - // This is for testing the hardening of the constellation. - self.maybe_close_random_pipeline(); - if !self.handle_request() { - break; - } - } - } - - 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; - *i += 1; - namespace_id - } - - /// Helper function for creating a pipeline - fn new_pipeline(&mut self, - pipeline_id: PipelineId, - parent_info: Option<(PipelineId, SubpageId)>, - initial_window_size: Option<TypedSize2D<PagePx, f32>>, - script_channel: Option<IpcSender<ConstellationControlMsg>>, - load_data: LoadData) { - let spawning_paint_only = script_channel.is_some(); - let (pipeline, unprivileged_pipeline_content, privileged_pipeline_content) = - Pipeline::create::<LTF, STF>(InitialPipelineState { - id: pipeline_id, - parent_info: parent_info, - constellation_chan: self.script_sender.clone(), - layout_to_constellation_chan: self.layout_sender.clone(), - panic_chan: self.panic_sender.clone(), - scheduler_chan: self.scheduler_chan.clone(), - compositor_proxy: self.compositor_proxy.clone_compositor_proxy(), - devtools_chan: self.devtools_chan.clone(), - bluetooth_thread: self.bluetooth_thread.clone(), - image_cache_thread: self.image_cache_thread.clone(), - font_cache_thread: self.font_cache_thread.clone(), - resource_thread: self.resource_thread.clone(), - storage_thread: self.storage_thread.clone(), - time_profiler_chan: self.time_profiler_chan.clone(), - mem_profiler_chan: self.mem_profiler_chan.clone(), - window_size: initial_window_size, - script_chan: script_channel, - load_data: load_data, - device_pixel_ratio: self.window_size.device_pixel_ratio, - pipeline_namespace_id: self.next_pipeline_namespace_id(), - webrender_api_sender: self.webrender_api_sender.clone(), - }); - - if spawning_paint_only { - privileged_pipeline_content.start_paint_thread(); - } else { - privileged_pipeline_content.start_all(); - - // Spawn the child process. - // - // Yes, that's all there is to it! - if opts::multiprocess() { - self.spawn_multiprocess(pipeline_id, unprivileged_pipeline_content); - } else { - unprivileged_pipeline_content.start_all::<LTF, STF>(false); - } - } - - assert!(!self.pipelines.contains_key(&pipeline_id)); - self.pipelines.insert(pipeline_id, pipeline); - } - - #[cfg(not(target_os = "windows"))] - fn spawn_multiprocess(&mut self, - pipeline_id: PipelineId, - unprivileged_pipeline_content: UnprivilegedPipelineContent) - { - // Note that this function can panic, due to process creation, - // avoiding this panic would require a mechanism for dealing - // with low-resource scenarios. - let (server, token) = - IpcOneShotServer::<IpcSender<UnprivilegedPipelineContent>>::new() - .expect("Failed to create IPC one-shot server."); - - // If there is a sandbox, use the `gaol` API to create the child process. - let child_process = if opts::get().sandbox { - let mut command = sandbox::Command::me().expect("Failed to get current sandbox."); - command.arg("--content-process").arg(token); - - if let Ok(value) = env::var("RUST_BACKTRACE") { - command.env("RUST_BACKTRACE", value); - } - - let profile = sandboxing::content_process_sandbox_profile(); - ChildProcess::Sandboxed(Sandbox::new(profile).start(&mut command) - .expect("Failed to start sandboxed child process!")) - } else { - let path_to_self = env::current_exe() - .expect("Failed to get current executor."); - let mut child_process = process::Command::new(path_to_self); - child_process.arg("--content-process"); - child_process.arg(token); - - if let Ok(value) = env::var("RUST_BACKTRACE") { - child_process.env("RUST_BACKTRACE", value); - } - - ChildProcess::Unsandboxed(child_process.spawn() - .expect("Failed to start unsandboxed child process!")) - }; - - self.child_processes.push(child_process); - let (_receiver, sender) = server.accept().expect("Server failed to accept."); - if let Err(e) = sender.send(unprivileged_pipeline_content) { - self.handle_send_error(pipeline_id, e); - } - } - - #[cfg(target_os = "windows")] - fn spawn_multiprocess(&mut self, _: PipelineId, _: UnprivilegedPipelineContent) { - error!("Multiprocess is not supported on Windows."); - process::exit(1); - } - - // Push a new (loading) pipeline to the list of pending frame changes - fn push_pending_frame(&mut self, new_pipeline_id: PipelineId, - old_pipeline_id: Option<PipelineId>) { - self.pending_frames.push(FrameChange { - old_pipeline_id: old_pipeline_id, - new_pipeline_id: new_pipeline_id, - document_ready: false, - }); - } - - // 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. - fn current_frame_tree_iter(&self, frame_id_root: Option<FrameId>) -> FrameTreeIterator { - FrameTreeIterator { - stack: frame_id_root.into_iter().collect(), - pipelines: &self.pipelines, - frames: &self.frames, - } - } - - // Create a new frame and update the internal bookkeeping. - fn new_frame(&mut self, pipeline_id: PipelineId) -> FrameId { - let id = self.next_frame_id; - let FrameId(ref mut i) = self.next_frame_id; - *i += 1; - - let frame = Frame::new(pipeline_id); - - assert!(!self.pipeline_to_frame_map.contains_key(&pipeline_id)); - assert!(!self.frames.contains_key(&id)); - - self.pipeline_to_frame_map.insert(pipeline_id, id); - self.frames.insert(id, frame); - - id - } - - /// Handles loading pages, navigation, and granting access to the compositor - #[allow(unsafe_code)] - fn handle_request(&mut self) -> bool { - enum Request { - Script(FromScriptMsg), - Compositor(FromCompositorMsg), - Layout(FromLayoutMsg), - Panic(PanicMsg) - } - - // Get one incoming request. - // This is one of the few places where the compositor is - // allowed to panic. If one of the receiver.recv() calls - // fails, it is because the matching sender has been - // reclaimed, but this can't happen in normal execution - // because the constellation keeps a pointer to the sender, - // so it should never be reclaimed. A possible scenario in - // which receiver.recv() fails is if some unsafe code - // produces undefined behaviour, resulting in the destructor - // being called. If this happens, there's not much we can do - // other than panic. - let request = { - let receiver_from_script = &self.script_receiver; - let receiver_from_compositor = &self.compositor_receiver; - let receiver_from_layout = &self.layout_receiver; - let receiver_from_panic = &self.panic_receiver; - select! { - msg = receiver_from_script.recv() => - Request::Script(msg.expect("Unexpected script channel panic in constellation")), - msg = receiver_from_compositor.recv() => - Request::Compositor(msg.expect("Unexpected compositor channel panic in constellation")), - msg = receiver_from_layout.recv() => - Request::Layout(msg.expect("Unexpected layout channel panic in constellation")), - msg = receiver_from_panic.recv() => - Request::Panic(msg.expect("Unexpected panic channel panic in constellation")) - } - }; - - // Process the request. - match request { - // Messages from compositor - - - Request::Compositor(FromCompositorMsg::Exit) => { - debug!("constellation exiting"); - self.handle_exit(); - return false; - } - // The compositor discovered the size of a subframe. This needs to be reflected by all - // frame trees in the navigation context containing the subframe. - Request::Compositor(FromCompositorMsg::FrameSize(pipeline_id, size)) => { - debug!("constellation got frame size message"); - self.handle_frame_size_msg(pipeline_id, &Size2D::from_untyped(&size)); - } - Request::Compositor(FromCompositorMsg::GetFrame(pipeline_id, resp_chan)) => { - debug!("constellation got get root pipeline message"); - self.handle_get_frame(pipeline_id, resp_chan); - } - Request::Compositor(FromCompositorMsg::GetPipeline(frame_id, resp_chan)) => { - debug!("constellation got get root pipeline message"); - self.handle_get_pipeline(frame_id, resp_chan); - } - Request::Compositor(FromCompositorMsg::GetPipelineTitle(pipeline_id)) => { - debug!("constellation got get-pipeline-title message"); - self.handle_get_pipeline_title_msg(pipeline_id); - } - Request::Compositor(FromCompositorMsg::KeyEvent(key, state, modifiers)) => { - debug!("constellation got key event message"); - self.handle_key_msg(key, state, modifiers); - } - // Load a new page from a typed url - // If there is already a pending page (self.pending_frames), it will not be overridden; - // However, if the id is not encompassed by another change, it will be. - Request::Compositor(FromCompositorMsg::LoadUrl(source_id, load_data)) => { - debug!("constellation got URL load message from compositor"); - self.handle_load_url_msg(source_id, load_data); - } - Request::Compositor(FromCompositorMsg::IsReadyToSaveImage(pipeline_states)) => { - let is_ready = self.handle_is_ready_to_save_image(pipeline_states); - if opts::get().is_running_problem_test { - println!("got ready to save image query, result is {:?}", is_ready); - } - let is_ready = is_ready == ReadyToSave::Ready; - self.compositor_proxy.send(ToCompositorMsg::IsReadyToSaveImageReply(is_ready)); - if opts::get().is_running_problem_test { - println!("sent response"); - } - } - // This should only be called once per constellation, and only by the browser - Request::Compositor(FromCompositorMsg::InitLoadUrl(url)) => { - debug!("constellation got init load URL message"); - self.handle_init_load(url); - } - // Handle a forward or back request - Request::Compositor(FromCompositorMsg::Navigate(pipeline_info, direction)) => { - debug!("constellation got navigation message from compositor"); - self.handle_navigate_msg(pipeline_info, direction); - } - Request::Compositor(FromCompositorMsg::WindowSize(new_size, size_type)) => { - debug!("constellation got window resize message"); - self.handle_window_size_msg(new_size, size_type); - } - Request::Compositor(FromCompositorMsg::TickAnimation(pipeline_id, tick_type)) => { - self.handle_tick_animation(pipeline_id, tick_type) - } - Request::Compositor(FromCompositorMsg::WebDriverCommand(command)) => { - debug!("constellation got webdriver command message"); - self.handle_webdriver_msg(command); - } - - - // Messages from script - - - Request::Script(FromScriptMsg::ScriptLoadedURLInIFrame(load_info)) => { - debug!("constellation got iframe URL load message {:?} {:?} {:?}", - load_info.containing_pipeline_id, - load_info.old_subpage_id, - load_info.new_subpage_id); - self.handle_script_loaded_url_in_iframe_msg(load_info); - } - Request::Script(FromScriptMsg::ChangeRunningAnimationsState(pipeline_id, animation_state)) => { - self.handle_change_running_animations_state(pipeline_id, animation_state) - } - // Load a new page from a mouse click - // If there is already a pending page (self.pending_frames), it will not be overridden; - // However, if the id is not encompassed by another change, it will be. - Request::Script(FromScriptMsg::LoadUrl(source_id, load_data)) => { - debug!("constellation got URL load message from script"); - self.handle_load_url_msg(source_id, load_data); - } - // A page loaded has completed all parsing, script, and reflow messages have been sent. - Request::Script(FromScriptMsg::LoadComplete(pipeline_id)) => { - debug!("constellation got load complete message"); - self.handle_load_complete_msg(&pipeline_id) - } - // The DOM load event fired on a document - Request::Script(FromScriptMsg::DOMLoad(pipeline_id)) => { - debug!("constellation got dom load message"); - self.handle_dom_load(pipeline_id) - } - // Handle a forward or back request - Request::Script(FromScriptMsg::Navigate(pipeline_info, direction)) => { - debug!("constellation got navigation message from script"); - self.handle_navigate_msg(pipeline_info, direction); - } - // Notification that the new document is ready to become active - Request::Script(FromScriptMsg::ActivateDocument(pipeline_id)) => { - debug!("constellation got activate document message"); - self.handle_activate_document_msg(pipeline_id); - } - // Update pipeline url after redirections - Request::Script(FromScriptMsg::SetFinalUrl(pipeline_id, final_url)) => { - // The script may have finished loading after we already started shutting down. - if let Some(ref mut pipeline) = self.pipelines.get_mut(&pipeline_id) { - debug!("constellation got set final url message"); - pipeline.url = final_url; - } else { - warn!("constellation got set final url message for dead pipeline"); - } - } - Request::Script(FromScriptMsg::MozBrowserEvent(pipeline_id, - subpage_id, - event)) => { - debug!("constellation got mozbrowser event message"); - self.handle_mozbrowser_event_msg(pipeline_id, - subpage_id, - event); - } - Request::Script(FromScriptMsg::Focus(pipeline_id)) => { - debug!("constellation got focus message"); - self.handle_focus_msg(pipeline_id); - } - Request::Script(FromScriptMsg::ForwardMouseButtonEvent( - pipeline_id, event_type, button, point)) => { - let event = CompositorEvent::MouseButtonEvent(event_type, button, point); - let msg = ConstellationControlMsg::SendEvent(pipeline_id, event); - let result = match self.pipelines.get(&pipeline_id) { - None => { debug!("Pipeline {:?} got mouse button event after closure.", pipeline_id); return true; } - Some(pipeline) => pipeline.script_chan.send(msg), - }; - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - } - Request::Script(FromScriptMsg::ForwardMouseMoveEvent(pipeline_id, point)) => { - let event = CompositorEvent::MouseMoveEvent(Some(point)); - let msg = ConstellationControlMsg::SendEvent(pipeline_id, event); - let result = match self.pipelines.get(&pipeline_id) { - None => { debug!("Pipeline {:?} got mouse move event after closure.", pipeline_id); return true; } - Some(pipeline) => pipeline.script_chan.send(msg), - }; - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - } - Request::Script(FromScriptMsg::GetClipboardContents(sender)) => { - let result = match self.clipboard_ctx { - Some(ref ctx) => match ctx.get_contents() { - Ok(result) => result, - Err(e) => { - warn!("Error getting clipboard contents ({}), defaulting to empty string", e); - "".to_owned() - }, - }, - None => "".to_owned() - }; - if let Err(e) = sender.send(result) { - warn!("Failed to send clipboard ({})", e); - } - } - Request::Script(FromScriptMsg::SetClipboardContents(s)) => { - if let Some(ref mut ctx) = self.clipboard_ctx { - if let Err(e) = ctx.set_contents(s) { - warn!("Error setting clipboard contents ({})", e); - } - } - } - Request::Script(FromScriptMsg::RemoveIFrame(pipeline_id, sender)) => { - debug!("constellation got remove iframe message"); - self.handle_remove_iframe_msg(pipeline_id); - if let Some(sender) = sender { - if let Err(e) = sender.send(()) { - warn!("Error replying to remove iframe ({})", e); - } - } - } - Request::Script(FromScriptMsg::NewFavicon(url)) => { - debug!("constellation got new favicon message"); - self.compositor_proxy.send(ToCompositorMsg::NewFavicon(url)); - } - Request::Script(FromScriptMsg::HeadParsed) => { - debug!("constellation got head parsed message"); - self.compositor_proxy.send(ToCompositorMsg::HeadParsed); - } - Request::Script(FromScriptMsg::CreateCanvasPaintThread(size, sender)) => { - debug!("constellation got create-canvas-paint-thread message"); - self.handle_create_canvas_paint_thread_msg(&size, sender) - } - Request::Script(FromScriptMsg::CreateWebGLPaintThread(size, attributes, sender)) => { - debug!("constellation got create-WebGL-paint-thread message"); - self.handle_create_webgl_paint_thread_msg(&size, attributes, sender) - } - Request::Script(FromScriptMsg::NodeStatus(message)) => { - debug!("constellation got NodeStatus message"); - self.compositor_proxy.send(ToCompositorMsg::Status(message)); - } - Request::Script(FromScriptMsg::SetDocumentState(pipeline_id, state)) => { - debug!("constellation got SetDocumentState message"); - self.document_states.insert(pipeline_id, state); - } - Request::Script(FromScriptMsg::Alert(pipeline_id, message, sender)) => { - debug!("constellation got Alert message"); - self.handle_alert(pipeline_id, message, sender); - } - - - // Messages from layout thread - - Request::Layout(FromLayoutMsg::ChangeRunningAnimationsState(pipeline_id, animation_state)) => { - self.handle_change_running_animations_state(pipeline_id, animation_state) - } - Request::Layout(FromLayoutMsg::SetCursor(cursor)) => { - self.handle_set_cursor_msg(cursor) - } - Request::Layout(FromLayoutMsg::ViewportConstrained(pipeline_id, constraints)) => { - debug!("constellation got viewport-constrained event message"); - self.handle_viewport_constrained_msg(pipeline_id, constraints); - } - - - // Panic messages - - Request::Panic((pipeline_id, panic_reason, backtrace)) => { - debug!("handling panic message ({:?})", pipeline_id); - self.handle_panic(pipeline_id, panic_reason, backtrace); - } - } - true - } - - fn handle_exit(&mut self) { - for (_id, ref pipeline) in &self.pipelines { - pipeline.exit(); - } - self.image_cache_thread.exit(); - if let Err(e) = self.resource_thread.send(net_traits::ControlMsg::Exit) { - warn!("Exit resource thread failed ({})", e); - } - if let Some(ref chan) = self.devtools_chan { - let msg = DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg); - if let Err(e) = chan.send(msg) { - warn!("Exit devtools failed ({})", e); - } - } - if let Err(e) = self.storage_thread.send(StorageThreadMsg::Exit) { - warn!("Exit storage thread failed ({})", e); - } - if let Err(e) = self.bluetooth_thread.send(BluetoothMethodMsg::Exit) { - warn!("Exit bluetooth thread failed ({})", e); - } - self.font_cache_thread.exit(); - self.compositor_proxy.send(ToCompositorMsg::ShutdownComplete); - } - - fn handle_send_error(&mut self, pipeline_id: PipelineId, err: IOError) { - // Treat send error the same as receiving a panic message - debug!("Pipeline {:?} send error ({}).", pipeline_id, err); - self.handle_panic(Some(pipeline_id), format!("Send failed ({})", err), String::from("<none>")); - } - - fn handle_panic(&mut self, pipeline_id: Option<PipelineId>, reason: String, backtrace: String) { - error!("Panic: {}", reason); - if !self.handled_panic || opts::get().full_backtraces { - // For the first panic, we print the full backtrace - error!("Backtrace:\n{}", backtrace); - } else { - error!("Backtrace skipped (run with -Z full-backtraces to see every backtrace)."); - } - - if opts::get().hard_fail { - // It's quite difficult to make Servo exit cleanly if some threads have failed. - // Hard fail exists for test runners so we crash and that's good enough. - error!("Pipeline failed in hard-fail mode. Crashing!"); - process::exit(1); - } - - debug!("Panic handler for pipeline {:?}: {}.", pipeline_id, reason); - - if let Some(pipeline_id) = pipeline_id { - - let parent_info = self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.parent_info); - let window_size = self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.size); - - // Notify the browser chrome that the pipeline has failed - self.trigger_mozbrowsererror(pipeline_id, reason, backtrace); - - self.close_pipeline(pipeline_id, ExitPipelineMode::Force); - - while let Some(pending_pipeline_id) = self.pending_frames.iter().find(|pending| { - pending.old_pipeline_id == Some(pipeline_id) - }).map(|frame| frame.new_pipeline_id) { - warn!("removing pending frame change for failed pipeline"); - self.close_pipeline(pending_pipeline_id, ExitPipelineMode::Force); - } - - warn!("creating replacement pipeline for about:failure"); - - let new_pipeline_id = PipelineId::new(); - self.new_pipeline(new_pipeline_id, - parent_info, - window_size, - None, - LoadData::new(Url::parse("about:failure").expect("infallible"), None, None)); - - self.push_pending_frame(new_pipeline_id, Some(pipeline_id)); - - } - - self.handled_panic = true; - } - - fn handle_init_load(&mut self, url: Url) { - let window_size = self.window_size.visible_viewport; - let root_pipeline_id = PipelineId::new(); - debug_assert!(PipelineId::fake_root_pipeline_id() == root_pipeline_id); - self.new_pipeline(root_pipeline_id, None, Some(window_size), None, LoadData::new(url.clone(), None, None)); - self.handle_load_start_msg(&root_pipeline_id); - self.push_pending_frame(root_pipeline_id, None); - self.compositor_proxy.send(ToCompositorMsg::ChangePageUrl(root_pipeline_id, url)); - } - - fn handle_frame_size_msg(&mut self, - pipeline_id: PipelineId, - size: &TypedSize2D<PagePx, f32>) { - let msg = ConstellationControlMsg::Resize(pipeline_id, WindowSizeData { - visible_viewport: *size, - initial_viewport: *size * ScaleFactor::new(1.0), - device_pixel_ratio: self.window_size.device_pixel_ratio, - }, WindowSizeType::Initial); - - // Store the new rect inside the pipeline - let result = { - // Find the pipeline that corresponds to this rectangle. It's possible that this - // pipeline may have already exited before we process this message, so just - // early exit if that occurs. - match self.pipelines.get_mut(&pipeline_id) { - Some(pipeline) => { - pipeline.size = Some(*size); - pipeline.script_chan.send(msg) - } - None => return, - } - }; - - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - } - - fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) { - let parent_info = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.parent_info, - None => return warn!("Pipeline {:?} loaded after closure.", pipeline_id), - }; - let subframe_parent_id = match parent_info { - Some(ref parent) => parent.0, - None => return warn!("Pipeline {:?} has no parent.", pipeline_id), - }; - let msg = ConstellationControlMsg::DispatchFrameLoadEvent { - target: pipeline_id, - parent: subframe_parent_id, - }; - let result = match self.pipelines.get(&subframe_parent_id) { - Some(pipeline) => pipeline.script_chan.send(msg), - None => return warn!("Pipeline {:?} subframe loaded after closure.", subframe_parent_id), - }; - if let Err(e) = result { - self.handle_send_error(subframe_parent_id, e); - } - } - - // The script thread associated with pipeline_id has loaded a URL in an iframe via script. This - // will result in a new pipeline being spawned and a frame tree being added to - // containing_page_pipeline_id's frame tree's children. This message is never the result of a - // page navigation. - fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfo) { - - let old_pipeline_id = load_info.old_subpage_id - .and_then(|old_subpage_id| self.subpage_map.get(&(load_info.containing_pipeline_id, old_subpage_id))) - .cloned(); - - let (load_data, script_chan, window_size) = { - - let old_pipeline = old_pipeline_id - .and_then(|old_pipeline_id| self.pipelines.get(&old_pipeline_id)); - - let source_pipeline = match self.pipelines.get(&load_info.containing_pipeline_id) { - Some(source_pipeline) => source_pipeline, - None => return warn!("Script loaded url in closed iframe {}.", load_info.containing_pipeline_id), - }; - - // If no url is specified, reload. - let load_data = load_info.load_data.unwrap_or_else(|| { - let url = match old_pipeline { - Some(old_pipeline) => old_pipeline.url.clone(), - None => Url::parse("about:blank").expect("infallible"), - }; - - // TODO - loaddata here should have referrer info (not None, None) - LoadData::new(url, None, None) - }); - - // Compare the pipeline's url to the new url. If the origin is the same, - // then reuse the script thread in creating the new pipeline - let source_url = &source_pipeline.url; - - // FIXME(#10968): this should probably match the origin check in - // HTMLIFrameElement::contentDocument. - let same_script = source_url.host() == load_data.url.host() && - source_url.port() == load_data.url.port() && - load_info.sandbox == IFrameSandboxState::IFrameUnsandboxed; - - // Reuse the script thread if the URL is same-origin - let script_chan = if same_script { - debug!("Constellation: loading same-origin iframe, \ - parent url {:?}, iframe url {:?}", source_url, load_data.url); - Some(source_pipeline.script_chan.clone()) - } else { - debug!("Constellation: loading cross-origin iframe, \ - parent url {:?}, iframe url {:?}", source_url, load_data.url); - None - }; - - let window_size = old_pipeline.and_then(|old_pipeline| old_pipeline.size); - - if let Some(old_pipeline) = old_pipeline { - old_pipeline.freeze(); - } - - (load_data, script_chan, window_size) - - }; - - // Create the new pipeline, attached to the parent and push to pending frames - self.new_pipeline(load_info.new_pipeline_id, - Some((load_info.containing_pipeline_id, load_info.new_subpage_id)), - window_size, - script_chan, - load_data); - - self.subpage_map.insert((load_info.containing_pipeline_id, load_info.new_subpage_id), - load_info.new_pipeline_id); - - self.push_pending_frame(load_info.new_pipeline_id, old_pipeline_id); - } - - fn handle_set_cursor_msg(&mut self, cursor: Cursor) { - self.compositor_proxy.send(ToCompositorMsg::SetCursor(cursor)) - } - - fn handle_change_running_animations_state(&mut self, - pipeline_id: PipelineId, - animation_state: AnimationState) { - self.compositor_proxy.send(ToCompositorMsg::ChangeRunningAnimationsState(pipeline_id, - animation_state)) - } - - fn handle_tick_animation(&mut self, pipeline_id: PipelineId, tick_type: AnimationTickType) { - let result = match tick_type { - AnimationTickType::Script => { - let msg = ConstellationControlMsg::TickAllAnimations(pipeline_id); - match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.script_chan.send(msg), - None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id), - } - } - AnimationTickType::Layout => { - let msg = LayoutControlMsg::TickAnimations; - match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.layout_chan.0.send(msg), - None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id), - } - } - }; - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - } - - fn handle_alert(&mut self, pipeline_id: PipelineId, message: String, sender: IpcSender<bool>) { - let display_alert_dialog = if prefs::get_pref("dom.mozbrowser.enabled").as_boolean().unwrap_or(false) { - let parent_pipeline_info = self.pipelines.get(&pipeline_id).and_then(|source| source.parent_info); - if let Some(_) = parent_pipeline_info { - let root_pipeline_id = self.root_frame_id - .and_then(|root_frame_id| self.frames.get(&root_frame_id)) - .map(|root_frame| root_frame.current); - - let ancestor_info = self.get_root_pipeline_and_containing_parent(&pipeline_id); - if let Some(ancestor_info) = ancestor_info { - if root_pipeline_id == Some(ancestor_info.0) { - match root_pipeline_id.and_then(|pipeline_id| self.pipelines.get(&pipeline_id)) { - Some(root_pipeline) => { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsershowmodalprompt - let event = MozBrowserEvent::ShowModalPrompt("alert".to_owned(), "Alert".to_owned(), - String::from(message), "".to_owned()); - root_pipeline.trigger_mozbrowser_event(ancestor_info.1, event); - } - None => return warn!("Alert sent to Pipeline {:?} after closure.", root_pipeline_id), - } - } else { - warn!("A non-current frame is trying to show an alert.") - } - } - false - } else { - true - } - } else { - true - }; - - let result = sender.send(display_alert_dialog); - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - } - - fn handle_load_url_msg(&mut self, source_id: PipelineId, load_data: LoadData) { - self.load_url(source_id, load_data); - } - - fn load_url(&mut self, source_id: PipelineId, load_data: LoadData) -> Option<PipelineId> { - // If this load targets an iframe, its framing element may exist - // in a separate script thread than the framed document that initiated - // the new load. The framing element must be notified about the - // requested change so it can update its internal state. - let parent_info = self.pipelines.get(&source_id).and_then(|source| source.parent_info); - match parent_info { - Some((parent_pipeline_id, subpage_id)) => { - self.handle_load_start_msg(&source_id); - // Message the constellation to find the script thread for this iframe - // and issue an iframe load through there. - let msg = ConstellationControlMsg::Navigate(parent_pipeline_id, subpage_id, load_data); - let result = match self.pipelines.get(&parent_pipeline_id) { - Some(parent_pipeline) => parent_pipeline.script_chan.send(msg), - None => { - warn!("Pipeline {:?} child loaded after closure", parent_pipeline_id); - return None; - }, - }; - if let Err(e) = result { - self.handle_send_error(parent_pipeline_id, e); - } - Some(source_id) - } - None => { - // Make sure no pending page would be overridden. - for frame_change in &self.pending_frames { - if frame_change.old_pipeline_id == Some(source_id) { - // id that sent load msg is being changed already; abort - return None; - } - } - - if !self.pipeline_is_in_current_frame(source_id) { - // Disregard this load if the navigating pipeline is not actually - // active. This could be caused by a delayed navigation (eg. from - // a timer) or a race between multiple navigations (such as an - // onclick handler on an anchor element). - return None; - } - - self.handle_load_start_msg(&source_id); - // Being here means either there are no pending frames, or none of the pending - // changes would be overridden by changing the subframe associated with source_id. - - // Create the new pipeline - let window_size = self.pipelines.get(&source_id).and_then(|source| source.size); - let new_pipeline_id = PipelineId::new(); - self.new_pipeline(new_pipeline_id, None, window_size, None, load_data); - self.push_pending_frame(new_pipeline_id, Some(source_id)); - - // Send message to ScriptThread that will suspend all timers - match self.pipelines.get(&source_id) { - Some(source) => source.freeze(), - None => warn!("Pipeline {:?} loaded after closure", source_id), - }; - Some(new_pipeline_id) - } - } - } - - fn handle_load_start_msg(&mut self, pipeline_id: &PipelineId) { - if let Some(frame_id) = self.pipeline_to_frame_map.get(pipeline_id) { - if let Some(frame) = self.frames.get(frame_id) { - let forward = !frame.next.is_empty(); - let back = !frame.prev.is_empty(); - self.compositor_proxy.send(ToCompositorMsg::LoadStart(back, forward)); - } - } - } - - fn handle_load_complete_msg(&mut self, pipeline_id: &PipelineId) { - if let Some(&frame_id) = self.pipeline_to_frame_map.get(pipeline_id) { - if let Some(frame) = self.frames.get(&frame_id) { - let forward = frame.next.is_empty(); - let back = frame.prev.is_empty(); - let root = self.root_frame_id.is_none() || self.root_frame_id == Some(frame_id); - self.compositor_proxy.send(ToCompositorMsg::LoadComplete(back, forward, root)); - } - } - } - - fn handle_dom_load(&mut self, pipeline_id: PipelineId) { - let mut webdriver_reset = false; - if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel { - debug!("Sending load to WebDriver"); - if expected_pipeline_id == pipeline_id { - let _ = reply_chan.send(webdriver_msg::LoadStatus::LoadComplete); - webdriver_reset = true; - } - } - if webdriver_reset { - self.webdriver.load_channel = None; - } - - self.handle_subframe_loaded(pipeline_id); - } - - fn handle_navigate_msg(&mut self, - pipeline_info: Option<(PipelineId, SubpageId)>, - direction: constellation_msg::NavigationDirection) { - debug!("received message to navigate {:?}", direction); - - // Get the frame id associated with the pipeline that sent - // the navigate message, or use root frame id by default. - let frame_id = pipeline_info - .and_then(|info| self.subpage_map.get(&info)) - .and_then(|pipeline_id| self.pipeline_to_frame_map.get(&pipeline_id)) - .cloned() - .or(self.root_frame_id); - - // If the frame_id lookup fails, then we are in the middle of tearing down - // the root frame, so it is reasonable to silently ignore the navigation. - let frame_id = match frame_id { - None => return warn!("Navigation after root's closure."), - Some(frame_id) => frame_id, - }; - - // Check if the currently focused pipeline is the pipeline being replaced - // (or a child of it). This has to be done here, before the current - // frame tree is modified below. - let update_focus_pipeline = self.focused_pipeline_in_tree(frame_id); - - // Get the ids for the previous and next pipelines. - let (prev_pipeline_id, next_pipeline_id) = match self.frames.get_mut(&frame_id) { - Some(frame) => { - let next = match direction { - NavigationDirection::Forward => { - match frame.next.pop() { - None => { - warn!("no next page to navigate to"); - return; - }, - Some(next) => { - frame.prev.push(frame.current); - next - }, - } - } - NavigationDirection::Back => { - match frame.prev.pop() { - None => { - warn!("no previous page to navigate to"); - return; - }, - Some(prev) => { - frame.next.push(frame.current); - prev - }, - } - } - }; - let prev = frame.current; - frame.current = next; - (prev, next) - }, - None => { - warn!("no frame to navigate from"); - return; - }, - }; - - // If the currently focused pipeline is the one being changed (or a child - // of the pipeline being changed) then update the focus pipeline to be - // the replacement. - if update_focus_pipeline { - self.focus_pipeline_id = Some(next_pipeline_id); - } - - // Suspend the old pipeline, and resume the new one. - if let Some(prev_pipeline) = self.pipelines.get(&prev_pipeline_id) { - prev_pipeline.freeze(); - } - if let Some(next_pipeline) = self.pipelines.get(&next_pipeline_id) { - next_pipeline.thaw(); - } - - // Set paint permissions correctly for the compositor layers. - self.revoke_paint_permission(prev_pipeline_id); - self.send_frame_tree_and_grant_paint_permission(); - - // Update the owning iframe to point to the new subpage id. - // This makes things like contentDocument work correctly. - if let Some((parent_pipeline_id, subpage_id)) = pipeline_info { - let new_subpage_id = match self.pipelines.get(&next_pipeline_id) { - None => return warn!("Pipeline {:?} navigated to after closure.", next_pipeline_id), - Some(pipeline) => match pipeline.parent_info { - None => return warn!("Pipeline {:?} has no parent info.", next_pipeline_id), - Some((_, new_subpage_id)) => new_subpage_id, - }, - }; - let msg = ConstellationControlMsg::UpdateSubpageId(parent_pipeline_id, - subpage_id, - new_subpage_id, - next_pipeline_id); - let result = match self.pipelines.get(&parent_pipeline_id) { - None => return warn!("Pipeline {:?} child navigated after closure.", parent_pipeline_id), - Some(pipeline) => pipeline.script_chan.send(msg), - }; - if let Err(e) = result { - self.handle_send_error(parent_pipeline_id, e); - } - - // If this is an iframe, send a mozbrowser location change event. - // This is the result of a back/forward navigation. - self.trigger_mozbrowserlocationchange(next_pipeline_id); - } - } - - fn handle_key_msg(&mut self, key: Key, state: KeyState, mods: KeyModifiers) { - // Send to the explicitly focused pipeline (if it exists), or the root - // frame's current pipeline. If neither exist, fall back to sending to - // the compositor below. - let root_pipeline_id = self.root_frame_id - .and_then(|root_frame_id| self.frames.get(&root_frame_id)) - .map(|root_frame| root_frame.current); - let pipeline_id = self.focus_pipeline_id.or(root_pipeline_id); - - match pipeline_id { - Some(pipeline_id) => { - let event = CompositorEvent::KeyEvent(key, state, mods); - let msg = ConstellationControlMsg::SendEvent(pipeline_id, event); - let result = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.script_chan.send(msg), - None => return debug!("Pipeline {:?} got key event after closure.", pipeline_id), - }; - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - }, - None => { - let event = ToCompositorMsg::KeyEvent(key, state, mods); - self.compositor_proxy.clone_compositor_proxy().send(event); - } - } - } - - fn handle_get_pipeline_title_msg(&mut self, pipeline_id: PipelineId) { - let result = match self.pipelines.get(&pipeline_id) { - None => return self.compositor_proxy.send(ToCompositorMsg::ChangePageTitle(pipeline_id, None)), - Some(pipeline) => pipeline.script_chan.send(ConstellationControlMsg::GetTitle(pipeline_id)), - }; - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - } - - fn handle_mozbrowser_event_msg(&mut self, - containing_pipeline_id: PipelineId, - subpage_id: SubpageId, - event: MozBrowserEvent) { - assert!(prefs::get_pref("dom.mozbrowser.enabled").as_boolean().unwrap_or(false)); - - // Find the script channel for the given parent pipeline, - // and pass the event to that script thread. - // If the pipeline lookup fails, it is because we have torn down the pipeline, - // so it is reasonable to silently ignore the event. - match self.pipelines.get(&containing_pipeline_id) { - Some(pipeline) => pipeline.trigger_mozbrowser_event(subpage_id, event), - None => warn!("Pipeline {:?} handling mozbrowser event after closure.", containing_pipeline_id), - } - } - - fn handle_get_pipeline(&mut self, frame_id: Option<FrameId>, - resp_chan: IpcSender<Option<(PipelineId, bool)>>) { - let current_pipeline_id = frame_id.or(self.root_frame_id) - .and_then(|frame_id| self.frames.get(&frame_id)) - .map(|frame| frame.current); - let current_pipeline_id_loaded = current_pipeline_id - .map(|id| (id, true)); - let pipeline_id_loaded = self.pending_frames.iter().rev() - .find(|x| x.old_pipeline_id == current_pipeline_id) - .map(|x| (x.new_pipeline_id, x.document_ready)) - .or(current_pipeline_id_loaded); - if let Err(e) = resp_chan.send(pipeline_id_loaded) { - warn!("Failed get_pipeline response ({}).", e); - } - } - - fn handle_get_frame(&mut self, - pipeline_id: PipelineId, - resp_chan: IpcSender<Option<FrameId>>) { - let frame_id = self.pipeline_to_frame_map.get(&pipeline_id).map(|x| *x); - if let Err(e) = resp_chan.send(frame_id) { - warn!("Failed get_frame response ({}).", e); - } - } - - fn focus_parent_pipeline(&mut self, pipeline_id: PipelineId) { - let parent_info = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.parent_info, - None => return warn!("Pipeline {:?} focus parent after closure.", pipeline_id), - }; - let (containing_pipeline_id, subpage_id) = match parent_info { - Some(info) => info, - None => return warn!("Pipeline {:?} focus has no parent.", pipeline_id), - }; - - // Send a message to the parent of the provided pipeline (if it exists) - // telling it to mark the iframe element as focused. - let msg = ConstellationControlMsg::FocusIFrame(containing_pipeline_id, subpage_id); - let result = match self.pipelines.get(&containing_pipeline_id) { - Some(pipeline) => pipeline.script_chan.send(msg), - None => return warn!("Pipeline {:?} focus after closure.", containing_pipeline_id), - }; - if let Err(e) = result { - self.handle_send_error(containing_pipeline_id, e); - } - self.focus_parent_pipeline(containing_pipeline_id); - } - - fn handle_focus_msg(&mut self, pipeline_id: PipelineId) { - self.focus_pipeline_id = Some(pipeline_id); - - // Focus parent iframes recursively - self.focus_parent_pipeline(pipeline_id); - } - - fn handle_remove_iframe_msg(&mut self, pipeline_id: PipelineId) { - let frame_id = self.pipeline_to_frame_map.get(&pipeline_id).map(|id| *id); - match frame_id { - Some(frame_id) => { - // This iframe has already loaded and been added to the frame tree. - self.close_frame(frame_id, ExitPipelineMode::Normal); - } - None => { - // This iframe is currently loading / painting for the first time. - // In this case, it doesn't exist in the frame tree, but the pipeline - // still needs to be shut down. - self.close_pipeline(pipeline_id, ExitPipelineMode::Normal); - } - } - } - - fn handle_create_canvas_paint_thread_msg( - &mut self, - size: &Size2D<i32>, - response_sender: IpcSender<IpcSender<CanvasMsg>>) { - let webrender_api = self.webrender_api_sender.clone(); - let sender = CanvasPaintThread::start(*size, webrender_api); - if let Err(e) = response_sender.send(sender) { - warn!("Create canvas paint thread response failed ({})", e); - } - } - - fn handle_create_webgl_paint_thread_msg( - &mut self, - size: &Size2D<i32>, - attributes: GLContextAttributes, - response_sender: IpcSender<Result<(IpcSender<CanvasMsg>, GLLimits), String>>) { - let webrender_api = self.webrender_api_sender.clone(); - let response = WebGLPaintThread::start(*size, attributes, webrender_api); - - if let Err(e) = response_sender.send(response) { - warn!("Create WebGL paint thread response failed ({})", e); - } - } - - fn handle_webdriver_msg(&mut self, msg: WebDriverCommandMsg) { - // Find the script channel for the given parent pipeline, - // and pass the event to that script thread. - match msg { - WebDriverCommandMsg::LoadUrl(pipeline_id, load_data, reply) => { - self.load_url_for_webdriver(pipeline_id, load_data, reply); - }, - WebDriverCommandMsg::Refresh(pipeline_id, reply) => { - let load_data = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => LoadData::new(pipeline.url.clone(), None, None), - None => return warn!("Pipeline {:?} Refresh after closure.", pipeline_id), - }; - self.load_url_for_webdriver(pipeline_id, load_data, reply); - } - WebDriverCommandMsg::ScriptCommand(pipeline_id, cmd) => { - let control_msg = ConstellationControlMsg::WebDriverScriptCommand(pipeline_id, cmd); - let result = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.script_chan.send(control_msg), - None => return warn!("Pipeline {:?} ScriptCommand after closure.", pipeline_id), - }; - if let Err(e) = result { - self.handle_send_error(pipeline_id, e); - } - }, - WebDriverCommandMsg::SendKeys(pipeline_id, cmd) => { - let script_channel = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline.script_chan.clone(), - None => return warn!("Pipeline {:?} SendKeys after closure.", pipeline_id), - }; - for (key, mods, state) in cmd { - let event = CompositorEvent::KeyEvent(key, state, mods); - let control_msg = ConstellationControlMsg::SendEvent(pipeline_id, event); - if let Err(e) = script_channel.send(control_msg) { - return self.handle_send_error(pipeline_id, e); - } - } - }, - WebDriverCommandMsg::TakeScreenshot(pipeline_id, reply) => { - let current_pipeline_id = self.root_frame_id - .and_then(|root_frame_id| self.frames.get(&root_frame_id)) - .map(|root_frame| root_frame.current); - if Some(pipeline_id) == current_pipeline_id { - self.compositor_proxy.send(ToCompositorMsg::CreatePng(reply)); - } else { - if let Err(e) = reply.send(None) { - warn!("Screenshot reply failed ({})", e); - } - } - }, - } - } - - fn load_url_for_webdriver(&mut self, - pipeline_id: PipelineId, - load_data: LoadData, - reply: IpcSender<webdriver_msg::LoadStatus>) { - let new_pipeline_id = self.load_url(pipeline_id, load_data); - if let Some(id) = new_pipeline_id { - self.webdriver.load_channel = Some((id, reply)); - } - } - - fn add_or_replace_pipeline_in_frame_tree(&mut self, frame_change: FrameChange) { - - // If the currently focused pipeline is the one being changed (or a child - // of the pipeline being changed) then update the focus pipeline to be - // the replacement. - if let Some(old_pipeline_id) = frame_change.old_pipeline_id { - if let Some(&old_frame_id) = self.pipeline_to_frame_map.get(&old_pipeline_id) { - if self.focused_pipeline_in_tree(old_frame_id) { - self.focus_pipeline_id = Some(frame_change.new_pipeline_id); - } - } - } - - let evicted_frames = frame_change.old_pipeline_id.and_then(|old_pipeline_id| { - // The new pipeline is replacing an old one. - // Remove paint permissions for the pipeline being replaced. - self.revoke_paint_permission(old_pipeline_id); - - // Add new pipeline to navigation frame, and return frames evicted from history. - self.pipeline_to_frame_map.get(&old_pipeline_id).cloned() - .and_then(|frame_id| { - self.pipeline_to_frame_map.insert(frame_change.new_pipeline_id, frame_id); - self.frames.get_mut(&frame_id) - }).map(|frame| frame.load(frame_change.new_pipeline_id)) - }); - - if let None = evicted_frames { - // The new pipeline is in a new frame with no history - let frame_id = self.new_frame(frame_change.new_pipeline_id); - - // If a child frame, add it to the parent pipeline. Otherwise - // it must surely be the root frame being created! - match self.pipelines.get(&frame_change.new_pipeline_id).and_then(|pipeline| pipeline.parent_info) { - Some((parent_id, _)) => { - if let Some(parent) = self.pipelines.get_mut(&parent_id) { - parent.add_child(frame_id); - } - } - None => { - assert!(self.root_frame_id.is_none()); - self.root_frame_id = Some(frame_id); - } - } - } - - // Build frame tree and send permission - self.send_frame_tree_and_grant_paint_permission(); - - // If this is an iframe, send a mozbrowser location change event. - // This is the result of a link being clicked and a navigation completing. - self.trigger_mozbrowserlocationchange(frame_change.new_pipeline_id); - - // Remove any evicted frames - for pipeline_id in evicted_frames.unwrap_or_default() { - self.close_pipeline(pipeline_id, ExitPipelineMode::Normal); - } - - } - - fn handle_activate_document_msg(&mut self, pipeline_id: PipelineId) { - debug!("Document ready to activate {:?}", pipeline_id); - - if let Some(ref child_pipeline) = self.pipelines.get(&pipeline_id) { - if let Some(ref parent_info) = child_pipeline.parent_info { - if let Some(parent_pipeline) = self.pipelines.get(&parent_info.0) { - let _ = parent_pipeline.script_chan - .send(ConstellationControlMsg::FramedContentChanged( - parent_info.0, - parent_info.1)); - } - } - } - - // If this pipeline is already part of the current frame tree, - // we don't need to do anything. - if self.pipeline_is_in_current_frame(pipeline_id) { - return; - } - - // Find the pending frame change whose new pipeline id is pipeline_id. - // If it is found, mark this pending frame as ready to be enabled. - let pending_index = self.pending_frames.iter().rposition(|frame_change| { - frame_change.new_pipeline_id == pipeline_id - }); - if let Some(pending_index) = pending_index { - self.pending_frames[pending_index].document_ready = true; - } - - // This is a bit complex. We need to loop through pending frames and find - // ones that can be swapped. A frame can be swapped (enabled) once it is - // ready to layout (has document_ready set), and also has no dependencies - // (i.e. the pipeline it is replacing has been enabled and now has a frame). - // The outer loop is required because any time a pipeline is enabled, that - // may affect whether other pending frames are now able to be enabled. On the - // other hand, if no frames can be enabled after looping through all pending - // frames, we can safely exit the loop, knowing that we will need to wait on - // a dependent pipeline to be ready to paint. - while let Some(valid_frame_change) = self.pending_frames.iter().rposition(|frame_change| { - let waiting_on_dependency = frame_change.old_pipeline_id.map_or(false, |old_pipeline_id| { - self.pipeline_to_frame_map.get(&old_pipeline_id).is_none() - }); - frame_change.document_ready && !waiting_on_dependency - }) { - let frame_change = self.pending_frames.swap_remove(valid_frame_change); - self.add_or_replace_pipeline_in_frame_tree(frame_change); - } - } - - /// Called when the window is resized. - fn handle_window_size_msg(&mut self, new_size: WindowSizeData, size_type: WindowSizeType) { - debug!("handle_window_size_msg: {:?} {:?}", new_size.initial_viewport.to_untyped(), - new_size.visible_viewport.to_untyped()); - - if let Some(root_frame_id) = self.root_frame_id { - // Send Resize (or ResizeInactive) messages to each - // pipeline in the frame tree. - let frame = match self.frames.get(&root_frame_id) { - None => return warn!("Frame {:?} resized after closing.", root_frame_id), - Some(frame) => frame, - }; - let pipeline_id = frame.current; - let pipeline = match self.pipelines.get(&pipeline_id) { - None => return warn!("Pipeline {:?} resized after closing.", pipeline_id), - Some(pipeline) => pipeline, - }; - let _ = pipeline.script_chan.send(ConstellationControlMsg::Resize( - pipeline.id, - new_size, - size_type - )); - for pipeline_id in frame.prev.iter().chain(&frame.next) { - let pipeline = match self.pipelines.get(&pipeline_id) { - None => { - warn!("Inactive pipeline {:?} resized after closing.", pipeline_id); - continue; - }, - Some(pipeline) => pipeline, - }; - let _ = pipeline.script_chan.send(ConstellationControlMsg::ResizeInactive( - pipeline.id, - new_size - )); - } - } - - // Send resize message to any pending pipelines that aren't loaded yet. - for pending_frame in &self.pending_frames { - let pipeline_id = pending_frame.new_pipeline_id; - let pipeline = match self.pipelines.get(&pipeline_id) { - None => { warn!("Pending pipeline {:?} is closed", pipeline_id); continue; } - Some(pipeline) => pipeline, - }; - if pipeline.parent_info.is_none() { - let _ = pipeline.script_chan.send(ConstellationControlMsg::Resize( - pipeline.id, - new_size, - size_type - )); - } - } - - self.window_size = new_size; - } - - /// Handle updating actual viewport / zoom due to @viewport rules - fn handle_viewport_constrained_msg(&mut self, - pipeline_id: PipelineId, - constraints: ViewportConstraints) { - self.compositor_proxy.send(ToCompositorMsg::ViewportConstrained(pipeline_id, constraints)); - } - - /// Checks the state of all script and layout pipelines to see if they are idle - /// and compares the current layout state to what the compositor has. This is used - /// to check if the output image is "stable" and can be written as a screenshot - /// for reftests. - /// Since this function is only used in reftests, we do not harden it against panic. - fn handle_is_ready_to_save_image(&mut self, - pipeline_states: HashMap<PipelineId, Epoch>) -> ReadyToSave { - // Note that this function can panic, due to ipc-channel creation failure. - // avoiding this panic would require a mechanism for dealing - // with low-resource scenarios. - // - // If there is no root frame yet, the initial page has - // not loaded, so there is nothing to save yet. - if self.root_frame_id.is_none() { - return ReadyToSave::NoRootFrame; - } - - // If there are pending loads, wait for those to complete. - if self.pending_frames.len() > 0 { - return ReadyToSave::PendingFrames; - } - - // Step through the current frame tree, 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 - // screenshot can safely be written. - for frame in self.current_frame_tree_iter(self.root_frame_id) { - - let pipeline_id = frame.current; - - let pipeline = match self.pipelines.get(&pipeline_id) { - None => { warn!("Pipeline {:?} screenshot while closing.", pipeline_id); continue; }, - 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 (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); - let msg = LayoutControlMsg::GetWebFontLoadState(sender); - if let Err(e) = pipeline.layout_chan.0.send(msg) { - warn!("Get web font failed ({})", e); - } - if receiver.recv().unwrap_or(true) { - return ReadyToSave::WebFontNotLoaded; - } - - // See if this pipeline has reached idle script state yet. - match self.document_states.get(&frame.current) { - Some(&DocumentState::Idle) => {} - Some(&DocumentState::Pending) | None => { - return ReadyToSave::DocumentLoading; - } - } - - // Check the visible rectangle for this pipeline. If the constellation has received a - // size for the pipeline, then its painting should be up to date. If the constellation - // *hasn't* received a size, it could be that the layer was hidden by script before the - // compositor discovered it, so we just don't check the layer. - if let Some(size) = pipeline.size { - // If the rectangle for this pipeline is zero sized, it will - // never be painted. In this case, don't query the layout - // thread as it won't contribute to the final output image. - if size == Size2D::zero() { - continue; - } - - // Get the epoch that the compositor has drawn for this pipeline. - let compositor_epoch = pipeline_states.get(&frame.current); - 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 (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); - let LayoutControlChan(ref layout_chan) = pipeline.layout_chan; - if let Err(e) = layout_chan.send(LayoutControlMsg::GetCurrentEpoch(sender)) { - warn!("Failed to send GetCurrentEpoch ({}).", e); - } - match receiver.recv() { - Err(e) => warn!("Failed to receive current epoch ({}).", e), - Ok(layout_thread_epoch) => if layout_thread_epoch != *compositor_epoch { - return ReadyToSave::EpochMismatch; - }, - } - } - None => { - // The compositor doesn't know about this pipeline yet. - // Assume it hasn't rendered yet. - return ReadyToSave::PipelineUnknown; - } - } - } - } - - // All script threads are idle and layout epochs match compositor, so output image! - ReadyToSave::Ready - } - - /// Checks whether the pipeline or its ancestors are private - #[allow(dead_code)] - fn check_is_pipeline_private(&self, pipeline_id: PipelineId) -> bool { - let mut pipeline_id = Some(pipeline_id); - while let Some(pipeline) = pipeline_id.and_then(|id| self.pipelines.get(&id)) { - if pipeline.is_private { - return true; - } - pipeline_id = pipeline.parent_info.map(|(parent_pipeline_id, _)| parent_pipeline_id); - } - false - } - - // Close a frame (and all children) - fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) { - // Store information about the pipelines to be closed. Then close the - // pipelines, before removing ourself from the frames hash map. This - // ordering is vital - so that if close_pipeline() ends up closing - // any child frames, they can be removed from the parent frame correctly. - let parent_info = self.frames.get(&frame_id) - .and_then(|frame| self.pipelines.get(&frame.current)) - .and_then(|pipeline| pipeline.parent_info); - - let pipelines_to_close = { - let mut pipelines_to_close = vec!(); - - if let Some(frame) = self.frames.get(&frame_id) { - pipelines_to_close.extend_from_slice(&frame.next); - pipelines_to_close.push(frame.current); - pipelines_to_close.extend_from_slice(&frame.prev); - } - - pipelines_to_close - }; - - for pipeline_id in &pipelines_to_close { - self.close_pipeline(*pipeline_id, exit_mode); - } - - if self.frames.remove(&frame_id).is_none() { - warn!("Closing frame {:?} twice.", frame_id); - } - - if let Some((parent_pipeline_id, _)) = parent_info { - let parent_pipeline = match self.pipelines.get_mut(&parent_pipeline_id) { - None => return warn!("Pipeline {:?} child closed after parent.", parent_pipeline_id), - Some(parent_pipeline) => parent_pipeline, - }; - parent_pipeline.remove_child(frame_id); - } - } - - // Close all pipelines at and beneath a given frame - fn close_pipeline(&mut self, pipeline_id: PipelineId, exit_mode: ExitPipelineMode) { - // Store information about the frames to be closed. Then close the - // frames, before removing ourself from the pipelines hash map. This - // ordering is vital - so that if close_frames() ends up closing - // any child pipelines, they can be removed from the parent pipeline correctly. - let frames_to_close = { - let mut frames_to_close = vec!(); - - if let Some(pipeline) = self.pipelines.get(&pipeline_id) { - frames_to_close.extend_from_slice(&pipeline.children); - } - - frames_to_close - }; - - // Remove any child frames - for child_frame in &frames_to_close { - self.close_frame(*child_frame, exit_mode); - } - - let pipeline = match self.pipelines.remove(&pipeline_id) { - Some(pipeline) => pipeline, - None => return warn!("Closing pipeline {:?} twice.", pipeline_id), - }; - - // If a child pipeline, remove from subpage map - if let Some(info) = pipeline.parent_info { - self.subpage_map.remove(&info); - } - - // Remove assocation between this pipeline and its holding frame - self.pipeline_to_frame_map.remove(&pipeline_id); - - // Remove this pipeline from pending frames if it hasn't loaded yet. - let pending_index = self.pending_frames.iter().position(|frame_change| { - frame_change.new_pipeline_id == pipeline_id - }); - if let Some(pending_index) = pending_index { - self.pending_frames.remove(pending_index); - } - - // Inform script, compositor that this pipeline has exited. - match exit_mode { - ExitPipelineMode::Normal => pipeline.exit(), - ExitPipelineMode::Force => pipeline.force_exit(), - } - } - - // Randomly close a pipeline -if --random-pipeline-closure-probability is set - fn maybe_close_random_pipeline(&mut self) { - match self.random_pipeline_closure { - Some((ref mut rng, probability)) => if probability <= rng.gen::<f32>() { return }, - _ => return, - }; - // In order to get repeatability, we sort the pipeline ids. - let mut pipeline_ids: Vec<&PipelineId> = self.pipelines.keys().collect(); - pipeline_ids.sort(); - if let Some((ref mut rng, _)) = self.random_pipeline_closure { - if let Some(pipeline_id) = rng.choose(&*pipeline_ids) { - if let Some(pipeline) = self.pipelines.get(pipeline_id) { - // Don't kill the root pipeline - if pipeline.parent_info.is_none() { return; } - // Note that we deliberately do not do any of the tidying up - // associated with closing a pipeline. The constellation should cope! - info!("Randomly closing pipeline {}.", pipeline_id); - pipeline.force_exit(); - } - } - } - } - - // Convert a frame to a sendable form to pass to the compositor - fn frame_to_sendable(&self, frame_id: FrameId) -> Option<SendableFrameTree> { - self.frames.get(&frame_id).and_then(|frame: &Frame| { - self.pipelines.get(&frame.current).map(|pipeline: &Pipeline| { - let mut frame_tree = SendableFrameTree { - pipeline: pipeline.to_sendable(), - size: pipeline.size, - children: vec!(), - }; - - for child_frame_id in &pipeline.children { - if let Some(frame) = self.frame_to_sendable(*child_frame_id) { - frame_tree.children.push(frame); - } - } - - frame_tree - }) - }) - } - - // Revoke paint permission from a pipeline, and all children. - fn revoke_paint_permission(&self, pipeline_id: PipelineId) { - let frame_id = self.pipeline_to_frame_map.get(&pipeline_id).map(|frame_id| *frame_id); - for frame in self.current_frame_tree_iter(frame_id) { - self.pipelines.get(&frame.current).map(|pipeline| pipeline.revoke_paint_permission()); - } - } - - // Send the current frame tree to compositor, and grant paint - // permission to each pipeline in the current frame tree. - fn send_frame_tree_and_grant_paint_permission(&mut self) { - // Note that this function can panic, due to ipc-channel creation failure. - // avoiding this panic would require a mechanism for dealing - // with low-resource scenarios. - if let Some(root_frame_id) = self.root_frame_id { - if let Some(frame_tree) = self.frame_to_sendable(root_frame_id) { - - let (chan, port) = ipc::channel().expect("Failed to create IPC channel!"); - self.compositor_proxy.send(ToCompositorMsg::SetFrameTree(frame_tree, - chan, - self.compositor_sender.clone())); - if port.recv().is_err() { - warn!("Compositor has discarded SetFrameTree"); - return; // Our message has been discarded, probably shutting down. - } - } - } - - for frame in self.current_frame_tree_iter(self.root_frame_id) { - self.pipelines.get(&frame.current).map(|pipeline| pipeline.grant_paint_permission()); - } - } - - /// For a given pipeline, determine the iframe in the root pipeline that transitively contains - /// it. There could be arbitrary levels of nested iframes in between them. - fn get_root_pipeline_and_containing_parent(&self, pipeline_id: &PipelineId) -> Option<(PipelineId, SubpageId)> { - if let Some(pipeline) = self.pipelines.get(pipeline_id) { - if let Some(mut ancestor_info) = pipeline.parent_info { - if let Some(mut ancestor) = self.pipelines.get(&ancestor_info.0) { - while let Some(next_info) = ancestor.parent_info { - ancestor_info = next_info; - ancestor = match self.pipelines.get(&ancestor_info.0) { - Some(ancestor) => ancestor, - None => { - warn!("Get parent pipeline before root via closed pipeline {:?}.", ancestor_info.0); - return None; - }, - }; - } - return Some(ancestor_info); - } - } - } - None - } - - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserlocationchange - // Note that this is a no-op if the pipeline is not an immediate child iframe of the root - fn trigger_mozbrowserlocationchange(&self, pipeline_id: PipelineId) { - if !prefs::get_pref("dom.mozbrowser.enabled").as_boolean().unwrap_or(false) { return; } - - let event_info = self.pipelines.get(&pipeline_id).and_then(|pipeline| { - pipeline.parent_info.map(|(containing_pipeline_id, subpage_id)| { - (containing_pipeline_id, subpage_id, pipeline.url.to_string()) - }) - }); - - // If this is an iframe, then send the event with new url - if let Some((containing_pipeline_id, subpage_id, url)) = event_info { - if let Some(parent_pipeline) = self.pipelines.get(&containing_pipeline_id) { - if let Some(frame_id) = self.pipeline_to_frame_map.get(&pipeline_id) { - if let Some(frame) = self.frames.get(&frame_id) { - let can_go_backward = !frame.prev.is_empty(); - let can_go_forward = !frame.next.is_empty(); - let event = MozBrowserEvent::LocationChange(url, can_go_backward, can_go_forward); - parent_pipeline.trigger_mozbrowser_event(subpage_id, event); - } - } - } - } - } - - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsererror - // Note that this does not require the pipeline to be an immediate child of the root - fn trigger_mozbrowsererror(&self, pipeline_id: PipelineId, reason: String, backtrace: String) { - if !prefs::get_pref("dom.mozbrowser.enabled").as_boolean().unwrap_or(false) { return; } - - let ancestor_info = self.get_root_pipeline_and_containing_parent(&pipeline_id); - - if let Some(ancestor_info) = ancestor_info { - match self.pipelines.get(&ancestor_info.0) { - Some(ancestor) => { - let event = MozBrowserEvent::Error(MozBrowserErrorType::Fatal, Some(reason), Some(backtrace)); - ancestor.trigger_mozbrowser_event(ancestor_info.1, event); - }, - None => return warn!("Mozbrowsererror via closed pipeline {:?}.", ancestor_info.0), - } - } - } - - fn focused_pipeline_in_tree(&self, frame_id: FrameId) -> bool { - self.focus_pipeline_id.map_or(false, |pipeline_id| { - self.pipeline_exists_in_tree(pipeline_id, Some(frame_id)) - }) - } - - fn pipeline_is_in_current_frame(&self, pipeline_id: PipelineId) -> bool { - self.pipeline_exists_in_tree(pipeline_id, self.root_frame_id) - } - - fn pipeline_exists_in_tree(&self, - pipeline_id: PipelineId, - root_frame_id: Option<FrameId>) -> bool { - self.current_frame_tree_iter(root_frame_id) - .any(|current_frame| current_frame.current == pipeline_id) - } - -} diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs index b65f419be81..b4677cc20f6 100644 --- a/components/compositing/lib.rs +++ b/components/compositing/lib.rs @@ -5,7 +5,6 @@ #![feature(box_syntax)] #![feature(custom_derive)] #![feature(plugin)] -#![feature(mpsc_select)] #![feature(plugin)] #![plugin(plugins)] @@ -15,9 +14,6 @@ extern crate app_units; extern crate azure; -extern crate canvas; -extern crate canvas_traits; -extern crate clipboard; extern crate devtools_traits; extern crate euclid; #[cfg(not(target_os = "windows"))] @@ -33,10 +29,8 @@ extern crate layout_traits; extern crate log; extern crate msg; extern crate net_traits; -extern crate offscreen_gl_context; #[macro_use] extern crate profile_traits; -extern crate rand; extern crate script_traits; extern crate serde; extern crate style_traits; @@ -48,26 +42,25 @@ extern crate webrender; extern crate webrender_traits; pub use compositor_thread::{CompositorEventListener, CompositorProxy, CompositorThread}; -pub use constellation::Constellation; -use euclid::size::{Size2D}; +use euclid::size::{Size2D, TypedSize2D}; use gfx_traits::Epoch; use ipc_channel::ipc::{IpcSender}; use msg::constellation_msg::{FrameId, Key, KeyState, KeyModifiers, LoadData}; use msg::constellation_msg::{NavigationDirection, PipelineId, SubpageId}; use msg::constellation_msg::{WebDriverCommandMsg, WindowSizeData, WindowSizeType}; +use pipeline::CompositionPipeline; use std::collections::HashMap; use url::Url; +use util::geometry::PagePx; mod compositor; mod compositor_layer; pub mod compositor_thread; -pub mod constellation; mod delayed_composition; pub mod pipeline; #[cfg(not(target_os = "windows"))] pub mod sandboxing; mod surface_map; -mod timer_scheduler; mod touch; pub mod windowing; @@ -105,3 +98,9 @@ pub enum CompositorMsg { /// Dispatch a webdriver command WebDriverCommand(WebDriverCommandMsg), } + +pub struct SendableFrameTree { + pub pipeline: CompositionPipeline, + pub size: Option<TypedSize2D<PagePx, f32>>, + pub children: Vec<SendableFrameTree>, +} diff --git a/components/compositing/timer_scheduler.rs b/components/compositing/timer_scheduler.rs deleted file mode 100644 index d83c6170d23..00000000000 --- a/components/compositing/timer_scheduler.rs +++ /dev/null @@ -1,221 +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 http://mozilla.org/MPL/2.0/. */ - -use euclid::length::Length; -use ipc_channel::ipc::{self, IpcSender}; -use ipc_channel::router::ROUTER; -use script_traits::{MsDuration, NsDuration, precise_time_ms, precise_time_ns}; -use script_traits::{TimerEvent, TimerEventRequest}; -use std::cell::RefCell; -use std::cmp::{self, Ord}; -use std::collections::BinaryHeap; -use std::sync::Arc; -use std::sync::atomic::{self, AtomicBool}; -use std::sync::mpsc::{channel, Receiver, Select}; -use std::thread::{self, spawn, Thread}; -use std::time::Duration; -use util::thread::spawn_named; - -/// A quick hack to work around the removal of [`std::old_io::timer::Timer`]( -/// http://doc.rust-lang.org/1.0.0-beta/std/old_io/timer/struct.Timer.html ) -struct CancelableOneshotTimer { - thread: Thread, - canceled: Arc<AtomicBool>, - port: Receiver<()>, -} - -impl CancelableOneshotTimer { - fn new(duration: MsDuration) -> CancelableOneshotTimer { - let (tx, rx) = channel(); - let canceled = Arc::new(AtomicBool::new(false)); - let canceled_clone = canceled.clone(); - - let thread = spawn(move || { - let due_time = precise_time_ms() + duration; - - let mut park_time = duration; - - loop { - thread::park_timeout(Duration::from_millis(park_time.get())); - - if canceled_clone.load(atomic::Ordering::Relaxed) { - return; - } - - // park_timeout_ms does not guarantee parking for the - // given amout. We might have woken up early. - let current_time = precise_time_ms(); - if current_time >= due_time { - let _ = tx.send(()); - return; - } - park_time = due_time - current_time; - } - }).thread().clone(); - - CancelableOneshotTimer { - thread: thread, - canceled: canceled, - port: rx, - } - } - - fn port(&self) -> &Receiver<()> { - &self.port - } - - fn cancel(&self) { - self.canceled.store(true, atomic::Ordering::Relaxed); - self.thread.unpark(); - } -} - -pub struct TimerScheduler { - port: Receiver<TimerEventRequest>, - - scheduled_events: RefCell<BinaryHeap<ScheduledEvent>>, - - timer: RefCell<Option<CancelableOneshotTimer>>, -} - -struct ScheduledEvent { - request: TimerEventRequest, - for_time: NsDuration, -} - -impl Ord for ScheduledEvent { - fn cmp(&self, other: &ScheduledEvent) -> cmp::Ordering { - self.for_time.cmp(&other.for_time).reverse() - } -} - -impl PartialOrd for ScheduledEvent { - fn partial_cmp(&self, other: &ScheduledEvent) -> Option<cmp::Ordering> { - Some(self.cmp(other)) - } -} - -impl Eq for ScheduledEvent {} -impl PartialEq for ScheduledEvent { - fn eq(&self, other: &ScheduledEvent) -> bool { - self as *const ScheduledEvent == other as *const ScheduledEvent - } -} - -enum Task { - HandleRequest(TimerEventRequest), - DispatchDueEvents, -} - -impl TimerScheduler { - pub fn start() -> IpcSender<TimerEventRequest> { - let (chan, port) = ipc::channel().unwrap(); - - let timer_scheduler = TimerScheduler { - port: ROUTER.route_ipc_receiver_to_new_mpsc_receiver(port), - - scheduled_events: RefCell::new(BinaryHeap::new()), - - timer: RefCell::new(None), - }; - - spawn_named("TimerScheduler".to_owned(), move || { - timer_scheduler.run_event_loop(); - }); - - chan - } - - fn run_event_loop(&self) { - while let Some(thread) = self.receive_next_task() { - match thread { - Task::HandleRequest(request) => self.handle_request(request), - Task::DispatchDueEvents => self.dispatch_due_events(), - } - } - } - - #[allow(unsafe_code)] - fn receive_next_task(&self) -> Option<Task> { - let port = &self.port; - let timer = self.timer.borrow(); - let timer_port = timer.as_ref().map(|timer| timer.port()); - - if let Some(ref timer_port) = timer_port { - let sel = Select::new(); - let mut scheduler_handle = sel.handle(port); - let mut timer_handle = sel.handle(timer_port); - - unsafe { - scheduler_handle.add(); - timer_handle.add(); - } - - let ret = sel.wait(); - if ret == scheduler_handle.id() { - port.recv().ok().map(Task::HandleRequest) - } else if ret == timer_handle.id() { - timer_port.recv().ok().map(|_| Task::DispatchDueEvents) - } else { - panic!("unexpected select result!") - } - } else { - port.recv().ok().map(Task::HandleRequest) - } - } - - fn handle_request(&self, request: TimerEventRequest) { - let TimerEventRequest(_, _, _, duration_ms) = request; - let duration_ns = Length::new(duration_ms.get() * 1000 * 1000); - let schedule_for = precise_time_ns() + duration_ns; - - let previously_earliest = self.scheduled_events.borrow().peek() - .map_or(Length::new(u64::max_value()), |scheduled| scheduled.for_time); - - self.scheduled_events.borrow_mut().push(ScheduledEvent { - request: request, - for_time: schedule_for, - }); - - if schedule_for < previously_earliest { - self.start_timer_for_next_event(); - } - } - - fn dispatch_due_events(&self) { - let now = precise_time_ns(); - - { - let mut events = self.scheduled_events.borrow_mut(); - - while !events.is_empty() && events.peek().as_ref().unwrap().for_time <= now { - let event = events.pop().unwrap(); - let TimerEventRequest(chan, source, id, _) = event.request; - - let _ = chan.send(TimerEvent(source, id)); - } - } - - self.start_timer_for_next_event(); - } - - fn start_timer_for_next_event(&self) { - let events = self.scheduled_events.borrow(); - let next_event = events.peek(); - - let mut timer = self.timer.borrow_mut(); - - if let Some(ref mut timer) = *timer { - timer.cancel(); - } - - *timer = next_event.map(|next_event| { - let delay_ns = next_event.for_time.get().saturating_sub(precise_time_ns().get()); - // Round up, we'd rather be late than early… - let delay_ms = Length::new(delay_ns.saturating_add(999999) / (1000 * 1000)); - - CancelableOneshotTimer::new(delay_ms) - }); - } -} |