Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Share script threads per-tab and per-eTLD+1.
  • Loading branch information
Alan Jeffrey committed Nov 22, 2016
1 parent a89ba50 commit 7ae19c0
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 88 deletions.
117 changes: 70 additions & 47 deletions components/constellation/constellation.rs
Expand Up @@ -32,6 +32,7 @@ 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};
Expand All @@ -54,7 +55,7 @@ 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;
Expand Down Expand Up @@ -133,6 +134,11 @@ pub struct Constellation<Message, LTF, STF> {
/// 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>,

Expand Down Expand Up @@ -484,6 +490,14 @@ fn log_entry(record: &LogRecord) -> Option<LogEntry> {

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>
Expand Down Expand Up @@ -523,6 +537,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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!(),
Expand Down Expand Up @@ -584,26 +599,50 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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.
Expand Down Expand Up @@ -649,6 +688,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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);
}
Expand Down Expand Up @@ -1199,13 +1244,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
// 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");

Expand All @@ -1219,11 +1263,10 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>

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,
Expand Down Expand Up @@ -1252,8 +1295,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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,
Expand Down Expand Up @@ -1320,7 +1364,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
// 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));

Expand All @@ -1340,47 +1384,24 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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 {
Expand Down Expand Up @@ -1517,7 +1538,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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),
Expand Down Expand Up @@ -2272,6 +2294,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>

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);
}
Expand Down
29 changes: 14 additions & 15 deletions components/constellation/pipeline.rs
Expand Up @@ -115,8 +115,9 @@ pub struct InitialPipelineState {
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,
Expand Down Expand Up @@ -146,16 +147,23 @@ impl Pipeline {
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(),
Expand Down Expand Up @@ -191,15 +199,6 @@ impl Pipeline {
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");

Expand Down
25 changes: 12 additions & 13 deletions components/script/script_thread.rs
Expand Up @@ -1173,11 +1173,11 @@ impl ScriptThread {

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,
Expand All @@ -1187,7 +1187,7 @@ impl ScriptThread {
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,
Expand All @@ -1198,19 +1198,18 @@ impl ScriptThread {
image_cache_thread: self.image_cache_thread.clone(),
content_process_shutdown_chan: content_process_shutdown_chan,
layout_threads: layout_threads,
};

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

// 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);
}
Expand Down
11 changes: 6 additions & 5 deletions components/script_traits/lib.rs
Expand Up @@ -162,19 +162,20 @@ impl LoadData {
}
}

/// 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.
Expand Down

This file was deleted.

0 comments on commit 7ae19c0

Please sign in to comment.