/* 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 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 `BrowsingContext` objects. Each browsing context
//!   gives the constellation's view of a `WindowProxy`.
//!   Each browsing context stores an independent
//!   session history, created by navigation. 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 browsing context: top-level ones (for
//! example tabs in a browser UI), and nested ones (typically caused
//! by `iframe` elements). Browsing contexts have a hierarchy
//! (typically caused by `iframe`s containing `iframe`s), giving rise
//! to a forest whose roots are top-level browsing context.  The logical
//! relationship between these types is:
//!
//! ```
//! +------------+                      +------------+                 +---------+
//! |  Browsing  | ------parent?------> |  Pipeline  | --event_loop--> |  Event  |
//! |  Context   | ------current------> |            |                 |  Loop   |
//! |            | ------prev*--------> |            | <---pipeline*-- |         |
//! |            | ------next*--------> |            |                 +---------+
//! |            |                      |            |
//! |            | <-top_level--------- |            |
//! |            | <-browsing_context-- |            |
//! +------------+                      +------------+
//! ```
//
//! Complicating matters, there are also mozbrowser iframes, which are top-level
//! iframes 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 browsing context 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;
use browsingcontext::{BrowsingContext, SessionHistoryChange, SessionHistoryEntry};
use browsingcontext::{FullyActiveBrowsingContextsIterator, AllBrowsingContextsIterator};
use canvas::canvas_paint_thread::CanvasPaintThread;
use canvas::webgl_paint_thread::WebGLPaintThread;
use canvas_traits::CanvasMsg;
use clipboard::{ClipboardContext, ClipboardProvider};
use compositing::SendableFrameTree;
use compositing::compositor_thread::CompositorProxy;
use compositing::compositor_thread::Msg as ToCompositorMsg;
use debugger;
use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg};
use euclid::scale_factor::ScaleFactor;
use euclid::size::{Size2D, TypedSize2D};
use event_loop::EventLoop;
use gfx::font_cache_thread::FontCacheThread;
use gfx_traits::Epoch;
use ipc_channel::{Error as IpcError};
use ipc_channel::ipc::{self, IpcSender, IpcReceiver};
use ipc_channel::router::ROUTER;
use itertools::Itertools;
use layout_traits::LayoutThreadFactory;
use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord};
use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, FrameType, PipelineId};
use msg::constellation_msg::{Key, KeyModifiers, KeyState};
use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection};
use net_traits::{self, IpcSend, FetchResponseMsg, ResourceThreads};
use net_traits::pub_domains::reg_host;
use net_traits::request::RequestInit;
use net_traits::storage_thread::{StorageThreadMsg, StorageType};
use network_listener::NetworkListener;
use offscreen_gl_context::{GLContextAttributes, GLLimits};
use pipeline::{InitialPipelineState, Pipeline};
use profile_traits::mem;
use profile_traits::time;
use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext};
use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData};
use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg};
use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory};
use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg};
use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, WindowSizeData};
use script_traits::{SWManagerMsg, ScopeThings, UpdatePipelineIdReason, WindowSizeType};
use serde::{Deserialize, Serialize};
use servo_config::opts;
use servo_config::prefs::PREFS;
use servo_rand::{Rng, SeedableRng, ServoRng, random};
use servo_remutex::ReentrantMutex;
use servo_url::{Host, ImmutableOrigin, ServoUrl};
use std::borrow::ToOwned;
use std::cmp::Ordering;
use std::collections::{HashMap, VecDeque};
use std::iter::once;
use std::marker::PhantomData;
use std::process;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;
use style_traits::CSSPixel;
use style_traits::cursor::Cursor;
use style_traits::viewport::ViewportConstraints;
use timer_scheduler::TimerScheduler;
use webrender_traits;
use webvr_traits::{WebVREvent, WebVRMsg};

/// 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). Script and layout communicate using a `Message`
/// type.
pub struct Constellation<Message, LTF, STF> {
    /// An IPC channel for script threads to send messages to the constellation.
    /// This is the script threads' view of `script_receiver`.
    script_sender: IpcSender<FromScriptMsg>,

    /// A channel for the constellation to receive messages from script threads.
    /// This is the constellation's view of `script_sender`.
    script_receiver: Receiver<Result<FromScriptMsg, IpcError>>,

    /// An IPC channel for layout threads to send messages to the constellation.
    /// This is the layout threads' view of `layout_receiver`.
    layout_sender: IpcSender<FromLayoutMsg>,

    /// A channel for the constellation to receive messages from layout threads.
    /// This is the constellation's view of `layout_sender`.
    layout_receiver: Receiver<Result<FromLayoutMsg, IpcError>>,

    /// A channel for network listener to send messages to the constellation.
    network_listener_sender: Sender<(PipelineId, FetchResponseMsg)>,

    /// A channel for the constellation to receive messages from network listener.
    network_listener_receiver: Receiver<(PipelineId, FetchResponseMsg)>,

    /// A channel for the constellation to receive messages from the compositor thread.
    compositor_receiver: Receiver<FromCompositorMsg>,

    /// A channel (the implementation of which is port-specific) for the
    /// constellation to send messages to the compositor thread.
    compositor_proxy: CompositorProxy,

    /// 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 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 for the constellation to send messages to the font
    /// cache thread.
    font_cache_thread: FontCacheThread,

    /// A channel for the constellation to send messages to the
    /// debugger thread.
    debugger_chan: Option<debugger::Sender>,

    /// A channel for the constellation to send messages to the
    /// devtools thread.
    devtools_chan: Option<Sender<DevtoolsControlMsg>>,

    /// An IPC channel for the constellation to send messages to the
    /// bluetooth thread.
    bluetooth_thread: IpcSender<BluetoothRequest>,

    /// An IPC channel for the constellation to send messages to the
    /// Service Worker Manager thread.
    swmanager_chan: Option<IpcSender<ServiceWorkerMsg>>,

    /// 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<SWManagerMsg>,

    /// 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<Result<SWManagerMsg, IpcError>>,

    /// A channel for the constellation to send messages to the
    /// time profiler thread.
    time_profiler_chan: time::ProfilerChan,

    /// A channel for the constellation to send messages to the
    /// memory profiler thread.
    mem_profiler_chan: mem::ProfilerChan,

    /// A channel for the constellation to send messages to the
    /// timer thread.
    scheduler_chan: IpcSender<TimerSchedulerMsg>,

    /// 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 browsing context. We store the event loops in a map
    /// indexed by top-level browsing context id
    /// (as a `TopLevelBrowsingContextId`) and registered
    /// domain name (as a `Host`) 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<TopLevelBrowsingContextId, HashMap<Host, Weak<EventLoop>>>,

    /// The set of all the pipelines in the browser.
    /// (See the `pipeline` module for more details.)
    pipelines: HashMap<PipelineId, Pipeline>,

    /// The set of all the browsing contexts in the browser.
    browsing_contexts: HashMap<BrowsingContextId, BrowsingContext>,

    /// When a navigation is performed, we do not immediately update
    /// the session history, instead we ask the event loop to begin loading
    /// the new document, and do not update the browsing context until the
    /// document is active. Between starting the load and it activating,
    /// we store a `SessionHistoryChange` object for the navigation in progress.
    pending_changes: Vec<SessionHistoryChange>,

    /// The currently focused pipeline for key events.
    focus_pipeline_id: Option<PipelineId>,

    /// 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,

    /// Means of accessing the clipboard
    clipboard_ctx: Option<ClipboardContext>,

    /// Bits of state used to interact with the webdriver implementation
    webdriver: WebDriverData,

    /// Document states for loaded pipelines (used only when writing screenshots).
    document_states: HashMap<PipelineId, DocumentState>,

    /// Are we shutting down?
    shutting_down: bool,

    /// Have we seen any warnings? Hopefully always empty!
    /// The buffer contains `(thread_name, reason)` entries.
    handled_warnings: VecDeque<(Option<String>, String)>,

    /// The random number generator and probability for closing pipelines.
    /// This is for testing the hardening of the constellation.
    random_pipeline_closure: Option<(ServoRng, f32)>,

    /// Phantom data that keeps the Rust type system happy.
    phantom: PhantomData<(Message, LTF, STF)>,

    /// A channel through which messages can be sent to the webvr thread.
    webvr_thread: Option<IpcSender<WebVRMsg>>,
}

/// State needed to construct a constellation.
pub struct InitialConstellationState {
    /// A channel through which messages can be sent to the compositor.
    pub compositor_proxy: CompositorProxy,

    /// A channel to the debugger, if applicable.
    pub debugger_chan: Option<debugger::Sender>,

    /// A channel to the developer tools, if applicable.
    pub devtools_chan: Option<Sender<DevtoolsControlMsg>>,

    /// A channel to the bluetooth thread.
    pub bluetooth_thread: IpcSender<BluetoothRequest>,

    /// 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,

    /// 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,
}

/// Data needed for webdriver
struct WebDriverData {
    load_channel: Option<(PipelineId, IpcSender<webdriver_msg::LoadStatus>)>,
    resize_channel: Option<IpcSender<WindowSizeData>>,
}

impl WebDriverData {
    fn new() -> WebDriverData {
        WebDriverData {
            load_channel: None,
            resize_channel: None,
        }
    }
}

/// 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 {
    NoTopLevelBrowsingContext,
    PendingChanges,
    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 {
    /// A channel to the constellation
    pub constellation_chan: Arc<ReentrantMutex<IpcSender<FromScriptMsg>>>,
}

impl FromScriptLogger {
    /// Create a new constellation logger.
    pub fn new(constellation_chan: IpcSender<FromScriptMsg>) -> FromScriptLogger {
        FromScriptLogger {
            constellation_chan: Arc::new(ReentrantMutex::new(constellation_chan))
        }
    }

    /// The maximum log level the constellation logger is interested in.
    pub fn filter(&self) -> LogLevelFilter {
        LogLevelFilter::Warn
    }
}

impl Log for FromScriptLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Warn
    }

    fn log(&self, record: &LogRecord) {
        if let Some(entry) = log_entry(record) {
            debug!("Sending log entry {:?}.", entry);
            let top_level_id = TopLevelBrowsingContextId::installed();
            let thread_name = thread::current().name().map(ToOwned::to_owned);
            let msg = FromScriptMsg::LogEntry(top_level_id, thread_name, entry);
            let chan = self.constellation_chan.lock().unwrap_or_else(|err| err.into_inner());
            let _ = chan.send(msg);
        }
    }
}

/// A logger directed at the constellation from the compositor
#[derive(Clone)]
pub struct FromCompositorLogger {
    /// A channel to the constellation
    pub constellation_chan: Arc<ReentrantMutex<Sender<FromCompositorMsg>>>,
}

impl FromCompositorLogger {
    /// Create a new constellation logger.
    pub fn new(constellation_chan: Sender<FromCompositorMsg>) -> FromCompositorLogger {
        FromCompositorLogger {
            constellation_chan: Arc::new(ReentrantMutex::new(constellation_chan))
        }
    }

    /// The maximum log level the constellation logger is interested in.
    pub fn filter(&self) -> LogLevelFilter {
        LogLevelFilter::Warn
    }
}

impl Log for FromCompositorLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Warn
    }

    fn log(&self, record: &LogRecord) {
        if let Some(entry) = log_entry(record) {
            debug!("Sending log entry {:?}.", entry);
            let top_level_id = TopLevelBrowsingContextId::installed();
            let thread_name = thread::current().name().map(ToOwned::to_owned);
            let msg = FromCompositorMsg::LogEntry(top_level_id, thread_name, entry);
            let chan = self.constellation_chan.lock().unwrap_or_else(|err| err.into_inner());
            let _ = chan.send(msg);
        }
    }
}

/// 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<LogEntry> {
    match record.level() {
        LogLevel::Error if thread::panicking() => Some(LogEntry::Panic(
            format!("{}", record.args()),
            format!("{:?}", Backtrace::new())
        )),
        LogLevel::Error => Some(LogEntry::Error(
            format!("{}", record.args())
        )),
        LogLevel::Warn => Some(LogEntry::Warn(
            format!("{}", record.args())
        )),
        _ => None,
    }
}

/// The number of warnings to include in each crash report.
const WARNINGS_BUFFER_SIZE: usize = 32;

/// Route an ipc receiver to an mpsc receiver, preserving any errors.
/// This is the same as `route_ipc_receiver_to_new_mpsc_receiver`,
/// but does not panic on deserializtion errors.
fn route_ipc_receiver_to_new_mpsc_receiver_preserving_errors<T>(ipc_receiver: IpcReceiver<T>)
    -> Receiver<Result<T, IpcError>>
    where T: Deserialize + Serialize + Send + 'static
{
        let (mpsc_sender, mpsc_receiver) = channel();
        ROUTER.add_route(ipc_receiver.to_opaque(), Box::new(move |message| {
            drop(mpsc_sender.send(message.to::<T>()))
        }));
        mpsc_receiver
}

impl<Message, LTF, STF> Constellation<Message, LTF, STF>
    where LTF: LayoutThreadFactory<Message=Message>,
          STF: ScriptThreadFactory<Message=Message>
{
    /// Create a new constellation thread.
    pub fn start(state: InitialConstellationState) -> (Sender<FromCompositorMsg>, IpcSender<SWManagerMsg>) {
        let (compositor_sender, compositor_receiver) = channel();

        // service worker manager to communicate with constellation
        let (swmanager_sender, swmanager_receiver) = ipc::channel().expect("ipc channel failure");
        let sw_mgr_clone = swmanager_sender.clone();

        thread::Builder::new().name("Constellation".to_owned()).spawn(move || {
            let (ipc_script_sender, ipc_script_receiver) = ipc::channel().expect("ipc channel failure");
            let script_receiver = route_ipc_receiver_to_new_mpsc_receiver_preserving_errors(ipc_script_receiver);

            let (ipc_layout_sender, ipc_layout_receiver) = ipc::channel().expect("ipc channel failure");
            let layout_receiver = route_ipc_receiver_to_new_mpsc_receiver_preserving_errors(ipc_layout_receiver);

            let (network_listener_sender, network_listener_receiver) = channel();

            let swmanager_receiver = route_ipc_receiver_to_new_mpsc_receiver_preserving_errors(swmanager_receiver);

            PipelineNamespace::install(PipelineNamespaceId(0));

            let mut constellation: Constellation<Message, LTF, STF> = Constellation {
                script_sender: ipc_script_sender,
                layout_sender: ipc_layout_sender,
                script_receiver: script_receiver,
                compositor_receiver: compositor_receiver,
                layout_receiver: layout_receiver,
                network_listener_sender: network_listener_sender,
                network_listener_receiver: network_listener_receiver,
                compositor_proxy: state.compositor_proxy,
                debugger_chan: state.debugger_chan,
                devtools_chan: state.devtools_chan,
                bluetooth_thread: state.bluetooth_thread,
                public_resource_threads: state.public_resource_threads,
                private_resource_threads: state.private_resource_threads,
                font_cache_thread: state.font_cache_thread,
                swmanager_chan: None,
                swmanager_receiver: swmanager_receiver,
                swmanager_sender: sw_mgr_clone,
                event_loops: HashMap::new(),
                pipelines: HashMap::new(),
                browsing_contexts: HashMap::new(),
                pending_changes: vec!(),
                // We initialize the namespace at 1, since we reserved namespace 0 for the constellation
                next_pipeline_namespace_id: PipelineNamespaceId(1),
                focus_pipeline_id: None,
                time_profiler_chan: state.time_profiler_chan,
                mem_profiler_chan: state.mem_profiler_chan,
                window_size: WindowSizeData {
                    initial_viewport: opts::get().initial_window_size.to_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 {
                    match ClipboardContext::new() {
                        Ok(c) => Some(c),
                        Err(e) => {
                            warn!("Error creating clipboard context ({})", e);
                            None
                        },
                    }
                } else {
                    None
                },
                webdriver: WebDriverData::new(),
                scheduler_chan: TimerScheduler::start(),
                document_states: HashMap::new(),
                webrender_api_sender: state.webrender_api_sender,
                shutting_down: false,
                handled_warnings: VecDeque::new(),
                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 = ServoRng::from_seed(&[seed]);
                    warn!("Randomly closing pipelines.");
                    info!("Using seed {} for random pipeline closure.", seed);
                    (rng, prob)
                }),
                webvr_thread: None
            };

            constellation.run();
        }).expect("Thread spawning failed");

        (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
            // This is for testing the hardening of the constellation.
            self.maybe_close_random_pipeline();
            self.handle_request();
        }
        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;
        *i += 1;
        namespace_id
    }

    /// Helper function for creating a pipeline
    fn new_pipeline(&mut self,
                    pipeline_id: PipelineId,
                    browsing_context_id: BrowsingContextId,
                    top_level_browsing_context_id: TopLevelBrowsingContextId,
                    parent_info: Option<(PipelineId, FrameType)>,
                    initial_window_size: Option<TypedSize2D<f32, CSSPixel>>,
                    // TODO: we have to provide ownership of the LoadData
                    // here, because it will be send on an ipc channel,
                    // and ipc channels take onership of their data.
                    // https://github.com/servo/ipc-channel/issues/138
                    load_data: LoadData,
                    sandbox: IFrameSandboxState,
                    is_private: bool) {
        if self.shutting_down { return; }

        debug!("Creating new pipeline {} in browsing context {}.", pipeline_id, browsing_context_id);

        let (event_loop, host) = match sandbox {
            IFrameSandboxState::IFrameSandboxed => (None, None),
            IFrameSandboxState::IFrameUnsandboxed => {
                // If this is an about:blank load, it must share the creator's event loop.
                // This must match the logic in the script thread when determining the proper origin.
                if load_data.url.as_str() != "about:blank" {
                    match reg_host(&load_data.url) {
                        None => (None, None),
                        Some(host) => {
                            let event_loop = self.event_loops.get(&top_level_browsing_context_id)
                                .and_then(|map| map.get(&host))
                                .and_then(|weak| weak.upgrade());
                            match event_loop {
                                None => (None, Some(host)),
                                Some(event_loop) => (Some(event_loop.clone()), None),
                            }
                        },
                    }
                } else if let Some(parent) = parent_info
                        .and_then(|(pipeline_id, _)| self.pipelines.get(&pipeline_id)) {
                    (Some(parent.event_loop.clone()), None)
                } else if let Some(creator) = load_data.creator_pipeline_id
                        .and_then(|pipeline_id| self.pipelines.get(&pipeline_id)) {
                    (Some(creator.event_loop.clone()), None)
                } else {
                    (None, None)
                }
            },
        };

        let resource_threads = if is_private {
            self.private_resource_threads.clone()
        } else {
            self.public_resource_threads.clone()
        };

        let parent_visibility = parent_info
            .and_then(|(parent_pipeline_id, _)| self.pipelines.get(&parent_pipeline_id))
            .map(|pipeline| pipeline.visible);

        let prev_visibility = self.browsing_contexts.get(&browsing_context_id)
            .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
            .map(|pipeline| pipeline.visible)
            .or(parent_visibility);

        let result = Pipeline::spawn::<Message, LTF, STF>(InitialPipelineState {
            id: pipeline_id,
            browsing_context_id: browsing_context_id,
            top_level_browsing_context_id: top_level_browsing_context_id,
            parent_info: parent_info,
            constellation_chan: self.script_sender.clone(),
            layout_to_constellation_chan: self.layout_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(),
            swmanager_thread: self.swmanager_sender.clone(),
            font_cache_thread: self.font_cache_thread.clone(),
            resource_threads: resource_threads,
            time_profiler_chan: self.time_profiler_chan.clone(),
            mem_profiler_chan: self.mem_profiler_chan.clone(),
            window_size: initial_window_size,
            event_loop: event_loop,
            load_data: load_data,
            device_pixel_ratio: self.window_size.device_pixel_ratio,
            pipeline_namespace_id: self.next_pipeline_namespace_id(),
            prev_visibility: prev_visibility,
            webrender_api_sender: self.webrender_api_sender.clone(),
            is_private: is_private,
            webvr_thread: self.webvr_thread.clone()
        });

        let pipeline = match result {
            Ok(result) => result,
            Err(e) => return self.handle_send_error(pipeline_id, e),
        };

        if let Some(host) = host {
            debug!("Adding new host entry {} for top-level browsing context {}.", host, top_level_browsing_context_id);
            self.event_loops.entry(top_level_browsing_context_id)
                .or_insert_with(HashMap::new)
                .insert(host, Rc::downgrade(&pipeline.event_loop));
        }

        assert!(!self.pipelines.contains_key(&pipeline_id));
        self.pipelines.insert(pipeline_id, pipeline);
    }

    /// Get an iterator for the fully active browsing contexts in a subtree.
    fn fully_active_descendant_browsing_contexts_iter(&self, browsing_context_id: BrowsingContextId)
                                                      -> FullyActiveBrowsingContextsIterator
    {
        FullyActiveBrowsingContextsIterator {
            stack: vec!(browsing_context_id),
            pipelines: &self.pipelines,
            browsing_contexts: &self.browsing_contexts,
        }
    }

    /// Get an iterator for the fully active browsing contexts in a tree.
    fn fully_active_browsing_contexts_iter(&self, top_level_browsing_context_id: TopLevelBrowsingContextId)
                                           -> FullyActiveBrowsingContextsIterator
    {
        self.fully_active_descendant_browsing_contexts_iter(BrowsingContextId::from(top_level_browsing_context_id))
    }

    /// Get an iterator for the browsing contexts in a subtree.
    fn all_descendant_browsing_contexts_iter(&self, browsing_context_id: BrowsingContextId)
                                             -> AllBrowsingContextsIterator
    {
        AllBrowsingContextsIterator {
            stack: vec!(browsing_context_id),
            pipelines: &self.pipelines,
            browsing_contexts: &self.browsing_contexts,
        }
    }

    /// Get an iterator for the browsing contexts in a tree.
    fn all_browsing_contexts_iter(&self, top_level_browsing_context_id: TopLevelBrowsingContextId)
                                  -> AllBrowsingContextsIterator
    {
        self.all_descendant_browsing_contexts_iter(BrowsingContextId::from(top_level_browsing_context_id))
    }

    /// The joint session future is the merge of the session future of every
    /// browsing_context, sorted chronologically.
    fn joint_session_future<'a>(&'a self, top_level_browsing_context_id: TopLevelBrowsingContextId)
                                -> impl Iterator<Item = &'a SessionHistoryEntry> + 'a
    {
        self.all_browsing_contexts_iter(top_level_browsing_context_id)
            .map(|browsing_context| browsing_context.next.iter().rev())
            .kmerge_by(|a, b| a.instant.cmp(&b.instant) == Ordering::Less)
    }

    /// Is the joint session future empty?
    fn joint_session_future_is_empty(&self, top_level_browsing_context_id: TopLevelBrowsingContextId) -> bool {
        self.all_browsing_contexts_iter(top_level_browsing_context_id)
            .all(|browsing_context| browsing_context.next.is_empty())
    }

    /// The joint session past is the merge of the session past of every
    /// browsing_context, sorted reverse chronologically.
    fn joint_session_past<'a>(&'a self, top_level_browsing_context_id: TopLevelBrowsingContextId)
                              -> impl Iterator<Item = &'a SessionHistoryEntry> + 'a
    {
        self.all_browsing_contexts_iter(top_level_browsing_context_id)
            .map(|browsing_context| browsing_context.prev.iter().rev()
                 .scan(browsing_context.instant, |prev_instant, entry| {
                     let instant = *prev_instant;
                     *prev_instant = entry.instant;
                     Some((instant, entry))
                 }))
            .kmerge_by(|a, b| a.0.cmp(&b.0) == Ordering::Greater)
            .map(|(_, entry)| entry)
    }

    /// Is the joint session past empty?
    fn joint_session_past_is_empty(&self, top_level_browsing_context_id: TopLevelBrowsingContextId) -> bool {
        self.all_browsing_contexts_iter(top_level_browsing_context_id)
            .all(|browsing_context| browsing_context.prev.is_empty())
    }

    /// Create a new browsing context and update the internal bookkeeping.
    fn new_browsing_context(&mut self,
                 browsing_context_id: BrowsingContextId,
                 top_level_id: TopLevelBrowsingContextId,
                 pipeline_id: PipelineId,
                 load_data: LoadData) {
        debug!("Creating new browsing context {}", browsing_context_id);
        let browsing_context = BrowsingContext::new(browsing_context_id, top_level_id, pipeline_id, load_data);
        self.browsing_contexts.insert(browsing_context_id, browsing_context);

        // If a child browsing_context, add it to the parent pipeline.
        let parent_info = self.pipelines.get(&pipeline_id)
            .and_then(|pipeline| pipeline.parent_info);
        if let Some((parent_id, _)) = parent_info {
            if let Some(parent) = self.pipelines.get_mut(&parent_id) {
                parent.add_child(browsing_context_id);
            }
        }
    }

    fn add_pending_change(&mut self, change: SessionHistoryChange)
    {
        self.handle_load_start_msg(change.new_pipeline_id);
        self.pending_changes.push(change);
    }

    /// Handles loading pages, navigation, and granting access to the compositor
    #[allow(unsafe_code)]
    fn handle_request(&mut self) {
        enum Request {
            Script(FromScriptMsg),
            Compositor(FromCompositorMsg),
            Layout(FromLayoutMsg),
            NetworkListener((PipelineId, FetchResponseMsg)),
            FromSWManager(SWManagerMsg),
        }

        // 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_network_listener = &self.network_listener_receiver;
            let receiver_from_swmanager = &self.swmanager_receiver;
            select! {
                msg = receiver_from_script.recv() =>
                    msg.expect("Unexpected script channel panic in constellation").map(Request::Script),
                msg = receiver_from_compositor.recv() =>
                    Ok(Request::Compositor(msg.expect("Unexpected compositor channel panic in constellation"))),
                msg = receiver_from_layout.recv() =>
                    msg.expect("Unexpected layout channel panic in constellation").map(Request::Layout),
                msg = receiver_from_network_listener.recv() =>
                    Ok(Request::NetworkListener(
                        msg.expect("Unexpected network listener channel panic in constellation")
                    )),
                msg = receiver_from_swmanager.recv() =>
                    msg.expect("Unexpected panic channel panic in constellation").map(Request::FromSWManager)
            }
        };

        let request = match request {
            Ok(request) => request,
            Err(err) => return error!("Deserialization failed ({}).", err),
        };

        match request {
            Request::Compositor(message) => {
                self.handle_request_from_compositor(message)
            },
            Request::Script(message) => {
                self.handle_request_from_script(message);
            },
            Request::Layout(message) => {
                self.handle_request_from_layout(message);
            },
            Request::NetworkListener(message) => {
                self.handle_request_from_network_listener(message);
            },
            Request::FromSWManager(message) => {
                self.handle_request_from_swmanager(message);
            }
        }
    }

    fn handle_request_from_network_listener(&mut self, message: (PipelineId, FetchResponseMsg)) {
        let (id, message_) = message;
        let result = match self.pipelines.get(&id) {
            Some(pipeline) => {
                let msg = ConstellationControlMsg::NavigationResponse(id, message_);
                pipeline.event_loop.send(msg)
            },
            None => {
                return warn!("Pipeline {:?} got fetch data after closure!", id);
            },
        };
        if let Err(e) = result {
            self.handle_send_error(id, e);
        }
    }

    fn handle_request_from_swmanager(&mut self, message: SWManagerMsg) {
        match message {
            SWManagerMsg::OwnSender(sw_sender) => {
                // store service worker manager for communicating with it.
                self.swmanager_chan = Some(sw_sender);
            }
        }
    }

    fn handle_request_from_compositor(&mut self, message: FromCompositorMsg) {
        match message {
            FromCompositorMsg::Exit => {
                debug!("constellation exiting");
                self.handle_exit();
            }
            FromCompositorMsg::GetBrowsingContext(pipeline_id, resp_chan) => {
                debug!("constellation got get browsing context message");
                self.handle_get_browsing_context(pipeline_id, resp_chan);
            }
            FromCompositorMsg::GetPipeline(browsing_context_id, resp_chan) => {
                debug!("constellation got get pipeline message");
                self.handle_get_pipeline(browsing_context_id, resp_chan);
            }
            FromCompositorMsg::GetFocusTopLevelBrowsingContext(resp_chan) => {
                debug!("constellation got get focus browsing context message");
                let focus_browsing_context = self.focus_pipeline_id
                    .and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
                    .map(|pipeline| pipeline.top_level_browsing_context_id);
                let _ = resp_chan.send(focus_browsing_context);
            }
            FromCompositorMsg::GetPipelineTitle(pipeline_id) => {
                debug!("constellation got get-pipeline-title message");
                self.handle_get_pipeline_title_msg(pipeline_id);
            }
            FromCompositorMsg::KeyEvent(ch, key, state, modifiers) => {
                debug!("constellation got key event message");
                self.handle_key_msg(ch, key, state, modifiers);
            }
            // Load a new page from a typed url
            // If there is already a pending page (self.pending_changes), it will not be overridden;
            // However, if the id is not encompassed by another change, it will be.
            FromCompositorMsg::LoadUrl(source_id, load_data) => {
                debug!("constellation got URL load message from compositor");
                self.handle_load_url_msg(source_id, load_data, false);
            }
            FromCompositorMsg::IsReadyToSaveImage(pipeline_states) => {
                let is_ready = self.handle_is_ready_to_save_image(pipeline_states);
                debug!("Ready to save image {:?}.", is_ready);
                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
            FromCompositorMsg::InitLoadUrl(url) => {
                debug!("constellation got init load URL message");
                self.handle_init_load(url);
            }
            // Handle a forward or back request
            FromCompositorMsg::TraverseHistory(top_level_browsing_context_id, direction) => {
                debug!("constellation got traverse history message from compositor");
                self.handle_traverse_history_msg(top_level_browsing_context_id, direction);
            }
            FromCompositorMsg::WindowSize(top_level_browsing_context_id, new_size, size_type) => {
                debug!("constellation got window resize message");
                self.handle_window_size_msg(top_level_browsing_context_id, new_size, size_type);
            }
            FromCompositorMsg::TickAnimation(pipeline_id, tick_type) => {
                self.handle_tick_animation(pipeline_id, tick_type)
            }
            FromCompositorMsg::WebDriverCommand(command) => {
                debug!("constellation got webdriver command message");
                self.handle_webdriver_msg(command);
            }
            FromCompositorMsg::Reload(top_level_browsing_context_id) => {
                debug!("constellation got reload message");
                self.handle_reload_msg(top_level_browsing_context_id);
            }
            FromCompositorMsg::LogEntry(top_level_browsing_context_id, thread_name, entry) => {
                self.handle_log_entry(top_level_browsing_context_id, thread_name, entry);
            }
            FromCompositorMsg::SetWebVRThread(webvr_thread) => {
                assert!(self.webvr_thread.is_none());
                self.webvr_thread = Some(webvr_thread)
            }
            FromCompositorMsg::WebVREvents(pipeline_ids, events) => {
                debug!("constellation got {:?} WebVR events", events.len());
                self.handle_webvr_events(pipeline_ids, events);
            }
        }
    }

    fn handle_request_from_script(&mut self, message: FromScriptMsg) {
        match message {
            FromScriptMsg::PipelineExited(pipeline_id) => {
                self.handle_pipeline_exited(pipeline_id);
            }
            FromScriptMsg::InitiateNavigateRequest(req_init, pipeline_id) => {
                debug!("constellation got initiate navigate request message");
                self.handle_navigate_request(req_init, pipeline_id);
            }
            FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => {
                debug!("constellation got iframe URL load message {:?} {:?} {:?}",
                       load_info.info.parent_pipeline_id,
                       load_info.old_pipeline_id,
                       load_info.info.new_pipeline_id);
                self.handle_script_loaded_url_in_iframe_msg(load_info);
            }
            FromScriptMsg::ScriptNewIFrame(load_info, layout_sender) => {
                debug!("constellation got loaded `about:blank` in iframe message {:?} {:?}",
                       load_info.parent_pipeline_id,
                       load_info.new_pipeline_id);
                self.handle_script_new_iframe(load_info, layout_sender);
            }
            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_changes), it will not be overridden;
            // However, if the id is not encompassed by another change, it will be.
            FromScriptMsg::LoadUrl(source_id, load_data, replace) => {
                debug!("constellation got URL load message from script");
                self.handle_load_url_msg(source_id, load_data, replace);
            }
            // A page loaded has completed all parsing, script, and reflow messages have been sent.
            FromScriptMsg::LoadComplete(pipeline_id) => {
                debug!("constellation got load complete message");
                self.handle_load_complete_msg(pipeline_id)
            }
            // Handle a forward or back request
            FromScriptMsg::TraverseHistory(top_level_browsing_context_id, direction) => {
                debug!("constellation got traverse history message from script");
                self.handle_traverse_history_msg(top_level_browsing_context_id, direction);
            }
            // Handle a joint session history length request.
            FromScriptMsg::JointSessionHistoryLength(top_level_browsing_context_id, sender) => {
                debug!("constellation got joint session history length message from script");
                self.handle_joint_session_history_length(top_level_browsing_context_id, sender);
            }
            // Notification that the new document is ready to become active
            FromScriptMsg::ActivateDocument(pipeline_id) => {
                debug!("constellation got activate document message");
                self.handle_activate_document_msg(pipeline_id);
            }
            // Update pipeline url after redirections
            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");
                }
            }
            FromScriptMsg::PostMessage(browsing_context_id, origin, data) => {
                debug!("constellation got postMessage message");
                self.handle_post_message_msg(browsing_context_id, origin, data);
            }
            FromScriptMsg::MozBrowserEvent(pipeline_id, top_level_browsing_context_id, event) => {
                debug!("constellation got mozbrowser event message");
                self.handle_mozbrowser_event_msg(pipeline_id, top_level_browsing_context_id, event);
            }
            FromScriptMsg::Focus(pipeline_id) => {
                debug!("constellation got focus message");
                self.handle_focus_msg(pipeline_id);
            }
            FromScriptMsg::ForwardEvent(pipeline_id, event) => {
                let msg = ConstellationControlMsg::SendEvent(pipeline_id, event);
                let result = match self.pipelines.get(&pipeline_id) {
                    None => { debug!("Pipeline {:?} got event after closure.", pipeline_id); return; }
                    Some(pipeline) => pipeline.event_loop.send(msg),
                };
                if let Err(e) = result {
                    self.handle_send_error(pipeline_id, e);
                }
            }
            FromScriptMsg::GetClipboardContents(sender) => {
                let contents = match self.clipboard_ctx {
                    Some(ref mut ctx) => match ctx.get_contents() {
                        Ok(c) => c,
                        Err(e) => {
                            warn!("Error getting clipboard contents ({}), defaulting to empty string", e);
                            "".to_owned()
                        },
                    },
                    None => "".to_owned(),
                };
                if let Err(e) = sender.send(contents.to_owned()) {
                    warn!("Failed to send clipboard ({})", e);
                }
            }
            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);
                    }
                }
            }
            FromScriptMsg::SetVisible(pipeline_id, visible) => {
                debug!("constellation got set visible messsage");
                self.handle_set_visible_msg(pipeline_id, visible);
            }
            FromScriptMsg::VisibilityChangeComplete(pipeline_id, visible) => {
                debug!("constellation got set visibility change complete message");
                self.handle_visibility_change_complete(pipeline_id, visible);
            }
            FromScriptMsg::RemoveIFrame(browsing_context_id, sender) => {
                debug!("constellation got remove iframe message");
                let removed_pipeline_ids = self.handle_remove_iframe_msg(browsing_context_id);
                if let Err(e) = sender.send(removed_pipeline_ids) {
                    warn!("Error replying to remove iframe ({})", e);
                }
            }
            FromScriptMsg::NewFavicon(url) => {
                debug!("constellation got new favicon message");
                self.compositor_proxy.send(ToCompositorMsg::NewFavicon(url));
            }
            FromScriptMsg::HeadParsed => {
                debug!("constellation got head parsed message");
                self.compositor_proxy.send(ToCompositorMsg::HeadParsed);
            }
            FromScriptMsg::CreateCanvasPaintThread(size, sender) => {
                debug!("constellation got create-canvas-paint-thread message");
                self.handle_create_canvas_paint_thread_msg(&size, sender)
            }
            FromScriptMsg::CreateWebGLPaintThread(size, attributes, sender) => {
                debug!("constellation got create-WebGL-paint-thread message");
                self.handle_create_webgl_paint_thread_msg(&size, attributes, sender)
            }
            FromScriptMsg::NodeStatus(message) => {
                debug!("constellation got NodeStatus message");
                self.compositor_proxy.send(ToCompositorMsg::Status(message));
            }
            FromScriptMsg::SetDocumentState(pipeline_id, state) => {
                debug!("constellation got SetDocumentState message");
                self.document_states.insert(pipeline_id, state);
            }
            FromScriptMsg::Alert(pipeline_id, message, sender) => {
                debug!("constellation got Alert message");
                self.handle_alert(pipeline_id, message, sender);
            }

            FromScriptMsg::ScrollFragmentPoint(scroll_root_id, point, smooth) => {
                self.compositor_proxy.send(ToCompositorMsg::ScrollFragmentPoint(scroll_root_id,
                                                                                point,
                                                                                smooth));
            }

            FromScriptMsg::GetClientWindow(send) => {
                self.compositor_proxy.send(ToCompositorMsg::GetClientWindow(send));
            }

            FromScriptMsg::MoveTo(point) => {
                self.compositor_proxy.send(ToCompositorMsg::MoveTo(point));
            }

            FromScriptMsg::ResizeTo(size) => {
                self.compositor_proxy.send(ToCompositorMsg::ResizeTo(size));
            }

            FromScriptMsg::Exit => {
                self.compositor_proxy.send(ToCompositorMsg::Exit);
            }
            FromScriptMsg::LogEntry(top_level_browsing_context_id, thread_name, entry) => {
                self.handle_log_entry(top_level_browsing_context_id, thread_name, entry);
            }

            FromScriptMsg::SetTitle(pipeline_id, title) => {
                self.compositor_proxy.send(ToCompositorMsg::ChangePageTitle(pipeline_id, title))
            }

            FromScriptMsg::SendKeyEvent(ch, key, key_state, key_modifiers) => {
                self.compositor_proxy.send(ToCompositorMsg::KeyEvent(ch, key, key_state, key_modifiers))
            }

            FromScriptMsg::TouchEventProcessed(result) => {
                self.compositor_proxy.send(ToCompositorMsg::TouchEventProcessed(result))
            }
            FromScriptMsg::GetBrowsingContextId(pipeline_id, sender) => {
                let result = self.pipelines.get(&pipeline_id).map(|pipeline| pipeline.browsing_context_id);
                if let Err(e) = sender.send(result) {
                    warn!("Sending reply to get browsing context failed ({:?}).", e);
                }
            }
            FromScriptMsg::GetParentInfo(pipeline_id, sender) => {
                let result = self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.parent_info);
                if let Err(e) = sender.send(result) {
                    warn!("Sending reply to get parent info failed ({:?}).", e);
                }
            }
            FromScriptMsg::RegisterServiceWorker(scope_things, scope) => {
                debug!("constellation got store registration scope message");
                self.handle_register_serviceworker(scope_things, scope);
            }
            FromScriptMsg::ForwardDOMMessage(msg_vec, scope_url) => {
                if let Some(ref mgr) = self.swmanager_chan {
                    let _ = mgr.send(ServiceWorkerMsg::ForwardDOMMessage(msg_vec, scope_url));
                } else {
                    warn!("Unable to forward DOMMessage for postMessage call");
                }
            }
            FromScriptMsg::BroadcastStorageEvent(pipeline_id, storage, url, key, old_value, new_value) => {
                self.handle_broadcast_storage_event(pipeline_id, storage, url, key, old_value, new_value);
            }
            FromScriptMsg::SetFullscreenState(state) => {
                self.compositor_proxy.send(ToCompositorMsg::SetFullscreenState(state));
            }
        }
    }

    fn handle_request_from_layout(&mut self, message: FromLayoutMsg) {
        match message {
            FromLayoutMsg::ChangeRunningAnimationsState(pipeline_id, animation_state) => {
                self.handle_change_running_animations_state(pipeline_id, animation_state)
            }
            // Layout sends new sizes for all subframes. This needs to be reflected by all
            // frame trees in the navigation context containing the subframe.
            FromLayoutMsg::IFrameSizes(iframe_sizes) => {
                debug!("constellation got iframe size message");
                self.handle_iframe_size_msg(iframe_sizes);
            }
            FromLayoutMsg::SetCursor(cursor) => {
                self.handle_set_cursor_msg(cursor)
            }
            FromLayoutMsg::ViewportConstrained(pipeline_id, constraints) => {
                debug!("constellation got viewport-constrained event message");
                self.handle_viewport_constrained_msg(pipeline_id, constraints);
            }
        }
    }

    fn handle_register_serviceworker(&self, scope_things: ScopeThings, scope: ServoUrl) {
        if let Some(ref mgr) = self.swmanager_chan {
            let _ = mgr.send(ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope));
        } else {
            warn!("sending scope info to service worker manager failed");
        }
    }

    fn handle_broadcast_storage_event(&self, pipeline_id: PipelineId, storage: StorageType, url: ServoUrl,
                                      key: Option<String>, old_value: Option<String>, new_value: Option<String>) {
        let origin = url.origin();
        for pipeline in self.pipelines.values() {
            if (pipeline.id != pipeline_id) && (pipeline.url.origin() == origin) {
                let msg = ConstellationControlMsg::DispatchStorageEvent(
                    pipeline.id, storage, url.clone(), key.clone(), old_value.clone(), new_value.clone()
                );
                if let Err(err) = pipeline.event_loop.send(msg) {
                    warn!("Failed to broadcast storage event to pipeline {} ({:?}).", pipeline.id, err);
                }
            }
        }
    }

    fn handle_exit(&mut self) {
        // TODO: add a timer, which forces shutdown if threads aren't responsive.
        if self.shutting_down { return; }
        self.shutting_down = true;

        self.mem_profiler_chan.send(mem::ProfilerMsg::Exit);

        // Close the top-level browsing contexts
        let browsing_context_ids: Vec<BrowsingContextId> = self.browsing_contexts.values()
            .filter(|browsing_context| browsing_context.is_top_level())
            .map(|browsing_context| browsing_context.id)
            .collect();
        for browsing_context_id in browsing_context_ids {
            debug!("Removing top-level browsing context {}.", browsing_context_id);
            self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
        }

        // Close any pending changes and pipelines
        while let Some(pending) = self.pending_changes.pop() {
            debug!("Removing pending browsing context {}.", pending.browsing_context_id);
            self.close_browsing_context(pending.browsing_context_id, ExitPipelineMode::Normal);
            debug!("Removing pending pipeline {}.", pending.new_pipeline_id);
            self.close_pipeline(pending.new_pipeline_id, DiscardBrowsingContext::Yes, ExitPipelineMode::Normal);
        }

        // In case there are browsing contexts which weren't attached, we close them.
        let browsing_context_ids: Vec<BrowsingContextId> = self.browsing_contexts.keys().cloned().collect();
        for browsing_context_id in browsing_context_ids {
            debug!("Removing detached browsing context {}.", browsing_context_id);
            self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
        }

        // In case there are pipelines which weren't attached to the pipeline tree, we close them.
        let pipeline_ids: Vec<PipelineId> = self.pipelines.keys().cloned().collect();
        for pipeline_id in pipeline_ids {
            debug!("Removing detached pipeline {}.", pipeline_id);
            self.close_pipeline(pipeline_id, DiscardBrowsingContext::Yes, ExitPipelineMode::Normal);
        }
    }

    fn handle_shutdown(&mut self) {
        // At this point, there are no active pipelines,
        // so we can safely block on other threads, without worrying about deadlock.
        // Channels to receive signals when threads are done exiting.
        let (core_sender, core_receiver) = ipc::channel().expect("Failed to create IPC channel!");
        let (storage_sender, storage_receiver) = ipc::channel().expect("Failed to create IPC channel!");

        debug!("Exiting core resource threads.");
        if let Err(e) = self.public_resource_threads.send(net_traits::CoreResourceMsg::Exit(core_sender)) {
            warn!("Exit resource thread failed ({})", e);
        }

        if let Some(ref chan) = self.debugger_chan {
            debugger::shutdown_server(chan);
        }

        if let Some(ref chan) = self.devtools_chan {
            debug!("Exiting devtools.");
            let msg = DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg);
            if let Err(e) = chan.send(msg) {
                warn!("Exit devtools failed ({})", e);
            }
        }

        debug!("Exiting storage resource threads.");
        if let Err(e) = self.public_resource_threads.send(StorageThreadMsg::Exit(storage_sender)) {
            warn!("Exit storage thread failed ({})", e);
        }

        debug!("Exiting bluetooth thread.");
        if let Err(e) = self.bluetooth_thread.send(BluetoothRequest::Exit) {
            warn!("Exit bluetooth thread failed ({})", e);
        }

        debug!("Exiting service worker manager thread.");
        if let Some(mgr) = self.swmanager_chan.as_ref() {
            if let Err(e) = mgr.send(ServiceWorkerMsg::Exit) {
                warn!("Exit service worker manager failed ({})", e);
            }
        }

        if let Some(chan) = self.webvr_thread.as_ref() {
            debug!("Exiting WebVR thread.");
            if let Err(e) = chan.send(WebVRMsg::Exit) {
                warn!("Exit WebVR thread failed ({})", e);
            }
        }

        debug!("Exiting timer scheduler.");
        if let Err(e) = self.scheduler_chan.send(TimerSchedulerMsg::Exit) {
            warn!("Exit timer scheduler failed ({})", e);
        }

        debug!("Exiting font cache thread.");
        self.font_cache_thread.exit();

        // Receive exit signals from threads.
        if let Err(e) = core_receiver.recv() {
            warn!("Exit resource thread failed ({})", e);
        }
        if let Err(e) = storage_receiver.recv() {
            warn!("Exit storage thread failed ({})", e);
        }

        debug!("Asking compositor to complete shutdown.");
        self.compositor_proxy.send(ToCompositorMsg::ShutdownComplete);
    }

    fn handle_pipeline_exited(&mut self, pipeline_id: PipelineId) {
        debug!("Pipeline {:?} exited.", pipeline_id);
        self.pipelines.remove(&pipeline_id);
    }

    fn handle_send_error(&mut self, pipeline_id: PipelineId, err: IpcError) {
        // Treat send error the same as receiving a panic message
        error!("Pipeline {} send error ({}).", pipeline_id, err);
        let top_level_browsing_context_id = self.pipelines.get(&pipeline_id)
            .map(|pipeline| pipeline.top_level_browsing_context_id);
        if let Some(top_level_browsing_context_id) = top_level_browsing_context_id {
            let reason = format!("Send failed ({})", err);
            self.handle_panic(top_level_browsing_context_id, reason, None);
        }
    }

    fn handle_panic(&mut self,
                    top_level_browsing_context_id: TopLevelBrowsingContextId,
                    reason: String,
                    backtrace: Option<String>)
    {
        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.
            println!("Pipeline failed in hard-fail mode.  Crashing!");
            process::exit(1);
        }

        debug!("Panic handler for top-level browsing context {}: {}.", top_level_browsing_context_id, reason);

        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);

        // Notify the browser chrome that the pipeline has failed
        self.trigger_mozbrowsererror(top_level_browsing_context_id, reason, backtrace);

        let (window_size, pipeline_id) = {
            let browsing_context = self.browsing_contexts.get(&browsing_context_id);
            let window_size = browsing_context.and_then(|browsing_context| browsing_context.size);
            let pipeline_id = browsing_context.map(|browsing_context| browsing_context.pipeline_id);
            (window_size, pipeline_id)
        };

        let (pipeline_url, parent_info) = {
            let pipeline = pipeline_id.and_then(|id| self.pipelines.get(&id));
            let pipeline_url = pipeline.map(|pipeline| pipeline.url.clone());
            let parent_info = pipeline.and_then(|pipeline| pipeline.parent_info);
            (pipeline_url, parent_info)
        };

        self.close_browsing_context_children(browsing_context_id,
                                             DiscardBrowsingContext::No,
                                             ExitPipelineMode::Force);

        let failure_url = ServoUrl::parse("about:failure").expect("infallible");

        if let Some(pipeline_url) = pipeline_url {
            if pipeline_url == failure_url {
                return error!("about:failure failed");
            }
        }

        warn!("creating replacement pipeline for about:failure");

        let new_pipeline_id = PipelineId::new();
        let load_data = LoadData::new(failure_url, None, None, None);
        let sandbox = IFrameSandboxState::IFrameSandboxed;
        self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_browsing_context_id, parent_info,
                          window_size, load_data.clone(), sandbox, false);
        self.add_pending_change(SessionHistoryChange {
            top_level_browsing_context_id: top_level_browsing_context_id,
            browsing_context_id: browsing_context_id,
            new_pipeline_id: new_pipeline_id,
            load_data: load_data,
            replace_instant: None,
        });
    }

    fn handle_log_entry(&mut self,
                        top_level_browsing_context_id: Option<TopLevelBrowsingContextId>,
                        thread_name: Option<String>,
                        entry: LogEntry)
    {
        debug!("Received log entry {:?}.", entry);
        match (entry, top_level_browsing_context_id) {
            (LogEntry::Panic(reason, backtrace), Some(top_level_browsing_context_id)) => {
                self.handle_panic(top_level_browsing_context_id, reason, Some(backtrace));
            },
            (LogEntry::Panic(reason, _), _) | (LogEntry::Error(reason), _) | (LogEntry::Warn(reason), _) => {
                // VecDeque::truncate is unstable
                if WARNINGS_BUFFER_SIZE <= self.handled_warnings.len() {
                    self.handled_warnings.pop_front();
                }
                self.handled_warnings.push_back((thread_name, reason));
            },
        }
    }

    fn handle_webvr_events(&mut self, ids: Vec<PipelineId>, events: Vec<WebVREvent>) {
        for id in ids {
            match self.pipelines.get_mut(&id) {
                Some(ref pipeline) => {
                    // Notify script thread
                    let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvents(id, events.clone()));
                },
                None => warn!("constellation got webvr event for dead pipeline")
            }
        }
    }

    fn handle_init_load(&mut self, url: ServoUrl) {
        let window_size = self.window_size.initial_viewport;
        let pipeline_id = PipelineId::new();
        let top_level_browsing_context_id = TopLevelBrowsingContextId::new();
        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let load_data = LoadData::new(url.clone(), None, None, None);
        let sandbox = IFrameSandboxState::IFrameUnsandboxed;
        self.focus_pipeline_id = Some(pipeline_id);
        self.new_pipeline(pipeline_id,
                          browsing_context_id,
                          top_level_browsing_context_id,
                          None,
                          Some(window_size),
                          load_data.clone(),
                          sandbox,
                          false);
        self.add_pending_change(SessionHistoryChange {
            top_level_browsing_context_id: top_level_browsing_context_id,
            browsing_context_id: browsing_context_id,
            new_pipeline_id: pipeline_id,
            load_data: load_data,
            replace_instant: None,
        });
    }

    fn handle_iframe_size_msg(&mut self,
                              iframe_sizes: Vec<(BrowsingContextId, TypedSize2D<f32, CSSPixel>)>) {
        for (browsing_context_id, size) in iframe_sizes {
            let window_size = WindowSizeData {
                initial_viewport: size,
                device_pixel_ratio: self.window_size.device_pixel_ratio,
            };

            self.resize_browsing_context(window_size, WindowSizeType::Initial, browsing_context_id);
        }
    }

    fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) {
        let (browsing_context_id, parent_id) = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => match pipeline.parent_info {
                Some((parent_id, _)) => (pipeline.browsing_context_id, parent_id),
                None => return debug!("Pipeline {} has no parent.", pipeline_id),
            },
            None => return warn!("Pipeline {} loaded after closure.", pipeline_id),
        };
        let msg = ConstellationControlMsg::DispatchIFrameLoadEvent {
            target: browsing_context_id,
            parent: parent_id,
            child: pipeline_id,
        };
        let result = match self.pipelines.get(&parent_id) {
            Some(parent) => parent.event_loop.send(msg),
            None => return warn!("Parent {} browsing context loaded after closure.", parent_id),
        };
        if let Err(e) = result {
            self.handle_send_error(parent_id, e);
        }
    }

    fn handle_navigate_request(&self,
                              req_init: RequestInit,
                              id: PipelineId) {
        let listener = NetworkListener::new(
                           req_init,
                           id,
                           self.public_resource_threads.clone(),
                           self.network_listener_sender.clone());

        listener.initiate_fetch();
    }

    // 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 child being added to
    // the parent pipeline. This message is never the result of a
    // page navigation.
    fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfoWithData) {
        let (load_data, window_size, is_private) = {
            let old_pipeline = load_info.old_pipeline_id
                .and_then(|old_pipeline_id| self.pipelines.get(&old_pipeline_id));

            let source_pipeline = match self.pipelines.get(&load_info.info.parent_pipeline_id) {
                Some(source_pipeline) => source_pipeline,
                None => return warn!("Script loaded url in closed iframe {}.", load_info.info.parent_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 => ServoUrl::parse("about:blank").expect("infallible"),
                };

                // TODO - loaddata here should have referrer info (not None, None)
                LoadData::new(url, Some(source_pipeline.id), None, None)
            });

            let is_private = load_info.info.is_private || source_pipeline.is_private;

            let window_size = self.browsing_contexts.get(&load_info.info.browsing_context_id)
                .and_then(|browsing_context| browsing_context.size);

            (load_data, window_size, is_private)
        };

        let replace_instant = if load_info.info.replace {
            self.browsing_contexts.get(&load_info.info.browsing_context_id)
                .map(|browsing_context| browsing_context.instant)
        } else {
            None
        };

        // Create the new pipeline, attached to the parent and push to pending changes
        self.add_pending_change(SessionHistoryChange {
            top_level_browsing_context_id: load_info.info.top_level_browsing_context_id,
            browsing_context_id: load_info.info.browsing_context_id,
            new_pipeline_id: load_info.info.new_pipeline_id,
            load_data: load_data.clone(),
            replace_instant: replace_instant,
        });

        self.new_pipeline(load_info.info.new_pipeline_id,
                          load_info.info.browsing_context_id,
                          load_info.info.top_level_browsing_context_id,
                          Some((load_info.info.parent_pipeline_id, load_info.info.frame_type)),
                          window_size,
                          load_data,
                          load_info.sandbox,
                          is_private);
    }

    fn handle_script_new_iframe(&mut self,
                                load_info: IFrameLoadInfo,
                                layout_sender: IpcSender<LayoutControlMsg>) {
        let IFrameLoadInfo {
            parent_pipeline_id,
            new_pipeline_id,
            frame_type,
            replace,
            browsing_context_id,
            top_level_browsing_context_id,
            is_private,
        } = load_info;

        let url = ServoUrl::parse("about:blank").expect("infallible");

        let pipeline = {
            let parent_pipeline = match self.pipelines.get(&parent_pipeline_id) {
                Some(parent_pipeline) => parent_pipeline,
                None => return warn!("Script loaded url in closed iframe {}.", parent_pipeline_id),
            };

            let script_sender = parent_pipeline.event_loop.clone();

            Pipeline::new(new_pipeline_id,
                          browsing_context_id,
                          top_level_browsing_context_id,
                          Some((parent_pipeline_id, frame_type)),
                          script_sender,
                          layout_sender,
                          self.compositor_proxy.clone_compositor_proxy(),
                          is_private || parent_pipeline.is_private,
                          url.clone(),
                          parent_pipeline.visible)
        };

        // TODO: Referrer?
        let load_data = LoadData::new(url, Some(parent_pipeline_id), None, None);

        let replace_instant = if replace {
            self.browsing_contexts.get(&browsing_context_id).map(|browsing_context| browsing_context.instant)
        } else {
            None
        };

        assert!(!self.pipelines.contains_key(&new_pipeline_id));
        self.pipelines.insert(new_pipeline_id, pipeline);

        self.add_pending_change(SessionHistoryChange {
            top_level_browsing_context_id: top_level_browsing_context_id,
            browsing_context_id: browsing_context_id,
            new_pipeline_id: new_pipeline_id,
            load_data: load_data,
            replace_instant: replace_instant,
        });
    }

    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.event_loop.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.send(msg),
                    None => return warn!("Pipeline {:?} got layout 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 top_level_browsing_context_id = self.pipelines.get(&pipeline_id)
            .map(|pipeline| pipeline.top_level_browsing_context_id);
        let browser_pipeline_id = top_level_browsing_context_id
            .map(BrowsingContextId::from)
            .and_then(|browsing_context_id| self.browsing_contexts.get(&browsing_context_id))
            .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
            .and_then(|pipeline| pipeline.parent_info)
            .map(|(browser_pipeline_id, _)| browser_pipeline_id);
        let mozbrowser_modal_prompt = PREFS.is_mozbrowser_enabled() && browser_pipeline_id.is_some();

        if mozbrowser_modal_prompt {
            // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsershowmodalprompt
            let prompt_type = String::from("alert");
            let title = String::from("Alert");
            let return_value = String::from("");
            let event = MozBrowserEvent::ShowModalPrompt(prompt_type, title, message, return_value);
            match browser_pipeline_id.and_then(|id| self.pipelines.get(&id)) {
                None => warn!("Alert sent after browser pipeline closure."),
                Some(pipeline) => pipeline.trigger_mozbrowser_event(top_level_browsing_context_id, event),
            }
        }

        let result = sender.send(!mozbrowser_modal_prompt);
        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, replace: bool) {
        self.load_url(source_id, load_data, replace);
    }

    fn load_url(&mut self, source_id: PipelineId, load_data: LoadData, replace: bool) -> Option<PipelineId> {
        // Allow the embedder to handle the url itself
        let (chan, port) = ipc::channel().expect("Failed to create IPC channel!");
        self.compositor_proxy.send(ToCompositorMsg::AllowNavigation(load_data.url.clone(), chan));
        if let Ok(false) = port.recv() {
            return None;
        }

        debug!("Loading {} in pipeline {}.", load_data.url, source_id);
        // 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.
        //
        // If replace is true, the current entry is replaced instead of a new entry being added.
        let (browsing_context_id, parent_info) = match self.pipelines.get(&source_id) {
            Some(pipeline) => (pipeline.browsing_context_id, pipeline.parent_info),
            None => {
                warn!("Pipeline {} loaded after closure.", source_id);
                return None;
            }
        };
        match parent_info {
            Some((parent_pipeline_id, _)) => {
                // Find the script thread for the pipeline containing the iframe
                // and issue an iframe load through there.
                let msg = ConstellationControlMsg::Navigate(parent_pipeline_id,
                                                            browsing_context_id,
                                                            load_data,
                                                            replace);
                let result = match self.pipelines.get(&parent_pipeline_id) {
                    Some(parent_pipeline) => parent_pipeline.event_loop.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);
                }
                None
            }
            None => {
                // Make sure no pending page would be overridden.
                for change in &self.pending_changes {
                    if change.browsing_context_id == browsing_context_id {
                        // id that sent load msg is being changed already; abort
                        return None;
                    }
                }

                if self.get_activity(source_id) == DocumentActivity::Inactive {
                    // 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;
                }

                // Being here means either there are no pending changes, or none of the pending
                // changes would be overridden by changing the subframe associated with source_id.

                // Create the new pipeline
                let (top_level_id, window_size, timestamp) = match self.browsing_contexts.get(&browsing_context_id) {
                    Some(context) => (context.top_level_id, context.size, context.instant),
                    None => {
                        warn!("Browsing context {} loaded after closure.", browsing_context_id);
                        return None;
                    }
                };
                let new_pipeline_id = PipelineId::new();
                let sandbox = IFrameSandboxState::IFrameUnsandboxed;
                let replace_instant = if replace { Some(timestamp) } else { None };
                self.add_pending_change(SessionHistoryChange {
                    top_level_browsing_context_id: top_level_id,
                    browsing_context_id: browsing_context_id,
                    new_pipeline_id: new_pipeline_id,
                    load_data: load_data.clone(),
                    replace_instant: replace_instant,
                });
                self.new_pipeline(new_pipeline_id,
                                  browsing_context_id,
                                  top_level_id,
                                  None,
                                  window_size,
                                  load_data,
                                  sandbox,
                                  false);
                Some(new_pipeline_id)
            }
        }
    }

    fn handle_load_start_msg(&mut self, _pipeline_id: PipelineId) {
        self.compositor_proxy.send(ToCompositorMsg::LoadStart);
    }

    fn handle_load_complete_msg(&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.compositor_proxy.send(ToCompositorMsg::LoadComplete);
        self.handle_subframe_loaded(pipeline_id);
    }

    fn handle_traverse_history_msg(&mut self,
                                   top_level_browsing_context_id: TopLevelBrowsingContextId,
                                   direction: TraversalDirection)
    {
        let mut size = 0;
        let mut table = HashMap::new();

        match direction {
            TraversalDirection::Forward(delta) => {
                for entry in self.joint_session_future(top_level_browsing_context_id).take(delta) {
                    size = size + 1;
                    table.insert(entry.browsing_context_id, entry.clone());
                }
                if size < delta {
                    return debug!("Traversing forward too much.");
                }
            },
            TraversalDirection::Back(delta) => {
                for entry in self.joint_session_past(top_level_browsing_context_id).take(delta) {
                    size = size + 1;
                    table.insert(entry.browsing_context_id, entry.clone());
                }
                if size < delta {
                    return debug!("Traversing back too much.");
                }
            },
        }

        for (_, entry) in table {
            self.traverse_to_entry(entry);
        }
    }

    fn handle_joint_session_history_length(&self,
                                           top_level_browsing_context_id: TopLevelBrowsingContextId,
                                           sender: IpcSender<u32>)
    {
        // Initialize length at 1 to count for the current active entry
        let mut length = 1;
        for browsing_context in self.all_browsing_contexts_iter(top_level_browsing_context_id) {
            length += browsing_context.next.len();
            length += browsing_context.prev.len();
        }
        let _ = sender.send(length as u32);
    }

    fn handle_key_msg(&mut self, ch: Option<char>, key: Key, state: KeyState, mods: KeyModifiers) {
        // Send to the explicitly focused pipeline. If it doesn't exist, fall back to sending to
        // the compositor.
        match self.focus_pipeline_id {
            Some(pipeline_id) => {
                let event = CompositorEvent::KeyEvent(ch, key, state, mods);
                let msg = ConstellationControlMsg::SendEvent(pipeline_id, event);
                let result = match self.pipelines.get(&pipeline_id) {
                    Some(pipeline) => pipeline.event_loop.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(ch, key, state, mods);
                self.compositor_proxy.clone_compositor_proxy().send(event);
            }
        }
    }

    fn handle_reload_msg(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
            Some(browsing_context) => browsing_context.pipeline_id,
            None => return warn!("Browsing context {} got reload event after closure.", browsing_context_id),
        };
        let msg = ConstellationControlMsg::Reload(pipeline_id);
        let result = match self.pipelines.get(&pipeline_id) {
            None => return warn!("Pipeline {} got reload event after closure.", pipeline_id),
            Some(pipeline) => pipeline.event_loop.send(msg),
        };
        if let Err(e) = result {
            self.handle_send_error(pipeline_id, e);
        }
    }

    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.event_loop.send(ConstellationControlMsg::GetTitle(pipeline_id)),
        };
        if let Err(e) = result {
            self.handle_send_error(pipeline_id, e);
        }
    }

    fn handle_post_message_msg(&mut self,
                               browsing_context_id: BrowsingContextId,
                               origin: Option<ImmutableOrigin>,
                               data: Vec<u8>)
    {
        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
            None => return warn!("postMessage to closed browsing_context {}.", browsing_context_id),
            Some(browsing_context) => browsing_context.pipeline_id,
        };
        let msg = ConstellationControlMsg::PostMessage(pipeline_id, origin, data);
        let result = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => pipeline.event_loop.send(msg),
            None => return warn!("postMessage to closed pipeline {}.", pipeline_id),
        };
        if let Err(e) = result {
            self.handle_send_error(pipeline_id, e);
        }
    }

    fn handle_mozbrowser_event_msg(&mut self,
                                   pipeline_id: PipelineId,
                                   top_level_browsing_context_id: TopLevelBrowsingContextId,
                                   event: MozBrowserEvent) {
        assert!(PREFS.is_mozbrowser_enabled());

        // 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(&pipeline_id) {
            Some(pipeline) => pipeline.trigger_mozbrowser_event(Some(top_level_browsing_context_id), event),
            None => warn!("Pipeline {:?} handling mozbrowser event after closure.", pipeline_id),
        }
    }

    fn handle_get_pipeline(&mut self,
                           browsing_context_id: BrowsingContextId,
                           resp_chan: IpcSender<Option<PipelineId>>) {
        let current_pipeline_id = self.browsing_contexts.get(&browsing_context_id)
            .map(|browsing_context| browsing_context.pipeline_id);
        let pipeline_id_loaded = self.pending_changes.iter().rev()
            .find(|x| x.browsing_context_id == browsing_context_id)
            .map(|x| x.new_pipeline_id)
            .or(current_pipeline_id);
        if let Err(e) = resp_chan.send(pipeline_id_loaded) {
            warn!("Failed get_pipeline response ({}).", e);
        }
    }

    fn handle_get_browsing_context(&mut self,
                                   pipeline_id: PipelineId,
                                   resp_chan: IpcSender<Option<BrowsingContextId>>) {
        let browsing_context_id = self.pipelines.get(&pipeline_id).map(|pipeline| pipeline.browsing_context_id);
        if let Err(e) = resp_chan.send(browsing_context_id) {
            warn!("Failed get_browsing_context response ({}).", e);
        }
    }

    fn focus_parent_pipeline(&mut self, pipeline_id: PipelineId) {
        let (browsing_context_id, parent_info) = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => (pipeline.browsing_context_id, pipeline.parent_info),
            None => return warn!("Pipeline {:?} focus parent after closure.", pipeline_id),
        };
        let (parent_pipeline_id, _) = match parent_info {
            Some(info) => info,
            None => return debug!("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(parent_pipeline_id, browsing_context_id);
        let result = match self.pipelines.get(&parent_pipeline_id) {
            Some(pipeline) => pipeline.event_loop.send(msg),
            None => return warn!("Pipeline {:?} focus after closure.", parent_pipeline_id),
        };
        if let Err(e) = result {
            self.handle_send_error(parent_pipeline_id, e);
        }
        self.focus_parent_pipeline(parent_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, browsing_context_id: BrowsingContextId) -> Vec<PipelineId> {
        let result = self.all_descendant_browsing_contexts_iter(browsing_context_id)
            .flat_map(|browsing_context| browsing_context.next.iter().chain(browsing_context.prev.iter())
                      .filter_map(|entry| entry.pipeline_id)
                      .chain(once(browsing_context.pipeline_id)))
            .collect();
        self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
        result
    }

    fn handle_set_visible_msg(&mut self, pipeline_id: PipelineId, visible: bool) {
        let browsing_context_id = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => pipeline.browsing_context_id,
            None => return warn!("No browsing context associated with pipeline {:?}", pipeline_id),
        };

        let child_pipeline_ids: Vec<PipelineId> = self.all_descendant_browsing_contexts_iter(browsing_context_id)
            .flat_map(|browsing_context| browsing_context.prev.iter().chain(browsing_context.next.iter())
                      .filter_map(|entry| entry.pipeline_id)
                      .chain(once(browsing_context.pipeline_id)))
            .collect();

        for id in child_pipeline_ids {
            if let Some(pipeline) = self.pipelines.get_mut(&id) {
                pipeline.change_visibility(visible);
            }
        }
    }

    fn handle_visibility_change_complete(&mut self, pipeline_id: PipelineId, visibility: bool) {
        let (browsing_context_id, parent_pipeline_info) = match self.pipelines.get(&pipeline_id) {
            None => return warn!("Visibity change for closed pipeline {:?}.", pipeline_id),
            Some(pipeline) => (pipeline.browsing_context_id, pipeline.parent_info),
        };
        if let Some((parent_pipeline_id, _)) = parent_pipeline_info {
            let visibility_msg = ConstellationControlMsg::NotifyVisibilityChange(parent_pipeline_id,
                                                                                 browsing_context_id,
                                                                                 visibility);
            let  result = match self.pipelines.get(&parent_pipeline_id) {
                None => return warn!("Parent pipeline {:?} closed", parent_pipeline_id),
                Some(parent_pipeline) => parent_pipeline.event_loop.send(visibility_msg),
            };

            if let Err(e) = result {
                self.handle_send_error(parent_pipeline_id, e);
            }
        }
    }

    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,
                                              opts::get().enable_canvas_antialiasing);
        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::GetWindowSize(_, reply) => {
               let _ = reply.send(self.window_size);
            },
            WebDriverCommandMsg::SetWindowSize(_, size, reply) => {
                self.webdriver.resize_channel = Some(reply);
                self.compositor_proxy.send(ToCompositorMsg::ResizeTo(size));
            },
            WebDriverCommandMsg::LoadUrl(top_level_browsing_context_id, load_data, reply) => {
                self.load_url_for_webdriver(top_level_browsing_context_id, load_data, reply, false);
            },
            WebDriverCommandMsg::Refresh(top_level_browsing_context_id, reply) => {
                let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
                let load_data = match self.browsing_contexts.get(&browsing_context_id) {
                    Some(browsing_context) => browsing_context.load_data.clone(),
                    None => return warn!("Browsing context {} Refresh after closure.", browsing_context_id),
                };
                self.load_url_for_webdriver(top_level_browsing_context_id, load_data, reply, true);
            }
            WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
                let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
                    Some(browsing_context) => browsing_context.pipeline_id,
                    None => return warn!("Browsing context {} ScriptCommand after closure.", browsing_context_id),
                };
                let control_msg = ConstellationControlMsg::WebDriverScriptCommand(pipeline_id, cmd);
                let result = match self.pipelines.get(&pipeline_id) {
                    Some(pipeline) => pipeline.event_loop.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(browsing_context_id, cmd) => {
                let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
                    Some(browsing_context) => browsing_context.pipeline_id,
                    None => return warn!("Browsing context {} SendKeys after closure.", browsing_context_id),
                };
                let event_loop = match self.pipelines.get(&pipeline_id) {
                    Some(pipeline) => pipeline.event_loop.clone(),
                    None => return warn!("Pipeline {} SendKeys after closure.", pipeline_id),
                };
                for (key, mods, state) in cmd {
                    let event = CompositorEvent::KeyEvent(None, key, state, mods);
                    let control_msg = ConstellationControlMsg::SendEvent(pipeline_id, event);
                    if let Err(e) = event_loop.send(control_msg) {
                        return self.handle_send_error(pipeline_id, e);
                    }
                }
            },
            WebDriverCommandMsg::TakeScreenshot(_, reply) => {
                self.compositor_proxy.send(ToCompositorMsg::CreatePng(reply));
            },
        }
    }

    // https://html.spec.whatwg.org/multipage/#traverse-the-history
    fn traverse_to_entry(&mut self, entry: SessionHistoryEntry) {
        // Step 1.
        let browsing_context_id = entry.browsing_context_id;
        let pipeline_id = match entry.pipeline_id {
            Some(pipeline_id) => pipeline_id,
            None => {
                // If there is no pipeline, then the document for this
                // entry has been discarded, so we navigate to the entry
                // URL instead. When the document has activated, it will
                // traverse to the entry, but with the new pipeline id.
                debug!("Reloading document {} in browsing context {}.", entry.load_data.url, entry.browsing_context_id);
                // TODO: save the sandbox state so it can be restored here.
                let sandbox = IFrameSandboxState::IFrameUnsandboxed;
                let new_pipeline_id = PipelineId::new();
                let load_data = entry.load_data;
                let (top_level_id, parent_info, window_size, is_private) =
                    match self.browsing_contexts.get(&browsing_context_id)
                {
                    Some(browsing_context) => match self.pipelines.get(&browsing_context.pipeline_id) {
                        Some(pipeline) => (browsing_context.top_level_id,
                                           pipeline.parent_info,
                                           browsing_context.size,
                                           pipeline.is_private),
                        None => (browsing_context.top_level_id,
                                 None,
                                 browsing_context.size,
                                 false),
                    },
                    None => return warn!("no browsing context to traverse"),
                };
                self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_id, parent_info,
                                  window_size, load_data.clone(), sandbox, is_private);
                self.add_pending_change(SessionHistoryChange {
                    top_level_browsing_context_id: top_level_id,
                    browsing_context_id: browsing_context_id,
                    new_pipeline_id: new_pipeline_id,
                    load_data: load_data,
                    replace_instant: Some(entry.instant),
                });
                return;
            }
        };

        // 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_is_descendant_of(entry.browsing_context_id);

        let (old_pipeline_id, replaced_pipeline_id, top_level_id) =
            match self.browsing_contexts.get_mut(&browsing_context_id)
        {
            Some(browsing_context) => {
                let old_pipeline_id = browsing_context.pipeline_id;
                let top_level_id = browsing_context.top_level_id;
                let mut curr_entry = browsing_context.current();

                if entry.instant > browsing_context.instant {
                    // We are traversing to the future.
                    while let Some(next) = browsing_context.next.pop() {
                        browsing_context.prev.push(curr_entry);
                        curr_entry = next;
                        if entry.instant <= curr_entry.instant { break; }
                    }
                } else if entry.instant < browsing_context.instant {
                    // We are traversing to the past.
                    while let Some(prev) = browsing_context.prev.pop() {
                        browsing_context.next.push(curr_entry);
                        curr_entry = prev;
                        if entry.instant >= curr_entry.instant { break; }
                    }
                }

                debug_assert_eq!(entry.instant, curr_entry.instant);

                let replaced_pipeline_id = curr_entry.pipeline_id;

                browsing_context.update_current(pipeline_id, entry);

                (old_pipeline_id, replaced_pipeline_id, top_level_id)
            },
            None => return warn!("no browsing context to traverse"),
        };

        let parent_info = self.pipelines.get(&old_pipeline_id)
            .and_then(|pipeline| pipeline.parent_info);

        // 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(pipeline_id);
        }

        // If we replaced a pipeline, close it.
        if let Some(replaced_pipeline_id) = replaced_pipeline_id {
            if replaced_pipeline_id != pipeline_id {
                self.close_pipeline(replaced_pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
            }
        }

        // Deactivate the old pipeline, and activate the new one.
        self.update_activity(old_pipeline_id);
        self.update_activity(pipeline_id);
        self.notify_history_changed(top_level_id);

        // Set paint permissions correctly for the compositor layers.
        self.send_frame_tree(top_level_id);

        // Update the owning iframe to point to the new pipeline id.
        // This makes things like contentDocument work correctly.
        if let Some((parent_pipeline_id, frame_type)) = parent_info {
            let msg = ConstellationControlMsg::UpdatePipelineId(parent_pipeline_id,
                browsing_context_id, pipeline_id, UpdatePipelineIdReason::Traversal);
            let result = match self.pipelines.get(&parent_pipeline_id) {
                None => return warn!("Pipeline {:?} child traversed after closure.", parent_pipeline_id),
                Some(pipeline) => pipeline.event_loop.send(msg),
            };
            if let Err(e) = result {
                self.handle_send_error(parent_pipeline_id, e);
            }

            // If this is a mozbrowser iframe, send a mozbrowser location change event.
            // This is the result of a back/forward traversal.
            if frame_type == FrameType::MozBrowserIFrame {
                self.trigger_mozbrowserlocationchange(top_level_id);
            }
        }
    }

    fn notify_history_changed(&self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
        // Send a flat projection of the history.
        // The final vector is a concatenation of the LoadData of the past entries,
        // the current entry and the future entries.
        // LoadData of inner frames are ignored and replaced with the LoadData of the parent.

        // Ignore LoadData of non-top-level browsing contexts.
        let keep_load_data_if_top_browsing_context = |entry: &SessionHistoryEntry| {
            match entry.pipeline_id {
                None => Some(entry.load_data.clone()),
                Some(pipeline_id) => {
                    match self.pipelines.get(&pipeline_id) {
                        None => Some(entry.load_data.clone()),
                        Some(pipeline) => match pipeline.parent_info {
                            None => Some(entry.load_data.clone()),
                            Some(_) => None,
                        }
                    }
                }
            }
        };

        // If LoadData was ignored, use the LoadData of the previous SessionHistoryEntry, which
        // is the LoadData of the parent browsing context.
        let resolve_load_data = |previous_load_data: &mut LoadData, load_data| {
            let load_data = match load_data {
                None => previous_load_data.clone(),
                Some(load_data) => load_data,
            };
            *previous_load_data = load_data.clone();
            Some(load_data)
        };

        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let current_load_data = match self.browsing_contexts.get(&browsing_context_id) {
            Some(browsing_context) => browsing_context.load_data.clone(),
            None => return warn!("notify_history_changed error after top-level browsing context closed."),
        };

        let mut entries: Vec<LoadData> = self.joint_session_past(top_level_browsing_context_id)
            .map(&keep_load_data_if_top_browsing_context)
            .scan(current_load_data.clone(), &resolve_load_data)
            .collect();

        entries.reverse();

        let current_index = entries.len();

        entries.push(current_load_data.clone());

        entries.extend(self.joint_session_future(top_level_browsing_context_id)
                       .map(&keep_load_data_if_top_browsing_context)
                       .scan(current_load_data.clone(), &resolve_load_data));

        self.compositor_proxy.send(ToCompositorMsg::HistoryChanged(entries, current_index));
    }

    fn load_url_for_webdriver(&mut self,
                              top_level_browsing_context_id: TopLevelBrowsingContextId,
                              load_data: LoadData,
                              reply: IpcSender<webdriver_msg::LoadStatus>,
                              replace: bool)
    {
        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
            Some(browsing_context) => browsing_context.pipeline_id,
            None => return warn!("Webdriver load for closed browsing context {}.", browsing_context_id),
        };
        if let Some(new_pipeline_id) = self.load_url(pipeline_id, load_data, replace) {
            self.webdriver.load_channel = Some((new_pipeline_id, reply));
        }
    }

    fn change_session_history(&mut self, change: SessionHistoryChange) {
        debug!("Setting browsing context {} to be pipeline {}.", change.browsing_context_id, change.new_pipeline_id);

        // 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 self.focused_pipeline_is_descendant_of(change.browsing_context_id) {
            self.focus_pipeline_id = Some(change.new_pipeline_id);
        }

        let (evicted_id, new_context, navigated) = if let Some(instant) = change.replace_instant {
            debug!("Replacing pipeline in existing browsing context with timestamp {:?}.", instant);
            let entry = SessionHistoryEntry {
                browsing_context_id: change.browsing_context_id,
                pipeline_id: Some(change.new_pipeline_id),
                load_data: change.load_data.clone(),
                instant: instant,
            };
            self.traverse_to_entry(entry);
            (None, false, None)
        } else if let Some(browsing_context) = self.browsing_contexts.get_mut(&change.browsing_context_id) {
            debug!("Adding pipeline to existing browsing context.");
            let old_pipeline_id = browsing_context.pipeline_id;
            browsing_context.load(change.new_pipeline_id, change.load_data.clone());
            let evicted_id = browsing_context.prev.len()
                .checked_sub(PREFS.get("session-history.max-length").as_u64().unwrap_or(20) as usize)
                .and_then(|index| browsing_context.prev.get_mut(index))
                .and_then(|entry| entry.pipeline_id.take());
            (evicted_id, false, Some(old_pipeline_id))
        } else {
            debug!("Adding pipeline to new browsing context.");
            (None, true, None)
        };

        if let Some(evicted_id) = evicted_id {
            self.close_pipeline(evicted_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
        }

        if new_context {
            self.new_browsing_context(change.browsing_context_id,
                                      change.top_level_browsing_context_id,
                                      change.new_pipeline_id,
                                      change.load_data);
            self.update_activity(change.new_pipeline_id);
            self.notify_history_changed(change.top_level_browsing_context_id);
        };

        if let Some(old_pipeline_id) = navigated {
            // Deactivate the old pipeline, and activate the new one.
            self.update_activity(old_pipeline_id);
            self.update_activity(change.new_pipeline_id);
            // Clear the joint session future
            self.clear_joint_session_future(change.top_level_browsing_context_id);
            self.notify_history_changed(change.top_level_browsing_context_id);
        }

        // If the navigation is for a top-level browsing context, inform mozbrowser
        if change.browsing_context_id == change.top_level_browsing_context_id {
            self.trigger_mozbrowserlocationchange(change.top_level_browsing_context_id);
        }

        // Build frame tree
        self.send_frame_tree(change.top_level_browsing_context_id);
    }

    fn handle_activate_document_msg(&mut self, pipeline_id: PipelineId) {
        debug!("Document ready to activate {}", pipeline_id);

        // Notify the parent (if there is one).
        if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
            if let Some((parent_pipeline_id, _)) = pipeline.parent_info {
                if let Some(parent_pipeline) = self.pipelines.get(&parent_pipeline_id) {
                    let msg = ConstellationControlMsg::UpdatePipelineId(parent_pipeline_id,
                        pipeline.browsing_context_id, pipeline_id, UpdatePipelineIdReason::Navigation);
                    let _ = parent_pipeline.event_loop.send(msg);
                }
            }
        }

        // Find the pending change whose new pipeline id is pipeline_id.
        let pending_index = self.pending_changes.iter().rposition(|change| {
            change.new_pipeline_id == pipeline_id
        });

        // If it is found, remove it from the pending changes, and make it
        // the active document of its frame.
        if let Some(pending_index) = pending_index {
            let change = self.pending_changes.swap_remove(pending_index);
            self.change_session_history(change);
        }
    }

    /// Called when the window is resized.
    fn handle_window_size_msg(&mut self,
                              top_level_browsing_context_id: TopLevelBrowsingContextId,
                              new_size: WindowSizeData,
                              size_type: WindowSizeType)
    {
        debug!("handle_window_size_msg: {:?}", new_size.initial_viewport.to_untyped());

        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        self.resize_browsing_context(new_size, size_type, browsing_context_id);

        if let Some(resize_channel) = self.webdriver.resize_channel.take() {
            let _ = resize_channel.send(new_size);
        }

        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 focus browsing context yet, the initial page has
        // not loaded, so there is nothing to save yet.
        let top_level_browsing_context_id = self.focus_pipeline_id
            .and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
            .map(|pipeline| pipeline.top_level_browsing_context_id);
        let top_level_browsing_context_id = match top_level_browsing_context_id {
            Some(id) => id,
            None => return ReadyToSave::NoTopLevelBrowsingContext,
        };

        // If there are pending loads, wait for those to complete.
        if !self.pending_changes.is_empty() {
            return ReadyToSave::PendingChanges;
        }

        let (state_sender, state_receiver) = ipc::channel().expect("Failed to create IPC channel!");
        let (epoch_sender, epoch_receiver) = ipc::channel().expect("Failed to create IPC channel!");

        // Step through the fully active browsing contexts, checking that the script
        // thread is idle, and that the current epoch of the layout thread
        // matches what the compositor has painted. If all these conditions
        // are met, then the output image should not change and a reftest
        // screenshot can safely be written.
        for browsing_context in self.fully_active_browsing_contexts_iter(top_level_browsing_context_id) {
            let pipeline_id = browsing_context.pipeline_id;
            debug!("Checking readiness of browsing context {}, pipeline {}.", browsing_context.id, pipeline_id);

            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 msg = LayoutControlMsg::GetWebFontLoadState(state_sender.clone());
            if let Err(e) = pipeline.layout_chan.send(msg) {
                warn!("Get web font failed ({})", e);
            }
            if state_receiver.recv().unwrap_or(true) {
                return ReadyToSave::WebFontNotLoaded;
            }

            // See if this pipeline has reached idle script state yet.
            match self.document_states.get(&browsing_context.pipeline_id) {
                Some(&DocumentState::Idle) => {}
                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) = browsing_context.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 == TypedSize2D::zero() {
                    continue;
                }

                // Get the epoch that the compositor has drawn for this pipeline.
                let compositor_epoch = pipeline_states.get(&browsing_context.pipeline_id);
                match compositor_epoch {
                    Some(compositor_epoch) => {
                        // Synchronously query the layout thread to see if the current
                        // epoch matches what the compositor has drawn. If they match
                        // (and script is idle) then this pipeline won't change again
                        // and can be considered stable.
                        let message = LayoutControlMsg::GetCurrentEpoch(epoch_sender.clone());
                        if let Err(e) = pipeline.layout_chan.send(message) {
                            warn!("Failed to send GetCurrentEpoch ({}).", e);
                        }
                        match epoch_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
    }

    /// Get the current activity of a pipeline.
    fn get_activity(&self, pipeline_id: PipelineId) -> DocumentActivity {
        let mut ancestor_id = pipeline_id;
        loop {
            if let Some(ancestor) = self.pipelines.get(&ancestor_id) {
                if let Some(browsing_context) = self.browsing_contexts.get(&ancestor.browsing_context_id) {
                    if browsing_context.pipeline_id == ancestor_id {
                        if let Some((parent_id, FrameType::IFrame)) = ancestor.parent_info {
                            ancestor_id = parent_id;
                            continue;
                        } else {
                            return DocumentActivity::FullyActive;
                        }
                    }
                }
            }
            if pipeline_id == ancestor_id {
                return DocumentActivity::Inactive;
            } else {
                return DocumentActivity::Active;
            }
        }
    }

    /// Set the current activity of a pipeline.
    fn set_activity(&self, pipeline_id: PipelineId, activity: DocumentActivity) {
        debug!("Setting activity of {} to be {:?}.", pipeline_id, activity);
        if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
            pipeline.set_activity(activity);
            let child_activity = if activity == DocumentActivity::Inactive {
                DocumentActivity::Active
            } else {
                activity
            };
            for child_id in &pipeline.children {
                if let Some(child) = self.browsing_contexts.get(child_id) {
                    self.set_activity(child.pipeline_id, child_activity);
                }
            }
        }
    }

    /// Update the current activity of a pipeline.
    fn update_activity(&self, pipeline_id: PipelineId) {
        self.set_activity(pipeline_id, self.get_activity(pipeline_id));
    }

    /// Handle updating the size of a browsing context.
    /// This notifies every pipeline in the context of the new size.
    fn resize_browsing_context(&mut self,
                               new_size: WindowSizeData,
                               size_type: WindowSizeType,
                               browsing_context_id: BrowsingContextId)
    {
        if let Some(browsing_context) = self.browsing_contexts.get_mut(&browsing_context_id) {
            browsing_context.size = Some(new_size.initial_viewport);
        }

        if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) {
            // Send Resize (or ResizeInactive) messages to each
            // pipeline in the frame tree.
            let pipeline_id = browsing_context.pipeline_id;
            let pipeline = match self.pipelines.get(&pipeline_id) {
                None => return warn!("Pipeline {:?} resized after closing.", pipeline_id),
                Some(pipeline) => pipeline,
            };
            let _ = pipeline.event_loop.send(ConstellationControlMsg::Resize(
                pipeline.id,
                new_size,
                size_type
            ));
            let pipelines = browsing_context.prev.iter().chain(browsing_context.next.iter())
                .filter_map(|entry| entry.pipeline_id)
                .filter_map(|pipeline_id| self.pipelines.get(&pipeline_id));
            for pipeline in pipelines {
                let _ = pipeline.event_loop.send(ConstellationControlMsg::ResizeInactive(
                    pipeline.id,
                    new_size
                ));
            }
        }

        // Send resize message to any pending pipelines that aren't loaded yet.
        for change in &self.pending_changes {
            let pipeline_id = change.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.browsing_context_id == browsing_context_id {
                let _ = pipeline.event_loop.send(ConstellationControlMsg::Resize(
                    pipeline.id,
                    new_size,
                    size_type
                ));
            }
        }
    }

    fn clear_joint_session_future(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
        let browsing_context_ids: Vec<BrowsingContextId> =
            self.all_browsing_contexts_iter(top_level_browsing_context_id)
            .map(|browsing_context| browsing_context.id)
            .collect();
        for browsing_context_id in browsing_context_ids {
            let evicted = match self.browsing_contexts.get_mut(&browsing_context_id) {
                Some(browsing_context) => browsing_context.remove_forward_entries(),
                None => continue,
            };
            for entry in evicted {
                if let Some(pipeline_id) = entry.pipeline_id {
                    self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
                }
            }
        }
    }

    // Close a browsing context (and all children)
    fn close_browsing_context(&mut self, browsing_context_id: BrowsingContextId, exit_mode: ExitPipelineMode) {
        debug!("Closing browsing context {}.", browsing_context_id);

        self.close_browsing_context_children(browsing_context_id, DiscardBrowsingContext::Yes, exit_mode);

        let browsing_context = match self.browsing_contexts.remove(&browsing_context_id) {
            None => return warn!("Closing browsing context {:?} twice.", browsing_context_id),
            Some(browsing_context) => browsing_context,
        };

        if BrowsingContextId::from(browsing_context.top_level_id) == browsing_context_id {
            self.event_loops.remove(&browsing_context.top_level_id);
        }

        let parent_info = self.pipelines.get(&browsing_context.pipeline_id)
            .and_then(|pipeline| pipeline.parent_info);

        if let Some((parent_pipeline_id, _)) = parent_info {
            match self.pipelines.get_mut(&parent_pipeline_id) {
                None => return warn!("Pipeline {:?} child closed after parent.", parent_pipeline_id),
                Some(parent_pipeline) => parent_pipeline.remove_child(browsing_context_id),
            };
        }
        debug!("Closed browsing context {:?}.", browsing_context_id);
    }

    // Close the children of a browsing context
    fn close_browsing_context_children(&mut self,
                                       browsing_context_id: BrowsingContextId,
                                       dbc: DiscardBrowsingContext,
                                       exit_mode: ExitPipelineMode)
    {
        debug!("Closing browsing context children {}.", browsing_context_id);
        // Store information about the pipelines to be closed. Then close the
        // pipelines, before removing ourself from the browsing_contexts hash map. This
        // ordering is vital - so that if close_pipeline() ends up closing
        // any child browsing contexts, they can be removed from the parent browsing context correctly.
        let mut pipelines_to_close: Vec<PipelineId> = self.pending_changes.iter()
            .filter(|change| change.browsing_context_id == browsing_context_id)
            .map(|change| change.new_pipeline_id)
            .collect();

        if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) {
            pipelines_to_close.extend(browsing_context.next.iter().filter_map(|state| state.pipeline_id));
            pipelines_to_close.push(browsing_context.pipeline_id);
            pipelines_to_close.extend(browsing_context.prev.iter().filter_map(|state| state.pipeline_id));
        }

        for pipeline_id in pipelines_to_close {
            self.close_pipeline(pipeline_id, dbc, exit_mode);
        }

        debug!("Closed browsing context children {}.", browsing_context_id);
    }

    // Close all pipelines at and beneath a given browsing context
    fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) {
        debug!("Closing pipeline {:?}.", pipeline_id);
        // Store information about the browsing contexts to be closed. Then close the
        // browsing contexts, before removing ourself from the pipelines hash map. This
        // ordering is vital - so that if close_browsing_context() ends up closing
        // any child pipelines, they can be removed from the parent pipeline correctly.
        let browsing_contexts_to_close = {
            let mut browsing_contexts_to_close = vec!();

            if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
                browsing_contexts_to_close.extend_from_slice(&pipeline.children);
            }

            browsing_contexts_to_close
        };

        // Remove any child browsing contexts
        for child_browsing_context in &browsing_contexts_to_close {
            self.close_browsing_context(*child_browsing_context, exit_mode);
        }

        // Note, we don't remove the pipeline now, we wait for the message to come back from
        // the pipeline.
        let pipeline = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => pipeline,
            None => return warn!("Closing pipeline {:?} twice.", pipeline_id),
        };

        // Remove this pipeline from pending changes if it hasn't loaded yet.
        let pending_index = self.pending_changes.iter().position(|change| {
            change.new_pipeline_id == pipeline_id
        });
        if let Some(pending_index) = pending_index {
            self.pending_changes.remove(pending_index);
        }

        // Inform script, compositor that this pipeline has exited.
        match exit_mode {
            ExitPipelineMode::Normal => pipeline.exit(dbc),
            ExitPipelineMode::Force => pipeline.force_exit(dbc),
        }
        debug!("Closed pipeline {:?}.", pipeline_id);
    }

    // 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 mozbrowser pipeline
                    if PREFS.is_mozbrowser_enabled() && pipeline.parent_info.is_none() {
                        info!("Not closing mozbrowser pipeline {}.", pipeline_id);
                    } else {
                        // Note that we deliberately do not do any of the tidying up
                        // associated with closing a pipeline. The constellation should cope!
                        warn!("Randomly closing pipeline {}.", pipeline_id);
                        pipeline.force_exit(DiscardBrowsingContext::No);
                    }
                }
            }
        }
    }

    // Convert a browsing context to a sendable form to pass to the compositor
    fn browsing_context_to_sendable(&self, browsing_context_id: BrowsingContextId) -> Option<SendableFrameTree> {
        self.browsing_contexts.get(&browsing_context_id).and_then(|browsing_context| {
            self.pipelines.get(&browsing_context.pipeline_id).map(|pipeline| {
                let mut frame_tree = SendableFrameTree {
                    pipeline: pipeline.to_sendable(),
                    size: browsing_context.size,
                    children: vec!(),
                };

                for child_browsing_context_id in &pipeline.children {
                    if let Some(child) = self.browsing_context_to_sendable(*child_browsing_context_id) {
                        frame_tree.children.push(child);
                    }
                }

                frame_tree
            })
        })
    }

    // Send the current frame tree to compositor
    fn send_frame_tree(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
        // This might be a mozbrowser iframe, so we need to climb the parent hierarchy,
        // even though it's a top-level browsing context.
        let mut browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let mut pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
            Some(browsing_context) => browsing_context.pipeline_id,
            None => return warn!("Sending frame tree for discarded browsing context {}.", browsing_context_id),
        };

        while let Some(pipeline) = self.pipelines.get(&pipeline_id) {
            match pipeline.parent_info {
                Some((parent_id, _)) => pipeline_id = parent_id,
                None => {
                    browsing_context_id = pipeline.browsing_context_id;
                    break;
                },
            }
        }

        // 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.
        debug!("Sending frame tree for browsing context {}.", browsing_context_id);
        if let Some(frame_tree) = self.browsing_context_to_sendable(browsing_context_id) {
            let (chan, port) = ipc::channel().expect("Failed to create IPC channel!");
            self.compositor_proxy.send(ToCompositorMsg::SetFrameTree(frame_tree,
                                                                     chan));
            if port.recv().is_err() {
                warn!("Compositor has discarded SetFrameTree");
                return; // Our message has been discarded, probably shutting down.
            }
        }
    }

    // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserlocationchange
    // Note that this is a no-op if the pipeline is not a mozbrowser iframe
    fn trigger_mozbrowserlocationchange(&self,
                                        top_level_browsing_context_id: TopLevelBrowsingContextId)
    {
        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
            Some(browsing_context) => browsing_context.pipeline_id,
            None => return warn!("mozbrowser location change on closed browsing context {}.", browsing_context_id),
        };
        let (url, parent_info) = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => (pipeline.url.clone(), pipeline.parent_info),
            None => return warn!("mozbrowser location change on closed pipeline {}.", pipeline_id),
        };
        let parent_id = match parent_info {
            Some((parent_id, FrameType::MozBrowserIFrame)) => parent_id,
            _ => return debug!("mozbrowser location change on a regular iframe {}", browsing_context_id),
        };
        let can_go_forward = !self.joint_session_future_is_empty(top_level_browsing_context_id);
        let can_go_back = !self.joint_session_past_is_empty(top_level_browsing_context_id);
        let event = MozBrowserEvent::LocationChange(url.to_string(), can_go_back, can_go_forward);
        match self.pipelines.get(&parent_id) {
            Some(parent) => parent.trigger_mozbrowser_event(Some(top_level_browsing_context_id), event),
            None => return warn!("mozbrowser location change on closed parent {}", parent_id),
        };
    }

    // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsererror
    fn trigger_mozbrowsererror(&mut self,
                               top_level_browsing_context_id: TopLevelBrowsingContextId,
                               reason: String,
                               backtrace: Option<String>)
    {
        if !PREFS.is_mozbrowser_enabled() { return; }

        let mut report = String::new();
        for (thread_name, warning) in self.handled_warnings.drain(..) {
            report.push_str("\nWARNING: ");
            if let Some(thread_name) = thread_name {
                report.push_str("<");
                report.push_str(&*thread_name);
                report.push_str(">: ");
            }
            report.push_str(&*warning);
        }
        report.push_str("\nERROR: ");
        report.push_str(&*reason);
        if let Some(backtrace) = backtrace {
            report.push_str("\n\n");
            report.push_str(&*backtrace);
        }

        let event = MozBrowserEvent::Error(MozBrowserErrorType::Fatal, reason, report);
        let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
            Some(browsing_context) => browsing_context.pipeline_id,
            None => return warn!("Mozbrowser error after top-level browsing context closed."),
        };
        let parent_id = match self.pipelines.get(&pipeline_id) {
            Some(pipeline) => match pipeline.parent_info {
                Some((parent_id, FrameType::MozBrowserIFrame)) => parent_id,
                _ => return pipeline.trigger_mozbrowser_event(None, event),
            },
            None => return warn!("Mozbrowser error on a closed pipeline {}", pipeline_id),
        };
        match self.pipelines.get(&parent_id) {
            None => warn!("Mozbrowser error after parent pipeline {} closed.", parent_id),
            Some(parent) => parent.trigger_mozbrowser_event(Some(top_level_browsing_context_id), event),
        };
    }

    fn focused_pipeline_is_descendant_of(&self, browsing_context_id: BrowsingContextId) -> bool {
        self.focus_pipeline_id.map_or(false, |pipeline_id| {
            self.fully_active_descendant_browsing_contexts_iter(browsing_context_id)
                .any(|browsing_context| browsing_context.pipeline_id == pipeline_id)
        })
    }
}
