diff --git a/components/constellation/browsingcontext.rs b/components/constellation/browsingcontext.rs index e11aada3b2a..e63d0701258 100644 --- a/components/constellation/browsingcontext.rs +++ b/components/constellation/browsingcontext.rs @@ -4,7 +4,9 @@ use crate::pipeline::Pipeline; use euclid::TypedSize2D; -use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; +use msg::constellation_msg::{ + BrowsingContextGroupId, BrowsingContextId, PipelineId, TopLevelBrowsingContextId, +}; use std::collections::{HashMap, HashSet}; use style_traits::CSSPixel; @@ -34,6 +36,9 @@ pub struct NewBrowsingContextInfo { /// sorted reverse chronologically: in particular prev.pop() is the latest /// past entry, and next.pop() is the earliest future entry. pub struct BrowsingContext { + /// The browsing context group id where the top-level of this bc is found. + pub bc_group_id: BrowsingContextGroupId, + /// The browsing context id. pub id: BrowsingContextId, @@ -66,6 +71,7 @@ impl BrowsingContext { /// Create a new browsing context. /// Note this just creates the browsing context, it doesn't add it to the constellation's set of browsing contexts. pub fn new( + bc_group_id: BrowsingContextGroupId, id: BrowsingContextId, top_level_id: TopLevelBrowsingContextId, pipeline_id: PipelineId, @@ -77,14 +83,15 @@ impl BrowsingContext { let mut pipelines = HashSet::new(); pipelines.insert(pipeline_id); BrowsingContext { - id: id, - top_level_id: top_level_id, - size: size, - is_private: is_private, - is_visible: is_visible, - pipeline_id: pipeline_id, - parent_pipeline_id: parent_pipeline_id, - pipelines: pipelines, + bc_group_id, + id, + top_level_id, + size, + is_private, + is_visible, + pipeline_id, + parent_pipeline_id, + pipelines, } } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 11967e10038..f4ac1fe69e9 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -126,7 +126,8 @@ use layout_traits::LayoutThreadFactory; use log::{Level, LevelFilter, Log, Metadata, Record}; use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg}; use msg::constellation_msg::{ - BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId, + BrowsingContextGroupId, BrowsingContextId, HistoryStateId, PipelineId, + TopLevelBrowsingContextId, }; use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection}; use net_traits::pub_domains::reg_host; @@ -158,7 +159,7 @@ use servo_remutex::ReentrantMutex; use servo_url::{Host, ImmutableOrigin, ServoUrl}; use std::borrow::ToOwned; use std::collections::hash_map::Entry; -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::marker::PhantomData; use std::mem::replace; use std::process; @@ -183,6 +184,24 @@ struct Browser { session_history: JointSessionHistory, } +/// A browsing context group. +/// +/// https://html.spec.whatwg.org/multipage/#browsing-context-group +#[derive(Clone, Default)] +struct BrowsingContextGroup { + /// A browsing context group holds a set of top-level browsing contexts. + top_level_browsing_context_set: HashSet, + + /// The set of all event loops in this BrowsingContextGroup. + /// We store the event loops in a map + /// indexed by registered domain name (as a `Host`) to event loops. + /// It is important that scripts with the same eTLD+1, + /// who are part of the same browsing-context group + /// 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>, +} + /// 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, @@ -312,14 +331,6 @@ pub struct Constellation { /// WebRender thread. webrender_api_sender: webrender_api::RenderApiSender, - /// The set of all event loops in the browser. - /// We store the event loops in a map - /// indexed by registered domain name (as a `Host`) to event loops. - /// 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>, - /// The set of all the pipelines in the browser. (See the `pipeline` module /// for more details.) pipelines: HashMap, @@ -327,6 +338,14 @@ pub struct Constellation { /// The set of all the browsing contexts in the browser. browsing_contexts: HashMap, + /// A user agent holds a a set of browsing context groups. + /// + /// https://html.spec.whatwg.org/multipage/#browsing-context-group-set + browsing_context_group_set: HashMap, + + /// The Id counter for BrowsingContextGroup. + browsing_context_group_next_id: u32, + /// 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 @@ -675,7 +694,8 @@ where swmanager_chan: None, swmanager_receiver: swmanager_receiver, swmanager_sender: sw_mgr_clone, - event_loops: HashMap::new(), + browsing_context_group_set: Default::default(), + browsing_context_group_next_id: Default::default(), pipelines: HashMap::new(), browsing_contexts: HashMap::new(), pending_changes: vec![], @@ -750,6 +770,106 @@ where namespace_id } + fn next_browsing_context_group_id(&mut self) -> BrowsingContextGroupId { + let id = self.browsing_context_group_next_id; + self.browsing_context_group_next_id += 1; + BrowsingContextGroupId(id) + } + + fn get_event_loop( + &mut self, + host: &Host, + top_level_browsing_context_id: &TopLevelBrowsingContextId, + opener: &Option, + ) -> Result, &'static str> { + let bc_group = match opener { + Some(browsing_context_id) => { + let opener = self + .browsing_contexts + .get(&browsing_context_id) + .ok_or("Opener was closed before the openee started")?; + self.browsing_context_group_set + .get(&opener.bc_group_id) + .ok_or("Opener belongs to an unknow BC group")? + }, + None => self + .browsing_context_group_set + .iter() + .filter_map(|(_, bc_group)| { + if bc_group + .top_level_browsing_context_set + .contains(&top_level_browsing_context_id) + { + Some(bc_group) + } else { + None + } + }) + .last() + .ok_or( + "Trying to get an event-loop for a top-level belonging to an unknown BC group", + )?, + }; + bc_group + .event_loops + .get(host) + .ok_or("Trying to get an event-loop from an unknown BC group") + .map(|event_loop| event_loop.clone()) + } + + fn set_event_loop( + &mut self, + event_loop: Weak, + host: Host, + top_level_browsing_context_id: TopLevelBrowsingContextId, + opener: Option, + ) { + let relevant_top_level = if let Some(opener) = opener { + match self.browsing_contexts.get(&opener) { + Some(opener) => opener.top_level_id, + None => { + warn!("Setting event-loop for an unknown auxiliary"); + return; + }, + } + } else { + top_level_browsing_context_id + }; + let maybe_bc_group_id = self + .browsing_context_group_set + .iter() + .filter_map(|(id, bc_group)| { + if bc_group + .top_level_browsing_context_set + .contains(&top_level_browsing_context_id) + { + Some(id.clone()) + } else { + None + } + }) + .last(); + let bc_group_id = match maybe_bc_group_id { + Some(id) => id, + None => { + warn!("Trying to add an event-loop to an unknown BC group"); + return; + }, + }; + if let Some(bc_group) = self.browsing_context_group_set.get_mut(&bc_group_id) { + if !bc_group + .event_loops + .insert(host.clone(), event_loop) + .is_none() + { + warn!( + "Double-setting an event-loop for {:?} at {:?}", + host, relevant_top_level + ); + } + } + } + /// Helper function for creating a pipeline fn new_pipeline( &mut self, @@ -785,11 +905,22 @@ where match reg_host(&load_data.url) { None => (None, None), Some(host) => { - let event_loop = - self.event_loops.get(&host).and_then(|weak| weak.upgrade()); - match event_loop { - None => (None, Some(host)), - Some(event_loop) => (Some(event_loop), None), + match self.get_event_loop( + &host, + &top_level_browsing_context_id, + &opener, + ) { + Err(err) => { + warn!("{}", err); + (None, Some(host)) + }, + Ok(event_loop) => { + if let Some(event_loop) = event_loop.upgrade() { + (Some(event_loop), None) + } else { + (None, Some(host)) + } + }, } }, } @@ -867,9 +998,12 @@ where "Adding new host entry {} for top-level browsing context {}.", host, top_level_browsing_context_id ); - let _ = self - .event_loops - .insert(host, Rc::downgrade(&pipeline.pipeline.event_loop)); + self.set_event_loop( + Rc::downgrade(&pipeline.pipeline.event_loop), + host, + top_level_browsing_context_id, + opener, + ); } assert!(!self.pipelines.contains_key(&pipeline_id)); @@ -922,7 +1056,31 @@ where is_visible: bool, ) { debug!("Creating new browsing context {}", browsing_context_id); + let bc_group_id = match self + .browsing_context_group_set + .iter_mut() + .filter_map(|(id, bc_group)| { + if bc_group + .top_level_browsing_context_set + .contains(&top_level_id) + { + Some(id) + } else { + None + } + }) + .last() + { + Some(id) => id.clone(), + None => { + warn!( + "Top-level was unpexpectedly removed from its top_level_browsing_context_set." + ); + return; + }, + }; let browsing_context = BrowsingContext::new( + bc_group_id, browsing_context_id, top_level_id, pipeline_id, @@ -1877,6 +2035,15 @@ where }, ); + // https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context-group + let mut new_bc_group: BrowsingContextGroup = Default::default(); + let new_bc_group_id = self.next_browsing_context_group_id(); + new_bc_group + .top_level_browsing_context_set + .insert(top_level_browsing_context_id.clone()); + self.browsing_context_group_set + .insert(new_bc_group_id, new_bc_group); + self.new_pipeline( pipeline_id, browsing_context_id, @@ -1912,6 +2079,16 @@ where if self.active_browser_id == Some(top_level_browsing_context_id) { self.active_browser_id = None; } + let browsing_context = match self.browsing_contexts.get(&browsing_context_id) { + Some(bc) => bc, + None => { + warn!("BC has closed before it has started"); + return; + }, + }; + // https://html.spec.whatwg.org/multipage/#bcg-remove + self.browsing_context_group_set + .remove(&browsing_context.bc_group_id); } fn handle_iframe_size_msg(&mut self, iframe_sizes: Vec) { @@ -2214,6 +2391,26 @@ where session_history: JointSessionHistory::new(), }, ); + + // https://html.spec.whatwg.org/multipage/#bcg-append + let opener = match self.browsing_contexts.get(&opener_browsing_context_id) { + Some(id) => id, + None => { + warn!("Trying to append an unknow auxiliary to a BC group"); + return; + }, + }; + let bc_group = match self.browsing_context_group_set.get_mut(&opener.bc_group_id) { + Some(bc_group) => bc_group, + None => { + warn!("Trying to add a top-level to an unknown group."); + return; + }, + }; + bc_group + .top_level_browsing_context_set + .insert(new_top_level_browsing_context_id.clone()); + self.add_pending_change(SessionHistoryChange { top_level_browsing_context_id: new_top_level_browsing_context_id, browsing_context_id: new_browsing_context_id, diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index aa61491afa5..1edcd2ffe36 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -174,6 +174,9 @@ impl fmt::Display for BrowsingContextId { } } +#[derive(Clone, Default, Eq, Hash, PartialEq)] +pub struct BrowsingContextGroupId(pub u32); + thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell> = Cell::new(None)); #[derive(