servo: Merge #14211 - Share script threads by tab and by eTLD+1 (from asajeffrey:constellation-share-more-script-threads); r=jdm
authorAlan Jeffrey <ajeffrey@mozilla.com>
Tue, 22 Nov 2016 16:41:46 -0600
changeset 340205 255da5dc0bc6d23d8b1ad475c83fb1c390c80aed
parent 340204 97d8bc0d18a00d7f3ddaee05620e3d9a64210bf9
child 340206 e4c58dae5c934a2735357561170257a50fe2bbb0
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
servo: Merge #14211 - Share script threads by tab and by eTLD+1 (from asajeffrey:constellation-share-more-script-threads); r=jdm <!-- Please describe your changes on the following line: --> This PR shares script threads among all similar-origin documents in the same tab. This allows DOM object to be shared among same-origin same-tab documents. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #633. - [X] These changes do not require tests because refactoring. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 1535f84bf19790c96c9f79b3f55264927b989b7c
servo/components/constellation/constellation.rs
servo/components/constellation/pipeline.rs
servo/components/script/script_thread.rs
servo/components/script_traits/lib.rs
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -27,16 +27,17 @@ use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use layout_traits::LayoutThreadFactory;
 use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord};
 use msg::constellation_msg::{FrameId, FrameType, PipelineId};
 use msg::constellation_msg::{Key, KeyModifiers, KeyState};
 use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection};
 use net_traits::{self, IpcSend, ResourceThreads};
 use net_traits::image_cache_thread::ImageCacheThread;
+use net_traits::pub_domains::reg_suffix;
 use net_traits::storage_thread::{StorageThreadMsg, StorageType};
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use pipeline::{ChildProcess, InitialPipelineState, Pipeline};
 use profile_traits::mem;
 use profile_traits::time;
 use rand::{Rng, SeedableRng, StdRng, random};
 use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
 use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg};
@@ -49,17 +50,17 @@ use script_traits::{SWManagerMsg, ScopeT
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::collections::{HashMap, VecDeque};
 use std::io::Error as IOError;
 use std::iter::once;
 use std::marker::PhantomData;
 use std::mem::replace;
 use std::process;
-use std::rc::Rc;
+use std::rc::{Rc, Weak};
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, channel};
 use std::thread;
 use std::time::Instant;
 use style_traits::PagePx;
 use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
 use timer_scheduler::TimerScheduler;
@@ -128,16 +129,21 @@ pub struct Constellation<Message, LTF, S
     swmanager_chan: Option<IpcSender<ServiceWorkerMsg>>,
 
     /// to send messages to this object
     swmanager_sender: IpcSender<SWManagerMsg>,
 
     /// to receive sw manager message
     swmanager_receiver: Receiver<SWManagerMsg>,
 
+    /// A map from top-level frame id and registered domain name to script channels.
+    /// This double indirection ensures that separate tabs do not share script threads,
+    /// even if the same domain is loaded in each.
+    script_channels: HashMap<FrameId, HashMap<String, Weak<ScriptChan>>>,
+
     /// A list of all the pipelines. (See the `pipeline` module for more details.)
     pipelines: HashMap<PipelineId, Pipeline>,
 
     /// A list of all the frames
     frames: HashMap<FrameId, Frame>,
 
     /// A channel through which messages can be sent to the font cache.
     font_cache_thread: FontCacheThread,
@@ -479,16 +485,24 @@ fn log_entry(record: &LogRecord) -> Opti
             format!("{}", record.args())
         )),
         _ => None,
     }
 }
 
 const WARNINGS_BUFFER_SIZE: usize = 32;
 
