From 7ae19c07f00d46662222fa21658a216ec49f3839 Mon Sep 17 00:00:00 2001 From: Alan Jeffrey Date: Thu, 3 Nov 2016 11:41:07 -0500 Subject: [PATCH] Share script threads per-tab and per-eTLD+1. --- components/constellation/constellation.rs | 117 +++++++++++------- components/constellation/pipeline.rs | 29 +++-- components/script/script_thread.rs | 25 ++-- components/script_traits/lib.rs | 11 +- .../cross_origin_parentage.html.ini | 8 -- 5 files changed, 102 insertions(+), 88 deletions(-) delete mode 100644 tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.html.ini diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 52976bf1daf0..8cb82a5cdd50 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -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}; @@ -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; @@ -133,6 +134,11 @@ pub struct Constellation { /// to receive sw manager message swmanager_receiver: Receiver, + /// 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>>, + /// A list of all the pipelines. (See the `pipeline` module for more details.) pipelines: HashMap, @@ -484,6 +490,14 @@ fn log_entry(record: &LogRecord) -> Option { 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 Constellation where LTF: LayoutThreadFactory, STF: ScriptThreadFactory @@ -523,6 +537,7 @@ impl Constellation 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!(), @@ -584,26 +599,50 @@ impl Constellation pipeline_id: PipelineId, frame_id: FrameId, parent_info: Option<(PipelineId, FrameType)>, - old_pipeline_id: Option, initial_window_size: Option>, - script_channel: Option>, 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. @@ -649,6 +688,12 @@ impl Constellation 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); } @@ -1199,13 +1244,12 @@ impl Constellation // 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"); @@ -1219,11 +1263,10 @@ impl Constellation 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, @@ -1252,8 +1295,9 @@ impl Constellation 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, @@ -1320,7 +1364,7 @@ impl Constellation // 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)); @@ -1340,47 +1384,24 @@ impl Constellation 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 { @@ -1517,7 +1538,8 @@ impl Constellation 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), @@ -2272,6 +2294,7 @@ impl Constellation 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); } diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 44739fd6191d..d77db028f2b3 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -115,8 +115,9 @@ pub struct InitialPipelineState { pub window_size: Option>, /// Information about the device pixel ratio. pub device_pixel_ratio: ScaleFactor, - /// 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>, /// Information about the page to load. pub load_data: LoadData, @@ -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(), @@ -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"); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 8a5fd03c479f..135fccf58c3d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -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, @@ -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, @@ -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); } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 8a437ff24ee0..458972283db3 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -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, /// A port on which layout can receive messages from the pipeline. pub pipeline_port: IpcReceiver, /// A sender for the layout thread to communicate to the constellation. diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.html.ini deleted file mode 100644 index 508f1352c973..000000000000 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[cross_origin_parentage.html] - type: testharness - [Check the frame heriarchy 1] - expected: FAIL - - [Check the frame heriarchy 2] - expected: FAIL -