+/// The registered domain name (aka eTLD+1) for a URL.
+/// Returns None if the URL has no host name.
+/// Returns the registered suffix for the host name if it is a domain.
+/// Leaves the host name alone if it is an IP address.
+fn reg_host<'a>(url: &'a ServoUrl) -> Option<&'a str> {
+    url.domain().map(reg_suffix).or(url.host_str())
+}
+
 impl<Message, LTF, STF> Constellation<Message, LTF, STF>
     where LTF: LayoutThreadFactory<Message=Message>,
           STF: ScriptThreadFactory<Message=Message>
 {
     pub fn start(state: InitialConstellationState) -> (Sender<FromCompositorMsg>, IpcSender<SWManagerMsg>) {
         let (compositor_sender, compositor_receiver) = channel();
 
         // service worker manager to communicate with constellation
@@ -518,16 +532,17 @@ impl<Message, LTF, STF> Constellation<Me
                 bluetooth_thread: state.bluetooth_thread,
                 public_resource_threads: state.public_resource_threads,
                 private_resource_threads: state.private_resource_threads,
                 image_cache_thread: state.image_cache_thread,
                 font_cache_thread: state.font_cache_thread,
                 swmanager_chan: None,
                 swmanager_receiver: swmanager_receiver,
                 swmanager_sender: sw_mgr_clone,
+                script_channels: HashMap::new(),
                 pipelines: HashMap::new(),
                 frames: HashMap::new(),
                 pending_frames: vec!(),
                 // We initialize the namespace at 1, since we reserved namespace 0 for the constellation
                 next_pipeline_namespace_id: PipelineNamespaceId(1),
                 root_frame_id: FrameId::new(),
                 focus_pipeline_id: None,
                 time_profiler_chan: state.time_profiler_chan,
@@ -579,36 +594,60 @@ impl<Message, LTF, STF> Constellation<Me
         namespace_id
     }
 
     /// Helper function for creating a pipeline
     fn new_pipeline(&mut self,
                     pipeline_id: PipelineId,
                     frame_id: FrameId,
                     parent_info: Option<(PipelineId, FrameType)>,
-                    old_pipeline_id: Option<PipelineId>,
                     initial_window_size: Option<TypedSize2D<f32, PagePx>>,
-                    script_channel: Option<Rc<ScriptChan>>,
                     load_data: LoadData,
+                    sandbox: IFrameSandboxState,
                     is_private: bool) {
         if self.shutting_down { return; }
 
+        // TODO: can we get a case where the child pipeline is created
+        // before the parent is part of the frame tree?
+        let top_level_frame_id = match parent_info {
+            Some((_, FrameType::MozBrowserIFrame)) => frame_id,
+            Some((parent_id, _)) => self.get_top_level_frame_for_pipeline(parent_id),
+            None => self.root_frame_id,
+        };
+
+        let (script_channel, host) = match sandbox {
+            IFrameSandboxState::IFrameSandboxed => (None, None),
+            IFrameSandboxState::IFrameUnsandboxed => match reg_host(&load_data.url) {
+                None => (None, None),
+                Some(host) => {
+                    let script_channel = self.script_channels.get(&top_level_frame_id)
+                        .and_then(|map| map.get(host))
+                        .and_then(|weak| weak.upgrade());
+                    match script_channel {
+                        None => (None, Some(String::from(host))),
+                        Some(script_channel) => (Some(script_channel.clone()), None),
+                    }
+                },
+            },
+        };
+
         let resource_threads = if is_private {
             self.private_resource_threads.clone()
         } else {
             self.public_resource_threads.clone()
         };
 
-        let prev_visibility = if let Some(id) = old_pipeline_id {
-            self.pipelines.get(&id).map(|pipeline| pipeline.visible)
-        } else if let Some((parent_pipeline_id, _)) = parent_info {
-            self.pipelines.get(&parent_pipeline_id).map(|pipeline| pipeline.visible)
-        } else {
-            None
-        };
+        let parent_visibility = parent_info
+            .and_then(|(parent_pipeline_id, _)| self.pipelines.get(&parent_pipeline_id))
+            .map(|pipeline| pipeline.visible);
+
+        let prev_visibility = self.frames.get(&frame_id)
+            .and_then(|frame| self.pipelines.get(&frame.current.pipeline_id))
+            .map(|pipeline| pipeline.visible)
+            .or(parent_visibility);
 
         // TODO: think about the case where the child pipeline is created
         // before the parent is part of the frame tree.
         let top_level_frame_id = match parent_info {
             Some((_, FrameType::MozBrowserIFrame)) => frame_id,
             Some((parent_id, _)) => self.get_top_level_frame_for_pipeline(parent_id),
             None => self.root_frame_id,
         };
@@ -644,16 +683,22 @@ impl<Message, LTF, STF> Constellation<Me
             Ok(result) => result,
             Err(e) => return self.handle_send_error(pipeline_id, e),
         };
 
         if let Some(child_process) = child_process {
             self.child_processes.push(child_process);
         }
 
+        if let Some(host) = host {
+            self.script_channels.entry(top_level_frame_id)
+                .or_insert_with(HashMap::new)
+                .insert(host, Rc::downgrade(&pipeline.script_chan));
+        }
+
         assert!(!self.pipelines.contains_key(&pipeline_id));
         self.pipelines.insert(pipeline_id, pipeline);
     }
 
     // Get an iterator for the current frame tree. Specify self.root_frame_id to
     // iterate the entire tree, or a specific frame id to iterate only that sub-tree.
     fn current_frame_tree_iter(&self, frame_id_root: FrameId) -> FrameTreeIterator {
         FrameTreeIterator {
@@ -1194,41 +1239,39 @@ impl<Message, LTF, STF> Constellation<Me
             process::exit(1);
         }
 
         debug!("Panic handler for top-level frame {}: {}.", top_level_frame_id, reason);
 
         // Notify the browser chrome that the pipeline has failed
         self.trigger_mozbrowsererror(top_level_frame_id, reason, backtrace);
 
-        let frame_id = FrameId::from(top_level_frame_id);
-        let pipeline_id = self.frames.get(&frame_id).map(|frame| frame.current.pipeline_id);
+        let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.current.pipeline_id);
         let pipeline_url = pipeline_id.and_then(|id| self.pipelines.get(&id).map(|pipeline| pipeline.url.clone()));
         let parent_info = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.parent_info));
         let window_size = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.size));
 
-        self.close_frame_children(frame_id, ExitPipelineMode::Force);
+        self.close_frame_children(top_level_frame_id, 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);
-        self.new_pipeline(new_pipeline_id, frame_id, parent_info, pipeline_id,
-                          window_size, None, load_data, false);
-
+        let sandbox = IFrameSandboxState::IFrameSandboxed;
+        self.new_pipeline(new_pipeline_id, top_level_frame_id, parent_info, window_size, load_data, sandbox, false);
         self.pending_frames.push(FrameChange {
-            frame_id: frame_id,
+            frame_id: top_level_frame_id,
             old_pipeline_id: pipeline_id,
             new_pipeline_id: new_pipeline_id,
             document_ready: false,
             replace: false,
         });
     }
 
     fn handle_log_entry(&mut self, top_level_frame_id: Option<FrameId>, thread_name: Option<String>, entry: LogEntry) {
@@ -1247,18 +1290,19 @@ impl<Message, LTF, STF> Constellation<Me
             },
         }
     }
 
     fn handle_init_load(&mut self, url: ServoUrl) {
         let window_size = self.window_size.visible_viewport;
         let root_pipeline_id = PipelineId::new();
         let root_frame_id = self.root_frame_id;
-        self.new_pipeline(root_pipeline_id, root_frame_id, None, None, Some(window_size), None,
-                          LoadData::new(url.clone(), None, None), false);
+        let load_data = LoadData::new(url.clone(), None, None);
+        let sandbox = IFrameSandboxState::IFrameUnsandboxed;
+        self.new_pipeline(root_pipeline_id, root_frame_id, None, Some(window_size), load_data, sandbox, false);
         self.handle_load_start_msg(root_pipeline_id);
         self.pending_frames.push(FrameChange {
             frame_id: self.root_frame_id,
             old_pipeline_id: None,
             new_pipeline_id: root_pipeline_id,
             document_ready: false,
             replace: false,
         });
@@ -1315,17 +1359,17 @@ impl<Message, LTF, STF> Constellation<Me
         }
     }
 
     // The script thread associated with pipeline_id has loaded a URL in an iframe via script. This
     // will result in a new pipeline being spawned and a frame tree being added to
     // parent_pipeline_id's frame tree's children. This message is never the result of a
     // page navigation.
     fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfo) {
-        let (load_data, script_chan, window_size, is_private) = {
+        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.parent_pipeline_id) {
                 Some(source_pipeline) => source_pipeline,
                 None => return warn!("Script loaded url in closed iframe {}.", load_info.parent_pipeline_id),
             };
 
@@ -1335,57 +1379,34 @@ impl<Message, LTF, STF> Constellation<Me
                     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, None, None)
             });
 
-            // Compare the pipeline's url to the new url. If the origin is the same,
-            // then reuse the script thread in creating the new pipeline
-            let source_url = &source_pipeline.url;
-
             let is_private = load_info.is_private || source_pipeline.is_private;
 
-            // FIXME(#10968): this should probably match the origin check in
-            //                HTMLIFrameElement::contentDocument.
-            let same_script = source_url.host() == load_data.url.host() &&
-                              source_url.port() == load_data.url.port() &&
-                              load_info.sandbox == IFrameSandboxState::IFrameUnsandboxed &&
-                              source_pipeline.is_private == is_private;
-
-            // Reuse the script thread if the URL is same-origin
-            let script_chan = if same_script {
-                debug!("Constellation: loading same-origin iframe, \
-                        parent url {:?}, iframe url {:?}", source_url, load_data.url);
-                Some(source_pipeline.script_chan.clone())
-            } else {
-                debug!("Constellation: loading cross-origin iframe, \
-                        parent url {:?}, iframe url {:?}", source_url, load_data.url);
-                None
-            };
-
             let window_size = old_pipeline.and_then(|old_pipeline| old_pipeline.size);
 
             if let Some(old_pipeline) = old_pipeline {
                 old_pipeline.freeze();
             }
 
-            (load_data, script_chan, window_size, is_private)
+            (load_data, window_size, is_private)
         };
 
         // Create the new pipeline, attached to the parent and push to pending frames
         self.new_pipeline(load_info.new_pipeline_id,
                           load_info.frame_id,
-                          Some((load_info.parent_pipeline_id,  load_info.frame_type)),
-                          load_info.old_pipeline_id,
+                          Some((load_info.parent_pipeline_id, load_info.frame_type)),
                           window_size,
-                          script_chan,
                           load_data,
+                          load_info.sandbox,
                           is_private);
 
         self.pending_frames.push(FrameChange {
             frame_id: load_info.frame_id,
             old_pipeline_id: load_info.old_pipeline_id,
             new_pipeline_id: load_info.new_pipeline_id,
             document_ready: false,
             replace: load_info.replace,
@@ -1512,17 +1533,18 @@ impl<Message, LTF, STF> Constellation<Me
                 self.handle_load_start_msg(source_id);
                 // Being here means either there are no pending frames, or none of the pending
                 // changes would be overridden by changing the subframe associated with source_id.
 
                 // Create the new pipeline
                 let window_size = self.pipelines.get(&source_id).and_then(|source| source.size);
                 let new_pipeline_id = PipelineId::new();
                 let root_frame_id = self.root_frame_id;
-                self.new_pipeline(new_pipeline_id, root_frame_id, None, None, window_size, None, load_data, false);
+                let sandbox = IFrameSandboxState::IFrameUnsandboxed;
+                self.new_pipeline(new_pipeline_id, root_frame_id, None, window_size, load_data, sandbox, false);
                 self.pending_frames.push(FrameChange {
                     frame_id: root_frame_id,
                     old_pipeline_id: Some(source_id),
                     new_pipeline_id: new_pipeline_id,
                     document_ready: false,
                     replace: replace,
                 });
 
@@ -2267,16 +2289,17 @@ impl<Message, LTF, STF> Constellation<Me
     fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
         debug!("Closing frame {}.", frame_id);
         let parent_info = self.frames.get(&frame_id)
             .and_then(|frame| self.pipelines.get(&frame.current.pipeline_id))
             .and_then(|pipeline| pipeline.parent_info);
 
         self.close_frame_children(frame_id, exit_mode);
 
+        self.script_channels.remove(&frame_id);
         if self.frames.remove(&frame_id).is_none() {
             warn!("Closing frame {:?} twice.", frame_id);
         }
 
         if let Some((parent_pipeline_id, _)) = parent_info {
             let parent_pipeline = match self.pipelines.get_mut(&parent_pipeline_id) {
                 None => return warn!("Pipeline {:?} child closed after parent.", parent_pipeline_id),
                 Some(parent_pipeline) => parent_pipeline,
--- a/servo/components/constellation/pipeline.rs
+++ b/servo/components/constellation/pipeline.rs
@@ -110,18 +110,19 @@ pub struct InitialPipelineState {
     /// A channel to the time profiler thread.
     pub time_profiler_chan: time::ProfilerChan,
     /// A channel to the memory profiler thread.
     pub mem_profiler_chan: profile_mem::ProfilerChan,
     /// Information about the initial window size.
     pub window_size: Option<TypedSize2D<f32, PagePx>>,
     /// Information about the device pixel ratio.
     pub device_pixel_ratio: ScaleFactor<f32, ViewportPx, DevicePixel>,
-    /// A channel to the script thread, if applicable. If this is `Some`,
-    /// then `parent_info` must also be `Some`.
+    /// A channel to the script thread, if applicable.
+    /// If this is `None`, create a new script thread.
+    /// If this is `Some`, then reuse an existing script thread.
     pub script_chan: Option<Rc<ScriptChan>>,
     /// Information about the page to load.
     pub load_data: LoadData,
     /// The ID of the pipeline namespace for this script thread.
     pub pipeline_namespace_id: PipelineNamespaceId,
     /// Pipeline visibility to be inherited
     pub prev_visibility: Option<bool>,
     /// Webrender api.
@@ -141,26 +142,33 @@ impl Pipeline {
         // Note: we allow channel creation to panic, since recovering from this
         // probably requires a general low-memory strategy.
         let (pipeline_chan, pipeline_port) = ipc::channel()
             .expect("Pipeline main chan");;
 
         let (layout_content_process_shutdown_chan, layout_content_process_shutdown_port) =
             ipc::channel().expect("Pipeline layout content shutdown chan");
 
+        let device_pixel_ratio = state.device_pixel_ratio;
+        let window_size = state.window_size.map(|size| {
+            WindowSizeData {
+                visible_viewport: size,
+                initial_viewport: size * ScaleFactor::new(1.0),
+                device_pixel_ratio: device_pixel_ratio,
+            }
+        });
+
         let (script_chan, content_ports) = match state.script_chan {
             Some(script_chan) => {
-                let (parent_pipeline_id, frame_type) =
-                    state.parent_info.expect("script_pipeline != None but parent_info == None");
                 let new_layout_info = NewLayoutInfo {
-                    parent_pipeline_id: parent_pipeline_id,
+                    parent_info: state.parent_info,
                     new_pipeline_id: state.id,
                     frame_id: state.frame_id,
-                    frame_type: frame_type,
                     load_data: state.load_data.clone(),
+                    window_size: window_size,
                     pipeline_port: pipeline_port,
                     layout_to_constellation_chan: state.layout_to_constellation_chan.clone(),
                     content_process_shutdown_chan: layout_content_process_shutdown_chan.clone(),
                     layout_threads: PREFS.get("layout.threads").as_u64().expect("count") as usize,
                 };
 
                 if let Err(e) = script_chan.send(ConstellationControlMsg::AttachLayout(new_layout_info)) {
                     warn!("Sending to script during pipeline creation failed ({})", e);
@@ -186,25 +194,16 @@ impl Pipeline {
                         Ok(message) => if let Err(e) = devtools_chan.send(DevtoolsControlMsg::FromScript(message)) {
                             warn!("Sending to devtools failed ({})", e)
                         },
                     }
                 });
                 script_to_devtools_chan
             });
 
-            let device_pixel_ratio = state.device_pixel_ratio;
-            let window_size = state.window_size.map(|size| {
-                WindowSizeData {
-                    visible_viewport: size,
-                    initial_viewport: size * ScaleFactor::new(1.0),
-                    device_pixel_ratio: device_pixel_ratio,
-                }
-            });
-
             let (script_content_process_shutdown_chan, script_content_process_shutdown_port) =
                 ipc::channel().expect("Pipeline script content process shutdown chan");
 
             let unprivileged_pipeline_content = UnprivilegedPipelineContent {
                 id: state.id,
                 frame_id: state.frame_id,
                 top_level_frame_id: state.top_level_frame_id,
                 parent_info: state.parent_info,
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -1168,54 +1168,53 @@ impl ScriptThread {
                                       Point2D::new(-scroll_offset.x, -scroll_offset.y));
             }
         }
         window.set_scroll_offsets(scroll_offsets)
     }
 
     fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
         let NewLayoutInfo {
-            parent_pipeline_id,
+            parent_info,
             new_pipeline_id,
             frame_id,
-            frame_type,
             load_data,
+            window_size,
             pipeline_port,
             layout_to_constellation_chan,
             content_process_shutdown_chan,
             layout_threads,
         } = new_layout_info;
 
         let layout_pair = channel();
         let layout_chan = layout_pair.0.clone();
 
-        let layout_creation_info = NewLayoutThreadInfo {
+        let msg = message::Msg::CreateLayoutThread(NewLayoutThreadInfo {
             id: new_pipeline_id,
             url: load_data.url.clone(),
             is_parent: false,
             layout_pair: layout_pair,
             pipeline_port: pipeline_port,
             constellation_chan: layout_to_constellation_chan,
             script_chan: self.control_chan.clone(),
             image_cache_thread: self.image_cache_thread.clone(),
             content_process_shutdown_chan: content_process_shutdown_chan,
             layout_threads: layout_threads,
+        });
+
+        // Pick a layout thread, any layout thread
+        match self.documents.borrow().iter().next() {
+            None => panic!("Layout attached to empty script thread."),
+            // Tell the layout thread factory to actually spawn the thread.
+            Some((_, document)) => document.window().layout_chan().send(msg).unwrap(),
         };
 
-        let parent_window = self.documents.borrow().find_window(parent_pipeline_id)
-            .expect("ScriptThread: received a layout for a parent pipeline not in this script thread. This is a bug.");
-
-        // Tell layout to actually spawn the thread.
-        parent_window.layout_chan()
-                     .send(message::Msg::CreateLayoutThread(layout_creation_info))
-                     .unwrap();
-
         // Kick off the fetch for the new resource.
-        let new_load = InProgressLoad::new(new_pipeline_id, frame_id, Some((parent_pipeline_id, frame_type)),
-                                           layout_chan, parent_window.window_size(),
+        let new_load = InProgressLoad::new(new_pipeline_id, frame_id, parent_info,
+                                           layout_chan, window_size,
                                            load_data.url.clone());
         self.start_page_load(new_load, load_data);
     }
 
     fn handle_loads_complete(&self, pipeline: PipelineId) {
         let doc = match self.documents.borrow().find_document(pipeline) {
             Some(doc) => doc,
             None => return warn!("Message sent to closed pipeline {}.", pipeline),
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -157,29 +157,30 @@ impl LoadData {
             headers: Headers::new(),
             data: None,
             referrer_policy: referrer_policy,
             referrer_url: referrer_url,
         }
     }
 }
 
-/// The initial data associated with a newly-created framed pipeline.
+/// The initial data required to create a new layout attached to an existing script thread.
 #[derive(Deserialize, Serialize)]
 pub struct NewLayoutInfo {
-    /// Id of the parent of this new pipeline.
-    pub parent_pipeline_id: PipelineId,
+    /// The ID of the parent pipeline and frame type, if any.
+    /// If `None`, this is a root pipeline.
+    pub parent_info: Option<(PipelineId, FrameType)>,
     /// Id of the newly-created pipeline.
     pub new_pipeline_id: PipelineId,
     /// Id of the frame associated with this pipeline.
     pub frame_id: FrameId,
-    /// Type of the frame associated with this pipeline.
-    pub frame_type: FrameType,
     /// Network request data which will be initiated by the script thread.
     pub load_data: LoadData,
+    /// Information about the initial window size.
+    pub window_size: Option<WindowSizeData>,
     /// A port on which layout can receive messages from the pipeline.
     pub pipeline_port: IpcReceiver<LayoutControlMsg>,
     /// A sender for the layout thread to communicate to the constellation.
     pub layout_to_constellation_chan: IpcSender<LayoutMsg>,
     /// A shutdown channel so that layout can tell the content process to shut down when it's done.
     pub content_process_shutdown_chan: IpcSender<()>,
     /// Number of threads to use for layout.
     pub layout_threads: usize,